Typescript dashboard app code. (#37527)
* Typescript dashboard app code. Pulls out part of the massive embeddable API PR. I typscripted this code as part of that PR because it helped me find errors, but really unnecessarily blew up the size of that PR. So pulling out. * User filter types from kbn-es-query * Address code review feedback * Replace require with import
This commit is contained in:
parent
2177267ebc
commit
8d78f64391
|
@ -40,7 +40,7 @@ export interface FilterMeta {
|
|||
export interface Filter {
|
||||
$state: FilterState;
|
||||
meta: FilterMeta;
|
||||
query?: any;
|
||||
query?: object;
|
||||
}
|
||||
|
||||
export interface LatLon {
|
||||
|
|
|
@ -17,21 +17,31 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
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() {
|
||||
export function getAppStateMock(): AppStateClass {
|
||||
class AppStateMock {
|
||||
constructor(defaults) {
|
||||
constructor(defaults: any) {
|
||||
Object.assign(this, defaults);
|
||||
}
|
||||
|
||||
on() {}
|
||||
off() {}
|
||||
toJSON() { return ''; }
|
||||
toJSON() {
|
||||
return '';
|
||||
}
|
||||
save() {}
|
||||
translateHashToRison(stateHashOrRison: string | string[]) {
|
||||
return stateHashOrRison;
|
||||
}
|
||||
getQueryParamName() {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
return AppStateMock;
|
|
@ -18,7 +18,7 @@
|
|||
*/
|
||||
|
||||
/* global jest */
|
||||
export function getEmbeddableFactoryMock(config) {
|
||||
export function getEmbeddableFactoryMock(config?: any) {
|
||||
const embeddableFactoryMockDefaults = {
|
||||
create: jest.fn(() => Promise.resolve({})),
|
||||
};
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { SavedObjectDashboard } from '../saved_dashboard/saved_dashboard';
|
||||
|
||||
export function getSavedDashboardMock(
|
||||
config?: Partial<SavedObjectDashboard>
|
||||
): SavedObjectDashboard {
|
||||
return {
|
||||
id: '123',
|
||||
title: 'my dashboard',
|
||||
panelsJSON: '[]',
|
||||
searchSource: {
|
||||
getOwnField: (param: any) => param,
|
||||
setField: () => {},
|
||||
},
|
||||
copyOnSave: false,
|
||||
timeRestore: false,
|
||||
timeTo: 'now',
|
||||
timeFrom: 'now-15m',
|
||||
optionsJSON: '',
|
||||
lastSavedTitle: '',
|
||||
destroy: () => {},
|
||||
save: () => {
|
||||
return Promise.resolve('123');
|
||||
},
|
||||
...config,
|
||||
};
|
||||
}
|
|
@ -23,8 +23,9 @@ import _ from 'lodash';
|
|||
import { createAction } from 'redux-actions';
|
||||
import { EmbeddableMetadata, EmbeddableState } from 'ui/embeddable';
|
||||
import { getEmbeddableCustomization, getPanel } from '../../selectors';
|
||||
import { PanelId, PanelState } from '../selectors';
|
||||
import { PanelId } from '../selectors';
|
||||
import { updatePanel } from './panels';
|
||||
import { SavedDashboardPanel } from '../types';
|
||||
|
||||
import { KibanaAction, KibanaThunk } from '../../selectors/types';
|
||||
|
||||
|
@ -113,7 +114,7 @@ export function embeddableStateChanged(changeData: {
|
|||
const customization = getEmbeddableCustomization(getState(), panelId);
|
||||
if (!_.isEqual(embeddableState.customization, customization)) {
|
||||
const originalPanelState = getPanel(getState(), panelId);
|
||||
const newPanelState: PanelState = {
|
||||
const newPanelState: SavedDashboardPanel = {
|
||||
...originalPanelState,
|
||||
embeddableConfig: _.cloneDeep(embeddableState.customization),
|
||||
};
|
||||
|
|
|
@ -39,7 +39,5 @@ export interface UpdateDescriptionAction
|
|||
|
||||
export type MetadataActions = UpdateDescriptionAction | UpdateTitleAction;
|
||||
|
||||
export const updateDescription = createAction<UpdateDescriptionAction>(
|
||||
MetadataActionTypeKeys.UPDATE_DESCRIPTION
|
||||
);
|
||||
export const updateTitle = createAction<UpdateTitleAction>(MetadataActionTypeKeys.UPDATE_TITLE);
|
||||
export const updateDescription = createAction<string>(MetadataActionTypeKeys.UPDATE_DESCRIPTION);
|
||||
export const updateTitle = createAction<string>(MetadataActionTypeKeys.UPDATE_TITLE);
|
||||
|
|
|
@ -21,7 +21,8 @@
|
|||
|
||||
import { createAction } from 'redux-actions';
|
||||
import { KibanaAction } from '../../selectors/types';
|
||||
import { PanelId, PanelState, PanelStateMap } from '../selectors';
|
||||
import { PanelId } from '../selectors';
|
||||
import { SavedDashboardPanel, SavedDashboardPanelMap } from '../types';
|
||||
|
||||
export enum PanelActionTypeKeys {
|
||||
DELETE_PANEL = 'DELETE_PANEL',
|
||||
|
@ -36,10 +37,10 @@ export interface DeletePanelAction
|
|||
extends KibanaAction<PanelActionTypeKeys.DELETE_PANEL, PanelId> {}
|
||||
|
||||
export interface UpdatePanelAction
|
||||
extends KibanaAction<PanelActionTypeKeys.UPDATE_PANEL, PanelState> {}
|
||||
extends KibanaAction<PanelActionTypeKeys.UPDATE_PANEL, SavedDashboardPanel> {}
|
||||
|
||||
export interface UpdatePanelsAction
|
||||
extends KibanaAction<PanelActionTypeKeys.UPDATE_PANELS, PanelStateMap> {}
|
||||
extends KibanaAction<PanelActionTypeKeys.UPDATE_PANELS, SavedDashboardPanelMap> {}
|
||||
|
||||
export interface ResetPanelTitleAction
|
||||
extends KibanaAction<PanelActionTypeKeys.RESET_PANEL_TITLE, PanelId> {}
|
||||
|
@ -53,7 +54,7 @@ export interface SetPanelTitleAction
|
|||
extends KibanaAction<PanelActionTypeKeys.SET_PANEL_TITLE, SetPanelTitleActionPayload> {}
|
||||
|
||||
export interface SetPanelsAction
|
||||
extends KibanaAction<PanelActionTypeKeys.SET_PANELS, PanelStateMap> {}
|
||||
extends KibanaAction<PanelActionTypeKeys.SET_PANELS, SavedDashboardPanelMap> {}
|
||||
|
||||
export type PanelActions =
|
||||
| DeletePanelAction
|
||||
|
@ -64,10 +65,10 @@ export type PanelActions =
|
|||
| SetPanelsAction;
|
||||
|
||||
export const deletePanel = createAction<PanelId>(PanelActionTypeKeys.DELETE_PANEL);
|
||||
export const updatePanel = createAction<PanelState>(PanelActionTypeKeys.UPDATE_PANEL);
|
||||
export const updatePanel = createAction<SavedDashboardPanel>(PanelActionTypeKeys.UPDATE_PANEL);
|
||||
export const resetPanelTitle = createAction<PanelId>(PanelActionTypeKeys.RESET_PANEL_TITLE);
|
||||
export const setPanelTitle = createAction<SetPanelTitleActionPayload>(
|
||||
PanelActionTypeKeys.SET_PANEL_TITLE
|
||||
);
|
||||
export const updatePanels = createAction<PanelStateMap>(PanelActionTypeKeys.UPDATE_PANELS);
|
||||
export const setPanels = createAction<PanelStateMap>(PanelActionTypeKeys.SET_PANELS);
|
||||
export const updatePanels = createAction<SavedDashboardPanelMap>(PanelActionTypeKeys.UPDATE_PANELS);
|
||||
export const setPanels = createAction<SavedDashboardPanelMap>(PanelActionTypeKeys.SET_PANELS);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -110,7 +110,7 @@ app.directive('dashboardApp', function ($injector) {
|
|||
|
||||
const dashboardStateManager = new DashboardStateManager({
|
||||
savedDashboard: dash,
|
||||
AppState,
|
||||
AppStateClass: AppState,
|
||||
hideWriteControls: dashboardConfig.getHideWriteControls(),
|
||||
addFilter: ({ field, value, operator, index }) => {
|
||||
filterActions.addFilter(field, value, operator, index, dashboardStateManager.getAppState(), filterManager);
|
||||
|
|
|
@ -22,29 +22,45 @@ import { DashboardViewMode } from './dashboard_view_mode';
|
|||
import { embeddableIsInitialized, setPanels } from './actions';
|
||||
import { getAppStateMock, getSavedDashboardMock } from './__tests__';
|
||||
import { store } from '../store';
|
||||
import { AppStateClass } from 'ui/state_management/app_state';
|
||||
import { DashboardAppState } from './types';
|
||||
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;
|
||||
describe('DashboardState', function() {
|
||||
let dashboardState: DashboardStateManager;
|
||||
const savedDashboard = getSavedDashboardMock();
|
||||
const mockTimefilter = {
|
||||
time: {},
|
||||
setTime: function (time) { this.time = time; },
|
||||
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 = { id: 'index1' };
|
||||
const mockIndexPattern: IndexPattern = { id: 'index1', fields: [], title: 'hi' };
|
||||
|
||||
function initDashboardState() {
|
||||
dashboardState = new DashboardStateManager({
|
||||
savedDashboard,
|
||||
AppState: getAppStateMock(),
|
||||
AppStateClass: getAppStateMock() as AppStateClass<DashboardAppState>,
|
||||
hideWriteControls: false,
|
||||
addFilter: () => {},
|
||||
});
|
||||
}
|
||||
|
||||
describe('syncTimefilterWithDashboard', function () {
|
||||
test('syncs quick time', function () {
|
||||
describe('syncTimefilterWithDashboard', function() {
|
||||
test('syncs quick time', function() {
|
||||
savedDashboard.timeRestore = true;
|
||||
savedDashboard.timeFrom = 'now/w';
|
||||
savedDashboard.timeTo = 'now/w';
|
||||
|
@ -59,7 +75,7 @@ describe('DashboardState', function () {
|
|||
expect(mockTimefilter.time.from).toBe('now/w');
|
||||
});
|
||||
|
||||
test('syncs relative time', function () {
|
||||
test('syncs relative time', function() {
|
||||
savedDashboard.timeRestore = true;
|
||||
savedDashboard.timeFrom = 'now-13d';
|
||||
savedDashboard.timeTo = 'now';
|
||||
|
@ -74,7 +90,7 @@ describe('DashboardState', function () {
|
|||
expect(mockTimefilter.time.from).toBe('now-13d');
|
||||
});
|
||||
|
||||
test('syncs absolute time', function () {
|
||||
test('syncs absolute time', function() {
|
||||
savedDashboard.timeRestore = true;
|
||||
savedDashboard.timeFrom = '2015-09-19 06:31:44.000';
|
||||
savedDashboard.timeTo = '2015-09-29 06:31:44.000';
|
||||
|
@ -90,7 +106,7 @@ describe('DashboardState', function () {
|
|||
});
|
||||
});
|
||||
|
||||
describe('isDirty', function () {
|
||||
describe('isDirty', function() {
|
||||
beforeAll(() => {
|
||||
initDashboardState();
|
||||
});
|
||||
|
@ -108,15 +124,32 @@ describe('DashboardState', function () {
|
|||
});
|
||||
});
|
||||
|
||||
describe('panelIndexPatternMapping', function () {
|
||||
describe('panelIndexPatternMapping', function() {
|
||||
beforeAll(() => {
|
||||
initDashboardState();
|
||||
});
|
||||
|
||||
function simulateNewEmbeddableWithIndexPatterns({ panelId, indexPatterns }) {
|
||||
store.dispatch(setPanels({ [panelId]: { panelIndex: panelId } }));
|
||||
function simulateNewEmbeddableWithIndexPatterns({
|
||||
panelId,
|
||||
indexPatterns,
|
||||
}: {
|
||||
panelId: string;
|
||||
indexPatterns?: IndexPattern[];
|
||||
}) {
|
||||
store.dispatch(
|
||||
setPanels({
|
||||
[panelId]: {
|
||||
id: '123',
|
||||
panelIndex: panelId,
|
||||
version: '1',
|
||||
type: 'hi',
|
||||
embeddableConfig: {},
|
||||
gridData: { x: 1, y: 1, h: 1, w: 1, i: '1' },
|
||||
},
|
||||
})
|
||||
);
|
||||
const metadata = { title: 'my embeddable title', editUrl: 'editme', indexPatterns };
|
||||
store.dispatch(embeddableIsInitialized({ metadata, panelId: panelId }));
|
||||
store.dispatch(embeddableIsInitialized({ metadata, panelId }));
|
||||
}
|
||||
|
||||
test('initially has no index patterns', () => {
|
||||
|
@ -124,13 +157,19 @@ describe('DashboardState', function () {
|
|||
});
|
||||
|
||||
test('registers index pattern when an embeddable is initialized with one', async () => {
|
||||
simulateNewEmbeddableWithIndexPatterns({ panelId: 'foo1', indexPatterns: [mockIndexPattern] });
|
||||
simulateNewEmbeddableWithIndexPatterns({
|
||||
panelId: 'foo1',
|
||||
indexPatterns: [mockIndexPattern],
|
||||
});
|
||||
await new Promise(resolve => process.nextTick(resolve));
|
||||
expect(dashboardState.getPanelIndexPatterns().length).toBe(1);
|
||||
});
|
||||
|
||||
test('registers unique index patterns', async () => {
|
||||
simulateNewEmbeddableWithIndexPatterns({ panelId: 'foo2', indexPatterns: [mockIndexPattern] });
|
||||
simulateNewEmbeddableWithIndexPatterns({
|
||||
panelId: 'foo2',
|
||||
indexPatterns: [mockIndexPattern],
|
||||
});
|
||||
await new Promise(resolve => process.nextTick(resolve));
|
||||
expect(dashboardState.getPanelIndexPatterns().length).toBe(1);
|
||||
});
|
|
@ -20,10 +20,18 @@
|
|||
import { i18n } from '@kbn/i18n';
|
||||
import _ from 'lodash';
|
||||
|
||||
import { stateMonitorFactory, StateMonitor } from 'ui/state_management/state_monitor_factory';
|
||||
import { StaticIndexPattern } from 'ui/index_patterns';
|
||||
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 moment from 'moment';
|
||||
import { DashboardViewMode } from './dashboard_view_mode';
|
||||
import { FilterUtils } from './lib/filter_utils';
|
||||
import { PanelUtils } from './panel/panel_utils';
|
||||
import { store } from '../store';
|
||||
|
||||
import {
|
||||
updateViewMode,
|
||||
setPanels,
|
||||
|
@ -41,7 +49,6 @@ import {
|
|||
closeContextMenu,
|
||||
requestReload,
|
||||
} from './actions';
|
||||
import { stateMonitorFactory } from 'ui/state_management/state_monitor_factory';
|
||||
import { createPanelState } from './panel';
|
||||
import { getAppStateDefaults, migrateAppState } from './lib';
|
||||
import {
|
||||
|
@ -59,6 +66,16 @@ import {
|
|||
getQuery,
|
||||
getFilters,
|
||||
} from '../selectors';
|
||||
import { SavedObjectDashboard } from './saved_dashboard/saved_dashboard';
|
||||
import {
|
||||
DashboardAppState,
|
||||
SavedDashboardPanel,
|
||||
SavedDashboardPanelMap,
|
||||
StagedFilter,
|
||||
DashboardAppStateParameters,
|
||||
} from './types';
|
||||
|
||||
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
|
||||
|
@ -67,21 +84,48 @@ import {
|
|||
* versa. They should be as decoupled as possible so updating the store won't affect bwc of urls.
|
||||
*/
|
||||
export class DashboardStateManager {
|
||||
public savedDashboard: SavedObjectDashboard;
|
||||
public appState: DashboardAppState;
|
||||
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: Array<(status: { dirty: boolean }) => void>;
|
||||
private stateMonitor: StateMonitor<DashboardAppStateParameters>;
|
||||
private panelIndexPatternMapping: { [key: string]: StaticIndexPattern[] } = {};
|
||||
private addFilter: AddFilterFuntion;
|
||||
private unsubscribe: () => void;
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {SavedDashboard} savedDashboard
|
||||
* @param {AppState} AppState The AppState class to use when instantiating a new AppState instance.
|
||||
* @param {boolean} hideWriteControls true if write controls should be hidden.
|
||||
* @param {function} addFilter a function that can be used to add a filter bar filter
|
||||
* @param savedDashboard
|
||||
* @param AppState The AppState class to use when instantiating a new AppState instance.
|
||||
* @param hideWriteControls true if write controls should be hidden.
|
||||
* @param addFilter a function that can be used to add a filter bar filter
|
||||
*/
|
||||
constructor({ savedDashboard, AppState, hideWriteControls, addFilter }) {
|
||||
constructor({
|
||||
savedDashboard,
|
||||
AppStateClass,
|
||||
hideWriteControls,
|
||||
addFilter,
|
||||
}: {
|
||||
savedDashboard: SavedObjectDashboard;
|
||||
AppStateClass: TAppStateClass<DashboardAppState>;
|
||||
hideWriteControls: boolean;
|
||||
addFilter: AddFilterFuntion;
|
||||
}) {
|
||||
this.savedDashboard = savedDashboard;
|
||||
this.hideWriteControls = hideWriteControls;
|
||||
this.addFilter = addFilter;
|
||||
|
||||
this.stateDefaults = getAppStateDefaults(this.savedDashboard, this.hideWriteControls);
|
||||
|
||||
this.appState = new AppState(this.stateDefaults);
|
||||
this.appState = new AppStateClass(this.stateDefaults);
|
||||
|
||||
// Initializing appState does two things - first it translates the defaults into AppState, second it updates
|
||||
// appState based on the URL (the url trumps the defaults). This means if we update the state format at all and
|
||||
|
@ -102,28 +146,44 @@ 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());
|
||||
|
||||
// Always start out with all panels minimized when a dashboard is first loaded.
|
||||
store.dispatch(minimizePanel());
|
||||
this._pushAppStateChangesToStore();
|
||||
this.pushAppStateChangesToStore();
|
||||
|
||||
this.changeListeners = [];
|
||||
|
||||
this.unsubscribe = store.subscribe(() => this._handleStoreChanges());
|
||||
this.stateMonitor.onChange(status => {
|
||||
this.unsubscribe = store.subscribe(() => this.handleStoreChanges());
|
||||
this.stateMonitor.onChange((status: { dirty: boolean }) => {
|
||||
this.changeListeners.forEach(listener => listener(status));
|
||||
this._pushAppStateChangesToStore();
|
||||
this.pushAppStateChangesToStore();
|
||||
});
|
||||
}
|
||||
|
||||
registerChangeListener(callback) {
|
||||
public registerChangeListener(callback: (status: { dirty: boolean }) => void) {
|
||||
this.changeListeners.push(callback);
|
||||
}
|
||||
|
||||
_areStoreAndAppStatePanelsEqual() {
|
||||
private areStoreAndAppStatePanelsEqual() {
|
||||
const state = store.getState();
|
||||
const storePanels = getPanels(store.getState());
|
||||
const appStatePanels = this.getPanels();
|
||||
|
@ -132,44 +192,46 @@ export class DashboardStateManager {
|
|||
return false;
|
||||
}
|
||||
|
||||
return appStatePanels.every((appStatePanel) => {
|
||||
return appStatePanels.every(appStatePanel => {
|
||||
const storePanel = getPanel(state, appStatePanel.panelIndex);
|
||||
return _.isEqual(appStatePanel, storePanel);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* Time is part of global state so we need to deal with it outside of pushAppStateChangesToStore.
|
||||
*/
|
||||
handleTimeChange(newTimeFilter) {
|
||||
store.dispatch(updateTimeRange({
|
||||
from: FilterUtils.convertTimeToUTCString(newTimeFilter.from),
|
||||
to: FilterUtils.convertTimeToUTCString(newTimeFilter.to),
|
||||
}));
|
||||
public handleTimeChange(newTimeRange: TimeRange) {
|
||||
const from = FilterUtils.convertTimeToUTCString(newTimeRange.from);
|
||||
const to = FilterUtils.convertTimeToUTCString(newTimeRange.to);
|
||||
store.dispatch(
|
||||
updateTimeRange({
|
||||
from: from ? from.toString() : '',
|
||||
to: to ? to.toString() : '',
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
handleRefreshConfigChange({ pause, value }) {
|
||||
store.dispatch(updateRefreshConfig({
|
||||
isPaused: pause,
|
||||
interval: value,
|
||||
}));
|
||||
public handleRefreshConfigChange({ pause, value }: { pause: boolean; value: number }) {
|
||||
store.dispatch(
|
||||
updateRefreshConfig({
|
||||
isPaused: pause,
|
||||
interval: value,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes made to app state outside of direct calls to this class will need to be propagated to the store.
|
||||
* @private
|
||||
*/
|
||||
_pushAppStateChangesToStore() {
|
||||
private pushAppStateChangesToStore() {
|
||||
// We need these checks, or you can get into a loop where a change is triggered by the store, which updates
|
||||
// AppState, which then dispatches the change here, which will end up triggering setState warnings.
|
||||
if (!this._areStoreAndAppStatePanelsEqual()) {
|
||||
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, panel) => {
|
||||
const panelsMap = this.getPanels().reduce((acc: SavedDashboardPanelMap, panel) => {
|
||||
acc[panel.panelIndex] = _.cloneDeep(panel);
|
||||
return acc;
|
||||
}, {});
|
||||
|
@ -220,10 +282,12 @@ export class DashboardStateManager {
|
|||
_pushFiltersToStore() {
|
||||
const state = store.getState();
|
||||
const dashboardFilters = this.getDashboardFilterBars();
|
||||
if (!_.isEqual(
|
||||
FilterUtils.cleanFiltersForComparison(dashboardFilters),
|
||||
FilterUtils.cleanFiltersForComparison(getFilters(state))
|
||||
)) {
|
||||
if (
|
||||
!_.isEqual(
|
||||
FilterUtils.cleanFiltersForComparison(dashboardFilters),
|
||||
FilterUtils.cleanFiltersForComparison(getFilters(state))
|
||||
)
|
||||
) {
|
||||
store.dispatch(updateFilters(dashboardFilters));
|
||||
}
|
||||
}
|
||||
|
@ -232,24 +296,28 @@ export class DashboardStateManager {
|
|||
store.dispatch(requestReload());
|
||||
}
|
||||
|
||||
_handleStoreChanges() {
|
||||
private handleStoreChanges() {
|
||||
let dirty = false;
|
||||
if (!this._areStoreAndAppStatePanelsEqual()) {
|
||||
const panels = getPanels(store.getState());
|
||||
if (!this.areStoreAndAppStatePanelsEqual()) {
|
||||
const panels: SavedDashboardPanelMap = getPanels(store.getState());
|
||||
this.appState.panels = [];
|
||||
this.panelIndexPatternMapping = {};
|
||||
Object.values(panels).map(panel => {
|
||||
Object.values(panels).map((panel: SavedDashboardPanel) => {
|
||||
this.appState.panels.push(_.cloneDeep(panel));
|
||||
});
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
_.forEach(getEmbeddables(store.getState()), (embeddable, panelId) => {
|
||||
if (embeddable.initialized && !this.panelIndexPatternMapping.hasOwnProperty(panelId)) {
|
||||
if (
|
||||
panelId &&
|
||||
embeddable.initialized &&
|
||||
!this.panelIndexPatternMapping.hasOwnProperty(panelId)
|
||||
) {
|
||||
const embeddableMetadata = getEmbeddableMetadata(store.getState(), panelId);
|
||||
if (embeddableMetadata.indexPatterns) {
|
||||
if (embeddableMetadata && embeddableMetadata.indexPatterns) {
|
||||
this.panelIndexPatternMapping[panelId] = _.compact(embeddableMetadata.indexPatterns);
|
||||
this.dirty = true;
|
||||
dirty = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -272,16 +340,16 @@ export class DashboardStateManager {
|
|||
this.saveState();
|
||||
}
|
||||
|
||||
getFullScreenMode() {
|
||||
public getFullScreenMode() {
|
||||
return this.appState.fullScreenMode;
|
||||
}
|
||||
|
||||
setFullScreenMode(fullScreenMode) {
|
||||
public setFullScreenMode(fullScreenMode: boolean) {
|
||||
this.appState.fullScreenMode = fullScreenMode;
|
||||
this.saveState();
|
||||
}
|
||||
|
||||
getPanelIndexPatterns() {
|
||||
public getPanelIndexPatterns() {
|
||||
const indexPatterns = _.flatten(Object.values(this.panelIndexPatternMapping));
|
||||
return _.uniq(indexPatterns, 'id');
|
||||
}
|
||||
|
@ -289,7 +357,7 @@ export class DashboardStateManager {
|
|||
/**
|
||||
* Resets the state back to the last saved version of the dashboard.
|
||||
*/
|
||||
resetState() {
|
||||
public resetState() {
|
||||
// In order to show the correct warning, we have to store the unsaved
|
||||
// title on the dashboard object. We should fix this at some point, but this is how all the other object
|
||||
// save panels work at the moment.
|
||||
|
@ -317,66 +385,68 @@ export class DashboardStateManager {
|
|||
* Returns an object which contains the current filter state of this.savedDashboard.
|
||||
* @returns {{timeTo: String, timeFrom: String, filterBars: Array, query: Object}}
|
||||
*/
|
||||
getFilterState() {
|
||||
public getFilterState() {
|
||||
return {
|
||||
timeTo: this.savedDashboard.timeTo,
|
||||
timeFrom: this.savedDashboard.timeFrom,
|
||||
filterBars: this.getDashboardFilterBars(),
|
||||
query: this.getDashboardQuery()
|
||||
query: this.getDashboardQuery(),
|
||||
};
|
||||
}
|
||||
|
||||
getTitle() {
|
||||
public getTitle() {
|
||||
return this.appState.title;
|
||||
}
|
||||
|
||||
getDescription() {
|
||||
public getDescription() {
|
||||
return this.appState.description;
|
||||
}
|
||||
|
||||
setDescription(description) {
|
||||
public setDescription(description: string) {
|
||||
this.appState.description = description;
|
||||
this.saveState();
|
||||
}
|
||||
|
||||
setTitle(title) {
|
||||
public setTitle(title: string) {
|
||||
this.appState.title = title;
|
||||
this.savedDashboard.title = title;
|
||||
this.saveState();
|
||||
}
|
||||
|
||||
getAppState() {
|
||||
public getAppState() {
|
||||
return this.appState;
|
||||
}
|
||||
|
||||
getQuery() {
|
||||
public getQuery() {
|
||||
return this.appState.query;
|
||||
}
|
||||
|
||||
getUseMargins() {
|
||||
public getUseMargins() {
|
||||
// Existing dashboards that don't define this should default to false.
|
||||
return this.appState.options.useMargins === undefined ? false : this.appState.options.useMargins;
|
||||
return this.appState.options.useMargins === undefined
|
||||
? false
|
||||
: this.appState.options.useMargins;
|
||||
}
|
||||
|
||||
setUseMargins(useMargins) {
|
||||
public setUseMargins(useMargins: boolean) {
|
||||
this.appState.options.useMargins = useMargins;
|
||||
this.saveState();
|
||||
}
|
||||
|
||||
getHidePanelTitles() {
|
||||
public getHidePanelTitles() {
|
||||
return this.appState.options.hidePanelTitles;
|
||||
}
|
||||
|
||||
setHidePanelTitles(hidePanelTitles) {
|
||||
public setHidePanelTitles(hidePanelTitles: boolean) {
|
||||
this.appState.options.hidePanelTitles = hidePanelTitles;
|
||||
this.saveState();
|
||||
}
|
||||
|
||||
getTimeRestore() {
|
||||
public getTimeRestore() {
|
||||
return this.appState.timeRestore;
|
||||
}
|
||||
|
||||
setTimeRestore(timeRestore) {
|
||||
public setTimeRestore(timeRestore: boolean) {
|
||||
this.appState.timeRestore = timeRestore;
|
||||
this.saveState();
|
||||
}
|
||||
|
@ -384,23 +454,23 @@ export class DashboardStateManager {
|
|||
/**
|
||||
* @returns {boolean}
|
||||
*/
|
||||
getIsTimeSavedWithDashboard() {
|
||||
public getIsTimeSavedWithDashboard() {
|
||||
return this.savedDashboard.timeRestore;
|
||||
}
|
||||
|
||||
getDashboardFilterBars() {
|
||||
public getDashboardFilterBars() {
|
||||
return FilterUtils.getFilterBarsForDashboard(this.savedDashboard);
|
||||
}
|
||||
|
||||
getDashboardQuery() {
|
||||
public getDashboardQuery() {
|
||||
return FilterUtils.getQueryFilterForDashboard(this.savedDashboard);
|
||||
}
|
||||
|
||||
getLastSavedFilterBars() {
|
||||
public getLastSavedFilterBars(): Filter[] {
|
||||
return this.lastSavedDashboardFilters.filterBars;
|
||||
}
|
||||
|
||||
getLastSavedQuery() {
|
||||
public getLastSavedQuery(): Query | string {
|
||||
return this.lastSavedDashboardFilters.query;
|
||||
}
|
||||
|
||||
|
@ -408,17 +478,14 @@ export class DashboardStateManager {
|
|||
* @returns {boolean} True if the query changed since the last time the dashboard was saved, or if it's a
|
||||
* new dashboard, if the query differs from the default.
|
||||
*/
|
||||
getQueryChanged() {
|
||||
public getQueryChanged() {
|
||||
const currentQuery = this.appState.query;
|
||||
const lastSavedQuery = this.getLastSavedQuery();
|
||||
|
||||
const isLegacyStringQuery = (
|
||||
_.isString(lastSavedQuery)
|
||||
&& _.isPlainObject(currentQuery)
|
||||
&& _.has(currentQuery, 'query')
|
||||
);
|
||||
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);
|
||||
|
@ -428,7 +495,7 @@ export class DashboardStateManager {
|
|||
* @returns {boolean} True if the filter bar state has changed since the last time the dashboard was saved,
|
||||
* or if it's a new dashboard, if the query differs from the default.
|
||||
*/
|
||||
getFilterBarChanged() {
|
||||
public getFilterBarChanged() {
|
||||
return !_.isEqual(
|
||||
FilterUtils.cleanFiltersForComparison(this.appState.filters),
|
||||
FilterUtils.cleanFiltersForComparison(this.getLastSavedFilterBars())
|
||||
|
@ -439,9 +506,12 @@ export class DashboardStateManager {
|
|||
* @param timeFilter
|
||||
* @returns {boolean} True if the time state has changed since the time saved with the dashboard.
|
||||
*/
|
||||
getTimeChanged(timeFilter) {
|
||||
public getTimeChanged(timeFilter: Timefilter) {
|
||||
return (
|
||||
!FilterUtils.areTimesEqual(this.lastSavedDashboardFilters.timeFrom, timeFilter.getTime().from) ||
|
||||
!FilterUtils.areTimesEqual(
|
||||
this.lastSavedDashboardFilters.timeFrom,
|
||||
timeFilter.getTime().from
|
||||
) ||
|
||||
!FilterUtils.areTimesEqual(this.lastSavedDashboardFilters.timeTo, timeFilter.getTime().to)
|
||||
);
|
||||
}
|
||||
|
@ -450,21 +520,21 @@ export class DashboardStateManager {
|
|||
*
|
||||
* @returns {DashboardViewMode}
|
||||
*/
|
||||
getViewMode() {
|
||||
public getViewMode() {
|
||||
return this.hideWriteControls ? DashboardViewMode.VIEW : this.appState.viewMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {boolean}
|
||||
*/
|
||||
getIsViewMode() {
|
||||
public getIsViewMode() {
|
||||
return this.getViewMode() === DashboardViewMode.VIEW;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {boolean}
|
||||
*/
|
||||
getIsEditMode() {
|
||||
public getIsEditMode() {
|
||||
return this.getViewMode() === DashboardViewMode.EDIT;
|
||||
}
|
||||
|
||||
|
@ -472,22 +542,24 @@ export class DashboardStateManager {
|
|||
*
|
||||
* @returns {boolean} True if the dashboard has changed since the last save (or, is new).
|
||||
*/
|
||||
getIsDirty(timeFilter) {
|
||||
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;
|
||||
return this.getIsEditMode() && (this.isDirty || hasTimeFilterChanged);
|
||||
}
|
||||
|
||||
getPanels() {
|
||||
public getPanels(): SavedDashboardPanel[] {
|
||||
return this.appState.panels;
|
||||
}
|
||||
|
||||
updatePanel(panelIndex, panelAttributes) {
|
||||
const panel = this.getPanels().find((panel) => panel.panelIndex === panelIndex);
|
||||
Object.assign(panel, panelAttributes);
|
||||
public updatePanel(panelIndex: string, panelAttributes: any) {
|
||||
const foundPanel = this.getPanels().find(
|
||||
(panel: SavedDashboardPanel) => panel.panelIndex === panelIndex
|
||||
);
|
||||
Object.assign(foundPanel, panelAttributes);
|
||||
this.saveState();
|
||||
return panel;
|
||||
return foundPanel;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -495,15 +567,15 @@ export class DashboardStateManager {
|
|||
* @param {number} id
|
||||
* @param {string} type
|
||||
*/
|
||||
addNewPanel = (id, type) => {
|
||||
public addNewPanel = (id: string, type: string) => {
|
||||
const maxPanelIndex = PanelUtils.getMaxPanelIndex(this.getPanels());
|
||||
const newPanel = createPanelState(id, type, maxPanelIndex, this.getPanels());
|
||||
const newPanel = createPanelState(id, type, maxPanelIndex.toString(), this.getPanels());
|
||||
this.getPanels().push(newPanel);
|
||||
this.saveState();
|
||||
}
|
||||
};
|
||||
|
||||
removePanel(panelIndex) {
|
||||
_.remove(this.getPanels(), (panel) => {
|
||||
public removePanel(panelIndex: string) {
|
||||
_.remove(this.getPanels(), panel => {
|
||||
if (panel.panelIndex === panelIndex) {
|
||||
delete this.panelIndexPatternMapping[panelIndex];
|
||||
return true;
|
||||
|
@ -518,7 +590,7 @@ export class DashboardStateManager {
|
|||
* @param timeFilter
|
||||
* @returns {Array.<string>} An array of user friendly strings indicating the filter types that have changed.
|
||||
*/
|
||||
getChangedFilterTypes(timeFilter) {
|
||||
public getChangedFilterTypes(timeFilter: Timefilter) {
|
||||
const changedFilters = [];
|
||||
if (this.getFilterBarChanged()) {
|
||||
changedFilters.push('filter');
|
||||
|
@ -533,25 +605,24 @@ export class DashboardStateManager {
|
|||
}
|
||||
|
||||
/**
|
||||
* @return {boolean} True if filters (query, filter bar filters, and time picker if time is stored
|
||||
* @return True if filters (query, filter bar filters, and time picker if time is stored
|
||||
* with the dashboard) have changed since the last saved state (or if the dashboard hasn't been saved,
|
||||
* the default state).
|
||||
*/
|
||||
getFiltersChanged(timeFilter) {
|
||||
public getFiltersChanged(timeFilter: Timefilter) {
|
||||
return this.getChangedFilterTypes(timeFilter).length > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates timeFilter to match the time saved with the dashboard.
|
||||
* @param {Object} timeFilter
|
||||
* @param {func} timeFilter.setTime
|
||||
* @param {func} timeFilter.setRefreshInterval
|
||||
*/
|
||||
syncTimefilterWithDashboard(timeFilter) {
|
||||
public syncTimefilterWithDashboard(timeFilter: Timefilter) {
|
||||
if (!this.getIsTimeSavedWithDashboard()) {
|
||||
throw new Error(i18n.translate('kbn.dashboard.stateManager.timeNotSavedWithDashboardErrorMessage', {
|
||||
defaultMessage: 'The time is not saved with this dashboard so should not be synced.',
|
||||
}));
|
||||
throw new Error(
|
||||
i18n.translate('kbn.dashboard.stateManager.timeNotSavedWithDashboardErrorMessage', {
|
||||
defaultMessage: 'The time is not saved with this dashboard so should not be synced.',
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
timeFilter.setTime({
|
||||
|
@ -567,7 +638,7 @@ export class DashboardStateManager {
|
|||
/**
|
||||
* Saves the current application state to the URL.
|
||||
*/
|
||||
saveState() {
|
||||
public saveState() {
|
||||
this.appState.save();
|
||||
}
|
||||
|
||||
|
@ -575,7 +646,7 @@ export class DashboardStateManager {
|
|||
* Applies the current filter state to the dashboard.
|
||||
* @param filter {Array.<Object>} An array of filter bar filters.
|
||||
*/
|
||||
applyFilters(query, filters) {
|
||||
public applyFilters(query: Query, filters: Filter[]) {
|
||||
this.appState.query = query;
|
||||
this.savedDashboard.searchSource.setField('query', query);
|
||||
this.savedDashboard.searchSource.setField('filter', filters);
|
||||
|
@ -584,27 +655,10 @@ export class DashboardStateManager {
|
|||
this._pushFiltersToStore();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a state monitor and saves it to this.stateMonitor. Used to track unsaved changes made to appState.
|
||||
*/
|
||||
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 => {
|
||||
this.isDirty = status.dirty;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param newMode {DashboardViewMode}
|
||||
*/
|
||||
switchViewMode(newMode) {
|
||||
public switchViewMode(newMode: DashboardViewMode) {
|
||||
this.appState.viewMode = newMode;
|
||||
this.saveState();
|
||||
}
|
||||
|
@ -612,7 +666,7 @@ export class DashboardStateManager {
|
|||
/**
|
||||
* Destroys and cleans up this object when it's no longer used.
|
||||
*/
|
||||
destroy() {
|
||||
public destroy() {
|
||||
if (this.stateMonitor) {
|
||||
this.stateMonitor.destroy();
|
||||
}
|
|
@ -37,8 +37,12 @@ import {
|
|||
import { DashboardViewMode } from '../dashboard_view_mode';
|
||||
import { DashboardPanel } from '../panel';
|
||||
import { PanelUtils } from '../panel/panel_utils';
|
||||
import { PanelState, PanelStateMap, Pre61PanelState } from '../selectors/types';
|
||||
import { GridData } from '../types';
|
||||
import {
|
||||
GridData,
|
||||
SavedDashboardPanel,
|
||||
Pre61SavedDashboardPanel,
|
||||
SavedDashboardPanelMap,
|
||||
} from '../types';
|
||||
|
||||
let lastValidGridSize = 0;
|
||||
|
||||
|
@ -117,10 +121,10 @@ const config = { monitorWidth: true };
|
|||
const ResponsiveSizedGrid = sizeMe(config)(ResponsiveGrid);
|
||||
|
||||
interface Props extends ReactIntl.InjectedIntlProps {
|
||||
panels: PanelStateMap;
|
||||
panels: SavedDashboardPanelMap;
|
||||
getEmbeddableFactory: (panelType: string) => EmbeddableFactory;
|
||||
dashboardViewMode: DashboardViewMode.EDIT | DashboardViewMode.VIEW;
|
||||
onPanelsUpdated: (updatedPanels: PanelStateMap) => void;
|
||||
onPanelsUpdated: (updatedPanels: SavedDashboardPanelMap) => void;
|
||||
maximizedPanelId?: string;
|
||||
useMargins: boolean;
|
||||
}
|
||||
|
@ -182,18 +186,18 @@ class DashboardGridUi extends React.Component<Props, State> {
|
|||
: PanelUtils.parseVersion('6.0.0');
|
||||
|
||||
if (panelVersion.major < 6 || (panelVersion.major === 6 && panelVersion.minor < 1)) {
|
||||
panel = PanelUtils.convertPanelDataPre_6_1(panel as Pre61PanelState);
|
||||
panel = PanelUtils.convertPanelDataPre_6_1((panel as unknown) as Pre61SavedDashboardPanel);
|
||||
}
|
||||
|
||||
if (panelVersion.major < 6 || (panelVersion.major === 6 && panelVersion.minor < 3)) {
|
||||
PanelUtils.convertPanelDataPre_6_3(panel as PanelState, this.props.useMargins);
|
||||
PanelUtils.convertPanelDataPre_6_3(panel as SavedDashboardPanel, this.props.useMargins);
|
||||
}
|
||||
|
||||
return (panel as PanelState).gridData;
|
||||
return (panel as SavedDashboardPanel).gridData;
|
||||
});
|
||||
}
|
||||
|
||||
public createEmbeddableFactoriesMap(panels: PanelStateMap) {
|
||||
public createEmbeddableFactoriesMap(panels: SavedDashboardPanelMap) {
|
||||
Object.values(panels).map(panel => {
|
||||
if (!this.embeddableFactoryMap[panel.type]) {
|
||||
this.embeddableFactoryMap[panel.type] = this.props.getEmbeddableFactory(panel.type);
|
||||
|
@ -211,17 +215,14 @@ class DashboardGridUi extends React.Component<Props, State> {
|
|||
|
||||
public onLayoutChange = (layout: PanelLayout[]) => {
|
||||
const { onPanelsUpdated, panels } = this.props;
|
||||
const updatedPanels = layout.reduce(
|
||||
(updatedPanelsAcc, panelLayout) => {
|
||||
updatedPanelsAcc[panelLayout.i] = {
|
||||
...panels[panelLayout.i],
|
||||
panelIndex: panelLayout.i,
|
||||
gridData: _.pick(panelLayout, ['x', 'y', 'w', 'h', 'i']),
|
||||
};
|
||||
return updatedPanelsAcc;
|
||||
},
|
||||
{} as PanelStateMap
|
||||
);
|
||||
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);
|
||||
};
|
||||
|
||||
|
@ -240,7 +241,9 @@ class DashboardGridUi extends React.Component<Props, State> {
|
|||
const { focusedPanelIndex } = this.state;
|
||||
|
||||
// Part of our unofficial API - need to render in a consistent order for plugins.
|
||||
const panelsInOrder = Object.keys(panels).map((key: string) => panels[key] as PanelState);
|
||||
const panelsInOrder = Object.keys(panels).map(
|
||||
(key: string) => panels[key] as SavedDashboardPanel
|
||||
);
|
||||
panelsInOrder.sort((panelA, panelB) => {
|
||||
if (panelA.gridData.y === panelB.gridData.y) {
|
||||
return panelA.gridData.x - panelB.gridData.x;
|
||||
|
|
|
@ -21,17 +21,18 @@ import { connect } from 'react-redux';
|
|||
import { Dispatch } from 'redux';
|
||||
import { updatePanels } from '../actions';
|
||||
import { getPanels, getUseMargins, getViewMode } from '../selectors';
|
||||
import { DashboardViewMode, PanelStateMap } from '../selectors/types';
|
||||
import { DashboardViewMode } from '../selectors/types';
|
||||
import { DashboardGrid } from './dashboard_grid';
|
||||
import { SavedDashboardPanelMap } from '../types';
|
||||
|
||||
interface DashboardGridContainerStateProps {
|
||||
panels: PanelStateMap;
|
||||
panels: SavedDashboardPanelMap;
|
||||
dashboardViewMode: DashboardViewMode;
|
||||
useMargins: boolean;
|
||||
}
|
||||
|
||||
interface DashboardGridContainerDispatchProps {
|
||||
onPanelsUpdated(updatedPanels: PanelStateMap): void;
|
||||
onPanelsUpdated(updatedPanels: SavedDashboardPanelMap): void;
|
||||
}
|
||||
|
||||
const mapStateToProps = ({ dashboard }: any): any => ({
|
||||
|
@ -41,7 +42,7 @@ const mapStateToProps = ({ dashboard }: any): any => ({
|
|||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch: Dispatch) => ({
|
||||
onPanelsUpdated: (updatedPanels: PanelStateMap) => dispatch(updatePanels(updatedPanels)),
|
||||
onPanelsUpdated: (updatedPanels: SavedDashboardPanelMap) => dispatch(updatePanels(updatedPanels)),
|
||||
});
|
||||
|
||||
export const DashboardGridContainer = connect<
|
||||
|
|
|
@ -18,7 +18,10 @@
|
|||
*/
|
||||
|
||||
import _ from 'lodash';
|
||||
import moment from 'moment';
|
||||
import moment, { Moment } from 'moment';
|
||||
import { QueryFilter } from 'ui/filter_manager/query_filter';
|
||||
import { Filter } from '@kbn/es-query';
|
||||
import { SavedObjectDashboard } from '../saved_dashboard/saved_dashboard';
|
||||
|
||||
/**
|
||||
* @typedef {Object} QueryFilter
|
||||
|
@ -33,7 +36,7 @@ export class FilterUtils {
|
|||
* @returns {Boolean} True if the filter is of the special query type
|
||||
* (e.g. goes in the query input bar), false otherwise (e.g. is in the filter bar).
|
||||
*/
|
||||
static isQueryFilter(filter) {
|
||||
public static isQueryFilter(filter: Filter) {
|
||||
return filter.query && !filter.meta;
|
||||
}
|
||||
|
||||
|
@ -43,7 +46,7 @@ export class FilterUtils {
|
|||
* @returns {Array.<Object>} An array of filters stored with the dashboard. Includes
|
||||
* both query filters and filter bar filters.
|
||||
*/
|
||||
static getDashboardFilters(dashboard) {
|
||||
public static getDashboardFilters(dashboard: SavedObjectDashboard): Filter[] {
|
||||
return dashboard.searchSource.getOwnField('filter');
|
||||
}
|
||||
|
||||
|
@ -52,7 +55,7 @@ export class FilterUtils {
|
|||
* @param {SavedDashboard} dashboard
|
||||
* @returns {QueryFilter}
|
||||
*/
|
||||
static getQueryFilterForDashboard(dashboard) {
|
||||
public static getQueryFilterForDashboard(dashboard: SavedObjectDashboard): QueryFilter | string {
|
||||
if (dashboard.searchSource.getOwnField('query')) {
|
||||
return dashboard.searchSource.getOwnField('query');
|
||||
}
|
||||
|
@ -68,21 +71,23 @@ export class FilterUtils {
|
|||
* @return {Array.<Object>} Array of filters that should appear in the filter bar for the
|
||||
* given dashboard
|
||||
*/
|
||||
static getFilterBarsForDashboard(dashboard) {
|
||||
public static getFilterBarsForDashboard(dashboard: SavedObjectDashboard) {
|
||||
return _.reject(this.getDashboardFilters(dashboard), this.isQueryFilter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the time to a utc formatted string. If the time is not valid (e.g. it might be in a relative format like
|
||||
* 'now-15m', then it just returns what it was passed).
|
||||
* Note** Changing these moment objects to a utc string will actually cause a bug because it'll be in a format not
|
||||
* expected by the time picker. This should get cleaned up and we should pick a single format to use everywhere.
|
||||
* @param time {string|Moment}
|
||||
* @returns {string} the time represented in utc format, or if the time range was not able to be parsed into a moment
|
||||
* object, it returns the same object it was given.
|
||||
*/
|
||||
static convertTimeToUTCString(time) {
|
||||
public static convertTimeToUTCString(time?: string | Moment): undefined | string | moment.Moment {
|
||||
if (moment(time).isValid()) {
|
||||
return moment(time).utc();
|
||||
} else {
|
||||
} else {
|
||||
return time;
|
||||
}
|
||||
}
|
||||
|
@ -95,7 +100,7 @@ export class FilterUtils {
|
|||
* @param timeB {string|Moment}
|
||||
* @returns {boolean}
|
||||
*/
|
||||
static areTimesEqual(timeA, timeB) {
|
||||
public static areTimesEqual(timeA?: string | Moment, timeB?: string | Moment) {
|
||||
return this.convertTimeToUTCString(timeA) === this.convertTimeToUTCString(timeB);
|
||||
}
|
||||
|
||||
|
@ -105,7 +110,7 @@ export class FilterUtils {
|
|||
* @param filters {Array.<Object>}
|
||||
* @returns {Array.<Object>}
|
||||
*/
|
||||
static cleanFiltersForComparison(filters) {
|
||||
return _.map(filters, (filter) => _.omit(filter, ['$$hashKey', '$state']));
|
||||
public static cleanFiltersForComparison(filters: Filter[]) {
|
||||
return _.map(filters, filter => _.omit(filter, ['$$hashKey', '$state']));
|
||||
}
|
||||
}
|
|
@ -19,25 +19,35 @@
|
|||
|
||||
import { DashboardViewMode } from '../dashboard_view_mode';
|
||||
import { FilterUtils } from './filter_utils';
|
||||
import { SavedObjectDashboard } from '../saved_dashboard/saved_dashboard';
|
||||
import {
|
||||
Pre61SavedDashboardPanel,
|
||||
Pre64SavedDashboardPanel,
|
||||
DashboardAppStateParameters,
|
||||
} from '../types';
|
||||
|
||||
export function getAppStateDefaults(savedDashboard, hideWriteControls) {
|
||||
export function getAppStateDefaults(
|
||||
savedDashboard: SavedObjectDashboard,
|
||||
hideWriteControls: boolean
|
||||
): DashboardAppStateParameters {
|
||||
const appState = {
|
||||
fullScreenMode: false,
|
||||
title: savedDashboard.title,
|
||||
description: savedDashboard.description,
|
||||
description: savedDashboard.description || '',
|
||||
timeRestore: savedDashboard.timeRestore,
|
||||
panels: savedDashboard.panelsJSON ? JSON.parse(savedDashboard.panelsJSON) : [],
|
||||
options: savedDashboard.optionsJSON ? JSON.parse(savedDashboard.optionsJSON) : {},
|
||||
query: FilterUtils.getQueryFilterForDashboard(savedDashboard),
|
||||
filters: FilterUtils.getFilterBarsForDashboard(savedDashboard),
|
||||
viewMode: savedDashboard.id || hideWriteControls ? DashboardViewMode.VIEW : DashboardViewMode.EDIT,
|
||||
viewMode:
|
||||
savedDashboard.id || hideWriteControls ? DashboardViewMode.VIEW : DashboardViewMode.EDIT,
|
||||
};
|
||||
|
||||
// For BWC in pre 6.1 versions where uiState was stored at the dashboard level, not at the panel level.
|
||||
// TODO: introduce a migration for this
|
||||
if (savedDashboard.uiStateJSON) {
|
||||
const uiState = JSON.parse(savedDashboard.uiStateJSON);
|
||||
appState.panels.forEach(panel => {
|
||||
appState.panels.forEach((panel: Pre61SavedDashboardPanel) => {
|
||||
panel.embeddableConfig = uiState[`P-${panel.panelIndex}`];
|
||||
});
|
||||
delete savedDashboard.uiStateJSON;
|
||||
|
@ -45,18 +55,17 @@ export function getAppStateDefaults(savedDashboard, hideWriteControls) {
|
|||
|
||||
// For BWC of pre 6.4 where search embeddables stored state directly on the panel and not under embeddableConfig.
|
||||
// TODO: introduce a migration for this
|
||||
appState.panels.forEach(panel => {
|
||||
appState.panels.forEach((panel: Pre64SavedDashboardPanel) => {
|
||||
if (panel.columns || panel.sort) {
|
||||
panel.embeddableConfig = {
|
||||
...panel.embeddableConfig,
|
||||
columns: panel.columns,
|
||||
sort: panel.sort
|
||||
sort: panel.sort,
|
||||
};
|
||||
delete panel.columns;
|
||||
delete panel.sort;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
return appState;
|
||||
}
|
|
@ -17,15 +17,17 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { SavedDashboardPanel, DashboardAppState } from '../types';
|
||||
|
||||
/**
|
||||
* Creates a new instance of AppState based of the saved dashboard.
|
||||
*
|
||||
* @param appState {AppState} AppState class to instantiate
|
||||
*/
|
||||
export function migrateAppState(appState) {
|
||||
export function migrateAppState(appState: DashboardAppState) {
|
||||
// For BWC in pre 6.1 versions where uiState was stored at the dashboard level, not at the panel level.
|
||||
if (appState.uiState) {
|
||||
appState.panels.forEach(panel => {
|
||||
appState.panels.forEach((panel: SavedDashboardPanel) => {
|
||||
panel.embeddableConfig = appState.uiState[`P-${panel.panelIndex}`];
|
||||
});
|
||||
delete appState.uiState;
|
|
@ -17,25 +17,25 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { SaveOptions } from 'ui/saved_objects/saved_object';
|
||||
import { Timefilter } from 'ui/timefilter';
|
||||
import { updateSavedDashboard } from './update_saved_dashboard';
|
||||
import { DashboardStateManager } from '../dashboard_state_manager';
|
||||
|
||||
/**
|
||||
* Saves the dashboard.
|
||||
* @param toJson {function} A custom toJson function. Used because the previous code used
|
||||
* @param toJson A custom toJson function. Used because the previous code used
|
||||
* the angularized toJson version, and it was unclear whether there was a reason not to use
|
||||
* JSON.stringify
|
||||
* @param timeFilter
|
||||
* @param dashboardStateManager {DashboardStateManager}
|
||||
* @param {object} [saveOptions={}]
|
||||
* @property {boolean} [saveOptions.confirmOverwrite=false] - If true, attempts to create the source so it
|
||||
* can confirm an overwrite if a document with the id already exists.
|
||||
* @property {boolean} [saveOptions.isTitleDuplicateConfirmed=false] - If true, save allowed with duplicate title
|
||||
* @property {func} [saveOptions.onTitleDuplicate] - function called if duplicate title exists.
|
||||
* When not provided, confirm modal will be displayed asking user to confirm or cancel save.
|
||||
* @returns {Promise<string>} A promise that if resolved, will contain the id of the newly saved
|
||||
* @returns A promise that if resolved, will contain the id of the newly saved
|
||||
* dashboard.
|
||||
*/
|
||||
export function saveDashboard(toJson, timeFilter, dashboardStateManager, saveOptions) {
|
||||
export function saveDashboard(
|
||||
toJson: (obj: any) => string,
|
||||
timeFilter: Timefilter,
|
||||
dashboardStateManager: DashboardStateManager,
|
||||
saveOptions: SaveOptions
|
||||
): Promise<string> {
|
||||
dashboardStateManager.saveState();
|
||||
|
||||
const savedDashboard = dashboardStateManager.savedDashboard;
|
||||
|
@ -43,10 +43,9 @@ export function saveDashboard(toJson, timeFilter, dashboardStateManager, saveOpt
|
|||
|
||||
updateSavedDashboard(savedDashboard, appState, timeFilter, toJson);
|
||||
|
||||
return savedDashboard.save(saveOptions)
|
||||
.then((id) => {
|
||||
dashboardStateManager.lastSavedDashboardFilters = dashboardStateManager.getFilterState();
|
||||
dashboardStateManager.resetState();
|
||||
return id;
|
||||
});
|
||||
return savedDashboard.save(saveOptions).then((id: string) => {
|
||||
dashboardStateManager.lastSavedDashboardFilters = dashboardStateManager.getFilterState();
|
||||
dashboardStateManager.resetState();
|
||||
return id;
|
||||
});
|
||||
}
|
|
@ -18,21 +18,35 @@
|
|||
*/
|
||||
|
||||
import _ from 'lodash';
|
||||
import { AppState } from 'ui/state_management/app_state';
|
||||
import { Timefilter } from 'ui/timefilter';
|
||||
import { RefreshInterval } from 'ui/timefilter/timefilter';
|
||||
import { FilterUtils } from './filter_utils';
|
||||
import { SavedObjectDashboard } from '../saved_dashboard/saved_dashboard';
|
||||
|
||||
export function updateSavedDashboard(savedDashboard, appState, timeFilter, toJson) {
|
||||
export function updateSavedDashboard(
|
||||
savedDashboard: SavedObjectDashboard,
|
||||
appState: AppState,
|
||||
timeFilter: Timefilter,
|
||||
toJson: <T>(object: T) => string
|
||||
) {
|
||||
savedDashboard.title = appState.title;
|
||||
savedDashboard.description = appState.description;
|
||||
savedDashboard.timeRestore = appState.timeRestore;
|
||||
savedDashboard.panelsJSON = toJson(appState.panels);
|
||||
savedDashboard.optionsJSON = toJson(appState.options);
|
||||
|
||||
savedDashboard.timeFrom = savedDashboard.timeRestore ?
|
||||
FilterUtils.convertTimeToUTCString(timeFilter.getTime().from)
|
||||
savedDashboard.timeFrom = savedDashboard.timeRestore
|
||||
? FilterUtils.convertTimeToUTCString(timeFilter.getTime().from)
|
||||
: undefined;
|
||||
savedDashboard.timeTo = savedDashboard.timeRestore ?
|
||||
FilterUtils.convertTimeToUTCString(timeFilter.getTime().to)
|
||||
savedDashboard.timeTo = savedDashboard.timeRestore
|
||||
? FilterUtils.convertTimeToUTCString(timeFilter.getTime().to)
|
||||
: undefined;
|
||||
const timeRestoreObj = _.pick(timeFilter.getRefreshInterval(), ['display', 'pause', 'section', 'value']);
|
||||
const timeRestoreObj: RefreshInterval = _.pick(timeFilter.getRefreshInterval(), [
|
||||
'display',
|
||||
'pause',
|
||||
'section',
|
||||
'value',
|
||||
]);
|
||||
savedDashboard.refreshInterval = savedDashboard.timeRestore ? timeRestoreObj : undefined;
|
||||
}
|
|
@ -18,10 +18,15 @@
|
|||
*/
|
||||
|
||||
import expect from '@kbn/expect';
|
||||
import { PanelState } from '../../selectors';
|
||||
import { createPanelState } from '../panel_state';
|
||||
import { SavedDashboardPanel } from '../../types';
|
||||
|
||||
function createPanelWithDimensions(x: number, y: number, w: number, h: number): PanelState {
|
||||
function createPanelWithDimensions(
|
||||
x: number,
|
||||
y: number,
|
||||
w: number,
|
||||
h: number
|
||||
): SavedDashboardPanel {
|
||||
return {
|
||||
id: 'foo',
|
||||
version: '6.3.0',
|
||||
|
|
|
@ -30,9 +30,10 @@ import {
|
|||
EmbeddableState,
|
||||
} from 'ui/embeddable';
|
||||
import { EmbeddableErrorAction } from '../actions';
|
||||
import { PanelId, PanelState } from '../selectors';
|
||||
import { PanelId } from '../selectors';
|
||||
import { PanelError } from './panel_error';
|
||||
import { PanelHeader } from './panel_header';
|
||||
import { SavedDashboardPanel } from '../types';
|
||||
|
||||
export interface DashboardPanelProps {
|
||||
viewOnlyMode: boolean;
|
||||
|
@ -48,7 +49,7 @@ export interface DashboardPanelProps {
|
|||
embeddableError: (errorMessage: EmbeddableErrorAction) => void;
|
||||
embeddableIsInitializing: () => void;
|
||||
initialized: boolean;
|
||||
panel: PanelState;
|
||||
panel: SavedDashboardPanel;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
|
|
|
@ -47,9 +47,9 @@ import {
|
|||
getPanelType,
|
||||
getViewMode,
|
||||
PanelId,
|
||||
PanelState,
|
||||
} from '../selectors';
|
||||
import { DashboardPanel } from './dashboard_panel';
|
||||
import { SavedDashboardPanel } from '../types';
|
||||
|
||||
export interface DashboardPanelContainerOwnProps {
|
||||
panelId: PanelId;
|
||||
|
@ -61,7 +61,7 @@ interface DashboardPanelContainerStateProps {
|
|||
viewOnlyMode: boolean;
|
||||
containerState: ContainerState;
|
||||
initialized: boolean;
|
||||
panel: PanelState;
|
||||
panel: SavedDashboardPanel;
|
||||
lastReloadRequestTime?: number;
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
jest.mock('ui/chrome', () => ({ getKibanaVersion: () => '6.0.0' }), { virtual: true });
|
||||
jest.mock('ui/metadata', () => ({
|
||||
metadata: {
|
||||
branch: 'my-metadata-branch',
|
||||
version: 'my-metadata-version',
|
||||
},
|
||||
}));
|
||||
|
||||
import { DEFAULT_PANEL_HEIGHT, DEFAULT_PANEL_WIDTH } from '../dashboard_constants';
|
||||
import { SavedDashboardPanel } from '../types';
|
||||
import { createPanelState } from './panel_state';
|
||||
|
||||
const panels: SavedDashboardPanel[] = [];
|
||||
|
||||
test('createPanelState adds a new panel state in 0,0 position', () => {
|
||||
const panelState = createPanelState('id', 'type', '1', panels);
|
||||
expect(panelState.type).toBe('type');
|
||||
expect(panelState.gridData.x).toBe(0);
|
||||
expect(panelState.gridData.y).toBe(0);
|
||||
expect(panelState.gridData.h).toBe(DEFAULT_PANEL_HEIGHT);
|
||||
expect(panelState.gridData.w).toBe(DEFAULT_PANEL_WIDTH);
|
||||
|
||||
panels.push(panelState);
|
||||
});
|
||||
|
||||
test('createPanelState adds a second new panel state', () => {
|
||||
const panelState = createPanelState('id2', 'type', '2', panels);
|
||||
expect(panelState.gridData.x).toBe(DEFAULT_PANEL_WIDTH);
|
||||
expect(panelState.gridData.y).toBe(0);
|
||||
expect(panelState.gridData.h).toBe(DEFAULT_PANEL_HEIGHT);
|
||||
expect(panelState.gridData.w).toBe(DEFAULT_PANEL_WIDTH);
|
||||
|
||||
panels.push(panelState);
|
||||
});
|
||||
|
||||
test('createPanelState adds a third new panel state', () => {
|
||||
const panelState = createPanelState('id3', 'type', '3', panels);
|
||||
expect(panelState.gridData.x).toBe(0);
|
||||
expect(panelState.gridData.y).toBe(DEFAULT_PANEL_HEIGHT);
|
||||
expect(panelState.gridData.h).toBe(DEFAULT_PANEL_HEIGHT);
|
||||
expect(panelState.gridData.w).toBe(DEFAULT_PANEL_WIDTH);
|
||||
|
||||
panels.push(panelState);
|
||||
});
|
||||
|
||||
test('createPanelState adds a new panel state in the top most position', () => {
|
||||
const panelsWithEmptySpace = panels.filter(panel => panel.gridData.x === 0);
|
||||
const panelState = createPanelState('id3', 'type', '3', panelsWithEmptySpace);
|
||||
expect(panelState.gridData.x).toBe(DEFAULT_PANEL_WIDTH);
|
||||
expect(panelState.gridData.y).toBe(0);
|
||||
expect(panelState.gridData.h).toBe(DEFAULT_PANEL_HEIGHT);
|
||||
expect(panelState.gridData.w).toBe(DEFAULT_PANEL_WIDTH);
|
||||
});
|
|
@ -23,29 +23,14 @@ import {
|
|||
DEFAULT_PANEL_HEIGHT,
|
||||
DEFAULT_PANEL_WIDTH,
|
||||
} from '../dashboard_constants';
|
||||
import { PanelState } from '../selectors';
|
||||
|
||||
/**
|
||||
* Represents a panel on a grid. Keeps track of position in the grid and what visualization it
|
||||
* contains.
|
||||
*
|
||||
* @typedef {Object} PanelState
|
||||
* @property {number} id - Id of the visualization contained in the panel.
|
||||
* @property {string} version - Version of Kibana this panel was created in.
|
||||
* @property {string} type - Type of the visualization in the panel.
|
||||
* @property {number} panelIndex - Unique id to represent this panel in the grid. Note that this is
|
||||
* NOT the index in the panels array. While it may initially represent that, it is not
|
||||
* updated with changes in a dashboard, and is simply used as a unique identifier. The name
|
||||
* remains as panelIndex for backward compatibility reasons - changing it can break reporting.
|
||||
* @property {Object} gridData
|
||||
* @property {number} gridData.w - Width of the panel.
|
||||
* @property {number} gridData.h - Height of the panel.
|
||||
* @property {number} gridData.x - Column position of the panel.
|
||||
* @property {number} gridData.y - Row position of the panel.
|
||||
*/
|
||||
import { SavedDashboardPanel } from '../types';
|
||||
|
||||
// Look for the smallest y and x value where the default panel will fit.
|
||||
function findTopLeftMostOpenSpace(width: number, height: number, currentPanels: PanelState[]) {
|
||||
function findTopLeftMostOpenSpace(
|
||||
width: number,
|
||||
height: number,
|
||||
currentPanels: SavedDashboardPanel[]
|
||||
) {
|
||||
let maxY = -1;
|
||||
|
||||
currentPanels.forEach(panel => {
|
||||
|
@ -65,6 +50,14 @@ function findTopLeftMostOpenSpace(width: number, height: number, currentPanels:
|
|||
currentPanels.forEach(panel => {
|
||||
for (let x = panel.gridData.x; x < panel.gridData.x + panel.gridData.w; x++) {
|
||||
for (let y = panel.gridData.y; y < panel.gridData.y + panel.gridData.h; y++) {
|
||||
const row = grid[y];
|
||||
if (row === undefined) {
|
||||
throw new Error(
|
||||
`Attempted to access a row that doesn't exist at ${y} for panel ${JSON.stringify(
|
||||
panel
|
||||
)}`
|
||||
);
|
||||
}
|
||||
grid[y][x] = 1;
|
||||
}
|
||||
}
|
||||
|
@ -96,22 +89,17 @@ function findTopLeftMostOpenSpace(width: number, height: number, currentPanels:
|
|||
}
|
||||
}
|
||||
}
|
||||
return { x: 0, y: Infinity };
|
||||
return { x: 0, y: maxY };
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and initializes a basic panel state.
|
||||
* @param {number} id
|
||||
* @param {string} type
|
||||
* @param {number} panelIndex
|
||||
* @param {Array} currentPanels
|
||||
* @return {PanelState}
|
||||
*/
|
||||
export function createPanelState(
|
||||
id: string,
|
||||
type: string,
|
||||
panelIndex: string,
|
||||
currentPanels: PanelState[]
|
||||
currentPanels: SavedDashboardPanel[]
|
||||
) {
|
||||
const { x, y } = findTopLeftMostOpenSpace(
|
||||
DEFAULT_PANEL_WIDTH,
|
||||
|
|
|
@ -21,8 +21,7 @@ import { i18n } from '@kbn/i18n';
|
|||
import _ from 'lodash';
|
||||
import chrome from 'ui/chrome';
|
||||
import { DEFAULT_PANEL_HEIGHT, DEFAULT_PANEL_WIDTH } from '../dashboard_constants';
|
||||
import { PanelState } from '../selectors';
|
||||
import { GridData } from '../types';
|
||||
import { GridData, SavedDashboardPanel } from '../types';
|
||||
|
||||
const PANEL_HEIGHT_SCALE_FACTOR = 5;
|
||||
const PANEL_HEIGHT_SCALE_FACTOR_WITH_MARGINS = 4;
|
||||
|
@ -36,7 +35,7 @@ export interface SemanticVersion {
|
|||
export class PanelUtils {
|
||||
// 6.1 switched from gridster to react grid. React grid uses different variables for tracking layout
|
||||
// eslint-disable-next-line @typescript-eslint/camelcase
|
||||
public static convertPanelDataPre_6_1(panel: any): PanelState {
|
||||
public static convertPanelDataPre_6_1(panel: any): SavedDashboardPanel {
|
||||
['col', 'row'].forEach(key => {
|
||||
if (!_.has(panel, key)) {
|
||||
throw new Error(
|
||||
|
@ -126,7 +125,7 @@ export class PanelUtils {
|
|||
};
|
||||
}
|
||||
|
||||
public static initPanelIndexes(panels: PanelState[]): void {
|
||||
public static initPanelIndexes(panels: SavedDashboardPanel[]): void {
|
||||
// find the largest panelIndex in all the panels
|
||||
let maxIndex = this.getMaxPanelIndex(panels);
|
||||
|
||||
|
@ -138,7 +137,7 @@ export class PanelUtils {
|
|||
});
|
||||
}
|
||||
|
||||
public static getMaxPanelIndex(panels: PanelState[]): number {
|
||||
public static getMaxPanelIndex(panels: SavedDashboardPanel[]): number {
|
||||
let maxId = panels.reduce((id, panel) => {
|
||||
return Math.max(id, Number(panel.panelIndex || id));
|
||||
}, 0);
|
||||
|
|
|
@ -20,7 +20,12 @@
|
|||
import _ from 'lodash';
|
||||
import { Reducer } from 'redux';
|
||||
import { PanelActions, PanelActionTypeKeys, SetPanelTitleActionPayload } from '../actions';
|
||||
import { PanelId, PanelState, PanelStateMap } from '../selectors';
|
||||
import { PanelId } from '../selectors';
|
||||
import { SavedDashboardPanel } from '../types';
|
||||
|
||||
interface PanelStateMap {
|
||||
[key: string]: SavedDashboardPanel;
|
||||
}
|
||||
|
||||
const deletePanel = (panels: PanelStateMap, panelId: PanelId): PanelStateMap => {
|
||||
const panelsCopy = { ...panels };
|
||||
|
@ -28,7 +33,7 @@ const deletePanel = (panels: PanelStateMap, panelId: PanelId): PanelStateMap =>
|
|||
return panelsCopy;
|
||||
};
|
||||
|
||||
const updatePanel = (panels: PanelStateMap, panelState: PanelState): PanelStateMap => ({
|
||||
const updatePanel = (panels: PanelStateMap, panelState: SavedDashboardPanel): PanelStateMap => ({
|
||||
...panels,
|
||||
[panelState.panelIndex]: panelState,
|
||||
});
|
||||
|
|
43
src/legacy/core_plugins/kibana/public/dashboard/saved_dashboard/saved_dashboard.d.ts
vendored
Normal file
43
src/legacy/core_plugins/kibana/public/dashboard/saved_dashboard/saved_dashboard.d.ts
vendored
Normal file
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { SearchSource } from 'ui/courier';
|
||||
import { SavedObject } from 'ui/saved_objects/saved_object';
|
||||
import moment from 'moment';
|
||||
import { RefreshInterval } from 'ui/timefilter/timefilter';
|
||||
|
||||
export interface SavedObjectDashboard extends SavedObject {
|
||||
id?: string;
|
||||
copyOnSave: boolean;
|
||||
timeRestore: boolean;
|
||||
// These optionally being moment objects rather than strings seems more like a bug than by design. It's due to
|
||||
// some code in udpate_saved_dashboard that should probably get cleaned up.
|
||||
timeTo: string | moment.Moment | undefined;
|
||||
timeFrom: string | moment.Moment | undefined;
|
||||
title: string;
|
||||
description?: string;
|
||||
panelsJSON: string;
|
||||
optionsJSON: string | undefined;
|
||||
// TODO: write a migration to rid of this, it's only around for bwc.
|
||||
uiStateJSON?: string;
|
||||
lastSavedTitle: string;
|
||||
searchSource: SearchSource;
|
||||
destroy: () => void;
|
||||
refreshInterval?: RefreshInterval;
|
||||
}
|
|
@ -18,15 +18,9 @@
|
|||
*/
|
||||
|
||||
import _ from 'lodash';
|
||||
import {
|
||||
ContainerState,
|
||||
EmbeddableMetadata,
|
||||
Filters,
|
||||
Query,
|
||||
RefreshConfig,
|
||||
TimeRange,
|
||||
} from 'ui/embeddable';
|
||||
import { ContainerState, EmbeddableMetadata, Query, RefreshConfig, TimeRange } from 'ui/embeddable';
|
||||
import { EmbeddableCustomization } from 'ui/embeddable/types';
|
||||
import { Filter } from '@kbn/es-query';
|
||||
import { DashboardViewMode } from '../dashboard_view_mode';
|
||||
import {
|
||||
DashboardMetadata,
|
||||
|
@ -34,14 +28,14 @@ import {
|
|||
EmbeddableReduxState,
|
||||
EmbeddablesMap,
|
||||
PanelId,
|
||||
PanelState,
|
||||
PanelStateMap,
|
||||
} from './types';
|
||||
import { SavedDashboardPanel, SavedDashboardPanelMap, StagedFilter } from '../types';
|
||||
|
||||
export const getPanels = (dashboard: DashboardState): Readonly<PanelStateMap> => dashboard.panels;
|
||||
export const getPanels = (dashboard: DashboardState): Readonly<SavedDashboardPanelMap> =>
|
||||
dashboard.panels;
|
||||
|
||||
export const getPanel = (dashboard: DashboardState, panelId: PanelId): PanelState =>
|
||||
getPanels(dashboard)[panelId] as PanelState;
|
||||
export const getPanel = (dashboard: DashboardState, panelId: PanelId): SavedDashboardPanel =>
|
||||
getPanels(dashboard)[panelId] as SavedDashboardPanel;
|
||||
|
||||
export const getPanelType = (dashboard: DashboardState, panelId: PanelId): string =>
|
||||
getPanel(dashboard, panelId).type;
|
||||
|
@ -118,7 +112,7 @@ export const getTimeRange = (dashboard: DashboardState): TimeRange => dashboard.
|
|||
export const getRefreshConfig = (dashboard: DashboardState): RefreshConfig =>
|
||||
dashboard.view.refreshConfig;
|
||||
|
||||
export const getFilters = (dashboard: DashboardState): Filters => dashboard.view.filters;
|
||||
export const getFilters = (dashboard: DashboardState): Filter[] => dashboard.view.filters;
|
||||
|
||||
export const getQuery = (dashboard: DashboardState): Query => dashboard.view.query;
|
||||
|
||||
|
@ -150,5 +144,5 @@ export const getContainerState = (dashboard: DashboardState, panelId: PanelId):
|
|||
/**
|
||||
* @return an array of filters any embeddables wish dashboard to apply
|
||||
*/
|
||||
export const getStagedFilters = (dashboard: DashboardState): Filters =>
|
||||
export const getStagedFilters = (dashboard: DashboardState): StagedFilter[] =>
|
||||
_.compact(_.map(dashboard.embeddables, 'stagedFilter'));
|
||||
|
|
|
@ -17,9 +17,10 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { EmbeddableMetadata, Filters, Query, RefreshConfig, TimeRange } from 'ui/embeddable';
|
||||
import { EmbeddableMetadata, Query, RefreshConfig, TimeRange } from 'ui/embeddable';
|
||||
import { Filter } from '@kbn/es-query';
|
||||
import { DashboardViewMode } from '../dashboard_view_mode';
|
||||
import { GridData } from '../types';
|
||||
import { SavedDashboardPanelMap } from '../types';
|
||||
|
||||
export type DashboardViewMode = DashboardViewMode;
|
||||
export interface ViewState {
|
||||
|
@ -32,22 +33,12 @@ export interface ViewState {
|
|||
readonly hidePanelTitles: boolean;
|
||||
readonly useMargins: boolean;
|
||||
readonly query: Query;
|
||||
readonly filters: Filters;
|
||||
readonly filters: Filter[];
|
||||
}
|
||||
|
||||
export type PanelId = string;
|
||||
export type SavedObjectId = string;
|
||||
|
||||
export interface PanelState {
|
||||
readonly id: SavedObjectId;
|
||||
readonly version: string;
|
||||
readonly type: string;
|
||||
panelIndex: PanelId;
|
||||
readonly embeddableConfig: any;
|
||||
readonly gridData: GridData;
|
||||
readonly title?: string;
|
||||
}
|
||||
|
||||
export interface EmbeddableReduxState {
|
||||
readonly metadata?: EmbeddableMetadata;
|
||||
readonly error?: string | object;
|
||||
|
@ -59,23 +50,6 @@ export interface EmbeddableReduxState {
|
|||
readonly lastReloadRequestTime: number;
|
||||
}
|
||||
|
||||
export interface Pre61PanelState {
|
||||
size_x: number;
|
||||
size_y: number;
|
||||
row: number;
|
||||
col: number;
|
||||
panelIndex: any; // earlier versions allowed this to be number or string
|
||||
id: string;
|
||||
type: string;
|
||||
// Embeddableconfig didn't actually exist on older panel states but `migrate_app_state.js` handles
|
||||
// stuffing it on.
|
||||
embeddableConfig: any;
|
||||
}
|
||||
|
||||
export interface PanelStateMap {
|
||||
[panelId: string]: PanelState | Pre61PanelState;
|
||||
}
|
||||
|
||||
export interface EmbeddablesMap {
|
||||
readonly [panelId: string]: EmbeddableReduxState;
|
||||
}
|
||||
|
@ -87,7 +61,7 @@ export interface DashboardMetadata {
|
|||
|
||||
export interface DashboardState {
|
||||
readonly view: ViewState;
|
||||
readonly panels: PanelStateMap;
|
||||
readonly panels: SavedDashboardPanelMap;
|
||||
readonly embeddables: EmbeddablesMap;
|
||||
readonly metadata: DashboardMetadata;
|
||||
}
|
||||
|
|
|
@ -17,6 +17,11 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { Query } from 'ui/embeddable';
|
||||
import { AppState } from 'ui/state_management/app_state';
|
||||
import { Filter } from '@kbn/es-query';
|
||||
import { DashboardViewMode } from './dashboard_view_mode';
|
||||
|
||||
export interface GridData {
|
||||
w: number;
|
||||
h: number;
|
||||
|
@ -24,3 +29,84 @@ export interface GridData {
|
|||
y: number;
|
||||
i: string;
|
||||
}
|
||||
|
||||
export interface SavedDashboardPanel {
|
||||
// TODO: Make id optional when embeddable API V2 is merged. At that point, it's okay to store panels
|
||||
// that aren't backed by saved object ids.
|
||||
readonly id: string;
|
||||
|
||||
readonly version: string;
|
||||
readonly type: string;
|
||||
panelIndex: string;
|
||||
embeddableConfig: any;
|
||||
readonly gridData: GridData;
|
||||
readonly title?: string;
|
||||
}
|
||||
|
||||
export interface Pre61SavedDashboardPanel {
|
||||
readonly size_x: number;
|
||||
readonly size_y: number;
|
||||
readonly row: number;
|
||||
readonly col: number;
|
||||
readonly panelIndex: number | string; // earlier versions allowed this to be number or string
|
||||
readonly id: string;
|
||||
readonly type: string;
|
||||
embeddableConfig: any;
|
||||
}
|
||||
|
||||
export interface Pre64SavedDashboardPanel {
|
||||
columns?: string;
|
||||
sort?: string;
|
||||
readonly id?: string;
|
||||
readonly version: string;
|
||||
readonly type: string;
|
||||
readonly panelIndex: string;
|
||||
readonly gridData: GridData;
|
||||
readonly title?: string;
|
||||
embeddableConfig: any;
|
||||
}
|
||||
|
||||
export interface DashboardAppStateDefaults {
|
||||
panels: SavedDashboardPanel[];
|
||||
fullScreenMode: boolean;
|
||||
title: string;
|
||||
description?: string;
|
||||
timeRestore: boolean;
|
||||
options: {
|
||||
useMargins: boolean;
|
||||
hidePanelTitles: boolean;
|
||||
};
|
||||
query: Query;
|
||||
filters: Filter[];
|
||||
viewMode: DashboardViewMode;
|
||||
}
|
||||
|
||||
export interface DashboardAppStateParameters {
|
||||
panels: SavedDashboardPanel[];
|
||||
fullScreenMode: boolean;
|
||||
title: string;
|
||||
description: string;
|
||||
timeRestore: boolean;
|
||||
options: {
|
||||
hidePanelTitles: boolean;
|
||||
useMargins: boolean;
|
||||
};
|
||||
query: Query | string;
|
||||
filters: Filter[];
|
||||
viewMode: DashboardViewMode;
|
||||
}
|
||||
|
||||
// This could probably be improved if we flesh out AppState more... though AppState will be going away
|
||||
// so maybe not worth too much time atm.
|
||||
export type DashboardAppState = DashboardAppStateParameters & AppState;
|
||||
|
||||
export interface SavedDashboardPanelMap {
|
||||
[key: string]: SavedDashboardPanel;
|
||||
}
|
||||
|
||||
export interface StagedFilter {
|
||||
field: string;
|
||||
value: string;
|
||||
operator: string;
|
||||
index: string;
|
||||
}
|
||||
|
|
|
@ -17,11 +17,13 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { Filters, Query, TimeRange } from 'ui/embeddable';
|
||||
import { Query, TimeRange } from 'ui/embeddable';
|
||||
import { Filter } from '@kbn/es-query';
|
||||
import { DashboardViewMode } from '../dashboard/dashboard_view_mode';
|
||||
import * as DashboardSelectors from '../dashboard/selectors';
|
||||
import { PanelId } from '../dashboard/selectors/types';
|
||||
import { CoreKibanaState } from './types';
|
||||
import { StagedFilter } from '../dashboard/types';
|
||||
|
||||
export const getDashboard = (state: CoreKibanaState): DashboardSelectors.DashboardState =>
|
||||
state.dashboard;
|
||||
|
@ -46,7 +48,7 @@ export const getEmbeddableStagedFilter = (state: CoreKibanaState, panelId: Panel
|
|||
export const getEmbeddableMetadata = (state: CoreKibanaState, panelId: PanelId) =>
|
||||
DashboardSelectors.getEmbeddableMetadata(getDashboard(state), panelId);
|
||||
|
||||
export const getStagedFilters = (state: CoreKibanaState): Filters =>
|
||||
export const getStagedFilters = (state: CoreKibanaState): StagedFilter[] =>
|
||||
DashboardSelectors.getStagedFilters(getDashboard(state));
|
||||
export const getViewMode = (state: CoreKibanaState): DashboardViewMode =>
|
||||
DashboardSelectors.getViewMode(getDashboard(state));
|
||||
|
@ -60,7 +62,7 @@ export const getHidePanelTitles = (state: CoreKibanaState): boolean =>
|
|||
DashboardSelectors.getHidePanelTitles(getDashboard(state));
|
||||
export const getTimeRange = (state: CoreKibanaState): TimeRange =>
|
||||
DashboardSelectors.getTimeRange(getDashboard(state));
|
||||
export const getFilters = (state: CoreKibanaState): Filters =>
|
||||
export const getFilters = (state: CoreKibanaState): Filter[] =>
|
||||
DashboardSelectors.getFilters(getDashboard(state));
|
||||
export const getQuery = (state: CoreKibanaState): Query =>
|
||||
DashboardSelectors.getQuery(getDashboard(state));
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
*/
|
||||
|
||||
import { Adapters } from 'ui/inspector';
|
||||
import { StaticIndexPattern } from 'ui/index_patterns';
|
||||
import { ContainerState } from './types';
|
||||
|
||||
export interface EmbeddableMetadata {
|
||||
|
@ -25,7 +26,7 @@ export interface EmbeddableMetadata {
|
|||
* Should specify any index pattern the embeddable uses. This will be used by the container to list out
|
||||
* available fields to filter on.
|
||||
*/
|
||||
indexPatterns?: object[];
|
||||
indexPatterns?: StaticIndexPattern[];
|
||||
|
||||
/**
|
||||
* The title, or name, of the embeddable.
|
||||
|
|
|
@ -21,4 +21,12 @@ export { EmbeddableFactory, OnEmbeddableStateChanged } from './embeddable_factor
|
|||
export * from './embeddable';
|
||||
export * from './context_menu_actions';
|
||||
export { EmbeddableFactoriesRegistryProvider } from './embeddable_factories_registry';
|
||||
export { ContainerState, EmbeddableState, Query, Filters, TimeRange, RefreshConfig } from './types';
|
||||
export {
|
||||
ContainerState,
|
||||
EmbeddableState,
|
||||
Query,
|
||||
Filters,
|
||||
Filter,
|
||||
TimeRange,
|
||||
RefreshConfig,
|
||||
} from './types';
|
||||
|
|
|
@ -17,6 +17,11 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { Filter } from '@kbn/es-query';
|
||||
|
||||
// Should go away soon once everyone imports from kbn/es-query
|
||||
export { Filter } from '@kbn/es-query';
|
||||
|
||||
export interface TimeRange {
|
||||
to: string;
|
||||
from: string;
|
||||
|
@ -31,12 +36,6 @@ export interface FilterMeta {
|
|||
disabled: boolean;
|
||||
}
|
||||
|
||||
// TODO: Filter object representation needs to be fleshed out.
|
||||
export interface Filter {
|
||||
meta: FilterMeta;
|
||||
query: object;
|
||||
}
|
||||
|
||||
export type Filters = Filter[];
|
||||
|
||||
export enum QueryLanguageType {
|
||||
|
@ -44,10 +43,13 @@ export enum QueryLanguageType {
|
|||
LUCENE = 'lucene',
|
||||
}
|
||||
|
||||
// It's a string sometimes in old version formats, before Kuery came along and there
|
||||
// was the language specifier.
|
||||
export interface Query {
|
||||
language: QueryLanguageType;
|
||||
query: string;
|
||||
}
|
||||
|
||||
export interface EmbeddableCustomization {
|
||||
[key: string]: object | string;
|
||||
}
|
||||
|
@ -58,7 +60,7 @@ export interface ContainerState {
|
|||
|
||||
timeRange: TimeRange;
|
||||
|
||||
filters: Filters;
|
||||
filters: Filter[];
|
||||
|
||||
refreshConfig: RefreshConfig;
|
||||
|
||||
|
|
|
@ -17,15 +17,13 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
|
||||
export function getSavedDashboardMock(config) {
|
||||
const defaults = {
|
||||
id: '123',
|
||||
title: 'my dashboard',
|
||||
panelsJSON: '[]',
|
||||
searchSource: {
|
||||
getOwnField: (param) => param
|
||||
}
|
||||
};
|
||||
return Object.assign(defaults, config);
|
||||
export interface SaveOptions {
|
||||
confirmOverwrite: boolean;
|
||||
isTitleDuplicateConfirmed: boolean;
|
||||
onTitleDuplicate: () => void;
|
||||
}
|
||||
|
||||
export interface SavedObject {
|
||||
save: (saveOptions: SaveOptions) => Promise<string>;
|
||||
copyOnSave: boolean;
|
||||
}
|
|
@ -18,4 +18,9 @@
|
|||
*/
|
||||
import { State } from './state';
|
||||
|
||||
export type AppState = State;
|
||||
export class AppState extends State {}
|
||||
|
||||
export type AppStateClass<
|
||||
TAppState extends AppState = AppState,
|
||||
TDefaults = { [key: string]: any }
|
||||
> = new (defaults: TDefaults) => TAppState;
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
export interface State {
|
||||
export class State {
|
||||
[key: string]: any;
|
||||
translateHashToRison: (stateHashOrRison: string | string[]) => string | string[];
|
||||
getQueryParamName: () => string;
|
||||
|
|
|
@ -20,7 +20,10 @@ import { cloneDeep, isEqual, isPlainObject, set } from 'lodash';
|
|||
import { State } from './state';
|
||||
|
||||
export const stateMonitorFactory = {
|
||||
create: (state: State, customInitialState: State) => stateMonitor(state, customInitialState),
|
||||
create: <TStateDefault extends { [key: string]: unknown }>(
|
||||
state: State,
|
||||
customInitialState: TStateDefault
|
||||
) => stateMonitor<TStateDefault>(state, customInitialState),
|
||||
};
|
||||
|
||||
interface StateStatus {
|
||||
|
@ -28,22 +31,32 @@ interface StateStatus {
|
|||
dirty: boolean;
|
||||
}
|
||||
|
||||
export interface StateMonitor<TStateDefault extends { [key: string]: unknown }> {
|
||||
setInitialState: (innerCustomInitialState: TStateDefault) => void;
|
||||
ignoreProps: (props: string[] | string) => void;
|
||||
onChange: (callback: ChangeHandlerFn) => StateMonitor<TStateDefault>;
|
||||
destroy: () => void;
|
||||
}
|
||||
|
||||
type ChangeHandlerFn = (status: StateStatus, type: string | null, keys: string[]) => void;
|
||||
|
||||
function stateMonitor(state: State, customInitialState: State) {
|
||||
function stateMonitor<TStateDefault extends { [key: string]: unknown }>(
|
||||
state: State,
|
||||
customInitialState: TStateDefault
|
||||
): StateMonitor<TStateDefault> {
|
||||
let destroyed = false;
|
||||
let ignoredProps: string[] = [];
|
||||
let changeHandlers: ChangeHandlerFn[] | undefined = [];
|
||||
let initialState: State;
|
||||
let initialState: TStateDefault;
|
||||
|
||||
setInitialState(customInitialState);
|
||||
|
||||
function setInitialState(innerCustomInitialState: State) {
|
||||
function setInitialState(innerCustomInitialState: TStateDefault) {
|
||||
// state.toJSON returns a reference, clone so we can mutate it safely
|
||||
initialState = cloneDeep(innerCustomInitialState) || cloneDeep(state.toJSON());
|
||||
}
|
||||
|
||||
function removeIgnoredProps(innerState: State) {
|
||||
function removeIgnoredProps(innerState: TStateDefault) {
|
||||
ignoredProps.forEach(path => {
|
||||
set(innerState, path, true);
|
||||
});
|
||||
|
@ -84,7 +97,7 @@ function stateMonitor(state: State, customInitialState: State) {
|
|||
}
|
||||
|
||||
return {
|
||||
setInitialState(innerCustomInitialState: State) {
|
||||
setInitialState(innerCustomInitialState: TStateDefault) {
|
||||
if (!isPlainObject(innerCustomInitialState)) {
|
||||
throw new TypeError('The default state must be an object');
|
||||
}
|
||||
|
@ -102,7 +115,7 @@ function stateMonitor(state: State, customInitialState: State) {
|
|||
}
|
||||
},
|
||||
|
||||
ignoreProps(props: string[]) {
|
||||
ignoreProps(props: string[] | string) {
|
||||
ignoredProps = ignoredProps.concat(props);
|
||||
removeIgnoredProps(initialState);
|
||||
return this;
|
||||
|
|
41
src/legacy/ui/public/timefilter/timefilter.d.ts
vendored
41
src/legacy/ui/public/timefilter/timefilter.d.ts
vendored
|
@ -17,5 +17,44 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
export type Timefilter = any;
|
||||
import { Moment } from 'moment';
|
||||
import { TimeRange } from './time_history';
|
||||
import moment = require('moment');
|
||||
|
||||
// NOTE: These types are somewhat guessed, they may be incorrect.
|
||||
|
||||
export interface RefreshInterval {
|
||||
display?: string;
|
||||
pause: boolean;
|
||||
section?: string;
|
||||
value: number;
|
||||
}
|
||||
|
||||
export interface Timefilter {
|
||||
time: {
|
||||
// NOTE: It's unclear if this is supposed to actually allow a moment object, or undefined, or if this is just
|
||||
// a bug... should be investigated. This should probably be the TimeRange type, but most TimeRange interfaces
|
||||
// don't account for the possibility of the moment object, and it is actually a possibility.
|
||||
to: string | moment.Moment | undefined;
|
||||
from: string | moment.Moment | undefined;
|
||||
};
|
||||
getTime: () => {
|
||||
to: string | moment.Moment | undefined;
|
||||
from: string | moment.Moment | undefined;
|
||||
};
|
||||
setTime: (
|
||||
timeRange: {
|
||||
to: string | moment.Moment | undefined;
|
||||
from: string | moment.Moment | undefined;
|
||||
}
|
||||
) => void;
|
||||
setRefreshInterval: (refreshInterval: RefreshInterval) => void;
|
||||
getRefreshInterval: () => RefreshInterval;
|
||||
disableAutoRefreshSelector: () => void;
|
||||
disableTimeRangeSelector: () => void;
|
||||
enableAutoRefreshSelector: () => void;
|
||||
off: (event: string, reload: () => void) => void;
|
||||
on: (event: string, reload: () => void) => void;
|
||||
}
|
||||
|
||||
export const timefilter: Timefilter;
|
||||
|
|
|
@ -33,7 +33,7 @@ export interface FilterMeta {
|
|||
|
||||
export interface Filter {
|
||||
meta: FilterMeta;
|
||||
query: object;
|
||||
query?: object;
|
||||
}
|
||||
|
||||
export type Filters = Filter[];
|
||||
|
|
Loading…
Reference in a new issue