Commit 423dfa6d authored by Payton Burdette's avatar Payton Burdette Committed by Phil Hughes

Add polling to base component

Add polling ability to the MR widget
base component for extensions to use.
parent 00c39d32
...@@ -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;
this.fetchCollapsedData(this.$props) if (this.$options.enablePolling) {
.then((data) => { this.initExtensionPolling();
this.collapsedData = data; } else {
this.loadingState = null; this.fetchCollapsedData(this.$props)
}) .then((data) => {
.catch((e) => { this.setCollapsedData(data);
this.loadingState = LOADING_STATES.collapsedError; })
.catch((e) => {
this.setCollapsedError(e);
});
}
},
setCollapsedData(data) {
this.collapsedData = data;
this.loadingState = null;
},
setCollapsedError(e) {
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,34 +81,17 @@ export default { ...@@ -81,34 +81,17 @@ 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) => {
}, return data[key];
data: this.terraformReportsPath, });
method: 'fetchPlans', })
successCallback: ({ data }) => { .catch(() => {
if (Object.keys(data).length > 0) { const invalidData = { tf_report_error: 'api_error' };
poll.stop(); return [invalidData];
const result = Object.keys(data).map((key) => {
return data[key];
});
resolve(result);
}
},
errorCallback: () => {
const invalidData = { tf_report_error: 'api_error' };
poll.stop();
const result = [invalidData];
resolve(result);
},
}); });
poll.makeRequest();
});
}, },
createReportRow(report, iconName) { createReportRow(report, iconName) {
const addNum = Number(report.create); const addNum = Number(report.create);
......
...@@ -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