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:
parent
fcd60ca0d9
commit
85aac53fee
|
@ -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 = {
|
||||
|
|
|
@ -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 => (
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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');
|
45
x-pack/plugins/canvas/public/state/actions/resolved_args.ts
Normal file
45
x-pack/plugins/canvas/public/state/actions/resolved_args.ts
Normal 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
|
||||
);
|
|
@ -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());
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -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);
|
||||
};
|
||||
};
|
66
x-pack/plugins/canvas/public/state/middleware/in_flight.ts
Normal file
66
x-pack/plugins/canvas/public/state/middleware/in_flight.ts
Normal 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: [],
|
||||
});
|
Loading…
Reference in a new issue