Commit 93051179 by Hugo Häggmark Committed by GitHub

BackendSrv: Fixes queue countdown when unsubscribe is before response (#28323)

parent 5036c875
...@@ -26,30 +26,28 @@ const getTestContext = () => { ...@@ -26,30 +26,28 @@ const getTestContext = () => {
const fetchMock = jest.fn().mockReturnValue(fetchResult); const fetchMock = jest.fn().mockReturnValue(fetchResult);
const setInProgressMock = jest.fn(); const setInProgressMock = jest.fn();
const setDoneMock = jest.fn();
const queueMock: FetchQueue = ({ const queueMock: FetchQueue = ({
add: jest.fn(), add: jest.fn(),
setInProgress: setInProgressMock, setInProgress: setInProgressMock,
setDone: setDoneMock, setDone: jest.fn(),
getUpdates: jest.fn(), getUpdates: jest.fn(),
} as unknown) as FetchQueue; } as unknown) as FetchQueue;
const responseQueue = new ResponseQueue(queueMock, fetchMock); const responseQueue = new ResponseQueue(queueMock, fetchMock);
return { id, options, expects, fetchMock, setInProgressMock, setDoneMock, responseQueue, fetchResult }; return { id, options, expects, fetchMock, setInProgressMock, responseQueue, fetchResult };
}; };
describe('ResponseQueue', () => { describe('ResponseQueue', () => {
describe('add', () => { describe('add', () => {
describe('when called', () => { describe('when called', () => {
it('then the matching fetchQueue entry should be set to inProgress', () => { it('then the matching fetchQueue entry should be set to inProgress', () => {
const { id, options, setInProgressMock, setDoneMock, responseQueue } = getTestContext(); const { id, options, setInProgressMock, responseQueue } = getTestContext();
responseQueue.add(id, options); responseQueue.add(id, options);
expect(setInProgressMock.mock.calls).toEqual([['id']]); expect(setInProgressMock.mock.calls).toEqual([['id']]);
expect(setDoneMock).not.toHaveBeenCalled();
}); });
it('then a response entry with correct id should be published', done => { it('then a response entry with correct id should be published', done => {
...@@ -81,14 +79,13 @@ describe('ResponseQueue', () => { ...@@ -81,14 +79,13 @@ describe('ResponseQueue', () => {
describe('and when the fetch Observable is completed', () => { describe('and when the fetch Observable is completed', () => {
it('then the matching fetchQueue entry should be set to Done', done => { it('then the matching fetchQueue entry should be set to Done', done => {
const { id, options, responseQueue, setInProgressMock, setDoneMock } = getTestContext(); const { id, options, responseQueue, setInProgressMock } = getTestContext();
subscribeTester({ subscribeTester({
observable: responseQueue.getResponses(id).pipe(first()), observable: responseQueue.getResponses(id).pipe(first()),
expectCallback: data => { expectCallback: data => {
data.observable.subscribe().unsubscribe(); data.observable.subscribe().unsubscribe();
expect(setInProgressMock.mock.calls).toEqual([['id']]); expect(setInProgressMock.mock.calls).toEqual([['id']]);
expect(setDoneMock.mock.calls).toEqual([['id']]);
}, },
doneCallback: done, doneCallback: done,
}); });
......
import { Observable, Subject } from 'rxjs'; import { Observable, Subject } from 'rxjs';
import { filter, finalize } from 'rxjs/operators'; import { filter } from 'rxjs/operators';
import { BackendSrvRequest, FetchResponse } from '@grafana/runtime'; import { BackendSrvRequest, FetchResponse } from '@grafana/runtime';
import { FetchQueue } from './FetchQueue'; import { FetchQueue } from './FetchQueue';
...@@ -27,17 +27,7 @@ export class ResponseQueue { ...@@ -27,17 +27,7 @@ export class ResponseQueue {
// Let the fetchQueue know that this id has started data fetching. // Let the fetchQueue know that this id has started data fetching.
fetchQueue.setInProgress(id); fetchQueue.setInProgress(id);
this.responses.next({ this.responses.next({ id, observable: fetch(options) });
id,
observable: fetch(options).pipe(
// finalize is called whenever this observable is unsubscribed/errored/completed/canceled
// https://rxjs.dev/api/operators/finalize
finalize(() => {
// Let the fetchQueue know that this id is done.
fetchQueue.setDone(id);
})
),
});
}); });
} }
......
...@@ -65,10 +65,11 @@ export class BackendSrv implements BackendService { ...@@ -65,10 +65,11 @@ export class BackendSrv implements BackendService {
} }
fetch<T>(options: BackendSrvRequest): Observable<FetchResponse<T>> { fetch<T>(options: BackendSrvRequest): Observable<FetchResponse<T>> {
return new Observable(observer => { // We need to match an entry added to the queue stream with the entry that is eventually added to the response stream
// We need to match an entry added to the queue stream with the entry that is eventually added to the response stream const id = uuidv4();
const id = uuidv4(); const fetchQueue = this.fetchQueue;
return new Observable(observer => {
// Subscription is an object that is returned whenever you subscribe to an Observable. // Subscription is an object that is returned whenever you subscribe to an Observable.
// You can also use it as a container of many subscriptions and when it is unsubscribed all subscriptions within are also unsubscribed. // You can also use it as a container of many subscriptions and when it is unsubscribed all subscriptions within are also unsubscribed.
const subscriptions: Subscription = new Subscription(); const subscriptions: Subscription = new Subscription();
...@@ -89,6 +90,9 @@ export class BackendSrv implements BackendService { ...@@ -89,6 +90,9 @@ export class BackendSrv implements BackendService {
// This returned function will be called whenever the returned Observable from the fetch<T> function is unsubscribed/errored/completed/canceled. // This returned function will be called whenever the returned Observable from the fetch<T> function is unsubscribed/errored/completed/canceled.
return function unsubscribe() { return function unsubscribe() {
// Change status to Done moved here from ResponseQueue because this unsubscribe was called before the responseQueue produced a result
fetchQueue.setDone(id);
// When subscriptions is unsubscribed all the implicitly added subscriptions above are also unsubscribed. // When subscriptions is unsubscribed all the implicitly added subscriptions above are also unsubscribed.
subscriptions.unsubscribe(); subscriptions.unsubscribe();
}; };
......
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