Commit 6b887cc3 authored by Phil Hughes's avatar Phil Hughes

Merge branch '217184-Add-Delay-Funcationality-to-Polling' into 'master'

Create Poll#makeDelayedRequest Method and Implement on Security Dashboard/Vulnerabilities

Closes #217184

See merge request gitlab-org/gitlab!39782
parents 3476df8d 5ffec09f
......@@ -46,6 +46,19 @@ import { normalizeHeaders } from './common_utils';
* 4. If HTTP response is 200, we poll.
* 5. If HTTP response is different from 200, we stop polling.
*
* @example
* // With initial delay (for, for example, reducing unnecessary requests)
*
* const poll = new Poll({
* resource: this.service,
* method: 'fetchNotes',
* successCallback: () => {},
* errorCallback: () => {},
* });
*
* // Performs the first request in 2.5s and then uses the `Poll-Interval` header.
* poll.makeDelayedRequest(2500);
*
*/
export default class Poll {
constructor(options = {}) {
......@@ -74,6 +87,10 @@ export default class Poll {
this.options.successCallback(response);
}
makeDelayedRequest(delay = 0) {
this.timeoutID = setTimeout(() => this.makeRequest(), delay);
}
makeRequest() {
const { resource, method, data, errorCallback, notificationCallback } = this.options;
......
......@@ -116,7 +116,8 @@ export default {
if (!this.poll) this.createNotesPoll();
if (!Visibility.hidden()) {
this.poll.makeRequest();
// delays the initial request by 6 seconds
this.poll.makeDelayedRequest(6 * 1000);
}
Visibility.change(() => {
......
......@@ -159,12 +159,23 @@ describe('Vulnerability Footer', () => {
});
describe('new notes polling', () => {
jest.useFakeTimers();
const getDiscussion = (entries, index) => entries.at(index).props('discussion');
const createNotesRequest = (...notes) =>
mockAxios
.onGet(minimumProps.notesUrl)
.replyOnce(200, { notes, last_fetched_at: Date.now() });
// Following #217184 the vulnerability polling uses an initial timeout
// which we need to run and then wait for the subsequent request.
const startTimeoutsAndAwaitRequests = async () => {
expect(setTimeout).toHaveBeenCalledTimes(1);
jest.runAllTimers();
return axios.waitForAll();
};
beforeEach(() => {
const historyItems = [
{ id: 1, notes: [{ id: 100, note: 'some note', discussion_id: 1 }] },
......@@ -178,7 +189,9 @@ describe('Vulnerability Footer', () => {
const note = { id: 100, note: 'updated note', discussion_id: 1 };
createNotesRequest(note);
return axios.waitForAll().then(() => {
return axios.waitForAll().then(async () => {
await startTimeoutsAndAwaitRequests();
const entries = historyEntries();
expect(entries).toHaveLength(2);
const discussion = getDiscussion(entries, 0);
......@@ -191,7 +204,9 @@ describe('Vulnerability Footer', () => {
const note = { id: 101, note: 'new note', discussion_id: 1 };
createNotesRequest(note);
return axios.waitForAll().then(() => {
return axios.waitForAll().then(async () => {
await startTimeoutsAndAwaitRequests();
const entries = historyEntries();
expect(entries).toHaveLength(2);
const discussion = getDiscussion(entries, 0);
......@@ -204,7 +219,9 @@ describe('Vulnerability Footer', () => {
const note = { id: 300, note: 'new note on a new discussion', discussion_id: 3 };
createNotesRequest(note);
return axios.waitForAll().then(() => {
return axios.waitForAll().then(async () => {
await startTimeoutsAndAwaitRequests();
const entries = historyEntries();
expect(entries).toHaveLength(3);
const discussion = getDiscussion(entries, 2);
......@@ -226,7 +243,9 @@ describe('Vulnerability Footer', () => {
it('shows an error if the notes poll fails', () => {
mockAxios.onGet(minimumProps.notesUrl).replyOnce(500);
return axios.waitForAll().then(() => {
return axios.waitForAll().then(async () => {
await startTimeoutsAndAwaitRequests();
expect(historyEntries()).toHaveLength(2);
expect(mockAxios.history.get).toHaveLength(2);
expect(createFlash).toHaveBeenCalled();
......@@ -239,6 +258,8 @@ describe('Vulnerability Footer', () => {
createNotesRequest(note);
await axios.waitForAll();
await startTimeoutsAndAwaitRequests();
expect(spy).toHaveBeenCalledTimes(1);
expect(spy).toHaveBeenCalledWith('VULNERABILITY_STATE_CHANGED');
});
......
......@@ -128,6 +128,35 @@ describe('Poll', () => {
});
});
describe('with delayed initial request', () => {
it('delays the first request', async done => {
mockServiceCall({ status: 200, headers: { 'poll-interval': 1 } });
const Polling = new Poll({
resource: service,
method: 'fetch',
data: { page: 1 },
successCallback: callbacks.success,
errorCallback: callbacks.error,
});
Polling.makeDelayedRequest(1);
expect(Polling.timeoutID).toBeTruthy();
waitForAllCallsToFinish(2, () => {
Polling.stop();
expect(service.fetch.mock.calls).toHaveLength(2);
expect(service.fetch).toHaveBeenCalledWith({ page: 1 });
expect(callbacks.success).toHaveBeenCalled();
expect(callbacks.error).not.toHaveBeenCalled();
done();
});
});
});
describe('stop', () => {
it('stops polling when method is called', done => {
mockServiceCall({ status: 200, headers: { 'poll-interval': 1 } });
......
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