diff --git a/package.json b/package.json index ac1f69aec3e5..eaf180bc5ec5 100644 --- a/package.json +++ b/package.json @@ -84,7 +84,6 @@ "@kbn/pm": "link:packages/kbn-pm", "@kbn/test-subj-selector": "link:packages/kbn-test-subj-selector", "@kbn/ui-framework": "link:packages/kbn-ui-framework", - "@types/prop-types": "^15.5.3", "JSONStream": "1.1.1", "accept-language-parser": "1.2.0", "angular": "1.6.9", @@ -230,10 +229,15 @@ "@types/execa": "^0.9.0", "@types/getopts": "^2.0.0", "@types/glob": "^5.0.35", + "@types/jest": "^22.2.3", "@types/listr": "^0.13.0", + "@types/lodash": "^3.10.1", "@types/minimatch": "^2.0.29", + "@types/prop-types": "^15.5.3", "@types/react": "^16.3.14", "@types/react-dom": "^16.0.5", + "@types/redux": "^3.6.31", + "@types/redux-actions": "^2.2.1", "angular-mocks": "1.4.7", "babel-eslint": "8.1.2", "babel-jest": "^22.4.3", diff --git a/src/core_plugins/kibana/public/dashboard/actions/embeddables.js b/src/core_plugins/kibana/public/dashboard/actions/embeddables.js deleted file mode 100644 index 5c9af2c0e084..000000000000 --- a/src/core_plugins/kibana/public/dashboard/actions/embeddables.js +++ /dev/null @@ -1,60 +0,0 @@ -/* - * 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 { createAction } from 'redux-actions'; -import _ from 'lodash'; - -import { - updatePanel -} from './panels'; - -import { - getPanel, - getEmbeddableCustomization, -} from '../../selectors/dashboard_selectors'; - -export const embeddableIsInitializing = createAction('EMBEDDABLE_IS_INITIALIZING'); -export const embeddableIsInitialized = createAction('EMBEDDABLE_INITIALIZED'); -export const setStagedFilter = createAction('SET_STAGED_FILTER'); -export const clearStagedFilters = createAction('CLEAR_STAGED_FILTERS'); -export const embeddableError = createAction('EMBEDDABLE_ERROR'); - -/** - * The main point of communication from the embeddable to the dashboard. Any time state in the embeddable - * changes, this function will be called. The data is then extracted from EmbeddableState and stored in - * redux so the appropriate actions are taken and UI updated. - - * @param {string} panelId - the id of the panel whose state has changed. - * @param {EmbeddableState} embeddableState - the new state of the embeddable. - */ -export function embeddableStateChanged({ panelId, embeddableState }) { - return (dispatch, getState) => { - // Translate embeddableState to things redux cares about. - const customization = getEmbeddableCustomization(getState(), panelId); - if (!_.isEqual(embeddableState.customization, customization)) { - const panel = getPanel(getState(), panelId); - dispatch(updatePanel({ ...panel, embeddableConfig: _.cloneDeep(embeddableState.customization) })); - } - - if (embeddableState.stagedFilter) { - dispatch(setStagedFilter({ stagedFilter: embeddableState.stagedFilter, panelId })); - } - }; -} - diff --git a/src/core_plugins/kibana/public/dashboard/actions/embeddables.test.js b/src/core_plugins/kibana/public/dashboard/actions/embeddables.test.ts similarity index 88% rename from src/core_plugins/kibana/public/dashboard/actions/embeddables.test.js rename to src/core_plugins/kibana/public/dashboard/actions/embeddables.test.ts index e9f7f558d183..51fd81ccb6f6 100644 --- a/src/core_plugins/kibana/public/dashboard/actions/embeddables.test.js +++ b/src/core_plugins/kibana/public/dashboard/actions/embeddables.test.ts @@ -20,14 +20,12 @@ import { store } from '../../store'; import { clearStagedFilters, - setStagedFilter, embeddableIsInitialized, embeddableIsInitializing, + setStagedFilter, } from '../actions'; -import { - getStagedFilters, -} from '../../selectors'; +import { getStagedFilters } from '../../selectors'; beforeAll(() => { store.dispatch(embeddableIsInitializing('foo1')); @@ -43,13 +41,17 @@ describe('staged filters', () => { }); test('can set a staged filter', () => { - store.dispatch(setStagedFilter({ stagedFilter: ['imafilter'], panelId: 'foo1' })); + store.dispatch( + setStagedFilter({ stagedFilter: ['imafilter'], panelId: 'foo1' }) + ); const stagedFilters = getStagedFilters(store.getState()); expect(stagedFilters.length).toBe(1); }); test('getStagedFilters returns filters for all embeddables', () => { - store.dispatch(setStagedFilter({ stagedFilter: ['imafilter'], panelId: 'foo2' })); + store.dispatch( + setStagedFilter({ stagedFilter: ['imafilter'], panelId: 'foo2' }) + ); const stagedFilters = getStagedFilters(store.getState()); expect(stagedFilters.length).toBe(2); }); @@ -60,4 +62,3 @@ describe('staged filters', () => { expect(stagedFilters.length).toBe(0); }); }); - diff --git a/src/core_plugins/kibana/public/dashboard/actions/embeddables.ts b/src/core_plugins/kibana/public/dashboard/actions/embeddables.ts new file mode 100644 index 000000000000..65b52a2c235f --- /dev/null +++ b/src/core_plugins/kibana/public/dashboard/actions/embeddables.ts @@ -0,0 +1,144 @@ +/* + * 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 _ from 'lodash'; +import { Dispatch } from 'redux'; +import { createAction } from 'redux-actions'; +import { + CoreKibanaState, + getEmbeddableCustomization, + getPanel, +} from '../../selectors'; +import { PanelId, PanelState } from '../selectors'; +import { updatePanel } from './panels'; + +import { EmbeddableMetadata, EmbeddableState } from 'ui/embeddable'; +import { KibanaAction } from '../../selectors/types'; + +export enum EmbeddableActionTypeKeys { + EMBEDDABLE_IS_INITIALIZING = 'EMBEDDABLE_IS_INITIALIZING', + EMBEDDABLE_IS_INITIALIZED = 'EMBEDDABLE_IS_INITIALIZED', + SET_STAGED_FILTER = 'SET_STAGED_FILTER', + CLEAR_STAGED_FILTERS = 'CLEAR_STAGED_FILTERS', + EMBEDDABLE_ERROR = 'EMBEDDABLE_ERROR', +} + +export interface EmbeddableIsInitializingAction + extends KibanaAction< + EmbeddableActionTypeKeys.EMBEDDABLE_IS_INITIALIZING, + PanelId + > {} + +export interface EmbeddableIsInitializedActionPayload { + panelId: PanelId; + metadata: EmbeddableMetadata; +} + +export interface EmbeddableIsInitializedAction + extends KibanaAction< + EmbeddableActionTypeKeys.EMBEDDABLE_IS_INITIALIZED, + EmbeddableIsInitializedActionPayload + > {} + +export interface SetStagedFilterActionPayload { + panelId: PanelId; + stagedFilter: object; +} + +export interface SetStagedFilterAction + extends KibanaAction< + EmbeddableActionTypeKeys.SET_STAGED_FILTER, + SetStagedFilterActionPayload + > {} + +export interface ClearStagedFiltersAction + extends KibanaAction< + EmbeddableActionTypeKeys.CLEAR_STAGED_FILTERS, + undefined + > {} + +export interface EmbeddableErrorActionPayload { + error: string | object; + panelId: PanelId; +} + +export interface EmbeddableErrorAction + extends KibanaAction< + EmbeddableActionTypeKeys.EMBEDDABLE_ERROR, + EmbeddableErrorActionPayload + > {} + +export type EmbeddableActions = + | EmbeddableIsInitializingAction + | EmbeddableIsInitializedAction + | ClearStagedFiltersAction + | SetStagedFilterAction + | EmbeddableErrorAction; + +export const embeddableIsInitializing = createAction( + EmbeddableActionTypeKeys.EMBEDDABLE_IS_INITIALIZING +); +export const embeddableIsInitialized = createAction< + EmbeddableIsInitializedActionPayload +>(EmbeddableActionTypeKeys.EMBEDDABLE_IS_INITIALIZED); +export const setStagedFilter = createAction( + EmbeddableActionTypeKeys.SET_STAGED_FILTER +); +export const clearStagedFilters = createAction( + EmbeddableActionTypeKeys.CLEAR_STAGED_FILTERS +); +export const embeddableError = createAction( + EmbeddableActionTypeKeys.EMBEDDABLE_ERROR +); + +/** + * The main point of communication from the embeddable to the dashboard. Any time state in the embeddable + * changes, this function will be called. The data is then extracted from EmbeddableState and stored in + * redux so the appropriate actions are taken and UI updated. + * + * @param changeData.panelId - the id of the panel whose state has changed. + * @param changeData.embeddableState - the new state of the embeddable. + */ +export function embeddableStateChanged(changeData: { + panelId: PanelId; + embeddableState: EmbeddableState; +}) { + const { panelId, embeddableState } = changeData; + return ( + dispatch: Dispatch, + getState: () => CoreKibanaState + ) => { + // Translate embeddableState to things redux cares about. + const customization = getEmbeddableCustomization(getState(), panelId); + if (!_.isEqual(embeddableState.customization, customization)) { + const originalPanelState = getPanel(getState(), panelId); + const newPanelState: PanelState = { + ...originalPanelState, + embeddableConfig: _.cloneDeep(embeddableState.customization), + }; + dispatch(updatePanel(newPanelState)); + } + + if (embeddableState.stagedFilter) { + dispatch( + setStagedFilter({ stagedFilter: embeddableState.stagedFilter, panelId }) + ); + } + }; +} diff --git a/src/core_plugins/kibana/public/dashboard/actions/index.js b/src/core_plugins/kibana/public/dashboard/actions/index.ts similarity index 92% rename from src/core_plugins/kibana/public/dashboard/actions/index.js rename to src/core_plugins/kibana/public/dashboard/actions/index.ts index 5de4da955f0c..7a5a8e209684 100644 --- a/src/core_plugins/kibana/public/dashboard/actions/index.js +++ b/src/core_plugins/kibana/public/dashboard/actions/index.ts @@ -20,8 +20,4 @@ export * from './view'; export * from './panels'; export * from './embeddables'; - -export { - updateDescription, - updateTitle, -} from './metadata'; +export * from './metadata'; diff --git a/src/core_plugins/kibana/public/dashboard/actions/metadata.ts b/src/core_plugins/kibana/public/dashboard/actions/metadata.ts new file mode 100644 index 000000000000..ead28e7a555a --- /dev/null +++ b/src/core_plugins/kibana/public/dashboard/actions/metadata.ts @@ -0,0 +1,51 @@ +/* + * 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 { createAction } from 'redux-actions'; +import { KibanaAction } from '../../selectors/types'; + +export enum MetadataActionTypeKeys { + UPDATE_DESCRIPTION = 'UPDATE_DESCRIPTION', + UPDATE_TITLE = 'UPDATE_TITLE', +} + +export type UpdateTitleActionPayload = string; + +export interface UpdateTitleAction + extends KibanaAction< + MetadataActionTypeKeys.UPDATE_TITLE, + UpdateTitleActionPayload + > {} + +export type UpdateDescriptionActionPayload = string; + +export interface UpdateDescriptionAction + extends KibanaAction< + MetadataActionTypeKeys.UPDATE_DESCRIPTION, + UpdateDescriptionActionPayload + > {} + +export type MetadataActions = UpdateDescriptionAction | UpdateTitleAction; + +export const updateDescription = createAction( + MetadataActionTypeKeys.UPDATE_DESCRIPTION +); +export const updateTitle = createAction( + MetadataActionTypeKeys.UPDATE_TITLE +); diff --git a/src/core_plugins/kibana/public/dashboard/actions/panels.js b/src/core_plugins/kibana/public/dashboard/actions/panels.js deleted file mode 100644 index bd35b8d21bcd..000000000000 --- a/src/core_plugins/kibana/public/dashboard/actions/panels.js +++ /dev/null @@ -1,43 +0,0 @@ -/* - * 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 { createAction } from 'redux-actions'; - -export const deletePanel = createAction('DELETE_PANEL'); -export const updatePanel = createAction('UPDATE_PANEL'); -export const resetPanelTitle = createAction('RESET_PANEl_TITLE'); -export const setPanelTitle = createAction('SET_PANEl_TITLE', - /** - * @param title {string} - * @param panelIndex {string} - */ - (title, panelIndex) => ({ title, panelIndex }) -); - -/** - * @param panels {Array} - * @return {Object} - */ -export const updatePanels = createAction('UPDATE_PANELS'); - -/** - * @param panels {Array} - * @return {Object} - */ -export const setPanels = createAction('SET_PANELS'); diff --git a/src/core_plugins/kibana/public/dashboard/actions/panels.ts b/src/core_plugins/kibana/public/dashboard/actions/panels.ts new file mode 100644 index 000000000000..314ae9c59362 --- /dev/null +++ b/src/core_plugins/kibana/public/dashboard/actions/panels.ts @@ -0,0 +1,84 @@ +/* + * 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 { createAction } from 'redux-actions'; +import { KibanaAction } from '../../selectors/types'; +import { PanelId, PanelsMap, PanelState } from '../selectors'; + +export enum PanelActionTypeKeys { + DELETE_PANEL = 'DELETE_PANEL', + UPDATE_PANEL = 'UPDATE_PANEL', + RESET_PANEl_TITLE = 'RESET_PANEl_TITLE', + SET_PANEl_TITLE = 'SET_PANEl_TITLE', + UPDATE_PANELS = 'UPDATE_PANELS', + SET_PANELS = 'SET_PANELS', +} + +export interface DeletePanelAction + extends KibanaAction {} + +export interface UpdatePanelAction + extends KibanaAction {} + +export interface UpdatePanelsAction + extends KibanaAction {} + +export interface ResetPanelTitleAction + extends KibanaAction {} + +export interface SetPanelTitleActionPayload { + panelId: PanelId; + title: string; +} + +export interface SetPanelTitleAction + extends KibanaAction< + PanelActionTypeKeys.SET_PANEl_TITLE, + SetPanelTitleActionPayload + > {} + +export interface SetPanelsAction + extends KibanaAction {} + +export type PanelActions = + | DeletePanelAction + | UpdatePanelAction + | ResetPanelTitleAction + | UpdatePanelsAction + | SetPanelTitleAction + | SetPanelsAction; + +export const deletePanel = createAction( + PanelActionTypeKeys.DELETE_PANEL +); +export const updatePanel = createAction( + PanelActionTypeKeys.UPDATE_PANEL +); +export const resetPanelTitle = createAction( + PanelActionTypeKeys.RESET_PANEl_TITLE +); +export const setPanelTitle = createAction( + PanelActionTypeKeys.SET_PANEl_TITLE +); +export const updatePanels = createAction( + PanelActionTypeKeys.UPDATE_PANELS +); +export const setPanels = createAction( + PanelActionTypeKeys.SET_PANELS +); diff --git a/src/core_plugins/kibana/public/dashboard/actions/view.js b/src/core_plugins/kibana/public/dashboard/actions/view.js deleted file mode 100644 index c5971741c320..000000000000 --- a/src/core_plugins/kibana/public/dashboard/actions/view.js +++ /dev/null @@ -1,31 +0,0 @@ -/* - * 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 { createAction } from 'redux-actions'; - -export const updateViewMode = createAction('UPDATE_VIEW_MODE'); -export const setVisibleContextMenuPanelId = createAction('SET_VISIBLE_CONTEXT_MENU_PANEL_ID'); -export const maximizePanel = createAction('MAXIMIZE_PANEl'); -export const minimizePanel = createAction('MINIMIZE_PANEL'); -export const updateIsFullScreenMode = createAction('UPDATE_IS_FULL_SCREEN_MODE'); -export const updateUseMargins = createAction('UPDATE_USE_MARGINS'); -export const updateHidePanelTitles = createAction('HIDE_PANEL_TITLES'); -export const updateTimeRange = createAction('UPDATE_TIME_RANGE'); -export const updateFilters = createAction('UPDATE_FILTERS'); -export const updateQuery = createAction('UPDATE_QUERY'); diff --git a/src/core_plugins/kibana/public/dashboard/actions/view.ts b/src/core_plugins/kibana/public/dashboard/actions/view.ts new file mode 100644 index 000000000000..81fcd9fd8a06 --- /dev/null +++ b/src/core_plugins/kibana/public/dashboard/actions/view.ts @@ -0,0 +1,115 @@ +/* + * 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 { createAction } from 'redux-actions'; +import { Filters, Query, TimeRange } from 'ui/embeddable'; +import { KibanaAction } from '../../selectors/types'; +import { DashboardViewMode } from '../dashboard_view_mode'; +import { PanelId } from '../selectors'; + +export enum ViewActionTypeKeys { + UPDATE_VIEW_MODE = 'UPDATE_VIEW_MODE', + SET_VISIBLE_CONTEXT_MENU_PANEL_ID = 'SET_VISIBLE_CONTEXT_MENU_PANEL_ID', + MAXIMIZE_PANEl = 'MAXIMIZE_PANEl', + MINIMIZE_PANEL = 'MINIMIZE_PANEL', + UPDATE_IS_FULL_SCREEN_MODE = 'UPDATE_IS_FULL_SCREEN_MODE', + UPDATE_USE_MARGINS = 'UPDATE_USE_MARGINS', + UPDATE_HIDE_PANEL_TITLES = 'UPDATE_HIDE_PANEL_TITLES', + UPDATE_TIME_RANGE = 'UPDATE_TIME_RANGE', + UPDATE_FILTERS = 'UPDATE_FILTERS', + UPDATE_QUERY = 'UPDATE_QUERY', +} + +export interface UpdateViewModeAction + extends KibanaAction< + ViewActionTypeKeys.UPDATE_VIEW_MODE, + DashboardViewMode + > {} + +export interface SetVisibleContextMenuPanelIdAction + extends KibanaAction< + ViewActionTypeKeys.SET_VISIBLE_CONTEXT_MENU_PANEL_ID, + PanelId + > {} + +export interface MaximizePanelAction + extends KibanaAction {} + +export interface MinimizePanelAction + extends KibanaAction {} + +export interface UpdateIsFullScreenModeAction + extends KibanaAction< + ViewActionTypeKeys.UPDATE_IS_FULL_SCREEN_MODE, + boolean + > {} + +export interface UpdateUseMarginsAction + extends KibanaAction {} + +export interface UpdateHidePanelTitlesAction + extends KibanaAction {} + +export interface UpdateTimeRangeAction + extends KibanaAction {} + +export interface UpdateFiltersAction + extends KibanaAction {} + +export interface UpdateQueryAction + extends KibanaAction {} + +export type ViewActions = + | UpdateViewModeAction + | SetVisibleContextMenuPanelIdAction + | MaximizePanelAction + | MinimizePanelAction + | UpdateIsFullScreenModeAction + | UpdateUseMarginsAction + | UpdateHidePanelTitlesAction + | UpdateTimeRangeAction + | UpdateFiltersAction + | UpdateQueryAction; + +export const updateViewMode = createAction( + ViewActionTypeKeys.UPDATE_VIEW_MODE +); +export const setVisibleContextMenuPanelId = createAction( + ViewActionTypeKeys.SET_VISIBLE_CONTEXT_MENU_PANEL_ID +); +export const maximizePanel = createAction( + ViewActionTypeKeys.MAXIMIZE_PANEl +); +export const minimizePanel = createAction(ViewActionTypeKeys.MINIMIZE_PANEL); +export const updateIsFullScreenMode = createAction( + ViewActionTypeKeys.UPDATE_IS_FULL_SCREEN_MODE +); +export const updateUseMargins = createAction( + ViewActionTypeKeys.UPDATE_USE_MARGINS +); +export const updateHidePanelTitles = createAction( + ViewActionTypeKeys.UPDATE_HIDE_PANEL_TITLES +); +export const updateTimeRange = createAction( + ViewActionTypeKeys.UPDATE_TIME_RANGE +); +export const updateFilters = createAction( + ViewActionTypeKeys.UPDATE_FILTERS +); +export const updateQuery = createAction(ViewActionTypeKeys.UPDATE_QUERY); diff --git a/src/core_plugins/kibana/public/dashboard/dashboard_app.html b/src/core_plugins/kibana/public/dashboard/dashboard_app.html index 0bca05558864..49521b3cc09d 100644 --- a/src/core_plugins/kibana/public/dashboard/dashboard_app.html +++ b/src/core_plugins/kibana/public/dashboard/dashboard_app.html @@ -70,4 +70,4 @@ > - + \ No newline at end of file diff --git a/src/core_plugins/kibana/public/dashboard/dashboard_context.js b/src/core_plugins/kibana/public/dashboard/dashboard_context.js index 6958fea64e0e..9768d1253987 100644 --- a/src/core_plugins/kibana/public/dashboard/dashboard_context.js +++ b/src/core_plugins/kibana/public/dashboard/dashboard_context.js @@ -29,30 +29,30 @@ export function dashboardContextProvider(Private, getAppState) { const appState = getAppState(); const queryFilter = Private(FilterBarQueryFilterProvider); const bool = { must: [], must_not: [] }; - if (!appState) return { bool: bool }; + if (!appState) { return { bool: bool }; } const filterBarFilters = queryFilter.getFilters(); const queryBarQuery = appState.query; if (queryBarQuery.language === 'lucene') { // Add the query bar filter, its handled differently. const query = luceneStringToDsl(queryBarQuery.query); - if (query) bool.must.push(query); + if (query) { bool.must.push(query); } } // Add each of the filter bar filters _.each(filterBarFilters, function (filter) { const esFilter = _.omit(filter, function (val, key) { - if (key === 'meta' || key[0] === '$') return true; + if (key === 'meta' || key[0] === '$') { return true; } return false; }); - if (filter.meta.disabled) return; + if (filter.meta.disabled) { return; } if (filter.meta.negate) { bool.must_not = bool.must_not || []; - if (esFilter.query || esFilter) bool.must_not.push(migrateFilter(esFilter.query || esFilter)); + if (esFilter.query || esFilter) { bool.must_not.push(migrateFilter(esFilter.query || esFilter)); } } else { bool.must = bool.must || []; - if (esFilter.query || esFilter) bool.must.push(migrateFilter(esFilter.query || esFilter)); + if (esFilter.query || esFilter) { bool.must.push(migrateFilter(esFilter.query || esFilter)); } } }); diff --git a/src/core_plugins/kibana/public/dashboard/dashboard_view_mode.js b/src/core_plugins/kibana/public/dashboard/dashboard_view_mode.ts similarity index 77% rename from src/core_plugins/kibana/public/dashboard/dashboard_view_mode.js rename to src/core_plugins/kibana/public/dashboard/dashboard_view_mode.ts index 9181fb97c9c2..e9f968249090 100644 --- a/src/core_plugins/kibana/public/dashboard/dashboard_view_mode.js +++ b/src/core_plugins/kibana/public/dashboard/dashboard_view_mode.ts @@ -17,16 +17,7 @@ * under the License. */ -/** - * A dashboard mode. - * @typedef {string} DashboardMode - */ - -/** - * Dashboard view modes. - * @type {{EDIT: DashboardViewMode, VIEW: DashboardViewMode}} - */ -export const DashboardViewMode = { - EDIT: 'edit', - VIEW: 'view' -}; +export enum DashboardViewMode { + EDIT = 'edit', + VIEW = 'view', +} diff --git a/src/core_plugins/kibana/public/dashboard/panel/panel_header/panel_header_container.test.js b/src/core_plugins/kibana/public/dashboard/panel/panel_header/panel_header_container.test.js index f760a30d1f30..2a248763ca92 100644 --- a/src/core_plugins/kibana/public/dashboard/panel/panel_header/panel_header_container.test.js +++ b/src/core_plugins/kibana/public/dashboard/panel/panel_header/panel_header_container.test.js @@ -62,12 +62,12 @@ test('Panel header shows embeddable title when nothing is set on the panel', () }); test('Panel header shows panel title when it is set on the panel', () => { - store.dispatch(setPanelTitle('my custom panel title', 'foo1')); + store.dispatch(setPanelTitle({ title: 'my custom panel title', panelId: 'foo1' })); expect(findTestSubject(component, 'dashboardPanelTitle').text()).toBe('my custom panel title'); }); test('Panel header shows no panel title when it is set to an empty string on the panel', () => { - store.dispatch(setPanelTitle('', 'foo1')); + store.dispatch(setPanelTitle({ title: '', panelId: 'foo1' })); expect(findTestSubject(component, 'dashboardPanelTitle').text()).toBe(''); }); diff --git a/src/core_plugins/kibana/public/dashboard/panel/panel_header/panel_options_menu_container.js b/src/core_plugins/kibana/public/dashboard/panel/panel_header/panel_options_menu_container.js index a64981479f80..285ea3422ab0 100644 --- a/src/core_plugins/kibana/public/dashboard/panel/panel_header/panel_options_menu_container.js +++ b/src/core_plugins/kibana/public/dashboard/panel/panel_header/panel_options_menu_container.js @@ -83,7 +83,7 @@ const mapDispatchToProps = (dispatch, { panelId }) => ({ onMaximizePanel: () => dispatch(maximizePanel(panelId)), onMinimizePanel: () => dispatch(minimizePanel()), onResetPanelTitle: () => dispatch(resetPanelTitle(panelId)), - onUpdatePanelTitle: (newTitle) => dispatch(setPanelTitle(newTitle, panelId)), + onUpdatePanelTitle: (newTitle) => dispatch(setPanelTitle({ title: newTitle, panelId: panelId })), }); const mergeProps = (stateProps, dispatchProps, ownProps) => { diff --git a/src/core_plugins/kibana/public/dashboard/reducers/embeddables.js b/src/core_plugins/kibana/public/dashboard/reducers/embeddables.js deleted file mode 100644 index 71ec9595b1cc..000000000000 --- a/src/core_plugins/kibana/public/dashboard/reducers/embeddables.js +++ /dev/null @@ -1,121 +0,0 @@ -/* - * 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 { handleActions } from 'redux-actions'; -import _ from 'lodash'; - -import { - embeddableIsInitializing, - embeddableError, - embeddableIsInitialized, - setStagedFilter, - clearStagedFilters, - deletePanel, -} from '../actions'; - -export const embeddables = handleActions({ - [clearStagedFilters]: - (embeddables) => { - return _.mapValues(embeddables, (embeddable) => _.omit({ ...embeddable }, ['stagedFilter'])); - }, - - [embeddableIsInitialized]: - /** - * - * @param embeddables {Object.} - * @param payload {Object} - * @param payload.panelId {string} Panel id of embeddable that was initialized - * @param payload.metadata {object} Metadata for the embeddable that was initialized - * @return {Object.} - */ - (embeddables, { payload }) => { - return { - ...embeddables, - [payload.panelId]: { - ...embeddables[payload.panelId], - initialized: true, - metadata: { ...payload.metadata }, - } - }; - }, - - // TODO: Currently only saved search uses this to apply a filter. When visualize uses it too, we will need to - // support multiple staged filters. - [setStagedFilter]: - (embeddables, { payload }) => { - return { - ...embeddables, - [payload.panelId]: { - ...embeddables[payload.panelId], - stagedFilter: payload.stagedFilter, - } - }; - }, - - [deletePanel]: - /** - * - * @param embeddables {Object.} - * @param payload {String} The id of the panel to delete - * @return {Object.} - */ - (embeddables, { payload }) => { - const embeddablesCopy = { ...embeddables }; - delete embeddablesCopy[payload]; - return embeddablesCopy; - }, - - [embeddableIsInitializing]: - /** - * - * @param embeddables {Object.} - * @param payload {Object} - * @param payload.panelId {String} - * @param payload.embeddable {Embeddable} - * @return {Object.} - */ - (embeddables, { payload }) => { - return { - ...embeddables, - [payload]: { - initialized: false, - error: undefined, - } - }; - }, - - [embeddableError]: - /** - * - * @param embeddables {Object.} - * @param payload {Object} - * @param payload.panelId {String} - * @param payload.error {String|Object} - * @return {Object.} - */ - (embeddables, { payload }) => { - return { - ...embeddables, - [payload.panelId]: { - ...embeddables[payload.panelId], - error: payload.error, - } - }; - } -}, {}); diff --git a/src/core_plugins/kibana/public/dashboard/reducers/embeddables.test.js b/src/core_plugins/kibana/public/dashboard/reducers/embeddables.test.ts similarity index 75% rename from src/core_plugins/kibana/public/dashboard/reducers/embeddables.test.js rename to src/core_plugins/kibana/public/dashboard/reducers/embeddables.test.ts index d69abbf9855e..6bc1140e1ebf 100644 --- a/src/core_plugins/kibana/public/dashboard/reducers/embeddables.test.js +++ b/src/core_plugins/kibana/public/dashboard/reducers/embeddables.test.ts @@ -17,17 +17,26 @@ * under the License. */ +import { getEmbeddableError, getEmbeddableInitialized } from '../../selectors'; import { store } from '../../store'; -import { - embeddableIsInitializing, setPanels, -} from '../actions'; -import { - getEmbeddableError, - getEmbeddableInitialized, -} from '../../selectors'; +import { embeddableIsInitializing, setPanels } from '../actions'; beforeAll(() => { - store.dispatch(setPanels({ 'foo1': { panelIndex: 'foo1' } })); + const panelData = { + embeddableConfig: {}, + gridData: { + h: 0, + id: '0', + w: 0, + x: 0, + y: 0, + }, + id: '123', + panelIndex: 'foo1', + type: 'mySpecialType', + version: '123', + }; + store.dispatch(setPanels({ foo1: panelData })); }); describe('embeddableIsInitializing', () => { @@ -42,4 +51,3 @@ describe('embeddableIsInitializing', () => { expect(error).toBe(undefined); }); }); - diff --git a/src/core_plugins/kibana/public/dashboard/reducers/embeddables.ts b/src/core_plugins/kibana/public/dashboard/reducers/embeddables.ts new file mode 100644 index 000000000000..905fa0b6cd2c --- /dev/null +++ b/src/core_plugins/kibana/public/dashboard/reducers/embeddables.ts @@ -0,0 +1,121 @@ +/* + * 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 _ from 'lodash'; +import { Reducer } from 'redux'; + +import { + EmbeddableActionTypeKeys, + EmbeddableErrorActionPayload, + EmbeddableIsInitializedActionPayload, + PanelActionTypeKeys, + SetStagedFilterActionPayload, +} from '../actions'; +import { + EmbeddableReduxState, + EmbeddablesMap, + PanelId, +} from '../selectors/types'; + +const embeddableIsInitializing = ( + embeddables: EmbeddablesMap, + panelId: PanelId +): EmbeddablesMap => ({ + ...embeddables, + [panelId]: { + error: undefined, + initialized: false, + metadata: {}, + stagedFilter: undefined, + }, +}); + +const embeddableIsInitialized = ( + embeddables: EmbeddablesMap, + { panelId, metadata }: EmbeddableIsInitializedActionPayload +): EmbeddablesMap => ({ + ...embeddables, + [panelId]: { + ...embeddables[panelId], + initialized: true, + metadata: { ...metadata }, + }, +}); + +const setStagedFilter = ( + embeddables: EmbeddablesMap, + { panelId, stagedFilter }: SetStagedFilterActionPayload +): EmbeddablesMap => ({ + ...embeddables, + [panelId]: { + ...embeddables[panelId], + stagedFilter, + }, +}); + +const embeddableError = ( + embeddables: EmbeddablesMap, + payload: EmbeddableErrorActionPayload +): EmbeddablesMap => ({ + ...embeddables, + [payload.panelId]: { + ...embeddables[payload.panelId], + error: payload.error, + }, +}); + +const clearStagedFilters = (embeddables: EmbeddablesMap): EmbeddablesMap => { + const omitStagedFilters = ( + embeddable: EmbeddableReduxState + ): EmbeddablesMap => _.omit({ ...embeddable }, ['stagedFilter']); + return _.mapValues(embeddables, omitStagedFilters); +}; + +const deleteEmbeddable = ( + embeddables: EmbeddablesMap, + panelId: PanelId +): EmbeddablesMap => { + const embeddablesCopy = { ...embeddables }; + delete embeddablesCopy[panelId]; + return embeddablesCopy; +}; + +export const embeddablesReducer: Reducer = ( + embeddables = {}, + action +): EmbeddablesMap => { + switch ( + action.type as EmbeddableActionTypeKeys | PanelActionTypeKeys.DELETE_PANEL + ) { + case EmbeddableActionTypeKeys.EMBEDDABLE_IS_INITIALIZING: + return embeddableIsInitializing(embeddables, action.payload); + case EmbeddableActionTypeKeys.EMBEDDABLE_IS_INITIALIZED: + return embeddableIsInitialized(embeddables, action.payload); + case EmbeddableActionTypeKeys.SET_STAGED_FILTER: + return setStagedFilter(embeddables, action.payload); + case EmbeddableActionTypeKeys.CLEAR_STAGED_FILTERS: + return clearStagedFilters(embeddables); + case EmbeddableActionTypeKeys.EMBEDDABLE_ERROR: + return embeddableError(embeddables, action.payload); + case PanelActionTypeKeys.DELETE_PANEL: + return deleteEmbeddable(embeddables, action.payload); + default: + return embeddables; + } +}; diff --git a/src/core_plugins/kibana/public/dashboard/reducers/index.js b/src/core_plugins/kibana/public/dashboard/reducers/index.ts similarity index 75% rename from src/core_plugins/kibana/public/dashboard/reducers/index.js rename to src/core_plugins/kibana/public/dashboard/reducers/index.ts index 45b2ea4ee0e6..1f4a26d255d3 100644 --- a/src/core_plugins/kibana/public/dashboard/reducers/index.js +++ b/src/core_plugins/kibana/public/dashboard/reducers/index.ts @@ -18,25 +18,17 @@ */ import { combineReducers } from 'redux'; -import { - embeddables, -} from './embeddables'; +import { embeddablesReducer } from './embeddables'; -import { - panels, -} from './panels'; +import { panelsReducer } from './panels'; -import { - view, -} from './view'; +import { viewReducer } from './view'; -import { - metadata, -} from './metadata'; +import { metadataReducer } from './metadata'; export const dashboard = combineReducers({ - view, - panels, - embeddables, - metadata, + embeddables: embeddablesReducer, + metadata: metadataReducer, + panels: panelsReducer, + view: viewReducer, }); diff --git a/src/core_plugins/kibana/public/dashboard/reducers/metadata.js b/src/core_plugins/kibana/public/dashboard/reducers/metadata.js deleted file mode 100644 index ddf997bfb0b9..000000000000 --- a/src/core_plugins/kibana/public/dashboard/reducers/metadata.js +++ /dev/null @@ -1,37 +0,0 @@ -/* - * 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 { handleActions } from 'redux-actions'; -import { updateTitle, updateDescription } from '../actions'; - -export const metadata = handleActions({ - [updateTitle]: (state, { payload }) => ({ - ...state, - title: payload - }), - - [updateDescription]: (state, { payload }) => ({ - ...state, - description: payload - }), - -}, { - title: '', - description: '', -}); diff --git a/src/core_plugins/kibana/public/dashboard/reducers/metadata.ts b/src/core_plugins/kibana/public/dashboard/reducers/metadata.ts new file mode 100644 index 000000000000..d67b2c6d2734 --- /dev/null +++ b/src/core_plugins/kibana/public/dashboard/reducers/metadata.ts @@ -0,0 +1,60 @@ +/* + * 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 { Reducer } from 'redux'; +import { + MetadataActions, + MetadataActionTypeKeys, + UpdateDescriptionActionPayload, + UpdateTitleActionPayload, +} from '../actions'; +import { DashboardMetadata } from '../selectors'; + +const updateTitle = ( + metadata: DashboardMetadata, + title: UpdateTitleActionPayload +) => ({ + ...metadata, + title, +}); + +const updateDescription = ( + metadata: DashboardMetadata, + description: UpdateDescriptionActionPayload +) => ({ + ...metadata, + description, +}); + +export const metadataReducer: Reducer = ( + metadata = { + description: '', + title: '', + }, + action +): DashboardMetadata => { + switch ((action as MetadataActions).type) { + case MetadataActionTypeKeys.UPDATE_TITLE: + return updateTitle(metadata, action.payload); + case MetadataActionTypeKeys.UPDATE_DESCRIPTION: + return updateDescription(metadata, action.payload); + default: + return metadata; + } +}; diff --git a/src/core_plugins/kibana/public/dashboard/reducers/panels.js b/src/core_plugins/kibana/public/dashboard/reducers/panels.js deleted file mode 100644 index c2d02297f223..000000000000 --- a/src/core_plugins/kibana/public/dashboard/reducers/panels.js +++ /dev/null @@ -1,120 +0,0 @@ -/* - * 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 _ from 'lodash'; -import { handleActions } from 'redux-actions'; - -import { - deletePanel, - updatePanel, - updatePanels, - setPanels, - resetPanelTitle, - setPanelTitle, -} from '../actions'; - -/** - * - * @param panel {PanelState} - new panel data (can be partial data) to merge with possibly existing panel data in - * the panels mapping. - * @param panel.panelIndex {String} The new panel data must specify the panelIndex so we know which panel to merge with. - * @param panels {Object.} - * @return {PanelState} - a new PanelState which has the merged data. - */ -function mergePanelData(panel, panels) { - return _.defaultsDeep(panel, panels[panel.panelIndex]); -} - -export const panels = handleActions({ - [setPanels]: - /** - * - * @param panels {Object.} - * @param payload {Object.} - * @return {Object.} - */ - (panels, { payload }) => _.cloneDeep(payload), - - [updatePanels]: - /** - * - * @param panels {Object.} - * @param payload {Object.} - * @return {Object.} - */ - (panels, { payload }) => { - const panelsCopy = { ...panels }; - Object.values(payload).forEach(panel => { - panelsCopy[panel.panelIndex] = mergePanelData(panel, panels); - }); - return panelsCopy; - }, - - [deletePanel]: - /** - * - * @param panels {Object.} - * @param payload {string} The panelIndex of the panel to delete - * @return {Object.} - */ - (panels, { payload }) => { - const panelsCopy = { ...panels }; - delete panelsCopy[payload]; - return panelsCopy; - }, - - [updatePanel]: - /** - * @param panels {Object.} - * @param payload {PanelState} The new panel state (is merged with existing). - * @param payload.panelIndex {string} The id of the panel to update. - * @return {Object.} - */ - (panels, { payload }) => ({ - ...panels, - [payload.panelIndex]: mergePanelData(payload, panels), - }), - - [resetPanelTitle]: - /** - * @param panels {Object.} - * @param payload {String} The id of the panel to reset it's title. - * @return {Object.} - */ - (panels, { payload }) => ({ - ...panels, - [payload]: { - ...panels[payload], - title: undefined, - } - }), - - [setPanelTitle]: - /** - * @param panels {Object.} - * @param payload {PanelState} The new panel state (is merged with existing). - * @param payload.panelIndex {String} The id of the panel to reset it's title. - * @param payload.title {String} The new title to use. - * @return {Object.} - */ - (panels, { payload }) => ({ - ...panels, - [payload.panelIndex]: mergePanelData(payload, panels), - }), -}, {}); diff --git a/src/core_plugins/kibana/public/dashboard/reducers/panels.test.js b/src/core_plugins/kibana/public/dashboard/reducers/panels.test.ts similarity index 77% rename from src/core_plugins/kibana/public/dashboard/reducers/panels.test.js rename to src/core_plugins/kibana/public/dashboard/reducers/panels.test.ts index c774a3cd8016..8b28cd1a7b1e 100644 --- a/src/core_plugins/kibana/public/dashboard/reducers/panels.test.js +++ b/src/core_plugins/kibana/public/dashboard/reducers/panels.test.ts @@ -17,27 +17,37 @@ * under the License. */ +import { getPanel, getPanelType } from '../../selectors'; import { store } from '../../store'; import { updatePanel, updatePanels } from '../actions'; -import { - getPanel, - getPanelType, -} from '../../selectors'; test('UpdatePanel updates a panel', () => { - store.dispatch(updatePanels([{ panelIndex: '1', gridData: {} }])); - - store.dispatch(updatePanel({ + const panelData = { + embeddableConfig: {}, + gridData: { + h: 0, + id: '0', + w: 0, + x: 0, + y: 0, + }, + id: '123', panelIndex: '1', type: 'mySpecialType', + version: '123', + }; + store.dispatch(updatePanels({ '1': panelData })); + const newPanelData = { + ...panelData, gridData: { + h: 1, + id: '1', + w: 10, x: 1, y: 5, - w: 10, - h: 1, - id: '1' - } - })); + }, + }; + store.dispatch(updatePanel(newPanelData)); const panel = getPanel(store.getState(), '1'); expect(panel.gridData.x).toBe(1); diff --git a/src/core_plugins/kibana/public/dashboard/reducers/panels.ts b/src/core_plugins/kibana/public/dashboard/reducers/panels.ts new file mode 100644 index 000000000000..92746743f8f6 --- /dev/null +++ b/src/core_plugins/kibana/public/dashboard/reducers/panels.ts @@ -0,0 +1,103 @@ +/* + * 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 _ from 'lodash'; +import { Reducer } from 'redux'; +import { + PanelActions, + PanelActionTypeKeys, + SetPanelTitleActionPayload, +} from '../actions'; +import { PanelId, PanelsMap, PanelState } from '../selectors'; + +/** + * @param panel - New panel data (can be partial data) to merge with possibly existing panel data in + * the panels mapping. + * @param panels + * @return a new PanelState which has the merged data. + */ +function mergePanelData(panel: PanelState, panels: PanelsMap): PanelState { + return _.defaultsDeep(panel, panels[panel.panelIndex]); +} + +const deletePanel = (panels: PanelsMap, panelId: PanelId): PanelsMap => { + const panelsCopy = { ...panels }; + delete panelsCopy[panelId]; + return panelsCopy; +}; + +const updatePanel = (panels: PanelsMap, panelState: PanelState): PanelsMap => ({ + ...panels, + [panelState.panelIndex]: mergePanelData(panelState, panels), +}); + +const updatePanels = ( + panels: PanelsMap, + updatedPanels: PanelsMap +): PanelsMap => { + const panelsCopy = { ...panels }; + Object.values(updatedPanels).forEach(panel => { + panelsCopy[panel.panelIndex] = mergePanelData(panel, panels); + }); + return panelsCopy; +}; + +const resetPanelTitle = (panels: PanelsMap, panelId: PanelId) => ({ + ...panels, + [panelId]: { + ...panels[panelId], + title: undefined, + }, +}); + +const setPanelTitle = ( + panels: PanelsMap, + payload: SetPanelTitleActionPayload +) => ({ + ...panels, + [payload.panelId]: { + ...panels[payload.panelId], + title: payload.title, + }, +}); + +const setPanels = (panels: PanelsMap, newPanels: PanelsMap) => + _.cloneDeep(newPanels); + +export const panelsReducer: Reducer = ( + panels = {}, + action +): PanelsMap => { + switch ((action as PanelActions).type) { + case PanelActionTypeKeys.DELETE_PANEL: + return deletePanel(panels, action.payload); + case PanelActionTypeKeys.UPDATE_PANEL: + return updatePanel(panels, action.payload); + case PanelActionTypeKeys.UPDATE_PANELS: + return updatePanels(panels, action.payload); + case PanelActionTypeKeys.RESET_PANEl_TITLE: + return resetPanelTitle(panels, action.payload); + case PanelActionTypeKeys.SET_PANEl_TITLE: + return setPanelTitle(panels, action.payload); + case PanelActionTypeKeys.SET_PANELS: + return setPanels(panels, action.payload); + default: + return panels; + } +}; diff --git a/src/core_plugins/kibana/public/dashboard/reducers/view.js b/src/core_plugins/kibana/public/dashboard/reducers/view.js deleted file mode 100644 index fbc8f94f6d65..000000000000 --- a/src/core_plugins/kibana/public/dashboard/reducers/view.js +++ /dev/null @@ -1,87 +0,0 @@ -/* - * 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 { handleActions, combineActions } from 'redux-actions'; -import { - updateViewMode, - maximizePanel, - minimizePanel, - updateUseMargins, - updateHidePanelTitles, - updateIsFullScreenMode, - updateTimeRange, - updateFilters, - updateQuery, - setVisibleContextMenuPanelId, -} from '../actions'; - -import { DashboardViewMode } from '../dashboard_view_mode'; - -export const view = handleActions({ - [setVisibleContextMenuPanelId]: (state, { payload }) => ({ - ...state, - visibleContextMenuPanelId: payload - }), - - [updateViewMode]: (state, { payload }) => ({ - ...state, - viewMode: payload - }), - - [updateTimeRange]: (state, { payload }) => ({ - ...state, - timeRange: payload - }), - - [updateFilters]: (state, { payload }) => ({ - ...state, - filters: payload - }), - - [updateQuery]: (state, { payload }) => ({ - ...state, - query: payload - }), - - [updateUseMargins]: (state, { payload }) => ({ - ...state, - useMargins: payload - }), - - [updateHidePanelTitles]: (state, { payload }) => ({ - ...state, - hidePanelTitles: payload - }), - - [combineActions(maximizePanel, minimizePanel)]: (state, { payload }) => ({ - ...state, - maximizedPanelId: payload - }), - - [updateIsFullScreenMode]: (state, { payload }) => ({ - ...state, - isFullScreenMode: payload - }), -}, { - isFullScreenMode: false, - viewMode: DashboardViewMode.VIEW, - maximizedPanelId: undefined, - useMargins: true, - hidePanelTitles: false, -}); diff --git a/src/core_plugins/kibana/public/dashboard/reducers/view.test.js b/src/core_plugins/kibana/public/dashboard/reducers/view.test.ts similarity index 100% rename from src/core_plugins/kibana/public/dashboard/reducers/view.test.js rename to src/core_plugins/kibana/public/dashboard/reducers/view.test.ts index 8f54ea435d93..1337376a753a 100644 --- a/src/core_plugins/kibana/public/dashboard/reducers/view.test.js +++ b/src/core_plugins/kibana/public/dashboard/reducers/view.test.ts @@ -19,16 +19,16 @@ import { store } from '../../store'; import { - updateIsFullScreenMode, - updateViewMode, maximizePanel, minimizePanel, + updateIsFullScreenMode, + updateViewMode, } from '../actions'; import { getFullScreenMode, - getViewMode, getMaximizedPanelId, + getViewMode, } from '../../selectors'; import { DashboardViewMode } from '../dashboard_view_mode'; diff --git a/src/core_plugins/kibana/public/dashboard/reducers/view.ts b/src/core_plugins/kibana/public/dashboard/reducers/view.ts new file mode 100644 index 000000000000..4870e2a3e997 --- /dev/null +++ b/src/core_plugins/kibana/public/dashboard/reducers/view.ts @@ -0,0 +1,117 @@ +/* + * 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 { Reducer } from 'redux'; +import { ViewActions, ViewActionTypeKeys } from '../actions'; + +import { Filters, Query, TimeRange } from 'ui/embeddable'; +import { QueryLanguageType } from 'ui/embeddable/types'; +import { DashboardViewMode } from '../dashboard_view_mode'; +import { PanelId, ViewState } from '../selectors'; + +const setVisibleContextMenuPanelId = (view: ViewState, panelId: PanelId) => ({ + ...view, + visibleContextMenuPanelId: panelId, +}); + +const updateHidePanelTitles = (view: ViewState, hidePanelTitles: boolean) => ({ + ...view, + hidePanelTitles, +}); + +const minimizePanel = (view: ViewState) => ({ + ...view, + maximizedPanelId: undefined, +}); + +const maximizePanel = (view: ViewState, panelId: PanelId) => ({ + ...view, + maximizedPanelId: panelId, +}); + +const updateIsFullScreenMode = ( + view: ViewState, + isFullScreenMode: boolean +) => ({ + ...view, + isFullScreenMode, +}); + +const updateTimeRange = (view: ViewState, timeRange: TimeRange) => ({ + ...view, + timeRange, +}); + +const updateFilters = (view: ViewState, filters: Filters) => ({ + ...view, + filters, +}); + +const updateQuery = (view: ViewState, query: Query) => ({ + ...view, + query, +}); + +const updateUseMargins = (view: ViewState, useMargins: boolean) => ({ + ...view, + useMargins, +}); + +const updateViewMode = (view: ViewState, viewMode: DashboardViewMode) => ({ + ...view, + viewMode, +}); + +export const viewReducer: Reducer = ( + view = { + filters: [], + hidePanelTitles: false, + isFullScreenMode: false, + query: { language: QueryLanguageType.LUCENE, query: '' }, + timeRange: { to: 'now', from: 'now-15m' }, + useMargins: true, + viewMode: DashboardViewMode.VIEW, + }, + action +): ViewState => { + switch ((action as ViewActions).type) { + case ViewActionTypeKeys.MINIMIZE_PANEL: + return minimizePanel(view); + case ViewActionTypeKeys.MAXIMIZE_PANEl: + return maximizePanel(view, action.payload); + case ViewActionTypeKeys.SET_VISIBLE_CONTEXT_MENU_PANEL_ID: + return setVisibleContextMenuPanelId(view, action.payload); + case ViewActionTypeKeys.UPDATE_HIDE_PANEL_TITLES: + return updateHidePanelTitles(view, action.payload); + case ViewActionTypeKeys.UPDATE_TIME_RANGE: + return updateTimeRange(view, action.payload); + case ViewActionTypeKeys.UPDATE_USE_MARGINS: + return updateUseMargins(view, action.payload); + case ViewActionTypeKeys.UPDATE_VIEW_MODE: + return updateViewMode(view, action.payload); + case ViewActionTypeKeys.UPDATE_IS_FULL_SCREEN_MODE: + return updateIsFullScreenMode(view, action.payload); + case ViewActionTypeKeys.UPDATE_FILTERS: + return updateFilters(view, action.payload); + case ViewActionTypeKeys.UPDATE_QUERY: + return updateQuery(view, action.payload); + default: + return view; + } +}; diff --git a/src/core_plugins/kibana/public/dashboard/selectors/dashboard.ts b/src/core_plugins/kibana/public/dashboard/selectors/dashboard.ts new file mode 100644 index 000000000000..3d77ce786400 --- /dev/null +++ b/src/core_plugins/kibana/public/dashboard/selectors/dashboard.ts @@ -0,0 +1,171 @@ +/* + * 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 _ from 'lodash'; +import { + ContainerState, + EmbeddableMetadata, + Filters, + Query, + TimeRange, +} from 'ui/embeddable'; +import { DashboardViewMode } from '../dashboard_view_mode'; +import { + DashboardMetadata, + DashboardState, + EmbeddableReduxState, + EmbeddablesMap, + PanelId, + PanelsMap, + PanelState, +} from './types'; + +export const getPanels = (dashboard: DashboardState): PanelsMap => + dashboard.panels; + +export const getPanel = ( + dashboard: DashboardState, + panelId: PanelId +): PanelState => getPanels(dashboard)[panelId]; + +export const getPanelType = ( + dashboard: DashboardState, + panelId: PanelId +): string => getPanel(dashboard, panelId).type; + +export const getEmbeddables = (dashboard: DashboardState): EmbeddablesMap => + dashboard.embeddables; + +// TODO: rename panel.embeddableConfig to embeddableCustomization. Because it's on the panel that's stored on a +// dashboard, renaming this will require a migration step. +export const getEmbeddableCustomization = ( + dashboard: DashboardState, + panelId: PanelId +): object => getPanel(dashboard, panelId).embeddableConfig; + +export const getEmbeddable = ( + dashboard: DashboardState, + panelId: PanelId +): EmbeddableReduxState => dashboard.embeddables[panelId]; + +export const getEmbeddableError = ( + dashboard: DashboardState, + panelId: PanelId +): string | object | undefined => getEmbeddable(dashboard, panelId).error; + +export const getEmbeddableTitle = ( + dashboard: DashboardState, + panelId: PanelId +): string | undefined => { + const embeddable = getEmbeddable(dashboard, panelId); + return embeddable && embeddable.initialized && embeddable.metadata + ? embeddable.metadata.title + : ''; +}; + +export const getEmbeddableInitialized = ( + dashboard: DashboardState, + panelId: PanelId +): boolean => getEmbeddable(dashboard, panelId).initialized; + +export const getEmbeddableStagedFilter = ( + dashboard: DashboardState, + panelId: PanelId +): object | undefined => getEmbeddable(dashboard, panelId).stagedFilter; + +export const getEmbeddableMetadata = ( + dashboard: DashboardState, + panelId: PanelId +): EmbeddableMetadata | undefined => getEmbeddable(dashboard, panelId).metadata; + +export const getEmbeddableEditUrl = ( + dashboard: DashboardState, + panelId: PanelId +): string | undefined => { + const embeddable = getEmbeddable(dashboard, panelId); + return embeddable && embeddable.initialized && embeddable.metadata + ? embeddable.metadata.editUrl + : ''; +}; + +export const getVisibleContextMenuPanelId = ( + dashboard: DashboardState +): PanelId | undefined => dashboard.view.visibleContextMenuPanelId; + +export const getUseMargins = (dashboard: DashboardState): boolean => + dashboard.view.useMargins; + +export const getViewMode = (dashboard: DashboardState): DashboardViewMode => + dashboard.view.viewMode; + +export const getFullScreenMode = (dashboard: DashboardState): boolean => + dashboard.view.isFullScreenMode; + +export const getHidePanelTitles = (dashboard: DashboardState): boolean => + dashboard.view.hidePanelTitles; + +export const getMaximizedPanelId = ( + dashboard: DashboardState +): PanelId | undefined => dashboard.view.maximizedPanelId; + +export const getTimeRange = (dashboard: DashboardState): TimeRange => + dashboard.view.timeRange; + +export const getFilters = (dashboard: DashboardState): Filters => + dashboard.view.filters; + +export const getQuery = (dashboard: DashboardState): Query => + dashboard.view.query; + +export const getMetadata = (dashboard: DashboardState): DashboardMetadata => + dashboard.metadata; + +export const getTitle = (dashboard: DashboardState): string => + dashboard.metadata.title; + +export const getDescription = (dashboard: DashboardState): string | undefined => + dashboard.metadata.description; + +export const getContainerState = ( + dashboard: DashboardState, + panelId: PanelId +): ContainerState => { + const time = getTimeRange(dashboard); + return { + customTitle: getPanel(dashboard, panelId).title, + embeddableCustomization: _.cloneDeep( + getEmbeddableCustomization(dashboard, panelId) || {} + ), + filters: getFilters(dashboard), + hidePanelTitles: getHidePanelTitles(dashboard), + isPanelExpanded: getMaximizedPanelId(dashboard) === panelId, + query: getQuery(dashboard), + timeRange: { + from: time.from, + to: time.to, + }, + viewMode: getViewMode(dashboard), + }; +}; + +/** + * @return an array of filters any embeddables wish dashboard to apply + */ +export const getStagedFilters = (dashboard: DashboardState): Filters => + _.compact(_.map(dashboard.embeddables, 'stagedFilter')); diff --git a/src/core_plugins/kibana/public/dashboard/selectors/index.js b/src/core_plugins/kibana/public/dashboard/selectors/index.js deleted file mode 100644 index b7f82b4adf06..000000000000 --- a/src/core_plugins/kibana/public/dashboard/selectors/index.js +++ /dev/null @@ -1,228 +0,0 @@ -/* - * 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 _ from 'lodash'; - -/** - * @typedef {Object} ViewState - * @property {DashboardViewMode} viewMode - * @property {boolean} isFullScreenMode - * @property {string|undefined} maximizedPanelId - * @property {string|undefined} getVisibleContextMenuPanelId - */ - -/** - * @typedef {Object} EmbeddableReduxState - * @property {string} title - * @property {string} editUrl - * @property {string|object} error - */ - -/** - * @typedef {Object} DashboardState - * @property {Object.} panels - * @property {Object.} embeddables - * @property {ViewState} view - */ - -/** - * @param dashboard {DashboardState} - * @return {Object.} - */ -export const getPanels = dashboard => dashboard.panels; - -/** - * @param dashboard {DashboardState} - * @param panelId {string} - * @return {PanelState} - */ -export const getPanel = (dashboard, panelId) => getPanels(dashboard)[panelId]; -/** - * @param dashboard {DashboardState} - * @param panelId {string} - * @return {string} - */ -export const getPanelType = (dashboard, panelId) => getPanel(dashboard, panelId).type; - -export const getEmbeddables = (dashboard) => dashboard.embeddables; - -// TODO: rename panel.embeddableConfig to embeddableCustomization. Because it's on the panel that's stored on a -// dashboard, renaming this will require a migration step. -export const getEmbeddableCustomization = (dashboard, panelId) => getPanel(dashboard, panelId).embeddableConfig; - -/** - * @param dashboard {DashboardState} - * @param panelId {string} - * @return {EmbeddableReduxState} - */ -export const getEmbeddable = (dashboard, panelId) => dashboard.embeddables[panelId]; - -/** - * @param dashboard {DashboardState} - * @param panelId {string} - * @return {string|Object} - */ -export const getEmbeddableError = (dashboard, panelId) => getEmbeddable(dashboard, panelId).error; -/** - * @param dashboard {DashboardState} - * @param panelId {string} - * @return {string} - */ -export const getEmbeddableTitle = (dashboard, panelId) => { - const embeddable = getEmbeddable(dashboard, panelId); - return embeddable && embeddable.initialized ? embeddable.metadata.title : ''; -}; - -/** - * @param dashboard {DashboardState} - * @param panelId {string} - * @return {boolean} - */ -export const getEmbeddableRenderComplete = (dashboard, panelId) => getEmbeddable(dashboard, panelId).renderComplete; - -/** - * @param dashboard {DashboardState} - * @param panelId {string} - * @return {boolean} - */ -export const getEmbeddableInitialized = (dashboard, panelId) => getEmbeddable(dashboard, panelId).initialized; - -export const getEmbeddableStagedFilter = (dashboard, panelId) => getEmbeddable(dashboard, panelId).stagedFilter; - -/** - * - * @param dashboard {DashboardState} - * @param panelId {string} - * @return {EmbeddableMetadata} - */ -export const getEmbeddableMetadata = (dashboard, panelId) => getEmbeddable(dashboard, panelId).metadata; - -/** - * @param dashboard {DashboardState} - * @param panelId {string} - * @return {string} - */ -export const getEmbeddableEditUrl = (dashboard, panelId) => { - const embeddable = getEmbeddable(dashboard, panelId); - return embeddable && embeddable.initialized ? embeddable.metadata.editUrl : ''; -}; - - -export const getVisibleContextMenuPanelId = dashboard => dashboard.view.visibleContextMenuPanelId; - -/** - * @param dashboard {DashboardState} - * @return {boolean} - */ -export const getUseMargins = dashboard => dashboard.view.useMargins; -/** - * @param dashboard {DashboardState} - * @return {DashboardViewMode} - */ -export const getViewMode = dashboard => dashboard.view.viewMode; -/** - * @param dashboard {DashboardState} - * @return {boolean} - */ -export const getFullScreenMode = dashboard => dashboard.view.isFullScreenMode; -/** - * @param dashboard {DashboardState} - * @return {boolean} - */ -export const getHidePanelTitles = dashboard => dashboard.view.hidePanelTitles; -/** - * @param dashboard {DashboardState} - * @return {string|undefined} - */ -export const getMaximizedPanelId = dashboard => dashboard.view.maximizedPanelId; - -/** - * @param dashboard {DashboardState} - * @return {{ from: {string}, to: {string}, mode: {string} }} - */ -export const getTimeRange = dashboard => dashboard.view.timeRange; - -export const getFilters = dashboard => dashboard.view.filters; - -export const getQuery = dashboard => dashboard.view.query; - -/** - * @typedef {Object} DashboardMetadata - * @property {string} title - * @property {string} description - */ - -/** - * @param dashboard {DashboardState} - * @return {DashboardMetadata} - */ -export const getMetadata = dashboard => dashboard.metadata; -/** - * @param dashboard {DashboardState} - * @return {string} - */ -export const getTitle = dashboard => dashboard.metadata.title; -/** - * @param dashboard {DashboardState} - * @return {string} - */ -export const getDescription = dashboard => dashboard.metadata.description; - -/** - * This state object is specifically for communicating to embeddables and it's structure is not tied to - * the redux tree structure. - * @typedef {Object} ContainerState - * @property {DashboardViewMode} viewMode - edit or view mode. - * @property {String} timeRange.to - either an absolute time range in utc format or a relative one (e.g. now-15m) - * @property {String} timeRange.from - either an absolute time range in utc format or a relative one (e.g. now-15m) - * @property {Object} embeddableCustomization - * @property {boolean} hidePanelTitles - * @property {boolean} isPanelExpanded - */ - -/** - * - * @param dashboard {DashboardState} - * @param panelId {string} - * @return {ContainerState} - */ -export const getContainerState = (dashboard, panelId) => { - const time = getTimeRange(dashboard); - return { - timeRange: { - to: time.to, - from: time.from, - }, - filters: getFilters(dashboard), - query: getQuery(dashboard), - embeddableCustomization: _.cloneDeep(getEmbeddableCustomization(dashboard, panelId) || {}), - hidePanelTitles: getHidePanelTitles(dashboard), - customTitle: getPanel(dashboard, panelId).title, - viewMode: getViewMode(dashboard), - isPanelExpanded: getMaximizedPanelId(dashboard) === panelId, - }; -}; - -/** - * - * @param embeddables {Array.} - * @return {Array.<{ field, value, operator, index }>} Array of filters any embeddables wish dashboard to apply - */ -export const getStagedFilters = ({ embeddables }) => _.compact(_.map(embeddables, 'stagedFilter')); - diff --git a/src/core_plugins/kibana/public/dashboard/actions/metadata.js b/src/core_plugins/kibana/public/dashboard/selectors/index.ts similarity index 82% rename from src/core_plugins/kibana/public/dashboard/actions/metadata.js rename to src/core_plugins/kibana/public/dashboard/selectors/index.ts index f61ae42134bc..c3d90748976d 100644 --- a/src/core_plugins/kibana/public/dashboard/actions/metadata.js +++ b/src/core_plugins/kibana/public/dashboard/selectors/index.ts @@ -17,7 +17,6 @@ * under the License. */ -import { createAction } from 'redux-actions'; +export * from './types'; -export const updateDescription = createAction('UPDATE_DESCRIPTION'); -export const updateTitle = createAction('UPDATE_TITLE'); +export * from './dashboard'; diff --git a/src/core_plugins/kibana/public/dashboard/selectors/types.ts b/src/core_plugins/kibana/public/dashboard/selectors/types.ts new file mode 100644 index 000000000000..7182608d8b97 --- /dev/null +++ b/src/core_plugins/kibana/public/dashboard/selectors/types.ts @@ -0,0 +1,81 @@ +/* + * 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 { EmbeddableMetadata, Filters, Query, TimeRange } from 'ui/embeddable'; +import { DashboardViewMode } from '../dashboard_view_mode'; + +export interface ViewState { + readonly viewMode: DashboardViewMode; + readonly isFullScreenMode: boolean; + readonly maximizedPanelId?: string; + readonly visibleContextMenuPanelId?: string; + readonly timeRange: TimeRange; + readonly hidePanelTitles: boolean; + readonly useMargins: boolean; + readonly query: Query; + readonly filters: Filters; +} + +export interface GridData { + readonly w: number; + readonly h: number; + readonly x: number; + readonly y: number; + readonly id: string; +} + +export type PanelId = string; +export type SavedObjectId = string; + +export interface PanelState { + readonly id: SavedObjectId; + readonly version: string; + readonly type: string; + readonly panelIndex: PanelId; + readonly embeddableConfig: object; + readonly gridData: GridData; + readonly title?: string; +} + +export interface EmbeddableReduxState { + readonly metadata?: EmbeddableMetadata; + readonly error?: string | object; + readonly initialized: boolean; + readonly stagedFilter?: object; +} + +export interface PanelsMap { + readonly [panelId: string]: PanelState; +} + +export interface EmbeddablesMap { + readonly [panelId: string]: EmbeddableReduxState; +} + +export interface DashboardMetadata { + readonly title: string; + readonly description?: string; +} + +export interface DashboardState { + readonly view: ViewState; + readonly panels: PanelsMap; + readonly embeddables: EmbeddablesMap; + readonly metadata: DashboardMetadata; +} diff --git a/src/core_plugins/kibana/public/reducers.js b/src/core_plugins/kibana/public/reducers.ts similarity index 89% rename from src/core_plugins/kibana/public/reducers.js rename to src/core_plugins/kibana/public/reducers.ts index 6d6d2063e501..5097134087d6 100644 --- a/src/core_plugins/kibana/public/reducers.js +++ b/src/core_plugins/kibana/public/reducers.ts @@ -19,11 +19,12 @@ import { combineReducers } from 'redux'; import { dashboard } from './dashboard/reducers'; +import { CoreKibanaState } from './selectors'; /** * Only a single reducer now, but eventually there should be one for each sub app that is part of the * core kibana plugins. */ -export const reducers = combineReducers({ - dashboard +export const reducers = combineReducers({ + dashboard, }); diff --git a/src/core_plugins/kibana/public/selectors/dashboard_selectors.js b/src/core_plugins/kibana/public/selectors/dashboard_selectors.js deleted file mode 100644 index 3795b810d85c..000000000000 --- a/src/core_plugins/kibana/public/selectors/dashboard_selectors.js +++ /dev/null @@ -1,59 +0,0 @@ -/* - * 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 * as DashboardSelectors from '../dashboard/selectors'; - -/** - * @typedef {Object} KibanaCoreAppState - * @property {Object} DashboardState - */ - -/** - * @param {KibanaCoreAppState} state - * @return {DashboardState} - */ -export const getDashboard = state => state.dashboard; - -export const getPanels = state => DashboardSelectors.getPanels(getDashboard(state)); -export const getPanel = (state, panelId) => DashboardSelectors.getPanel(getDashboard(state), panelId); -export const getPanelType = (state, panelId) => DashboardSelectors.getPanelType(getDashboard(state), panelId); - -export const getEmbeddables = state => DashboardSelectors.getEmbeddables(getDashboard(state)); -export const getEmbeddableError = (state, panelId) => - DashboardSelectors.getEmbeddableError((getDashboard(state)), panelId); -export const getEmbeddableInitialized = (state, panelId) => DashboardSelectors.getEmbeddableInitialized(getDashboard(state), panelId); -export const getEmbeddableCustomization = - (state, panelId) => DashboardSelectors.getEmbeddableCustomization(getDashboard(state), panelId); -export const getEmbeddableStagedFilter = - (state, panelId) => DashboardSelectors.getEmbeddableStagedFilter(getDashboard(state), panelId); -export const getEmbeddableMetadata = - (state, panelId) => DashboardSelectors.getEmbeddableMetadata(getDashboard(state), panelId); - -export const getStagedFilters = state => DashboardSelectors.getStagedFilters(getDashboard(state)); -export const getViewMode = state => DashboardSelectors.getViewMode(getDashboard(state)); -export const getFullScreenMode = state => DashboardSelectors.getFullScreenMode(getDashboard(state)); -export const getMaximizedPanelId = state => DashboardSelectors.getMaximizedPanelId(getDashboard(state)); -export const getUseMargins = state => DashboardSelectors.getUseMargins(getDashboard(state)); -export const getHidePanelTitles = state => DashboardSelectors.getHidePanelTitles(getDashboard(state)); -export const getTimeRange = state => DashboardSelectors.getTimeRange(getDashboard(state)); -export const getFilters = state => DashboardSelectors.getFilters(getDashboard(state)); -export const getQuery = state => DashboardSelectors.getQuery(getDashboard(state)); - -export const getTitle = state => DashboardSelectors.getTitle(getDashboard(state)); -export const getDescription = state => DashboardSelectors.getDescription(getDashboard(state)); diff --git a/src/core_plugins/kibana/public/selectors/dashboard_selectors.ts b/src/core_plugins/kibana/public/selectors/dashboard_selectors.ts new file mode 100644 index 000000000000..9bed7357d36f --- /dev/null +++ b/src/core_plugins/kibana/public/selectors/dashboard_selectors.ts @@ -0,0 +1,83 @@ +/* + * 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 { Filters, Query, TimeRange } from 'ui/embeddable'; +import { DashboardViewMode } from '../dashboard/dashboard_view_mode'; +import * as DashboardSelectors from '../dashboard/selectors'; +import { PanelId } from '../dashboard/selectors/types'; +import { CoreKibanaState } from './types'; + +export const getDashboard = ( + state: CoreKibanaState +): DashboardSelectors.DashboardState => state.dashboard; + +export const getPanels = (state: CoreKibanaState) => + DashboardSelectors.getPanels(getDashboard(state)); +export const getPanel = (state: CoreKibanaState, panelId: PanelId) => + DashboardSelectors.getPanel(getDashboard(state), panelId); +export const getPanelType = (state: CoreKibanaState, panelId: PanelId) => + DashboardSelectors.getPanelType(getDashboard(state), panelId); + +export const getEmbeddables = (state: CoreKibanaState) => + DashboardSelectors.getEmbeddables(getDashboard(state)); +export const getEmbeddableError = (state: CoreKibanaState, panelId: PanelId) => + DashboardSelectors.getEmbeddableError(getDashboard(state), panelId); +export const getEmbeddableInitialized = ( + state: CoreKibanaState, + panelId: PanelId +) => DashboardSelectors.getEmbeddableInitialized(getDashboard(state), panelId); +export const getEmbeddableCustomization = ( + state: CoreKibanaState, + panelId: PanelId +) => + DashboardSelectors.getEmbeddableCustomization(getDashboard(state), panelId); +export const getEmbeddableStagedFilter = ( + state: CoreKibanaState, + panelId: PanelId +) => DashboardSelectors.getEmbeddableStagedFilter(getDashboard(state), panelId); +export const getEmbeddableMetadata = ( + state: CoreKibanaState, + panelId: PanelId +) => DashboardSelectors.getEmbeddableMetadata(getDashboard(state), panelId); + +export const getStagedFilters = (state: CoreKibanaState): Filters => + DashboardSelectors.getStagedFilters(getDashboard(state)); +export const getViewMode = (state: CoreKibanaState): DashboardViewMode => + DashboardSelectors.getViewMode(getDashboard(state)); +export const getFullScreenMode = (state: CoreKibanaState): boolean => + DashboardSelectors.getFullScreenMode(getDashboard(state)); +export const getMaximizedPanelId = ( + state: CoreKibanaState +): PanelId | undefined => + DashboardSelectors.getMaximizedPanelId(getDashboard(state)); +export const getUseMargins = (state: CoreKibanaState): boolean => + DashboardSelectors.getUseMargins(getDashboard(state)); +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 => + DashboardSelectors.getFilters(getDashboard(state)); +export const getQuery = (state: CoreKibanaState): Query => + DashboardSelectors.getQuery(getDashboard(state)); + +export const getTitle = (state: CoreKibanaState): string => + DashboardSelectors.getTitle(getDashboard(state)); +export const getDescription = (state: CoreKibanaState): string | undefined => + DashboardSelectors.getDescription(getDashboard(state)); diff --git a/src/core_plugins/kibana/public/selectors/index.js b/src/core_plugins/kibana/public/selectors/index.ts similarity index 95% rename from src/core_plugins/kibana/public/selectors/index.js rename to src/core_plugins/kibana/public/selectors/index.ts index a7453b460be9..6c72362d160c 100644 --- a/src/core_plugins/kibana/public/selectors/index.js +++ b/src/core_plugins/kibana/public/selectors/index.ts @@ -18,3 +18,4 @@ */ export * from './dashboard_selectors'; +export { CoreKibanaState } from './types'; diff --git a/src/core_plugins/kibana/public/selectors/types.ts b/src/core_plugins/kibana/public/selectors/types.ts new file mode 100644 index 000000000000..8789f3ca1d5e --- /dev/null +++ b/src/core_plugins/kibana/public/selectors/types.ts @@ -0,0 +1,30 @@ +/* + * 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 { Action } from 'redux'; +import { DashboardState } from '../dashboard/selectors'; + +export interface CoreKibanaState { + readonly dashboard: DashboardState; +} + +export interface KibanaAction extends Action { + readonly type: T; + readonly payload: P; +} diff --git a/src/core_plugins/kibana/public/store.js b/src/core_plugins/kibana/public/store.ts similarity index 54% rename from src/core_plugins/kibana/public/store.js rename to src/core_plugins/kibana/public/store.ts index f877195a248d..0ae75249d5b2 100644 --- a/src/core_plugins/kibana/public/store.js +++ b/src/core_plugins/kibana/public/store.ts @@ -17,16 +17,35 @@ * under the License. */ -import { createStore, applyMiddleware, compose } from 'redux'; +import { applyMiddleware, compose, createStore } from 'redux'; import thunk from 'redux-thunk'; +import { QueryLanguageType } from 'ui/embeddable/types'; +import { DashboardViewMode } from './dashboard/dashboard_view_mode'; import { reducers } from './reducers'; +import { CoreKibanaState } from './selectors'; -const enhancers = [ applyMiddleware(thunk) ]; -window.__REDUX_DEVTOOLS_EXTENSION__ && enhancers.push(window.__REDUX_DEVTOOLS_EXTENSION__()); +const enhancers = [applyMiddleware(thunk)]; -export const store = createStore( +export const store = createStore( reducers, - {}, + { + dashboard: { + embeddables: {}, + metadata: { + title: 'New Dashboard', + }, + panels: {}, + view: { + filters: [], + hidePanelTitles: false, + isFullScreenMode: false, + query: { language: QueryLanguageType.LUCENE, query: '' }, + timeRange: { from: 'now-15m', to: 'now' }, + useMargins: true, + viewMode: DashboardViewMode.VIEW, + }, + }, + }, compose(...enhancers) ); diff --git a/src/ui/public/embeddable/embeddable.ts b/src/ui/public/embeddable/embeddable.ts index c977a74b176b..5119c2bb4ffb 100644 --- a/src/ui/public/embeddable/embeddable.ts +++ b/src/ui/public/embeddable/embeddable.ts @@ -28,7 +28,7 @@ export const embeddableShape = PropTypes.shape({ render: PropTypes.func.isRequired, }); -interface EmbeddableMetadata { +export interface EmbeddableMetadata { // TODO: change to an array, embeddables should be able to specify multiple index patterns they use. Also // see https://github.com/elastic/kibana/issues/19408 - this needs to be generalized to support embeddables that // use dynamic index patterns (vega, TSVB) instead of saved object index patterns (most other visualizations). diff --git a/src/ui/public/embeddable/index.ts b/src/ui/public/embeddable/index.ts index 408a0d5127d1..ca51c6d799cd 100644 --- a/src/ui/public/embeddable/index.ts +++ b/src/ui/public/embeddable/index.ts @@ -22,4 +22,10 @@ export * from './embeddable'; export { EmbeddableFactoriesRegistryProvider, } from './embeddable_factories_registry'; -export { ContainerState } from './types'; +export { + ContainerState, + EmbeddableState, + Query, + Filters, + TimeRange, +} from './types'; diff --git a/src/ui/public/embeddable/types.ts b/src/ui/public/embeddable/types.ts index 0f1e726e9429..697e83376af1 100644 --- a/src/ui/public/embeddable/types.ts +++ b/src/ui/public/embeddable/types.ts @@ -17,15 +17,38 @@ * under the License. */ +export interface TimeRange { + to: string; + from: string; +} + +// TODO: Filter object representation needs to be fleshed out. +export interface Filter { + meta: object; + query: object; +} + +export type Filters = Filter[]; + +export enum QueryLanguageType { + KUERY = 'kuery', + LUCENE = 'lucene', +} + +export interface Query { + language: QueryLanguageType; + query: string; +} + export interface ContainerState { // 'view' or 'edit'. Should probably be an enum but I'm undecided where to define it, here or in dashboard code. viewMode: string; - timeRange: { - // To and From should be either an absolute time range in utc format or a relative one (e.g. now-15m) - to: string; - from: string; - }; + timeRange: TimeRange; + + filters: Filters; + + query: Query; // The shape will be up to the embeddable type. embeddableCustomization?: object; @@ -42,6 +65,11 @@ export interface ContainerState { * Is the current panel in expanded mode */ isPanelExpanded: boolean; + + /** + * A way to override the underlying embeddable title and supply a title at the panel level. + */ + customTitle: string | undefined; } export interface EmbeddableState { diff --git a/yarn.lock b/yarn.lock index dc1a846439c8..449a94656c7e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -275,6 +275,10 @@ dependencies: "@types/node" "*" +"@types/jest@^22.2.3": + version "22.2.3" + resolved "https://registry.yarnpkg.com/@types/jest/-/jest-22.2.3.tgz#0157c0316dc3722c43a7b71de3fdf3acbccef10d" + "@types/json-schema@*": version "6.0.1" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-6.0.1.tgz#a761975746f1c1b2579c62e3a4b5e88f986f7e2e" @@ -289,6 +293,10 @@ dependencies: "@types/node" "*" +"@types/lodash@^3.10.1": + version "3.10.2" + resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-3.10.2.tgz#c1fbda1562ef5603c8192fe1fe65b017849d5873" + "@types/loglevel@^1.5.3": version "1.5.3" resolved "https://registry.yarnpkg.com/@types/loglevel/-/loglevel-1.5.3.tgz#adfce55383edc5998a2170ad581b3e23d6adb5b8" @@ -340,6 +348,14 @@ dependencies: csstype "^2.2.0" +"@types/redux-actions@^2.2.1": + version "2.3.0" + resolved "https://registry.yarnpkg.com/@types/redux-actions/-/redux-actions-2.3.0.tgz#d28d7913ec86ee9e20ecb33a1fed887ecb538149" + +"@types/redux@^3.6.31": + version "3.6.31" + resolved "https://registry.yarnpkg.com/@types/redux/-/redux-3.6.31.tgz#40eafa7575db36b912ce0059b85de98c205b0708" + "@types/retry@*", "@types/retry@^0.10.2": version "0.10.2" resolved "https://registry.yarnpkg.com/@types/retry/-/retry-0.10.2.tgz#bd1740c4ad51966609b058803ee6874577848b37"