Fixes 'Can't update during existing transition' warning message (#33880)

* Move files to TS files

* Fixes the React warning message

* Strongly type the actions and the middleware

* Adds tests for in_flight middleware

* Clean up

* Ignore non-ts file
This commit is contained in:
Corey Robertson 2019-04-09 16:51:52 -04:00 committed by GitHub
parent fcd60ca0d9
commit 85aac53fee
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 216 additions and 69 deletions

View file

@ -8,13 +8,12 @@ import { connect } from 'react-redux';
import { fetchAllRenderables } from '../../state/actions/elements';
import { setRefreshInterval } from '../../state/actions/workpad';
import { getInFlight } from '../../state/selectors/resolved_args';
import { getRefreshInterval, getElementStats } from '../../state/selectors/workpad';
import { getRefreshInterval } from '../../state/selectors/workpad';
import { RefreshControl as Component } from './refresh_control';
const mapStateToProps = state => ({
inFlight: getInFlight(state),
refreshInterval: getRefreshInterval(state),
elementStats: getElementStats(state),
});
const mapDispatchToProps = {

View file

@ -8,7 +8,6 @@ import React from 'react';
import PropTypes from 'prop-types';
import { EuiButtonEmpty } from '@elastic/eui';
import { Popover } from '../popover';
import { loadingIndicator } from '../../lib/loading_indicator';
import { AutoRefreshControls } from './auto_refresh_controls';
const getRefreshInterval = (val = '') => {
@ -37,21 +36,7 @@ const getRefreshInterval = (val = '') => {
}
};
export const RefreshControl = ({
inFlight,
elementStats,
setRefreshInterval,
refreshInterval,
doRefresh,
}) => {
const { pending } = elementStats;
if (inFlight || pending > 0) {
loadingIndicator.show();
} else {
loadingIndicator.hide();
}
export const RefreshControl = ({ inFlight, setRefreshInterval, refreshInterval, doRefresh }) => {
const setRefresh = val => setRefreshInterval(getRefreshInterval(val));
const popoverButton = handleClick => (

View file

@ -9,6 +9,11 @@ import { loadingCount } from 'ui/chrome';
let isActive = false;
export interface LoadingIndicatorInterface {
show: () => void;
hide: () => void;
}
export const loadingIndicator = {
show: () => {
if (!isActive) {

View file

@ -1,16 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { createAction } from 'redux-actions';
export const setLoading = createAction('setResolvedLoading');
export const setValue = createAction('setResolvedValue');
export const setValues = createAction('setResolvedValues');
export const clearValue = createAction('clearResolvedValue');
export const clearValues = createAction('clearResolvedValues');
export const inFlightActive = createAction('inFlightActive');
export const inFlightComplete = createAction('inFlightComplete');

View file

@ -0,0 +1,45 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { Action } from 'redux';
import { createAction } from 'redux-actions';
export const setLoadingActionType = 'setResolvedLoading';
export const setValueActionType = 'setResolvedValue';
export const inFlightActiveActionType = 'inFlightActive';
export const inFlightCompleteActionType = 'inFlightComplete';
type InFlightActive = Action<typeof inFlightActiveActionType>;
type InFlightComplete = Action<typeof inFlightCompleteActionType>;
interface SetResolvedLoadingPayload {
path: any[];
}
type SetResolvedLoading = Action<typeof setLoadingActionType> & {
payload: SetResolvedLoadingPayload;
};
interface SetResolvedValuePayload {
path: any[];
value: any;
}
type SetResolvedValue = Action<typeof setValueActionType> & {
payload: SetResolvedValuePayload;
};
export type Action = SetResolvedLoading | SetResolvedValue | InFlightActive | InFlightComplete;
export const setLoading = createAction<SetResolvedLoadingPayload>(setLoadingActionType);
export const setValue = createAction<SetResolvedValuePayload>(setValueActionType);
export const setValues = createAction('setResolvedValues');
export const clearValue = createAction('clearResolvedValue');
export const clearValues = createAction('clearResolvedValues');
export const inFlightActive = createAction<undefined>(inFlightActiveActionType, () => undefined);
export const inFlightComplete = createAction<undefined>(
inFlightCompleteActionType,
() => undefined
);

View file

@ -0,0 +1,98 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import {
inFlightActive,
inFlightComplete,
setLoading,
setValue,
} from '../../actions/resolved_args';
import { inFlightMiddlewareFactory } from '../in_flight';
const next = jest.fn();
const dispatch = jest.fn();
const loadingIndicator = {
show: jest.fn(),
hide: jest.fn(),
};
const pendingCache: string[] = [];
const testMiddleware = inFlightMiddlewareFactory({
loadingIndicator,
pendingCache,
})({ dispatch, getState: jest.fn() })(next);
describe('inflight middleware', () => {
beforeEach(() => {
dispatch.mockClear();
});
describe('loading indicator', () => {
beforeEach(() => {
loadingIndicator.show = jest.fn();
loadingIndicator.hide = jest.fn();
});
it('shows the loading indicator on inFlightActive action', () => {
const inFlightActiveAction = inFlightActive();
testMiddleware(inFlightActiveAction);
expect(loadingIndicator.show).toBeCalled();
});
it('hides the loading indicator on inFlightComplete action', () => {
const inFlightCompleteAction = inFlightComplete();
testMiddleware(inFlightCompleteAction);
expect(loadingIndicator.hide).toBeCalled();
});
describe('value', () => {
beforeEach(() => {
while (pendingCache.length) {
pendingCache.pop();
}
});
it('dispatches the inFlightAction for loadingValue actions', () => {
const path = ['some', 'path'];
const loadingAction = setLoading({ path });
testMiddleware(loadingAction);
expect(dispatch).toBeCalledWith(inFlightActive());
});
it('adds path to pendingCache for loadingValue actions', () => {
const expectedPath = 'path';
const path = [expectedPath];
const loadingAction = setLoading({ path });
testMiddleware(loadingAction);
expect(pendingCache[0]).toBe(expectedPath);
});
it('dispatches inFlight complete if all pending is resolved', () => {
const resolvedPath1 = 'path1';
const resolvedPath2 = 'path2';
const setAction1 = setValue({ path: [resolvedPath1], value: {} });
const setAction2 = setValue({ path: [resolvedPath2], value: {} });
pendingCache.push(resolvedPath1);
pendingCache.push(resolvedPath2);
testMiddleware(setAction1);
expect(dispatch).not.toBeCalled();
testMiddleware(setAction2);
expect(dispatch).toBeCalledWith(inFlightComplete());
});
});
});
});

View file

@ -1,35 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { convert } from '../../lib/modify_path';
import { setLoading, setValue, inFlightActive, inFlightComplete } from '../actions/resolved_args';
export const inFlight = ({ dispatch }) => next => {
const pendingCache = [];
return action => {
const isLoading = action.type === setLoading.toString();
const isSetting = action.type === setValue.toString();
if (isLoading || isSetting) {
const cacheKey = convert(action.payload.path).join('/');
if (isLoading) {
pendingCache.push(cacheKey);
dispatch(inFlightActive());
} else if (isSetting) {
const idx = pendingCache.indexOf(cacheKey);
pendingCache.splice(idx, 1);
if (pendingCache.length === 0) {
dispatch(inFlightComplete());
}
}
}
// execute the action
next(action);
};
};

View file

@ -0,0 +1,66 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { Dispatch, Middleware } from 'redux';
import {
loadingIndicator as defaultLoadingIndicator,
LoadingIndicatorInterface,
} from '../../lib/loading_indicator';
// @ts-ignore
import { convert } from '../../lib/modify_path';
interface InFlightMiddlewareOptions {
pendingCache: string[];
loadingIndicator: LoadingIndicatorInterface;
}
import {
Action as AnyAction,
inFlightActive,
inFlightActiveActionType,
inFlightComplete,
inFlightCompleteActionType,
setLoadingActionType,
setValueActionType,
} from '../actions/resolved_args';
const pathToKey = (path: any[]) => convert(path).join('/');
export const inFlightMiddlewareFactory = ({
loadingIndicator,
pendingCache,
}: InFlightMiddlewareOptions): Middleware => {
return ({ dispatch }) => (next: Dispatch) => {
return (action: AnyAction) => {
if (action.type === setLoadingActionType) {
const cacheKey = pathToKey(action.payload.path);
pendingCache.push(cacheKey);
dispatch(inFlightActive());
} else if (action.type === setValueActionType) {
const cacheKey = pathToKey(action.payload.path);
const idx = pendingCache.indexOf(cacheKey);
if (idx >= 0) {
pendingCache.splice(idx, 1);
}
if (pendingCache.length === 0) {
dispatch(inFlightComplete());
}
} else if (action.type === inFlightActiveActionType) {
loadingIndicator.show();
} else if (action.type === inFlightCompleteActionType) {
loadingIndicator.hide();
}
// execute the action
next(action);
};
};
};
export const inFlight = inFlightMiddlewareFactory({
loadingIndicator: defaultLoadingIndicator,
pendingCache: [],
});