mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[Dashboards] Add getSerializedState method to Dashboard API (#204140)
Adds a `getSerializedState` method to the Dashboard API.
This commit is contained in:
parent
65a75ffcb7
commit
d7280a1380
10 changed files with 396 additions and 170 deletions
|
@ -32,6 +32,7 @@ export { prefixReferencesFromPanel } from './dashboard_container/persistable_sta
|
|||
export {
|
||||
convertPanelsArrayToPanelMap,
|
||||
convertPanelMapToPanelsArray,
|
||||
generateNewPanelIds,
|
||||
} from './lib/dashboard_panel_converters';
|
||||
|
||||
export const UI_SETTINGS = {
|
||||
|
|
|
@ -41,6 +41,7 @@ import { initializeSearchSessionManager } from './search_session_manager';
|
|||
import { initializeViewModeManager } from './view_mode_manager';
|
||||
import { UnsavedPanelState } from '../dashboard_container/types';
|
||||
import { initializeTrackContentfulRender } from './track_contentful_render';
|
||||
import { getSerializedState } from './get_serialized_state';
|
||||
|
||||
export function getDashboardApi({
|
||||
creationOptions,
|
||||
|
@ -110,9 +111,11 @@ export function getDashboardApi({
|
|||
});
|
||||
function getState() {
|
||||
const { panels, references: panelReferences } = panelsManager.internalApi.getState();
|
||||
const { state: unifiedSearchState, references: searchSourceReferences } =
|
||||
unifiedSearchManager.internalApi.getState();
|
||||
const dashboardState: DashboardState = {
|
||||
...settingsManager.internalApi.getState(),
|
||||
...unifiedSearchManager.internalApi.getState(),
|
||||
...unifiedSearchState,
|
||||
panels,
|
||||
viewMode: viewModeManager.api.viewMode.value,
|
||||
};
|
||||
|
@ -130,6 +133,7 @@ export function getDashboardApi({
|
|||
dashboardState,
|
||||
controlGroupReferences,
|
||||
panelReferences,
|
||||
searchSourceReferences,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -168,6 +172,7 @@ export function getDashboardApi({
|
|||
unifiedSearchManager.internalApi.controlGroupReload$,
|
||||
unifiedSearchManager.internalApi.panelsReload$
|
||||
).pipe(debounceTime(0)),
|
||||
getSerializedState: () => getSerializedState(getState()),
|
||||
runInteractiveSave: async () => {
|
||||
trackOverlayApi.clearOverlays();
|
||||
const saveResult = await openSaveModal({
|
||||
|
@ -197,11 +202,13 @@ export function getDashboardApi({
|
|||
},
|
||||
runQuickSave: async () => {
|
||||
if (isManaged) return;
|
||||
const { controlGroupReferences, dashboardState, panelReferences } = getState();
|
||||
const { controlGroupReferences, dashboardState, panelReferences, searchSourceReferences } =
|
||||
getState();
|
||||
const saveResult = await getDashboardContentManagementService().saveDashboardState({
|
||||
controlGroupReferences,
|
||||
currentState: dashboardState,
|
||||
dashboardState,
|
||||
panelReferences,
|
||||
searchSourceReferences,
|
||||
saveOptions: {},
|
||||
lastSavedId: savedObjectId$.value,
|
||||
});
|
||||
|
|
|
@ -0,0 +1,170 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the "Elastic License
|
||||
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import type { DashboardPanelState } from '../../common';
|
||||
|
||||
import {
|
||||
dataService,
|
||||
embeddableService,
|
||||
savedObjectsTaggingService,
|
||||
} from '../services/kibana_services';
|
||||
import { getSampleDashboardState } from '../mocks';
|
||||
import { getSerializedState } from './get_serialized_state';
|
||||
|
||||
dataService.search.searchSource.create = jest.fn().mockResolvedValue({
|
||||
setField: jest.fn(),
|
||||
getSerializedFields: jest.fn().mockReturnValue({}),
|
||||
});
|
||||
|
||||
dataService.query.timefilter.timefilter.getTime = jest
|
||||
.fn()
|
||||
.mockReturnValue({ from: 'now-15m', to: 'now' });
|
||||
|
||||
dataService.query.timefilter.timefilter.getRefreshInterval = jest
|
||||
.fn()
|
||||
.mockReturnValue({ pause: true, value: 0 });
|
||||
|
||||
embeddableService.extract = jest
|
||||
.fn()
|
||||
.mockImplementation((attributes) => ({ state: attributes, references: [] }));
|
||||
|
||||
if (savedObjectsTaggingService) {
|
||||
savedObjectsTaggingService.getTaggingApi = jest.fn().mockReturnValue({
|
||||
ui: {
|
||||
updateTagsReferences: jest.fn((references, tags) => references),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
jest.mock('uuid', () => ({
|
||||
v4: jest.fn().mockReturnValue('54321'),
|
||||
}));
|
||||
|
||||
describe('getSerializedState', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should return the current state attributes and references', () => {
|
||||
const dashboardState = getSampleDashboardState();
|
||||
const result = getSerializedState({
|
||||
controlGroupReferences: [],
|
||||
generateNewIds: false,
|
||||
dashboardState,
|
||||
panelReferences: [],
|
||||
searchSourceReferences: [],
|
||||
});
|
||||
|
||||
expect(result.attributes).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"controlGroupInput": undefined,
|
||||
"description": "",
|
||||
"kibanaSavedObjectMeta": Object {
|
||||
"searchSource": Object {
|
||||
"filter": Array [],
|
||||
"query": Object {
|
||||
"language": "kuery",
|
||||
"query": "hi",
|
||||
},
|
||||
},
|
||||
},
|
||||
"options": Object {
|
||||
"hidePanelTitles": false,
|
||||
"syncColors": false,
|
||||
"syncCursor": true,
|
||||
"syncTooltips": false,
|
||||
"useMargins": true,
|
||||
},
|
||||
"panels": Array [],
|
||||
"refreshInterval": undefined,
|
||||
"timeFrom": undefined,
|
||||
"timeRestore": false,
|
||||
"timeTo": undefined,
|
||||
"title": "My Dashboard",
|
||||
"version": 3,
|
||||
}
|
||||
`);
|
||||
expect(result.references).toEqual([]);
|
||||
});
|
||||
|
||||
it('should generate new IDs for panels and references when generateNewIds is true', () => {
|
||||
const dashboardState = {
|
||||
...getSampleDashboardState(),
|
||||
panels: { oldPanelId: { type: 'visualization' } as unknown as DashboardPanelState },
|
||||
};
|
||||
const result = getSerializedState({
|
||||
controlGroupReferences: [],
|
||||
generateNewIds: true,
|
||||
dashboardState,
|
||||
panelReferences: [
|
||||
{
|
||||
name: 'oldPanelId:indexpattern_foobar',
|
||||
type: 'index-pattern',
|
||||
id: 'bizzbuzz',
|
||||
},
|
||||
],
|
||||
searchSourceReferences: [],
|
||||
});
|
||||
|
||||
expect(result.attributes.panels).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Object {
|
||||
"gridData": Object {
|
||||
"i": "54321",
|
||||
},
|
||||
"panelConfig": Object {},
|
||||
"panelIndex": "54321",
|
||||
"type": "visualization",
|
||||
"version": undefined,
|
||||
},
|
||||
]
|
||||
`);
|
||||
expect(result.references).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Object {
|
||||
"id": "bizzbuzz",
|
||||
"name": "54321:indexpattern_foobar",
|
||||
"type": "index-pattern",
|
||||
},
|
||||
]
|
||||
`);
|
||||
});
|
||||
|
||||
it('should include control group references', () => {
|
||||
const dashboardState = getSampleDashboardState();
|
||||
const controlGroupReferences = [
|
||||
{ name: 'control1:indexpattern', type: 'index-pattern', id: 'foobar' },
|
||||
];
|
||||
const result = getSerializedState({
|
||||
controlGroupReferences,
|
||||
generateNewIds: false,
|
||||
dashboardState,
|
||||
panelReferences: [],
|
||||
searchSourceReferences: [],
|
||||
});
|
||||
|
||||
expect(result.references).toEqual(controlGroupReferences);
|
||||
});
|
||||
|
||||
it('should include panel references', () => {
|
||||
const dashboardState = getSampleDashboardState();
|
||||
const panelReferences = [
|
||||
{ name: 'panel1:boogiewoogie', type: 'index-pattern', id: 'fizzbuzz' },
|
||||
];
|
||||
const result = getSerializedState({
|
||||
controlGroupReferences: [],
|
||||
generateNewIds: false,
|
||||
dashboardState,
|
||||
panelReferences,
|
||||
searchSourceReferences: [],
|
||||
});
|
||||
|
||||
expect(result.references).toEqual(panelReferences);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,150 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the "Elastic License
|
||||
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import { pick } from 'lodash';
|
||||
import moment, { Moment } from 'moment';
|
||||
import { RefreshInterval } from '@kbn/data-plugin/public';
|
||||
|
||||
import type { Reference } from '@kbn/content-management-utils';
|
||||
import { convertPanelMapToPanelsArray, extractReferences, generateNewPanelIds } from '../../common';
|
||||
import type { DashboardAttributes } from '../../server';
|
||||
|
||||
import { convertDashboardVersionToNumber } from '../services/dashboard_content_management_service/lib/dashboard_versioning';
|
||||
import {
|
||||
dataService,
|
||||
embeddableService,
|
||||
savedObjectsTaggingService,
|
||||
} from '../services/kibana_services';
|
||||
import { LATEST_DASHBOARD_CONTAINER_VERSION } from '../dashboard_container';
|
||||
import { DashboardState } from './types';
|
||||
|
||||
export const convertTimeToUTCString = (time?: string | Moment): undefined | string => {
|
||||
if (moment(time).isValid()) {
|
||||
return moment(time).utc().format('YYYY-MM-DDTHH:mm:ss.SSS[Z]');
|
||||
} else {
|
||||
// If it's not a valid moment date, then it should be a string representing a relative time
|
||||
// like 'now' or 'now-15m'.
|
||||
return time as string;
|
||||
}
|
||||
};
|
||||
|
||||
export const getSerializedState = ({
|
||||
controlGroupReferences,
|
||||
generateNewIds,
|
||||
dashboardState,
|
||||
panelReferences,
|
||||
searchSourceReferences,
|
||||
}: {
|
||||
controlGroupReferences?: Reference[];
|
||||
generateNewIds?: boolean;
|
||||
dashboardState: DashboardState;
|
||||
panelReferences?: Reference[];
|
||||
searchSourceReferences?: Reference[];
|
||||
}) => {
|
||||
const {
|
||||
query: {
|
||||
timefilter: { timefilter },
|
||||
},
|
||||
} = dataService;
|
||||
|
||||
const {
|
||||
tags,
|
||||
query,
|
||||
title,
|
||||
filters,
|
||||
timeRestore,
|
||||
description,
|
||||
|
||||
// Dashboard options
|
||||
useMargins,
|
||||
syncColors,
|
||||
syncCursor,
|
||||
syncTooltips,
|
||||
hidePanelTitles,
|
||||
controlGroupInput,
|
||||
} = dashboardState;
|
||||
|
||||
let { panels } = dashboardState;
|
||||
let prefixedPanelReferences = panelReferences;
|
||||
if (generateNewIds) {
|
||||
const { panels: newPanels, references: newPanelReferences } = generateNewPanelIds(
|
||||
panels,
|
||||
panelReferences
|
||||
);
|
||||
panels = newPanels;
|
||||
prefixedPanelReferences = newPanelReferences;
|
||||
//
|
||||
// do not need to generate new ids for controls.
|
||||
// ControlGroup Component is keyed on dashboard id so changing dashboard id mounts new ControlGroup Component.
|
||||
//
|
||||
}
|
||||
|
||||
const searchSource = { filter: filters, query };
|
||||
const options = {
|
||||
useMargins,
|
||||
syncColors,
|
||||
syncCursor,
|
||||
syncTooltips,
|
||||
hidePanelTitles,
|
||||
};
|
||||
const savedPanels = convertPanelMapToPanelsArray(panels, true);
|
||||
|
||||
/**
|
||||
* Parse global time filter settings
|
||||
*/
|
||||
const { from, to } = timefilter.getTime();
|
||||
const timeFrom = timeRestore ? convertTimeToUTCString(from) : undefined;
|
||||
const timeTo = timeRestore ? convertTimeToUTCString(to) : undefined;
|
||||
const refreshInterval = timeRestore
|
||||
? (pick(timefilter.getRefreshInterval(), [
|
||||
'display',
|
||||
'pause',
|
||||
'section',
|
||||
'value',
|
||||
]) as RefreshInterval)
|
||||
: undefined;
|
||||
|
||||
const rawDashboardAttributes: DashboardAttributes = {
|
||||
version: convertDashboardVersionToNumber(LATEST_DASHBOARD_CONTAINER_VERSION),
|
||||
controlGroupInput: controlGroupInput as DashboardAttributes['controlGroupInput'],
|
||||
kibanaSavedObjectMeta: { searchSource },
|
||||
description: description ?? '',
|
||||
refreshInterval,
|
||||
timeRestore,
|
||||
options,
|
||||
panels: savedPanels,
|
||||
timeFrom,
|
||||
title,
|
||||
timeTo,
|
||||
};
|
||||
|
||||
/**
|
||||
* Extract references from raw attributes and tags into the references array.
|
||||
*/
|
||||
const { attributes, references: dashboardReferences } = extractReferences(
|
||||
{
|
||||
attributes: rawDashboardAttributes,
|
||||
references: searchSourceReferences ?? [],
|
||||
},
|
||||
{ embeddablePersistableStateService: embeddableService }
|
||||
);
|
||||
|
||||
const savedObjectsTaggingApi = savedObjectsTaggingService?.getTaggingApi();
|
||||
const references = savedObjectsTaggingApi?.ui.updateTagsReferences
|
||||
? savedObjectsTaggingApi?.ui.updateTagsReferences(dashboardReferences, tags)
|
||||
: dashboardReferences;
|
||||
|
||||
const allReferences = [
|
||||
...references,
|
||||
...(prefixedPanelReferences ?? []),
|
||||
...(controlGroupReferences ?? []),
|
||||
...(searchSourceReferences ?? []),
|
||||
];
|
||||
return { attributes, references: allReferences };
|
||||
};
|
|
@ -32,6 +32,7 @@ export async function openSaveModal({
|
|||
isManaged,
|
||||
lastSavedId,
|
||||
panelReferences,
|
||||
searchSourceReferences,
|
||||
viewMode,
|
||||
}: {
|
||||
controlGroupReferences?: Reference[];
|
||||
|
@ -39,6 +40,7 @@ export async function openSaveModal({
|
|||
isManaged: boolean;
|
||||
lastSavedId: string | undefined;
|
||||
panelReferences: Reference[];
|
||||
searchSourceReferences: Reference[];
|
||||
viewMode: ViewMode;
|
||||
}) {
|
||||
if (viewMode === 'edit' && isManaged) {
|
||||
|
@ -101,8 +103,9 @@ export async function openSaveModal({
|
|||
const saveResult = await dashboardContentManagementService.saveDashboardState({
|
||||
controlGroupReferences,
|
||||
panelReferences,
|
||||
searchSourceReferences,
|
||||
saveOptions,
|
||||
currentState: dashboardStateToSave,
|
||||
dashboardState: dashboardStateToSave,
|
||||
lastSavedId,
|
||||
});
|
||||
|
||||
|
|
|
@ -53,6 +53,7 @@ import { IKbnUrlStateStorage } from '@kbn/kibana-utils-plugin/public';
|
|||
import { PublishesReload } from '@kbn/presentation-publishing/interfaces/fetch/publishes_reload';
|
||||
import { PublishesSearchSession } from '@kbn/presentation-publishing/interfaces/fetch/publishes_search_session';
|
||||
import { LocatorPublic } from '@kbn/share-plugin/common';
|
||||
import type { SavedObjectReference } from '@kbn/core-saved-objects-api-server';
|
||||
import { DashboardPanelMap, DashboardPanelState } from '../../common';
|
||||
import type { DashboardAttributes, DashboardOptions } from '../../server/content_management';
|
||||
import {
|
||||
|
@ -146,6 +147,10 @@ export type DashboardApi = CanExpandPanels &
|
|||
focusedPanelId$: PublishingSubject<string | undefined>;
|
||||
forceRefresh: () => void;
|
||||
getSettings: () => DashboardSettings;
|
||||
getSerializedState: () => {
|
||||
attributes: DashboardAttributes;
|
||||
references: SavedObjectReference[];
|
||||
};
|
||||
getDashboardPanelFromId: (id: string) => DashboardPanelState;
|
||||
hasOverlays$: PublishingSubject<boolean>;
|
||||
hasUnsavedChanges$: PublishingSubject<boolean>;
|
||||
|
|
|
@ -33,10 +33,12 @@ import fastIsEqual from 'fast-deep-equal';
|
|||
import { PublishingSubject, StateComparators } from '@kbn/presentation-publishing';
|
||||
import { ControlGroupApi } from '@kbn/controls-plugin/public';
|
||||
import { cloneDeep } from 'lodash';
|
||||
import type { SavedObjectReference } from '@kbn/core-saved-objects-api-server';
|
||||
import {
|
||||
GlobalQueryStateFromUrl,
|
||||
RefreshInterval,
|
||||
connectToQueryState,
|
||||
extractSearchSourceReferences,
|
||||
syncGlobalQueryStateWithUrl,
|
||||
} from '@kbn/data-plugin/public';
|
||||
import moment, { Moment } from 'moment';
|
||||
|
@ -324,16 +326,30 @@ export function initializeUnifiedSearchManager(
|
|||
setAndSyncTimeRange(lastSavedState.timeRange);
|
||||
}
|
||||
},
|
||||
getState: (): Pick<
|
||||
DashboardState,
|
||||
'filters' | 'query' | 'refreshInterval' | 'timeRange' | 'timeRestore'
|
||||
> => ({
|
||||
filters: unifiedSearchFilters$.value ?? DEFAULT_DASHBOARD_INPUT.filters,
|
||||
query: query$.value ?? DEFAULT_DASHBOARD_INPUT.query,
|
||||
refreshInterval: refreshInterval$.value,
|
||||
timeRange: timeRange$.value,
|
||||
timeRestore: timeRestore$.value ?? DEFAULT_DASHBOARD_INPUT.timeRestore,
|
||||
}),
|
||||
getState: (): {
|
||||
state: Pick<
|
||||
DashboardState,
|
||||
'filters' | 'query' | 'refreshInterval' | 'timeRange' | 'timeRestore'
|
||||
>;
|
||||
references: SavedObjectReference[];
|
||||
} => {
|
||||
// pinned filters are not serialized when saving the dashboard
|
||||
const serializableFilters = unifiedSearchFilters$.value?.filter((f) => !isFilterPinned(f));
|
||||
const [{ filter, query }, references] = extractSearchSourceReferences({
|
||||
filter: serializableFilters,
|
||||
query: query$.value,
|
||||
});
|
||||
return {
|
||||
state: {
|
||||
filters: filter ?? DEFAULT_DASHBOARD_INPUT.filters,
|
||||
query: (query as Query) ?? DEFAULT_DASHBOARD_INPUT.query,
|
||||
refreshInterval: refreshInterval$.value,
|
||||
timeRange: timeRange$.value,
|
||||
timeRestore: timeRestore$.value ?? DEFAULT_DASHBOARD_INPUT.timeRestore,
|
||||
},
|
||||
references,
|
||||
};
|
||||
},
|
||||
},
|
||||
cleanup: () => {
|
||||
controlGroupSubscriptions.unsubscribe();
|
||||
|
|
|
@ -47,7 +47,7 @@ describe('Save dashboard state', () => {
|
|||
|
||||
it('should save the dashboard using the same ID', async () => {
|
||||
const result = await saveDashboardState({
|
||||
currentState: {
|
||||
dashboardState: {
|
||||
...getSampleDashboardState(),
|
||||
title: 'BOO',
|
||||
} as unknown as DashboardContainerInput,
|
||||
|
@ -68,7 +68,7 @@ describe('Save dashboard state', () => {
|
|||
|
||||
it('should save the dashboard using a new id, and return redirect required', async () => {
|
||||
const result = await saveDashboardState({
|
||||
currentState: {
|
||||
dashboardState: {
|
||||
...getSampleDashboardState(),
|
||||
title: 'BooToo',
|
||||
} as unknown as DashboardContainerInput,
|
||||
|
@ -92,7 +92,7 @@ describe('Save dashboard state', () => {
|
|||
|
||||
it('should generate new panel IDs for dashboard panels when save as copy is true', async () => {
|
||||
const result = await saveDashboardState({
|
||||
currentState: {
|
||||
dashboardState: {
|
||||
...getSampleDashboardState(),
|
||||
title: 'BooThree',
|
||||
panels: { aVerySpecialVeryUniqueId: { type: 'boop' } },
|
||||
|
@ -118,7 +118,7 @@ describe('Save dashboard state', () => {
|
|||
|
||||
it('should update prefixes on references when save as copy is true', async () => {
|
||||
const result = await saveDashboardState({
|
||||
currentState: {
|
||||
dashboardState: {
|
||||
...getSampleDashboardState(),
|
||||
title: 'BooFour',
|
||||
panels: { idOne: { type: 'boop' } },
|
||||
|
@ -146,7 +146,7 @@ describe('Save dashboard state', () => {
|
|||
it('should return an error when the save fails.', async () => {
|
||||
contentManagementService.client.create = jest.fn().mockRejectedValue('Whoops');
|
||||
const result = await saveDashboardState({
|
||||
currentState: {
|
||||
dashboardState: {
|
||||
...getSampleDashboardState(),
|
||||
title: 'BooThree',
|
||||
panels: { idOne: { type: 'boop' } },
|
||||
|
|
|
@ -7,169 +7,37 @@
|
|||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import { pick } from 'lodash';
|
||||
import moment, { Moment } from 'moment';
|
||||
|
||||
import { extractSearchSourceReferences, RefreshInterval } from '@kbn/data-plugin/public';
|
||||
import { isFilterPinned } from '@kbn/es-query';
|
||||
|
||||
import type { SavedObjectReference } from '@kbn/core/server';
|
||||
import { getDashboardContentManagementCache } from '..';
|
||||
import { convertPanelMapToPanelsArray, extractReferences } from '../../../../common';
|
||||
import type {
|
||||
DashboardAttributes,
|
||||
DashboardCreateIn,
|
||||
DashboardCreateOut,
|
||||
DashboardUpdateIn,
|
||||
DashboardUpdateOut,
|
||||
} from '../../../../server/content_management';
|
||||
import { generateNewPanelIds } from '../../../../common/lib/dashboard_panel_converters';
|
||||
import { DASHBOARD_CONTENT_ID } from '../../../dashboard_constants';
|
||||
import { LATEST_DASHBOARD_CONTAINER_VERSION } from '../../../dashboard_container';
|
||||
import { dashboardSaveToastStrings } from '../../../dashboard_container/_dashboard_container_strings';
|
||||
import { getDashboardBackupService } from '../../dashboard_backup_service';
|
||||
import {
|
||||
contentManagementService,
|
||||
coreServices,
|
||||
dataService,
|
||||
embeddableService,
|
||||
savedObjectsTaggingService,
|
||||
} from '../../kibana_services';
|
||||
import { DashboardSearchSource, SaveDashboardProps, SaveDashboardReturn } from '../types';
|
||||
import { convertDashboardVersionToNumber } from './dashboard_versioning';
|
||||
|
||||
export const convertTimeToUTCString = (time?: string | Moment): undefined | string => {
|
||||
if (moment(time).isValid()) {
|
||||
return moment(time).utc().format('YYYY-MM-DDTHH:mm:ss.SSS[Z]');
|
||||
} else {
|
||||
// If it's not a valid moment date, then it should be a string representing a relative time
|
||||
// like 'now' or 'now-15m'.
|
||||
return time as string;
|
||||
}
|
||||
};
|
||||
import { contentManagementService, coreServices } from '../../kibana_services';
|
||||
import { SaveDashboardProps, SaveDashboardReturn } from '../types';
|
||||
import { getSerializedState } from '../../../dashboard_api/get_serialized_state';
|
||||
|
||||
export const saveDashboardState = async ({
|
||||
controlGroupReferences,
|
||||
lastSavedId,
|
||||
saveOptions,
|
||||
currentState,
|
||||
dashboardState,
|
||||
panelReferences,
|
||||
searchSourceReferences,
|
||||
}: SaveDashboardProps): Promise<SaveDashboardReturn> => {
|
||||
const {
|
||||
search: dataSearchService,
|
||||
query: {
|
||||
timefilter: { timefilter },
|
||||
},
|
||||
} = dataService;
|
||||
const dashboardContentManagementCache = getDashboardContentManagementCache();
|
||||
|
||||
const {
|
||||
tags,
|
||||
query,
|
||||
title,
|
||||
filters,
|
||||
timeRestore,
|
||||
description,
|
||||
|
||||
// Dashboard options
|
||||
useMargins,
|
||||
syncColors,
|
||||
syncCursor,
|
||||
syncTooltips,
|
||||
hidePanelTitles,
|
||||
controlGroupInput,
|
||||
} = currentState;
|
||||
|
||||
let { panels } = currentState;
|
||||
let prefixedPanelReferences = panelReferences;
|
||||
if (saveOptions.saveAsCopy) {
|
||||
const { panels: newPanels, references: newPanelReferences } = generateNewPanelIds(
|
||||
panels,
|
||||
panelReferences
|
||||
);
|
||||
panels = newPanels;
|
||||
prefixedPanelReferences = newPanelReferences;
|
||||
//
|
||||
// do not need to generate new ids for controls.
|
||||
// ControlGroup Component is keyed on dashboard id so changing dashboard id mounts new ControlGroup Component.
|
||||
//
|
||||
}
|
||||
|
||||
const { searchSource, searchSourceReferences } = await (async () => {
|
||||
const searchSourceFields = await dataSearchService.searchSource.create();
|
||||
searchSourceFields.setField(
|
||||
'filter', // save only unpinned filters
|
||||
filters.filter((filter) => !isFilterPinned(filter))
|
||||
);
|
||||
searchSourceFields.setField('query', query);
|
||||
|
||||
const rawSearchSourceFields = searchSourceFields.getSerializedFields();
|
||||
const [fields, references] = extractSearchSourceReferences(rawSearchSourceFields) as [
|
||||
DashboardSearchSource,
|
||||
SavedObjectReference[]
|
||||
];
|
||||
return { searchSourceReferences: references, searchSource: fields };
|
||||
})();
|
||||
|
||||
const options = {
|
||||
useMargins,
|
||||
syncColors,
|
||||
syncCursor,
|
||||
syncTooltips,
|
||||
hidePanelTitles,
|
||||
};
|
||||
const savedPanels = convertPanelMapToPanelsArray(panels, true);
|
||||
|
||||
/**
|
||||
* Parse global time filter settings
|
||||
*/
|
||||
const { from, to } = timefilter.getTime();
|
||||
const timeFrom = timeRestore ? convertTimeToUTCString(from) : undefined;
|
||||
const timeTo = timeRestore ? convertTimeToUTCString(to) : undefined;
|
||||
const refreshInterval = timeRestore
|
||||
? (pick(timefilter.getRefreshInterval(), [
|
||||
'display',
|
||||
'pause',
|
||||
'section',
|
||||
'value',
|
||||
]) as RefreshInterval)
|
||||
: undefined;
|
||||
|
||||
const rawDashboardAttributes: DashboardAttributes = {
|
||||
version: convertDashboardVersionToNumber(LATEST_DASHBOARD_CONTAINER_VERSION),
|
||||
controlGroupInput: controlGroupInput as DashboardAttributes['controlGroupInput'],
|
||||
kibanaSavedObjectMeta: { searchSource },
|
||||
description: description ?? '',
|
||||
refreshInterval,
|
||||
timeRestore,
|
||||
options,
|
||||
panels: savedPanels,
|
||||
timeFrom,
|
||||
title,
|
||||
timeTo,
|
||||
};
|
||||
|
||||
/**
|
||||
* Extract references from raw attributes and tags into the references array.
|
||||
*/
|
||||
const { attributes, references: dashboardReferences } = extractReferences(
|
||||
{
|
||||
attributes: rawDashboardAttributes,
|
||||
references: searchSourceReferences,
|
||||
},
|
||||
{ embeddablePersistableStateService: embeddableService }
|
||||
);
|
||||
|
||||
const savedObjectsTaggingApi = savedObjectsTaggingService?.getTaggingApi();
|
||||
const references = savedObjectsTaggingApi?.ui.updateTagsReferences
|
||||
? savedObjectsTaggingApi?.ui.updateTagsReferences(dashboardReferences, tags)
|
||||
: dashboardReferences;
|
||||
|
||||
const allReferences = [
|
||||
...references,
|
||||
...(prefixedPanelReferences ?? []),
|
||||
...(controlGroupReferences ?? []),
|
||||
];
|
||||
const { attributes, references } = getSerializedState({
|
||||
controlGroupReferences,
|
||||
generateNewIds: saveOptions.saveAsCopy,
|
||||
dashboardState,
|
||||
panelReferences,
|
||||
searchSourceReferences,
|
||||
});
|
||||
|
||||
/**
|
||||
* Save the saved object using the content management
|
||||
|
@ -183,7 +51,7 @@ export const saveDashboardState = async ({
|
|||
contentTypeId: DASHBOARD_CONTENT_ID,
|
||||
data: attributes,
|
||||
options: {
|
||||
references: allReferences,
|
||||
references,
|
||||
/** perform a "full" update instead, where the provided attributes will fully replace the existing ones */
|
||||
mergeAttributes: false,
|
||||
},
|
||||
|
@ -192,14 +60,14 @@ export const saveDashboardState = async ({
|
|||
contentTypeId: DASHBOARD_CONTENT_ID,
|
||||
data: attributes,
|
||||
options: {
|
||||
references: allReferences,
|
||||
references,
|
||||
},
|
||||
});
|
||||
const newId = result.item.id;
|
||||
|
||||
if (newId) {
|
||||
coreServices.notifications.toasts.addSuccess({
|
||||
title: dashboardSaveToastStrings.getSuccessString(currentState.title),
|
||||
title: dashboardSaveToastStrings.getSuccessString(dashboardState.title),
|
||||
className: 'eui-textBreakWord',
|
||||
'data-test-subj': 'saveDashboardSuccess',
|
||||
});
|
||||
|
@ -209,15 +77,15 @@ export const saveDashboardState = async ({
|
|||
*/
|
||||
if (newId !== lastSavedId) {
|
||||
getDashboardBackupService().clearState(lastSavedId);
|
||||
return { redirectRequired: true, id: newId, references: allReferences };
|
||||
return { redirectRequired: true, id: newId, references };
|
||||
} else {
|
||||
dashboardContentManagementCache.deleteDashboard(newId); // something changed in an existing dashboard, so delete it from the cache so that it can be re-fetched
|
||||
}
|
||||
}
|
||||
return { id: newId, references: allReferences };
|
||||
return { id: newId, references };
|
||||
} catch (error) {
|
||||
coreServices.notifications.toasts.addDanger({
|
||||
title: dashboardSaveToastStrings.getFailureString(currentState.title, error.message),
|
||||
title: dashboardSaveToastStrings.getFailureString(dashboardState.title, error.message),
|
||||
'data-test-subj': 'saveDashboardFailure',
|
||||
});
|
||||
return { error };
|
||||
|
|
|
@ -81,12 +81,18 @@ export type SavedDashboardSaveOpts = SavedObjectSaveOpts & { saveAsCopy?: boolea
|
|||
|
||||
export interface SaveDashboardProps {
|
||||
controlGroupReferences?: Reference[];
|
||||
currentState: DashboardState;
|
||||
dashboardState: DashboardState;
|
||||
saveOptions: SavedDashboardSaveOpts;
|
||||
panelReferences?: Reference[];
|
||||
searchSourceReferences?: Reference[];
|
||||
lastSavedId?: string;
|
||||
}
|
||||
|
||||
export interface GetDashboardStateReturn {
|
||||
attributes: DashboardAttributes;
|
||||
references: Reference[];
|
||||
}
|
||||
|
||||
export interface SaveDashboardReturn {
|
||||
id?: string;
|
||||
error?: string;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue