refactor(ui): prevent concurrent fetches

Refactor the timer so instead of calling fetch directly every  seconds we call it every 1 second, check if we could & should fetch (fetch isn't already/still in progress and  seconds passed since last fetch) and if needed call fetch.
This will also allow us to pause fetches when user is interacting with alerts so there's no whack-a-mole with buttons
This commit is contained in:
Łukasz Mierzwa
2018-09-20 11:34:13 +01:00
parent 6020336d98
commit 08f7e6a9fa
2 changed files with 85 additions and 32 deletions

View File

@@ -1,10 +1,12 @@
import React, { Component } from "react";
import PropTypes from "prop-types";
import { toJS } from "mobx";
import { observable, action } from "mobx";
import { observer } from "mobx-react";
import { AlertStore } from "Stores/AlertStore";
import moment from "moment";
import { AlertStore, AlertStoreStatuses } from "Stores/AlertStore";
import { Settings } from "Stores/Settings";
const Fetcher = observer(
@@ -14,40 +16,53 @@ const Fetcher = observer(
settingsStore: PropTypes.instanceOf(Settings).isRequired
};
timer = null;
lastTick = observable(
{
time: moment(0),
update() {
this.time = moment();
}
},
{
update: action
}
);
interval = null;
setTimer() {
fetchIfIdle = () => {
const { alertStore, settingsStore } = this.props;
const newInterval = toJS(settingsStore.fetchConfig.config.interval);
const nextTick = moment(this.lastTick.time).add(
settingsStore.fetchConfig.config.interval,
"seconds"
);
if (this.interval !== newInterval) {
if (this.timer !== null) clearInterval(this.timer);
const pastDeadline = moment().isSameOrAfter(nextTick);
this.interval = newInterval;
this.timer = setInterval(
() => alertStore.fetchWithThrottle(),
this.interval * 1000
);
const status = alertStore.status.value.toString();
const updateInProgress =
status === AlertStoreStatuses.Fetching.toString() ||
status === AlertStoreStatuses.Processing.toString();
if (pastDeadline && !updateInProgress) {
this.lastTick.update();
alertStore.fetchWithThrottle();
}
};
timerTick = () => {
this.fetchIfIdle();
};
componentDidMount() {
this.fetchIfIdle();
this.timer = setInterval(this.timerTick, 1000);
}
componentDidUpdate() {
const { alertStore } = this.props;
this.lastTick.update();
alertStore.fetchWithThrottle();
this.setTimer();
}
componentDidMount() {
const { alertStore } = this.props;
alertStore.fetchWithThrottle();
this.setTimer();
}
componentWillUnmount() {

View File

@@ -2,6 +2,8 @@ import React from "react";
import { mount } from "enzyme";
import { advanceTo, advanceBy, clear } from "jest-date-mock";
import { EmptyAPIResponse } from "__mocks__/Fetch";
import { AlertStore } from "Stores/AlertStore";
@@ -9,24 +11,30 @@ import { Settings } from "Stores/Settings";
import { Fetcher } from ".";
let alertStore;
let settingsStore;
let fetchSpy;
beforeAll(() => {
jest.useFakeTimers();
});
let alertStore;
let settingsStore;
beforeEach(() => {
fetch.mockResponse(JSON.stringify(EmptyAPIResponse()));
advanceTo(new Date(2000, 1, 1, 0, 0, 0));
alertStore = new AlertStore(["label=value"]);
fetchSpy = jest
.spyOn(alertStore, "fetchWithThrottle")
.mockImplementation(() => {});
settingsStore = new Settings();
settingsStore.fetchConfig.config.interval = 30;
});
afterEach(() => {
jest.clearAllTimers();
global.fetch.mockRestore();
jest.clearAllMocks();
clear();
});
const MockEmptyAPIResponseWithoutFilters = () => {
@@ -57,6 +65,30 @@ describe("<Fetcher />", () => {
expect(tree.html()).toBe(FetcherSpan("label=value", 60));
});
it("changing interval changes how often fetch is called", () => {
settingsStore.fetchConfig.config.interval = 1;
MountedFetcher();
expect(fetchSpy).toHaveBeenCalledTimes(1);
settingsStore.fetchConfig.config.interval = 600;
advanceBy(3 * 1000);
jest.runOnlyPendingTimers();
expect(fetchSpy).toHaveBeenCalledTimes(2);
advanceBy(32 * 1000);
jest.runOnlyPendingTimers();
expect(fetchSpy).toHaveBeenCalledTimes(2);
advanceBy(62 * 1000);
jest.runOnlyPendingTimers();
expect(fetchSpy).toHaveBeenCalledTimes(2);
advanceBy(602 * 1000);
jest.runOnlyPendingTimers();
expect(fetchSpy).toHaveBeenCalledTimes(3);
});
it("re-renders on filters change", () => {
MockEmptyAPIResponseWithoutFilters();
const tree = MountedFetcher();
@@ -86,13 +118,19 @@ describe("<Fetcher />", () => {
expect(fetchSpy).toHaveBeenCalledTimes(2);
});
it("keeps calling alertStore.fetchWithThrottle after running pending timers", () => {
const fetchSpy = jest.spyOn(alertStore, "fetchWithThrottle");
it("keeps calling alertStore.fetchWithThrottle every minute", () => {
MountedFetcher();
expect(fetchSpy).toHaveBeenCalledTimes(1);
advanceBy(62 * 1000);
jest.runOnlyPendingTimers();
expect(fetchSpy).toHaveBeenCalledTimes(2);
advanceBy(62 * 1000);
jest.runOnlyPendingTimers();
expect(fetchSpy).toHaveBeenCalledTimes(3);
advanceBy(62 * 1000);
jest.runOnlyPendingTimers();
expect(fetchSpy).toHaveBeenCalledTimes(4);
});