Commit c9e02b8a authored by Phil Hughes's avatar Phil Hughes

Merge branch 'pb-add-polling-to-widget-base-component' into 'master'

Add polling to MR widget base component

See merge request gitlab-org/gitlab!77605
parents ebc509a6 423dfa6d
...@@ -13,6 +13,7 @@ import * as Sentry from '@sentry/browser'; ...@@ -13,6 +13,7 @@ import * as Sentry from '@sentry/browser';
import api from '~/api'; import api from '~/api';
import { sprintf, s__, __ } from '~/locale'; import { sprintf, s__, __ } from '~/locale';
import SmartVirtualList from '~/vue_shared/components/smart_virtual_list.vue'; import SmartVirtualList from '~/vue_shared/components/smart_virtual_list.vue';
import Poll from '~/lib/utils/poll';
import { EXTENSION_ICON_CLASS, EXTENSION_ICONS } from '../../constants'; import { EXTENSION_ICON_CLASS, EXTENSION_ICONS } from '../../constants';
import StatusIcon from './status_icon.vue'; import StatusIcon from './status_icon.vue';
import Actions from './actions.vue'; import Actions from './actions.vue';
...@@ -132,19 +133,50 @@ export default { ...@@ -132,19 +133,50 @@ export default {
this.triggerRedisTracking(); this.triggerRedisTracking();
}, },
initExtensionPolling() {
const poll = new Poll({
resource: {
fetchData: () => this.fetchCollapsedData(this.$props),
},
method: 'fetchData',
successCallback: (data) => {
if (Object.keys(data).length > 0) {
poll.stop();
this.setCollapsedData(data);
}
},
errorCallback: (e) => {
poll.stop();
this.setCollapsedError(e);
},
});
poll.makeRequest();
},
loadCollapsedData() { loadCollapsedData() {
this.loadingState = LOADING_STATES.collapsedLoading; this.loadingState = LOADING_STATES.collapsedLoading;
if (this.$options.enablePolling) {
this.initExtensionPolling();
} else {
this.fetchCollapsedData(this.$props) this.fetchCollapsedData(this.$props)
.then((data) => { .then((data) => {
this.collapsedData = data; this.setCollapsedData(data);
this.loadingState = null;
}) })
.catch((e) => { .catch((e) => {
this.setCollapsedError(e);
});
}
},
setCollapsedData(data) {
this.collapsedData = data;
this.loadingState = null;
},
setCollapsedError(e) {
this.loadingState = LOADING_STATES.collapsedError; this.loadingState = LOADING_STATES.collapsedError;
Sentry.captureException(e); Sentry.captureException(e);
});
}, },
loadAllData() { loadAllData() {
if (this.hasFullData) return; if (this.hasFullData) return;
......
...@@ -13,6 +13,7 @@ export const registerExtension = (extension) => { ...@@ -13,6 +13,7 @@ export const registerExtension = (extension) => {
props: extension.props, props: extension.props,
i18n: extension.i18n, i18n: extension.i18n,
expandEvent: extension.expandEvent, expandEvent: extension.expandEvent,
enablePolling: extension.enablePolling,
computed: { computed: {
...Object.keys(extension.computed).reduce( ...Object.keys(extension.computed).reduce(
(acc, computedKey) => ({ (acc, computedKey) => ({
......
import { __, n__, s__, sprintf } from '~/locale'; import { __, n__, s__, sprintf } from '~/locale';
import axios from '~/lib/utils/axios_utils'; import axios from '~/lib/utils/axios_utils';
import Poll from '~/lib/utils/poll';
import { EXTENSION_ICONS } from '../../constants'; import { EXTENSION_ICONS } from '../../constants';
export default { export default {
name: 'WidgetTerraform', name: 'WidgetTerraform',
enablePolling: true,
i18n: { i18n: {
label: s__('Terraform|Terraform reports'), label: s__('Terraform|Terraform reports'),
loading: s__('Terraform|Loading Terraform reports...'), loading: s__('Terraform|Loading Terraform reports...'),
...@@ -81,33 +81,16 @@ export default { ...@@ -81,33 +81,16 @@ export default {
}, },
// Custom methods // Custom methods
fetchPlans() { fetchPlans() {
return new Promise((resolve) => { return axios
const poll = new Poll({ .get(this.terraformReportsPath)
resource: { .then(({ data }) => {
fetchPlans: () => axios.get(this.terraformReportsPath), return Object.keys(data).map((key) => {
},
data: this.terraformReportsPath,
method: 'fetchPlans',
successCallback: ({ data }) => {
if (Object.keys(data).length > 0) {
poll.stop();
const result = Object.keys(data).map((key) => {
return data[key]; return data[key];
}); });
})
resolve(result); .catch(() => {
}
},
errorCallback: () => {
const invalidData = { tf_report_error: 'api_error' }; const invalidData = { tf_report_error: 'api_error' };
poll.stop(); return [invalidData];
const result = [invalidData];
resolve(result);
},
});
poll.makeRequest();
}); });
}, },
createReportRow(report, iconName) { createReportRow(report, iconName) {
......
...@@ -9,6 +9,7 @@ import waitForPromises from 'helpers/wait_for_promises'; ...@@ -9,6 +9,7 @@ import waitForPromises from 'helpers/wait_for_promises';
import { securityReportMergeRequestDownloadPathsQueryResponse } from 'jest/vue_shared/security_reports/mock_data'; import { securityReportMergeRequestDownloadPathsQueryResponse } from 'jest/vue_shared/security_reports/mock_data';
import api from '~/api'; import api from '~/api';
import axios from '~/lib/utils/axios_utils'; import axios from '~/lib/utils/axios_utils';
import Poll from '~/lib/utils/poll';
import { setFaviconOverlay } from '~/lib/utils/favicon'; import { setFaviconOverlay } from '~/lib/utils/favicon';
import notify from '~/lib/utils/notify'; import notify from '~/lib/utils/notify';
import SmartInterval from '~/smart_interval'; import SmartInterval from '~/smart_interval';
...@@ -28,6 +29,8 @@ import { ...@@ -28,6 +29,8 @@ import {
workingExtension, workingExtension,
collapsedDataErrorExtension, collapsedDataErrorExtension,
fullDataErrorExtension, fullDataErrorExtension,
pollingExtension,
pollingErrorExtension,
} from './test_extensions'; } from './test_extensions';
jest.mock('~/api.js'); jest.mock('~/api.js');
...@@ -897,13 +900,19 @@ describe('MrWidgetOptions', () => { ...@@ -897,13 +900,19 @@ describe('MrWidgetOptions', () => {
}); });
describe('mock extension', () => { describe('mock extension', () => {
let pollRequest;
beforeEach(() => { beforeEach(() => {
pollRequest = jest.spyOn(Poll.prototype, 'makeRequest');
registerExtension(workingExtension); registerExtension(workingExtension);
createComponent(); createComponent();
}); });
afterEach(() => { afterEach(() => {
pollRequest.mockRestore();
registeredExtensions.extensions = []; registeredExtensions.extensions = [];
}); });
...@@ -957,6 +966,66 @@ describe('MrWidgetOptions', () => { ...@@ -957,6 +966,66 @@ describe('MrWidgetOptions', () => {
expect(collapsedSection.find(GlButton).exists()).toBe(true); expect(collapsedSection.find(GlButton).exists()).toBe(true);
expect(collapsedSection.find(GlButton).text()).toBe('Full report'); expect(collapsedSection.find(GlButton).text()).toBe('Full report');
}); });
it('extension polling is not called if enablePolling flag is not passed', () => {
// called one time due to parent component polling (mount)
expect(pollRequest).toHaveBeenCalledTimes(1);
});
});
describe('mock polling extension', () => {
let pollRequest;
let pollStop;
beforeEach(() => {
pollRequest = jest.spyOn(Poll.prototype, 'makeRequest');
pollStop = jest.spyOn(Poll.prototype, 'stop');
});
afterEach(() => {
pollRequest.mockRestore();
pollStop.mockRestore();
registeredExtensions.extensions = [];
});
describe('success', () => {
beforeEach(() => {
registerExtension(pollingExtension);
createComponent();
});
it('does not make additional requests after poll is successful', () => {
// called two times due to parent component polling (mount) and extension polling
expect(pollRequest).toHaveBeenCalledTimes(2);
expect(pollStop).toHaveBeenCalledTimes(1);
});
});
describe('error', () => {
let captureException;
beforeEach(() => {
captureException = jest.spyOn(Sentry, 'captureException');
registerExtension(pollingErrorExtension);
createComponent();
});
it('does not make additional requests after poll has failed', () => {
// called two times due to parent component polling (mount) and extension polling
expect(pollRequest).toHaveBeenCalledTimes(2);
expect(pollStop).toHaveBeenCalledTimes(1);
});
it('captures sentry error and displays error when poll has failed', () => {
expect(captureException).toHaveBeenCalledTimes(1);
expect(captureException).toHaveBeenCalledWith(new Error('Fetch error'));
expect(wrapper.findComponent(StatusIcon).props('iconName')).toBe('error');
});
});
}); });
describe('mock extension errors', () => { describe('mock extension errors', () => {
......
...@@ -97,3 +97,13 @@ export const fullDataErrorExtension = { ...@@ -97,3 +97,13 @@ export const fullDataErrorExtension = {
}, },
}, },
}; };
export const pollingExtension = {
...workingExtension,
enablePolling: true,
};
export const pollingErrorExtension = {
...collapsedDataErrorExtension,
enablePolling: true,
};
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment