Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Typescript dashboard app code. #37527

2 changes: 1 addition & 1 deletion packages/kbn-es-query/src/filters/lib/meta_filter.ts
Expand Up @@ -40,7 +40,7 @@ export interface FilterMeta {
export interface Filter {
$state: FilterState;
meta: FilterMeta;
query?: any;
query?: object;
}

export interface LatLon {
Expand Down
Expand Up @@ -17,14 +17,14 @@
* under the License.
*/

import { IAppState } from 'ui/state_management/app_state';
import { AppStateClass } from 'ui/state_management/app_state';

/**
* A poor excuse for a mock just to get some basic tests to run in jest without requiring the injector.
* This could be improved if we extract the appState and state classes externally of their angular providers.
* @return {AppStateMock}
*/
export function getAppStateMock(): IAppState {
export function getAppStateMock(): AppStateClass {
class AppStateMock {
constructor(defaults: any) {
Object.assign(this, defaults);
Expand Down
Expand Up @@ -22,7 +22,7 @@
import { createAction } from 'redux-actions';
import { KibanaAction } from '../../selectors/types';
import { PanelId } from '../selectors';
import { SavedDashboardPanel } from '../types';
import { SavedDashboardPanel, SavedDashboardPanelMap } from '../types';

export enum PanelActionTypeKeys {
DELETE_PANEL = 'DELETE_PANEL',
Expand All @@ -40,7 +40,7 @@ export interface UpdatePanelAction
extends KibanaAction<PanelActionTypeKeys.UPDATE_PANEL, SavedDashboardPanel> {}

export interface UpdatePanelsAction
extends KibanaAction<PanelActionTypeKeys.UPDATE_PANELS, { [key: string]: SavedDashboardPanel }> {}
extends KibanaAction<PanelActionTypeKeys.UPDATE_PANELS, SavedDashboardPanelMap> {}

export interface ResetPanelTitleAction
extends KibanaAction<PanelActionTypeKeys.RESET_PANEL_TITLE, PanelId> {}
Expand All @@ -54,7 +54,7 @@ export interface SetPanelTitleAction
extends KibanaAction<PanelActionTypeKeys.SET_PANEL_TITLE, SetPanelTitleActionPayload> {}

export interface SetPanelsAction
extends KibanaAction<PanelActionTypeKeys.SET_PANELS, { [key: string]: SavedDashboardPanel }> {}
extends KibanaAction<PanelActionTypeKeys.SET_PANELS, SavedDashboardPanelMap> {}

export type PanelActions =
| DeletePanelAction
Expand All @@ -70,9 +70,5 @@ export const resetPanelTitle = createAction<PanelId>(PanelActionTypeKeys.RESET_P
export const setPanelTitle = createAction<SetPanelTitleActionPayload>(
PanelActionTypeKeys.SET_PANEL_TITLE
);
export const updatePanels = createAction<{ [key: string]: SavedDashboardPanel }>(
PanelActionTypeKeys.UPDATE_PANELS
);
export const setPanels = createAction<{ [key: string]: SavedDashboardPanel }>(
PanelActionTypeKeys.SET_PANELS
);
export const updatePanels = createAction<SavedDashboardPanelMap>(PanelActionTypeKeys.UPDATE_PANELS);
export const setPanels = createAction<SavedDashboardPanelMap>(PanelActionTypeKeys.SET_PANELS);
Expand Up @@ -108,4 +108,4 @@ export const updateRefreshConfig = createAction<RefreshConfig>(
ViewActionTypeKeys.UPDATE_REFRESH_CONFIG
);
export const updateFilters = createAction<Filters>(ViewActionTypeKeys.UPDATE_FILTERS);
export const updateQuery = createAction<Query>(ViewActionTypeKeys.UPDATE_QUERY);
export const updateQuery = createAction<Query | string>(ViewActionTypeKeys.UPDATE_QUERY);
Expand Up @@ -22,31 +22,38 @@ import { DashboardViewMode } from './dashboard_view_mode';
import { embeddableIsInitialized, setPanels } from './actions';
import { getAppStateMock, getSavedDashboardMock } from './__tests__';
import { store } from '../store';
import { IAppState } from 'ui/state_management/app_state';
import { AppStateClass } from 'ui/state_management/app_state';
import { DashboardAppState } from './types';
import { TimeRange } from 'ui/embeddable';
import { IndexPattern } from 'ui/index_patterns';
import { Timefilter } from 'ui/timefilter';

jest.mock('ui/chrome', () => ({ getKibanaVersion: () => '6.0.0' }), { virtual: true });

describe('DashboardState', function() {
let dashboardState: DashboardStateManager;
const savedDashboard = getSavedDashboardMock();
const mockTimefilter: {
time: TimeRange;
setTime: (time: TimeRange) => void;
} = {
const mockTimefilter: Timefilter = {
time: { to: 'now', from: 'now-15m' },
setTime(time) {
this.time = time;
},
getTime() {
return this.time;
},
disableAutoRefreshSelector: jest.fn(),
setRefreshInterval: jest.fn(),
getRefreshInterval: jest.fn(),
disableTimeRangeSelector: jest.fn(),
enableAutoRefreshSelector: jest.fn(),
off: jest.fn(),
on: jest.fn(),
};
const mockIndexPattern: IndexPattern = { id: 'index1', fields: [], title: 'hi' };

function initDashboardState() {
dashboardState = new DashboardStateManager({
savedDashboard,
AppStateClass: getAppStateMock() as IAppState<DashboardAppState>,
AppStateClass: getAppStateMock() as AppStateClass<DashboardAppState>,
hideWriteControls: false,
addFilter: () => {},
});
Expand Down
Expand Up @@ -20,11 +20,12 @@
import { i18n } from '@kbn/i18n';
import _ from 'lodash';

import { stateMonitorFactory } from 'ui/state_management/state_monitor_factory';
import { stateMonitorFactory, StateMonitor } from 'ui/state_management/state_monitor_factory';
import { StaticIndexPattern } from 'ui/index_patterns';
import { IAppState } from 'ui/state_management/app_state';
import { TimeRange, Filter, Query } from 'ui/embeddable';
import { AppStateClass as TAppStateClass } from 'ui/state_management/app_state';
import { TimeRange, Query } from 'ui/embeddable';
import { Timefilter } from 'ui/timefilter';
import { Filter } from '@kbn/es-query';
import { DashboardViewMode } from './dashboard_view_mode';
import { FilterUtils } from './lib/filter_utils';
import { PanelUtils } from './panel/panel_utils';
Expand Down Expand Up @@ -65,16 +66,16 @@ import {
getFilters,
} from '../selectors';
import { SavedObjectDashboard } from './saved_dashboard/saved_dashboard';
import { DashboardAppState, SavedDashboardPanel } from './types';
import {
DashboardAppState,
SavedDashboardPanel,
SavedDashboardPanelMap,
StagedFilter,
DashboardAppStateParameters,
} from './types';
import moment = require('moment');
lukeelmers marked this conversation as resolved.
Show resolved Hide resolved

export type AddFilterFuntion = (
{
field,
value,
operator,
index,
}: { field: string; value: string; operator: string; index: string }
) => void;
export type AddFilterFuntion = ({ field, value, operator, index }: StagedFilter) => void;

/**
* Dashboard state manager handles connecting angular and redux state between the angular and react portions of the
Expand All @@ -85,12 +86,17 @@ export type AddFilterFuntion = (
export class DashboardStateManager {
public savedDashboard: SavedObjectDashboard;
public appState: DashboardAppState;
public lastSavedDashboardFilters: any;
private stateDefaults: any;
public lastSavedDashboardFilters: {
timeTo?: string | moment.Moment;
timeFrom?: string | moment.Moment;
filterBars: Filter[];
query: Query | string;
};
private stateDefaults: DashboardAppStateParameters;
private hideWriteControls: boolean;
public isDirty: boolean;
private changeListeners: any[];
private stateMonitor: any;
private changeListeners: Array<(status: { dirty: boolean }) => void>;
private stateMonitor: StateMonitor<DashboardAppStateParameters>;
private panelIndexPatternMapping: { [key: string]: StaticIndexPattern[] } = {};
private addFilter: AddFilterFuntion;
private unsubscribe: () => void;
Expand All @@ -109,7 +115,7 @@ export class DashboardStateManager {
addFilter,
}: {
savedDashboard: SavedObjectDashboard;
AppStateClass: IAppState<DashboardAppState>;
AppStateClass: TAppStateClass<DashboardAppState>;
hideWriteControls: boolean;
addFilter: AddFilterFuntion;
}) {
Expand Down Expand Up @@ -140,7 +146,23 @@ export class DashboardStateManager {

PanelUtils.initPanelIndexes(this.getPanels());

this.createStateMonitor();
/**
* Creates a state monitor and saves it to this.stateMonitor. Used to track unsaved changes made to appState.
*/
this.stateMonitor = stateMonitorFactory.create<DashboardAppStateParameters>(
this.appState,
this.stateDefaults
);

this.stateMonitor.ignoreProps('viewMode');
// Filters need to be compared manually because they sometimes have a $$hashkey stored on the object.
this.stateMonitor.ignoreProps('filters');
// Query needs to be compared manually because saved legacy queries get migrated in app state automatically
this.stateMonitor.ignoreProps('query');

this.stateMonitor.onChange((status: { dirty: boolean }) => {
this.isDirty = status.dirty;
});

store.dispatch(closeContextMenu());

Expand Down Expand Up @@ -178,16 +200,14 @@ export class DashboardStateManager {

/**
* Time is part of global state so we need to deal with it outside of pushAppStateChangesToStore.
* @param {String|Object} newTimeFilter.to -- either a string representing an absolute time in utc format,
* or a relative time (now-15m), or a moment object
* @param {String|Object} newTimeFilter.from - either a string representing an absolute or a relative time, or a
* moment object
*/
public handleTimeChange(newTimeFilter: Timefilter) {
public handleTimeChange(newTimeRange: TimeRange) {
const from = FilterUtils.convertTimeToUTCString(newTimeRange.from);
const to = FilterUtils.convertTimeToUTCString(newTimeRange.to);
store.dispatch(
updateTimeRange({
from: FilterUtils.convertTimeToUTCString(newTimeFilter.from).toString(),
to: FilterUtils.convertTimeToUTCString(newTimeFilter.to).toString(),
from: from ? from.toString() : '',
to: to ? to.toString() : '',
})
);
}
Expand All @@ -211,13 +231,10 @@ export class DashboardStateManager {
if (!this.areStoreAndAppStatePanelsEqual()) {
// Translate appState panels data into the data expected by redux, copying the panel objects as we do so
// because the panels inside appState can be mutated, while redux state should never be mutated directly.
const panelsMap = this.getPanels().reduce(
(acc: { [key: string]: SavedDashboardPanel }, panel) => {
acc[panel.panelIndex] = _.cloneDeep(panel);
return acc;
},
{}
);
const panelsMap = this.getPanels().reduce((acc: SavedDashboardPanelMap, panel) => {
acc[panel.panelIndex] = _.cloneDeep(panel);
return acc;
}, {});
store.dispatch(setPanels(panelsMap));
}

Expand Down Expand Up @@ -282,7 +299,7 @@ export class DashboardStateManager {
private handleStoreChanges() {
let dirty = false;
if (!this.areStoreAndAppStatePanelsEqual()) {
const panels: { [key: string]: SavedDashboardPanel } = getPanels(store.getState());
const panels: SavedDashboardPanelMap = getPanels(store.getState());
this.appState.panels = [];
this.panelIndexPatternMapping = {};
Object.values(panels).map((panel: SavedDashboardPanel) => {
Expand Down Expand Up @@ -449,11 +466,11 @@ export class DashboardStateManager {
return FilterUtils.getQueryFilterForDashboard(this.savedDashboard);
}

public getLastSavedFilterBars() {
public getLastSavedFilterBars(): Filter[] {
return this.lastSavedDashboardFilters.filterBars;
}

public getLastSavedQuery() {
public getLastSavedQuery(): Query | string {
return this.lastSavedDashboardFilters.query;
}

Expand All @@ -468,7 +485,7 @@ export class DashboardStateManager {
const isLegacyStringQuery =
_.isString(lastSavedQuery) && _.isPlainObject(currentQuery) && _.has(currentQuery, 'query');
if (isLegacyStringQuery) {
return lastSavedQuery !== currentQuery.query;
return (lastSavedQuery as string) !== (currentQuery as Query).query;
}

return !_.isEqual(currentQuery, lastSavedQuery);
Expand Down Expand Up @@ -525,7 +542,7 @@ export class DashboardStateManager {
*
* @returns {boolean} True if the dashboard has changed since the last save (or, is new).
*/
public getIsDirty(timeFilter?: TimeRange) {
public getIsDirty(timeFilter?: Timefilter) {
// Filter bar comparison is done manually (see cleanFiltersForComparison for the reason) and time picker
// changes are not tracked by the state monitor.
const hasTimeFilterChanged = timeFilter ? this.getFiltersChanged(timeFilter) : false;
Expand Down Expand Up @@ -638,23 +655,6 @@ export class DashboardStateManager {
this._pushFiltersToStore();
}

/**
* Creates a state monitor and saves it to this.stateMonitor. Used to track unsaved changes made to appState.
*/
public createStateMonitor() {
this.stateMonitor = stateMonitorFactory.create(this.appState, this.stateDefaults);

this.stateMonitor.ignoreProps('viewMode');
// Filters need to be compared manually because they sometimes have a $$hashkey stored on the object.
this.stateMonitor.ignoreProps('filters');
// Query needs to be compared manually because saved legacy queries get migrated in app state automatically
this.stateMonitor.ignoreProps('query');

this.stateMonitor.onChange((status: { dirty: boolean }) => {
this.isDirty = status.dirty;
});
}

/**
* @param newMode {DashboardViewMode}
*/
Expand Down
Expand Up @@ -37,7 +37,12 @@ import {
import { DashboardViewMode } from '../dashboard_view_mode';
import { DashboardPanel } from '../panel';
import { PanelUtils } from '../panel/panel_utils';
import { GridData, SavedDashboardPanel, Pre61SavedDashboardPanel } from '../types';
import {
GridData,
SavedDashboardPanel,
Pre61SavedDashboardPanel,
SavedDashboardPanelMap,
} from '../types';

let lastValidGridSize = 0;

Expand Down Expand Up @@ -116,10 +121,10 @@ const config = { monitorWidth: true };
const ResponsiveSizedGrid = sizeMe(config)(ResponsiveGrid);

interface Props extends ReactIntl.InjectedIntlProps {
panels: { [key: string]: SavedDashboardPanel };
panels: SavedDashboardPanelMap;
getEmbeddableFactory: (panelType: string) => EmbeddableFactory;
dashboardViewMode: DashboardViewMode.EDIT | DashboardViewMode.VIEW;
onPanelsUpdated: (updatedPanels: { [key: string]: SavedDashboardPanel }) => void;
onPanelsUpdated: (updatedPanels: SavedDashboardPanelMap) => void;
maximizedPanelId?: string;
useMargins: boolean;
}
Expand Down Expand Up @@ -192,7 +197,7 @@ class DashboardGridUi extends React.Component<Props, State> {
});
}

public createEmbeddableFactoriesMap(panels: { [key: string]: SavedDashboardPanel }) {
public createEmbeddableFactoriesMap(panels: SavedDashboardPanelMap) {
Object.values(panels).map(panel => {
if (!this.embeddableFactoryMap[panel.type]) {
this.embeddableFactoryMap[panel.type] = this.props.getEmbeddableFactory(panel.type);
Expand All @@ -210,17 +215,14 @@ class DashboardGridUi extends React.Component<Props, State> {

public onLayoutChange = (layout: PanelLayout[]) => {
const { onPanelsUpdated, panels } = this.props;
const updatedPanels = layout.reduce(
(updatedPanelsAcc: { [key: string]: SavedDashboardPanel }, panelLayout) => {
updatedPanelsAcc[panelLayout.i] = {
...panels[panelLayout.i],
panelIndex: panelLayout.i,
gridData: _.pick(panelLayout, ['x', 'y', 'w', 'h', 'i']),
};
return updatedPanelsAcc;
},
{}
);
const updatedPanels = layout.reduce((updatedPanelsAcc: SavedDashboardPanelMap, panelLayout) => {
updatedPanelsAcc[panelLayout.i] = {
...panels[panelLayout.i],
panelIndex: panelLayout.i,
gridData: _.pick(panelLayout, ['x', 'y', 'w', 'h', 'i']),
};
return updatedPanelsAcc;
}, {});
onPanelsUpdated(updatedPanels);
};

Expand Down