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

Expand Up @@ -17,21 +17,31 @@
* under the License.
*/

import { IAppState } 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(): IAppState {
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;
Expand Down
Expand Up @@ -18,7 +18,7 @@
*/

/* global jest */
export function getEmbeddableFactoryMock(config) {
export function getEmbeddableFactoryMock(config?: any) {
const embeddableFactoryMockDefaults = {
create: jest.fn(() => Promise.resolve({})),
};
Expand Down
@@ -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,
};
}
Expand Up @@ -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';

Expand Down Expand Up @@ -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),
};
Expand Down
Expand Up @@ -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);
19 changes: 12 additions & 7 deletions src/legacy/core_plugins/kibana/public/dashboard/actions/panels.ts
Expand Up @@ -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 } from '../types';

export enum PanelActionTypeKeys {
DELETE_PANEL = 'DELETE_PANEL',
Expand All @@ -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, { [key: string]: SavedDashboardPanel }> {}

export interface ResetPanelTitleAction
extends KibanaAction<PanelActionTypeKeys.RESET_PANEL_TITLE, PanelId> {}
Expand All @@ -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, { [key: string]: SavedDashboardPanel }> {}

export type PanelActions =
| DeletePanelAction
Expand All @@ -64,10 +65,14 @@ 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<{ [key: string]: SavedDashboardPanel }>(
PanelActionTypeKeys.UPDATE_PANELS
);
export const setPanels = createAction<{ [key: string]: SavedDashboardPanel }>(
PanelActionTypeKeys.SET_PANELS
);
Expand Up @@ -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);
Expand Down
Expand Up @@ -22,29 +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 { DashboardAppState } from './types';
import { TimeRange } from 'ui/embeddable';
import { IndexPattern } from 'ui/index_patterns';

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: {
time: TimeRange;
setTime: (time: TimeRange) => void;
} = {
time: { to: 'now', from: 'now-15m' },
setTime(time) {
this.time = time;
},
};
const mockIndexPattern = { id: 'index1' };
const mockIndexPattern: IndexPattern = { id: 'index1', fields: [], title: 'hi' };

function initDashboardState() {
dashboardState = new DashboardStateManager({
savedDashboard,
AppState: getAppStateMock(),
AppStateClass: getAppStateMock() as IAppState<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';
Expand All @@ -59,7 +68,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';
Expand All @@ -74,7 +83,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';
Expand All @@ -90,7 +99,7 @@ describe('DashboardState', function () {
});
});

describe('isDirty', function () {
describe('isDirty', function() {
beforeAll(() => {
initDashboardState();
});
Expand All @@ -108,29 +117,52 @@ 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', () => {
expect(dashboardState.getPanelIndexPatterns().length).toBe(0);
});

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);
});
Expand Down