mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 09:19:04 -04:00
[Dashboard] Content Management onboard (#158761)
Onboard Dashboard onto Content Management. --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
f895c5c205
commit
b6f9825253
73 changed files with 1189 additions and 862 deletions
|
@ -81,7 +81,7 @@ describe('checking migration metadata changes on all registered SO types', () =>
|
|||
"connector_token": "5a9ac29fe9c740eb114e9c40517245c71706b005",
|
||||
"core-usage-stats": "b3c04da317c957741ebcdedfea4524049fdc79ff",
|
||||
"csp-rule-template": "c151324d5f85178169395eecb12bac6b96064654",
|
||||
"dashboard": "cf7c9c2334decab716fe519780cb4dc52967a91d",
|
||||
"dashboard": "1635368413415b340ae6f43fcd0a55c5dcdd4f41",
|
||||
"endpoint:user-artifact-manifest": "1c3533161811a58772e30cdc77bac4631da3ef2b",
|
||||
"enterprise_search_telemetry": "9ac912e1417fc8681e0cd383775382117c9e3d3d",
|
||||
"epm-packages": "2449bb565f987eff70b1b39578bb17e90c404c6e",
|
||||
|
|
|
@ -105,7 +105,7 @@ export const controlGroupInputToRawControlGroupAttributes = (
|
|||
|
||||
export const rawControlGroupAttributesToControlGroupInput = (
|
||||
rawControlGroupAttributes: RawControlGroupAttributes
|
||||
): Omit<ControlGroupInput, 'id'> | undefined => {
|
||||
): PersistableControlGroupInput | undefined => {
|
||||
const defaultControlGroupInput = getDefaultControlGroupInput();
|
||||
const { chainingSystem, controlStyle, ignoreParentSettingsJSON, panelsJSON } =
|
||||
rawControlGroupAttributes;
|
||||
|
|
|
@ -8,29 +8,28 @@
|
|||
|
||||
import type { SavedObjectReference } from '@kbn/core/public';
|
||||
import type { Serializable } from '@kbn/utility-types';
|
||||
import { GridData } from '../content_management';
|
||||
|
||||
import { GridData } from '..';
|
||||
|
||||
interface SavedObjectAttributes {
|
||||
interface KibanaAttributes {
|
||||
kibanaSavedObjectMeta: {
|
||||
searchSourceJSON: string;
|
||||
};
|
||||
}
|
||||
|
||||
interface Doc<Attributes extends SavedObjectAttributes = SavedObjectAttributes> {
|
||||
interface Doc<Attributes extends KibanaAttributes = KibanaAttributes> {
|
||||
references: SavedObjectReference[];
|
||||
attributes: Attributes;
|
||||
id: string;
|
||||
type: string;
|
||||
}
|
||||
|
||||
interface DocPre700<Attributes extends SavedObjectAttributes = SavedObjectAttributes> {
|
||||
interface DocPre700<Attributes extends KibanaAttributes = KibanaAttributes> {
|
||||
attributes: Attributes;
|
||||
id: string;
|
||||
type: string;
|
||||
}
|
||||
|
||||
interface DashboardAttributes extends SavedObjectAttributes {
|
||||
interface DashboardAttributes extends KibanaAttributes {
|
||||
panelsJSON: string;
|
||||
description: string;
|
||||
version: number;
|
||||
|
@ -40,7 +39,7 @@ interface DashboardAttributes extends SavedObjectAttributes {
|
|||
optionsJSON?: string;
|
||||
}
|
||||
|
||||
interface DashboardAttributesTo720 extends SavedObjectAttributes {
|
||||
interface DashboardAttributesTo720 extends KibanaAttributes {
|
||||
panelsJSON: string;
|
||||
description: string;
|
||||
uiStateJSON?: string;
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
* 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 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 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
import type {
|
||||
ContentManagementServicesDefinition as ServicesDefinition,
|
||||
Version,
|
||||
} from '@kbn/object-versioning';
|
||||
|
||||
// We export the versioned service definition from this file and not the barrel to avoid adding
|
||||
// the schemas in the "public" js bundle
|
||||
|
||||
import { serviceDefinition as v1 } from './v1/cm_services';
|
||||
|
||||
export const cmServicesDefinition: { [version: Version]: ServicesDefinition } = {
|
||||
1: v1,
|
||||
};
|
11
src/plugins/dashboard/common/content_management/constants.ts
Normal file
11
src/plugins/dashboard/common/content_management/constants.ts
Normal file
|
@ -0,0 +1,11 @@
|
|||
/*
|
||||
* 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 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 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
export const LATEST_VERSION = 1;
|
||||
|
||||
export const CONTENT_ID = 'dashboard';
|
24
src/plugins/dashboard/common/content_management/index.ts
Normal file
24
src/plugins/dashboard/common/content_management/index.ts
Normal file
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* 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 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 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
export { LATEST_VERSION, CONTENT_ID } from './constants';
|
||||
|
||||
export type { DashboardContentType } from './types';
|
||||
|
||||
export type {
|
||||
GridData,
|
||||
DashboardItem,
|
||||
DashboardCrudTypes,
|
||||
DashboardAttributes,
|
||||
SavedDashboardPanel,
|
||||
} from './latest';
|
||||
|
||||
// Today "v1" === "latest" so the export under DashboardV1 namespace is not really useful
|
||||
// We leave it as a reference for future version when it will be needed to export/support older types
|
||||
// in the UIs.
|
||||
export * as DashboardV1 from './v1';
|
10
src/plugins/dashboard/common/content_management/latest.ts
Normal file
10
src/plugins/dashboard/common/content_management/latest.ts
Normal file
|
@ -0,0 +1,10 @@
|
|||
/*
|
||||
* 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 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 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
// Latest version is 1
|
||||
export * from './v1';
|
9
src/plugins/dashboard/common/content_management/types.ts
Normal file
9
src/plugins/dashboard/common/content_management/types.ts
Normal file
|
@ -0,0 +1,9 @@
|
|||
/*
|
||||
* 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 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 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
export type DashboardContentType = 'dashboard';
|
|
@ -0,0 +1,126 @@
|
|||
/*
|
||||
* 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 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 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
import { schema } from '@kbn/config-schema';
|
||||
import type { ContentManagementServicesDefinition as ServicesDefinition } from '@kbn/object-versioning';
|
||||
import {
|
||||
savedObjectSchema,
|
||||
objectTypeToGetResultSchema,
|
||||
createOptionsSchemas,
|
||||
createResultSchema,
|
||||
} from '@kbn/content-management-utils';
|
||||
|
||||
const dashboardAttributesSchema = schema.object(
|
||||
{
|
||||
// General
|
||||
title: schema.string(),
|
||||
description: schema.string({ defaultValue: '' }),
|
||||
|
||||
// Search
|
||||
kibanaSavedObjectMeta: schema.object({
|
||||
searchSourceJSON: schema.maybe(schema.string()),
|
||||
}),
|
||||
|
||||
// Time
|
||||
timeRestore: schema.maybe(schema.boolean()),
|
||||
timeFrom: schema.maybe(schema.string()),
|
||||
timeTo: schema.maybe(schema.string()),
|
||||
refreshInterval: schema.maybe(
|
||||
schema.object({
|
||||
pause: schema.boolean(),
|
||||
value: schema.number(),
|
||||
display: schema.maybe(schema.string()),
|
||||
section: schema.maybe(schema.number()),
|
||||
})
|
||||
),
|
||||
|
||||
// Dashboard Content
|
||||
controlGroupInput: schema.maybe(
|
||||
schema.object({
|
||||
panelsJSON: schema.maybe(schema.string()),
|
||||
controlStyle: schema.maybe(schema.string()),
|
||||
chainingSystem: schema.maybe(schema.string()),
|
||||
ignoreParentSettingsJSON: schema.maybe(schema.string()),
|
||||
})
|
||||
),
|
||||
panelsJSON: schema.string({ defaultValue: '[]' }),
|
||||
optionsJSON: schema.string({ defaultValue: '{}' }),
|
||||
|
||||
// Legacy
|
||||
hits: schema.maybe(schema.number()),
|
||||
version: schema.maybe(schema.number()),
|
||||
},
|
||||
{ unknowns: 'forbid' }
|
||||
);
|
||||
|
||||
const dashboardSavedObjectSchema = savedObjectSchema(dashboardAttributesSchema);
|
||||
|
||||
const searchOptionsSchema = schema.maybe(
|
||||
schema.object(
|
||||
{
|
||||
onlyTitle: schema.maybe(schema.boolean()),
|
||||
},
|
||||
{ unknowns: 'forbid' }
|
||||
)
|
||||
);
|
||||
|
||||
const createOptionsSchema = schema.object({
|
||||
id: schema.maybe(createOptionsSchemas.id),
|
||||
overwrite: schema.maybe(createOptionsSchemas.overwrite),
|
||||
references: schema.maybe(createOptionsSchemas.references),
|
||||
});
|
||||
|
||||
// Content management service definition.
|
||||
// We need it for BWC support between different versions of the content
|
||||
export const serviceDefinition: ServicesDefinition = {
|
||||
get: {
|
||||
out: {
|
||||
result: {
|
||||
schema: objectTypeToGetResultSchema(dashboardSavedObjectSchema),
|
||||
},
|
||||
},
|
||||
},
|
||||
create: {
|
||||
in: {
|
||||
options: {
|
||||
schema: createOptionsSchema,
|
||||
},
|
||||
data: {
|
||||
schema: dashboardAttributesSchema,
|
||||
},
|
||||
},
|
||||
out: {
|
||||
result: {
|
||||
schema: createResultSchema(dashboardSavedObjectSchema),
|
||||
},
|
||||
},
|
||||
},
|
||||
update: {
|
||||
in: {
|
||||
options: {
|
||||
schema: createOptionsSchema, // same schema as "create"
|
||||
},
|
||||
data: {
|
||||
schema: dashboardAttributesSchema,
|
||||
},
|
||||
},
|
||||
},
|
||||
search: {
|
||||
in: {
|
||||
options: {
|
||||
schema: searchOptionsSchema,
|
||||
},
|
||||
},
|
||||
},
|
||||
mSearch: {
|
||||
out: {
|
||||
result: {
|
||||
schema: dashboardSavedObjectSchema,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
16
src/plugins/dashboard/common/content_management/v1/index.ts
Normal file
16
src/plugins/dashboard/common/content_management/v1/index.ts
Normal file
|
@ -0,0 +1,16 @@
|
|||
/*
|
||||
* 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 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 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { DashboardCrudTypes } from './types';
|
||||
export type {
|
||||
GridData,
|
||||
DashboardCrudTypes,
|
||||
DashboardAttributes,
|
||||
SavedDashboardPanel,
|
||||
} from './types';
|
||||
export type DashboardItem = DashboardCrudTypes['Item'];
|
|
@ -6,38 +6,39 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import type {
|
||||
ContentManagementCrudTypes,
|
||||
SavedObjectCreateOptions,
|
||||
SavedObjectUpdateOptions,
|
||||
} from '@kbn/content-management-utils';
|
||||
import { Serializable } from '@kbn/utility-types';
|
||||
import { RefreshInterval } from '@kbn/data-plugin/common';
|
||||
import { RawControlGroupAttributes } from '@kbn/controls-plugin/common';
|
||||
|
||||
import { Serializable } from '@kbn/utility-types';
|
||||
import { DashboardOptions, GridData } from '../types';
|
||||
import { DashboardContentType } from '../types';
|
||||
|
||||
export type DashboardCrudTypes = ContentManagementCrudTypes<
|
||||
DashboardContentType,
|
||||
DashboardAttributes,
|
||||
Pick<SavedObjectCreateOptions, 'id' | 'references' | 'overwrite'>,
|
||||
Pick<SavedObjectUpdateOptions, 'references'>,
|
||||
{
|
||||
/** Flag to indicate to only search the text on the "title" field */
|
||||
onlyTitle?: boolean;
|
||||
}
|
||||
>;
|
||||
|
||||
/**
|
||||
* The attributes of the dashboard saved object. This interface should be the
|
||||
* source of truth for the latest dashboard attributes shape after all migrations.
|
||||
* Grid type for React Grid Layout
|
||||
*/
|
||||
export interface DashboardAttributes {
|
||||
controlGroupInput?: RawControlGroupAttributes;
|
||||
refreshInterval?: RefreshInterval;
|
||||
timeRestore: boolean;
|
||||
optionsJSON?: string;
|
||||
useMargins?: boolean;
|
||||
description: string;
|
||||
panelsJSON: string;
|
||||
timeFrom?: string;
|
||||
version: number;
|
||||
timeTo?: string;
|
||||
title: string;
|
||||
kibanaSavedObjectMeta: {
|
||||
searchSourceJSON: string;
|
||||
};
|
||||
export interface GridData {
|
||||
w: number;
|
||||
h: number;
|
||||
x: number;
|
||||
y: number;
|
||||
i: string;
|
||||
}
|
||||
|
||||
export type ParsedDashboardAttributes = Omit<DashboardAttributes, 'panelsJSON' | 'optionsJSON'> & {
|
||||
panels: SavedDashboardPanel[];
|
||||
options: DashboardOptions;
|
||||
};
|
||||
|
||||
/**
|
||||
* A saved dashboard panel parsed directly from the Dashboard Attributes panels JSON
|
||||
*/
|
||||
|
@ -51,3 +52,21 @@ export interface SavedDashboardPanel {
|
|||
version: string;
|
||||
title?: string;
|
||||
}
|
||||
|
||||
/* eslint-disable-next-line @typescript-eslint/consistent-type-definitions */
|
||||
export type DashboardAttributes = {
|
||||
controlGroupInput?: RawControlGroupAttributes;
|
||||
refreshInterval?: RefreshInterval;
|
||||
timeRestore: boolean;
|
||||
optionsJSON?: string;
|
||||
useMargins?: boolean;
|
||||
description: string;
|
||||
panelsJSON: string;
|
||||
timeFrom?: string;
|
||||
version: number;
|
||||
timeTo?: string;
|
||||
title: string;
|
||||
kibanaSavedObjectMeta: {
|
||||
searchSourceJSON: string;
|
||||
};
|
||||
};
|
|
@ -8,11 +8,12 @@
|
|||
|
||||
import { createExtract, createInject } from './dashboard_container_references';
|
||||
import { createEmbeddablePersistableStateServiceMock } from '@kbn/embeddable-plugin/common/mocks';
|
||||
import { DashboardContainerStateWithType } from '../../types';
|
||||
import { ParsedDashboardAttributesWithType } from '../../types';
|
||||
import { SavedObjectEmbeddableInput } from '@kbn/embeddable-plugin/public';
|
||||
|
||||
const persistableStateService = createEmbeddablePersistableStateServiceMock();
|
||||
|
||||
const dashboardWithExtractedPanel: DashboardContainerStateWithType = {
|
||||
const dashboardWithExtractedPanel: ParsedDashboardAttributesWithType = {
|
||||
id: 'id',
|
||||
type: 'dashboard',
|
||||
panels: {
|
||||
|
@ -33,7 +34,7 @@ const extractedSavedObjectPanelRef = {
|
|||
id: 'object-id',
|
||||
};
|
||||
|
||||
const unextractedDashboardState: DashboardContainerStateWithType = {
|
||||
const unextractedDashboardState: ParsedDashboardAttributesWithType = {
|
||||
id: 'id',
|
||||
type: 'dashboard',
|
||||
panels: {
|
||||
|
@ -56,7 +57,7 @@ describe('inject/extract by reference panel', () => {
|
|||
const injected = inject(
|
||||
dashboardWithExtractedPanel,
|
||||
references
|
||||
) as DashboardContainerStateWithType;
|
||||
) as ParsedDashboardAttributesWithType;
|
||||
|
||||
expect(injected).toEqual(unextractedDashboardState);
|
||||
});
|
||||
|
@ -71,7 +72,7 @@ describe('inject/extract by reference panel', () => {
|
|||
});
|
||||
});
|
||||
|
||||
const dashboardWithExtractedByValuePanel: DashboardContainerStateWithType = {
|
||||
const dashboardWithExtractedByValuePanel: ParsedDashboardAttributesWithType = {
|
||||
id: 'id',
|
||||
type: 'dashboard',
|
||||
panels: {
|
||||
|
@ -81,7 +82,7 @@ const dashboardWithExtractedByValuePanel: DashboardContainerStateWithType = {
|
|||
explicitInput: {
|
||||
id: 'panel_1',
|
||||
extracted_reference: 'ref',
|
||||
},
|
||||
} as Partial<SavedObjectEmbeddableInput> & { id: string; extracted_reference: string },
|
||||
},
|
||||
},
|
||||
};
|
||||
|
@ -92,7 +93,7 @@ const extractedByValueRef = {
|
|||
type: 'panel_type',
|
||||
};
|
||||
|
||||
const unextractedDashboardByValueState: DashboardContainerStateWithType = {
|
||||
const unextractedDashboardByValueState: ParsedDashboardAttributesWithType = {
|
||||
id: 'id',
|
||||
type: 'dashboard',
|
||||
panels: {
|
||||
|
@ -102,7 +103,7 @@ const unextractedDashboardByValueState: DashboardContainerStateWithType = {
|
|||
explicitInput: {
|
||||
id: 'panel_1',
|
||||
value: 'id',
|
||||
},
|
||||
} as Partial<SavedObjectEmbeddableInput> & { id: string; value: string },
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
@ -8,13 +8,14 @@
|
|||
|
||||
import {
|
||||
EmbeddableInput,
|
||||
EmbeddablePersistableStateService,
|
||||
EmbeddableStateWithType,
|
||||
EmbeddablePersistableStateService,
|
||||
} from '@kbn/embeddable-plugin/common';
|
||||
import { SavedObjectReference } from '@kbn/core/types';
|
||||
import { Reference } from '@kbn/content-management-utils';
|
||||
import { CONTROL_GROUP_TYPE, PersistableControlGroupInput } from '@kbn/controls-plugin/common';
|
||||
|
||||
import { DashboardPanelState } from '../types';
|
||||
import { DashboardContainerStateWithType } from '../../types';
|
||||
import { ParsedDashboardAttributesWithType } from '../../types';
|
||||
|
||||
const getPanelStatePrefix = (state: DashboardPanelState) => `${state.explicitInput.id}:`;
|
||||
|
||||
|
@ -24,8 +25,10 @@ const controlGroupId = 'dashboard_control_group';
|
|||
export const createInject = (
|
||||
persistableStateService: EmbeddablePersistableStateService
|
||||
): EmbeddablePersistableStateService['inject'] => {
|
||||
return (state: EmbeddableStateWithType, references: SavedObjectReference[]) => {
|
||||
const workingState = { ...state } as EmbeddableStateWithType | DashboardContainerStateWithType;
|
||||
return (state: EmbeddableStateWithType, references: Reference[]) => {
|
||||
const workingState = { ...state } as
|
||||
| EmbeddableStateWithType
|
||||
| ParsedDashboardAttributesWithType;
|
||||
|
||||
if ('panels' in workingState) {
|
||||
workingState.panels = { ...workingState.panels };
|
||||
|
@ -103,9 +106,11 @@ export const createExtract = (
|
|||
persistableStateService: EmbeddablePersistableStateService
|
||||
): EmbeddablePersistableStateService['extract'] => {
|
||||
return (state: EmbeddableStateWithType) => {
|
||||
const workingState = { ...state } as EmbeddableStateWithType | DashboardContainerStateWithType;
|
||||
const workingState = { ...state } as
|
||||
| EmbeddableStateWithType
|
||||
| ParsedDashboardAttributesWithType;
|
||||
|
||||
const references: SavedObjectReference[] = [];
|
||||
const references: Reference[] = [];
|
||||
|
||||
if ('panels' in workingState) {
|
||||
workingState.panels = { ...workingState.panels };
|
||||
|
@ -125,7 +130,6 @@ export const createExtract = (
|
|||
});
|
||||
|
||||
delete panel.explicitInput.savedObjectId;
|
||||
delete panel.explicitInput.type;
|
||||
}
|
||||
|
||||
const { state: panelState, references: panelReferences } = persistableStateService.extract({
|
||||
|
|
|
@ -17,7 +17,8 @@ import { RefreshInterval } from '@kbn/data-plugin/common';
|
|||
import { PersistableControlGroupInput } from '@kbn/controls-plugin/common';
|
||||
import { KibanaExecutionContext } from '@kbn/core-execution-context-common';
|
||||
|
||||
import { DashboardOptions, GridData } from '../types';
|
||||
import { DashboardOptions } from '../types';
|
||||
import { GridData } from '../content_management';
|
||||
|
||||
export interface DashboardPanelMap {
|
||||
[key: string]: DashboardPanelState;
|
||||
|
|
|
@ -9,8 +9,7 @@
|
|||
import {
|
||||
extractReferences,
|
||||
injectReferences,
|
||||
InjectDeps,
|
||||
ExtractDeps,
|
||||
InjectExtractDeps,
|
||||
} from './dashboard_saved_object_references';
|
||||
|
||||
import {
|
||||
|
@ -18,6 +17,7 @@ import {
|
|||
createInject,
|
||||
} from '../../dashboard_container/persistable_state/dashboard_container_references';
|
||||
import { createEmbeddablePersistableStateServiceMock } from '@kbn/embeddable-plugin/common/mocks';
|
||||
import { DashboardAttributes } from '../../content_management';
|
||||
|
||||
const embeddablePersistableStateServiceMock = createEmbeddablePersistableStateServiceMock();
|
||||
const dashboardInject = createInject(embeddablePersistableStateServiceMock);
|
||||
|
@ -38,15 +38,25 @@ embeddablePersistableStateServiceMock.inject.mockImplementation((state, referenc
|
|||
|
||||
return state;
|
||||
});
|
||||
const deps: InjectDeps & ExtractDeps = {
|
||||
const deps: InjectExtractDeps = {
|
||||
embeddablePersistableStateService: embeddablePersistableStateServiceMock,
|
||||
};
|
||||
|
||||
const commonAttributes: DashboardAttributes = {
|
||||
kibanaSavedObjectMeta: { searchSourceJSON: '' },
|
||||
timeRestore: false,
|
||||
panelsJSON: '',
|
||||
version: 1,
|
||||
description: '',
|
||||
title: '',
|
||||
};
|
||||
|
||||
describe('legacy extract references', () => {
|
||||
test('extracts references from panelsJSON', () => {
|
||||
const doc = {
|
||||
id: '1',
|
||||
attributes: {
|
||||
...commonAttributes,
|
||||
foo: true,
|
||||
panelsJSON: JSON.stringify([
|
||||
{
|
||||
|
@ -70,8 +80,15 @@ describe('legacy extract references', () => {
|
|||
expect(updatedDoc).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"attributes": Object {
|
||||
"description": "",
|
||||
"foo": true,
|
||||
"kibanaSavedObjectMeta": Object {
|
||||
"searchSourceJSON": "",
|
||||
},
|
||||
"panelsJSON": "[{\\"title\\":\\"Title 1\\",\\"version\\":\\"7.0.0\\",\\"panelRefName\\":\\"panel_0\\"},{\\"title\\":\\"Title 2\\",\\"version\\":\\"7.0.0\\",\\"panelRefName\\":\\"panel_1\\"}]",
|
||||
"timeRestore": false,
|
||||
"title": "",
|
||||
"version": 1,
|
||||
},
|
||||
"references": Array [
|
||||
Object {
|
||||
|
@ -93,6 +110,7 @@ describe('legacy extract references', () => {
|
|||
const doc = {
|
||||
id: '1',
|
||||
attributes: {
|
||||
...commonAttributes,
|
||||
foo: true,
|
||||
panelsJSON: JSON.stringify([
|
||||
{
|
||||
|
@ -113,6 +131,7 @@ describe('legacy extract references', () => {
|
|||
const doc = {
|
||||
id: '1',
|
||||
attributes: {
|
||||
...commonAttributes,
|
||||
foo: true,
|
||||
panelsJSON: JSON.stringify([
|
||||
{
|
||||
|
@ -127,8 +146,15 @@ describe('legacy extract references', () => {
|
|||
expect(extractReferences(doc, deps)).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"attributes": Object {
|
||||
"description": "",
|
||||
"foo": true,
|
||||
"kibanaSavedObjectMeta": Object {
|
||||
"searchSourceJSON": "",
|
||||
},
|
||||
"panelsJSON": "[{\\"version\\":\\"7.9.1\\",\\"type\\":\\"visualization\\",\\"embeddableConfig\\":{},\\"title\\":\\"Title 1\\"}]",
|
||||
"timeRestore": false,
|
||||
"title": "",
|
||||
"version": 1,
|
||||
},
|
||||
"references": Array [],
|
||||
}
|
||||
|
@ -214,6 +240,7 @@ describe('extractReferences', () => {
|
|||
const doc = {
|
||||
id: '1',
|
||||
attributes: {
|
||||
...commonAttributes,
|
||||
foo: true,
|
||||
panelsJSON: JSON.stringify([
|
||||
{
|
||||
|
@ -239,8 +266,15 @@ describe('extractReferences', () => {
|
|||
expect(updatedDoc).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"attributes": Object {
|
||||
"description": "",
|
||||
"foo": true,
|
||||
"kibanaSavedObjectMeta": Object {
|
||||
"searchSourceJSON": "",
|
||||
},
|
||||
"panelsJSON": "[{\\"version\\":\\"7.9.1\\",\\"type\\":\\"visualization\\",\\"panelIndex\\":\\"panel-1\\",\\"embeddableConfig\\":{},\\"title\\":\\"Title 1\\",\\"panelRefName\\":\\"panel_panel-1\\"},{\\"version\\":\\"7.9.1\\",\\"type\\":\\"visualization\\",\\"panelIndex\\":\\"panel-2\\",\\"embeddableConfig\\":{},\\"title\\":\\"Title 2\\",\\"panelRefName\\":\\"panel_panel-2\\"}]",
|
||||
"timeRestore": false,
|
||||
"title": "",
|
||||
"version": 1,
|
||||
},
|
||||
"references": Array [
|
||||
Object {
|
||||
|
@ -262,6 +296,7 @@ describe('extractReferences', () => {
|
|||
const doc = {
|
||||
id: '1',
|
||||
attributes: {
|
||||
...commonAttributes,
|
||||
foo: true,
|
||||
panelsJSON: JSON.stringify([
|
||||
{
|
||||
|
@ -274,7 +309,7 @@ describe('extractReferences', () => {
|
|||
references: [],
|
||||
};
|
||||
expect(() => extractReferences(doc, deps)).toThrowErrorMatchingInlineSnapshot(
|
||||
`"\\"type\\" attribute is missing from panel \\"0\\""`
|
||||
`"\\"type\\" attribute is missing from panel \\"undefined\\""`
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -282,6 +317,7 @@ describe('extractReferences', () => {
|
|||
const doc = {
|
||||
id: '1',
|
||||
attributes: {
|
||||
...commonAttributes,
|
||||
foo: true,
|
||||
panelsJSON: JSON.stringify([
|
||||
{
|
||||
|
@ -296,8 +332,15 @@ describe('extractReferences', () => {
|
|||
expect(extractReferences(doc, deps)).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"attributes": Object {
|
||||
"description": "",
|
||||
"foo": true,
|
||||
"kibanaSavedObjectMeta": Object {
|
||||
"searchSourceJSON": "",
|
||||
},
|
||||
"panelsJSON": "[{\\"version\\":\\"7.9.1\\",\\"type\\":\\"visualization\\",\\"embeddableConfig\\":{},\\"title\\":\\"Title 1\\"}]",
|
||||
"timeRestore": false,
|
||||
"title": "",
|
||||
"version": 1,
|
||||
},
|
||||
"references": Array [],
|
||||
}
|
||||
|
@ -308,6 +351,7 @@ describe('extractReferences', () => {
|
|||
describe('injectReferences', () => {
|
||||
test('returns injected attributes', () => {
|
||||
const attributes = {
|
||||
...commonAttributes,
|
||||
id: '1',
|
||||
title: 'test',
|
||||
panelsJSON: JSON.stringify([
|
||||
|
@ -339,9 +383,15 @@ describe('injectReferences', () => {
|
|||
|
||||
expect(newAttributes).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"description": "",
|
||||
"id": "1",
|
||||
"kibanaSavedObjectMeta": Object {
|
||||
"searchSourceJSON": "",
|
||||
},
|
||||
"panelsJSON": "[{\\"version\\":\\"7.9.0\\",\\"type\\":\\"visualization\\",\\"embeddableConfig\\":{},\\"title\\":\\"Title 1\\",\\"id\\":\\"1\\"},{\\"version\\":\\"7.9.0\\",\\"type\\":\\"visualization\\",\\"embeddableConfig\\":{},\\"title\\":\\"Title 2\\",\\"id\\":\\"2\\"}]",
|
||||
"timeRestore": false,
|
||||
"title": "test",
|
||||
"version": 1,
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
@ -350,11 +400,12 @@ describe('injectReferences', () => {
|
|||
const attributes = {
|
||||
id: '1',
|
||||
title: 'test',
|
||||
};
|
||||
} as unknown as DashboardAttributes;
|
||||
const newAttributes = injectReferences({ attributes, references: [] }, deps);
|
||||
expect(newAttributes).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"id": "1",
|
||||
"panelsJSON": "[]",
|
||||
"title": "test",
|
||||
}
|
||||
`);
|
||||
|
@ -362,6 +413,7 @@ describe('injectReferences', () => {
|
|||
|
||||
test('skips when panelsJSON is not an array', () => {
|
||||
const attributes = {
|
||||
...commonAttributes,
|
||||
id: '1',
|
||||
panelsJSON: '{}',
|
||||
title: 'test',
|
||||
|
@ -369,15 +421,22 @@ describe('injectReferences', () => {
|
|||
const newAttributes = injectReferences({ attributes, references: [] }, deps);
|
||||
expect(newAttributes).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"description": "",
|
||||
"id": "1",
|
||||
"panelsJSON": "{}",
|
||||
"kibanaSavedObjectMeta": Object {
|
||||
"searchSourceJSON": "",
|
||||
},
|
||||
"panelsJSON": "[]",
|
||||
"timeRestore": false,
|
||||
"title": "test",
|
||||
"version": 1,
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
test('skips a panel when panelRefName is missing', () => {
|
||||
const attributes = {
|
||||
...commonAttributes,
|
||||
id: '1',
|
||||
title: 'test',
|
||||
panelsJSON: JSON.stringify([
|
||||
|
@ -400,15 +459,22 @@ describe('injectReferences', () => {
|
|||
const newAttributes = injectReferences({ attributes, references }, deps);
|
||||
expect(newAttributes).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"description": "",
|
||||
"id": "1",
|
||||
"panelsJSON": "[{\\"version\\":\\"\\",\\"type\\":\\"visualization\\",\\"embeddableConfig\\":{},\\"title\\":\\"Title 1\\",\\"id\\":\\"1\\"},{\\"version\\":\\"\\",\\"embeddableConfig\\":{},\\"title\\":\\"Title 2\\"}]",
|
||||
"kibanaSavedObjectMeta": Object {
|
||||
"searchSourceJSON": "",
|
||||
},
|
||||
"panelsJSON": "[{\\"type\\":\\"visualization\\",\\"embeddableConfig\\":{},\\"title\\":\\"Title 1\\",\\"id\\":\\"1\\"},{\\"embeddableConfig\\":{},\\"title\\":\\"Title 2\\"}]",
|
||||
"timeRestore": false,
|
||||
"title": "test",
|
||||
"version": 1,
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
test(`fails when it can't find the reference in the array`, () => {
|
||||
const attributes = {
|
||||
...commonAttributes,
|
||||
id: '1',
|
||||
title: 'test',
|
||||
panelsJSON: JSON.stringify([
|
||||
|
|
|
@ -7,130 +7,66 @@
|
|||
*/
|
||||
import semverGt from 'semver/functions/gt';
|
||||
|
||||
import {
|
||||
RawControlGroupAttributes,
|
||||
PersistableControlGroupInput,
|
||||
} from '@kbn/controls-plugin/common';
|
||||
import { SavedObjectAttributes, SavedObjectReference } from '@kbn/core/types';
|
||||
import { Reference } from '@kbn/content-management-utils';
|
||||
import { EmbeddablePersistableStateService } from '@kbn/embeddable-plugin/common/types';
|
||||
import { rawControlGroupAttributesToControlGroupInput } from '@kbn/controls-plugin/common';
|
||||
|
||||
import { SavedDashboardPanel } from '../types';
|
||||
import {
|
||||
convertPanelStateToSavedDashboardPanel,
|
||||
convertSavedDashboardPanelToPanelState,
|
||||
convertPanelMapToSavedPanels,
|
||||
convertSavedPanelsToPanelMap,
|
||||
} from '../../lib/dashboard_panel_converters';
|
||||
import { DashboardPanelState } from '../../dashboard_container/types';
|
||||
import { DashboardContainerStateWithType } from '../../types';
|
||||
import { DashboardAttributesAndReferences, ParsedDashboardAttributesWithType } from '../../types';
|
||||
import { DashboardAttributes, SavedDashboardPanel } from '../../content_management';
|
||||
|
||||
export interface ExtractDeps {
|
||||
export interface InjectExtractDeps {
|
||||
embeddablePersistableStateService: EmbeddablePersistableStateService;
|
||||
}
|
||||
|
||||
export interface InjectDeps {
|
||||
embeddablePersistableStateService: EmbeddablePersistableStateService;
|
||||
}
|
||||
|
||||
interface SavedObjectAttributesAndReferences {
|
||||
attributes: SavedObjectAttributes;
|
||||
references: SavedObjectReference[];
|
||||
}
|
||||
|
||||
const isPre730Panel = (panel: Record<string, string>): boolean => {
|
||||
return 'version' in panel ? semverGt('7.3.0', panel.version) : true;
|
||||
return 'version' in panel && panel.version ? semverGt('7.3.0', panel.version) : true;
|
||||
};
|
||||
|
||||
function dashboardAttributesToState(attributes: SavedObjectAttributes): {
|
||||
state: DashboardContainerStateWithType;
|
||||
panels: SavedDashboardPanel[];
|
||||
} {
|
||||
let inputPanels = [] as SavedDashboardPanel[];
|
||||
function parseDashboardAttributesWithType(
|
||||
attributes: DashboardAttributes
|
||||
): ParsedDashboardAttributesWithType {
|
||||
let parsedPanels = [] as SavedDashboardPanel[];
|
||||
if (typeof attributes.panelsJSON === 'string') {
|
||||
inputPanels = JSON.parse(attributes.panelsJSON) as SavedDashboardPanel[];
|
||||
}
|
||||
|
||||
let controlGroupInput: PersistableControlGroupInput | undefined;
|
||||
if (attributes.controlGroupInput) {
|
||||
const rawControlGroupInput =
|
||||
attributes.controlGroupInput as unknown as RawControlGroupAttributes;
|
||||
if (rawControlGroupInput.panelsJSON && typeof rawControlGroupInput.panelsJSON === 'string') {
|
||||
const controlGroupPanels = JSON.parse(rawControlGroupInput.panelsJSON);
|
||||
if (controlGroupPanels && typeof controlGroupPanels === 'object') {
|
||||
controlGroupInput = {
|
||||
...rawControlGroupInput,
|
||||
panels: controlGroupPanels,
|
||||
};
|
||||
}
|
||||
const parsedJSON = JSON.parse(attributes.panelsJSON);
|
||||
if (Array.isArray(parsedJSON)) {
|
||||
parsedPanels = parsedJSON as SavedDashboardPanel[];
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
panels: inputPanels,
|
||||
state: {
|
||||
id: attributes.id as string,
|
||||
controlGroupInput,
|
||||
type: 'dashboard',
|
||||
panels: inputPanels.reduce<Record<string, DashboardPanelState>>((current, panel, index) => {
|
||||
const panelIndex = panel.panelIndex || `${index}`;
|
||||
current[panelIndex] = convertSavedDashboardPanelToPanelState(panel);
|
||||
return current;
|
||||
}, {}),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function panelStatesToPanels(
|
||||
panelStates: DashboardContainerStateWithType['panels'],
|
||||
originalPanels: SavedDashboardPanel[]
|
||||
): SavedDashboardPanel[] {
|
||||
return Object.entries(panelStates).map(([id, panelState]) => {
|
||||
// Find matching original panel to get the version
|
||||
let originalPanel = originalPanels.find((p) => p.panelIndex === id);
|
||||
|
||||
if (!originalPanel) {
|
||||
// Maybe original panel doesn't have a panel index and it's just straight up based on its index
|
||||
const numericId = parseInt(id, 10);
|
||||
originalPanel = isNaN(numericId) ? originalPanel : originalPanels[numericId];
|
||||
}
|
||||
|
||||
return convertPanelStateToSavedDashboardPanel(
|
||||
panelState,
|
||||
originalPanel?.version ? originalPanel.version : ''
|
||||
);
|
||||
});
|
||||
controlGroupInput:
|
||||
attributes.controlGroupInput &&
|
||||
rawControlGroupAttributesToControlGroupInput(attributes.controlGroupInput),
|
||||
type: 'dashboard',
|
||||
panels: convertSavedPanelsToPanelMap(parsedPanels),
|
||||
} as ParsedDashboardAttributesWithType;
|
||||
}
|
||||
|
||||
export function injectReferences(
|
||||
{ attributes, references = [] }: SavedObjectAttributesAndReferences,
|
||||
deps: InjectDeps
|
||||
): SavedObjectAttributes {
|
||||
// Skip if panelsJSON is missing otherwise this will cause saved object import to fail when
|
||||
// importing objects without panelsJSON. At development time of this, there is no guarantee each saved
|
||||
// object has panelsJSON in all previous versions of kibana.
|
||||
if (typeof attributes.panelsJSON !== 'string') {
|
||||
return attributes;
|
||||
}
|
||||
const parsedPanels = JSON.parse(attributes.panelsJSON);
|
||||
// Same here, prevent failing saved object import if ever panels aren't an array.
|
||||
if (!Array.isArray(parsedPanels)) {
|
||||
return attributes;
|
||||
}
|
||||
|
||||
const { panels, state } = dashboardAttributesToState(attributes);
|
||||
{ attributes, references = [] }: DashboardAttributesAndReferences,
|
||||
deps: InjectExtractDeps
|
||||
): DashboardAttributes {
|
||||
const parsedAttributes = parseDashboardAttributesWithType(attributes);
|
||||
|
||||
// inject references back into panels via the Embeddable persistable state service.
|
||||
const injectedState = deps.embeddablePersistableStateService.inject(
|
||||
state,
|
||||
parsedAttributes,
|
||||
references
|
||||
) as DashboardContainerStateWithType;
|
||||
const injectedPanels = panelStatesToPanels(injectedState.panels, panels);
|
||||
) as ParsedDashboardAttributesWithType;
|
||||
const injectedPanels = convertPanelMapToSavedPanels(injectedState.panels);
|
||||
|
||||
const newAttributes = {
|
||||
...attributes,
|
||||
panelsJSON: JSON.stringify(injectedPanels),
|
||||
} as SavedObjectAttributes;
|
||||
} as DashboardAttributes;
|
||||
|
||||
if (injectedState.controlGroupInput) {
|
||||
if (attributes.controlGroupInput && injectedState.controlGroupInput) {
|
||||
newAttributes.controlGroupInput = {
|
||||
...(attributes.controlGroupInput as SavedObjectAttributes),
|
||||
...attributes.controlGroupInput,
|
||||
panelsJSON: JSON.stringify(injectedState.controlGroupInput.panels),
|
||||
};
|
||||
}
|
||||
|
@ -139,41 +75,39 @@ export function injectReferences(
|
|||
}
|
||||
|
||||
export function extractReferences(
|
||||
{ attributes, references = [] }: SavedObjectAttributesAndReferences,
|
||||
deps: ExtractDeps
|
||||
): SavedObjectAttributesAndReferences {
|
||||
if (typeof attributes.panelsJSON !== 'string') {
|
||||
return { attributes, references };
|
||||
{ attributes, references = [] }: DashboardAttributesAndReferences,
|
||||
deps: InjectExtractDeps
|
||||
): DashboardAttributesAndReferences {
|
||||
const parsedAttributes = parseDashboardAttributesWithType(attributes);
|
||||
|
||||
const panels = parsedAttributes.panels;
|
||||
|
||||
if ((Object.values(panels) as unknown as Array<Record<string, string>>).some(isPre730Panel)) {
|
||||
return pre730ExtractReferences({ attributes, references });
|
||||
}
|
||||
|
||||
const { panels, state } = dashboardAttributesToState(attributes);
|
||||
if (!Array.isArray(panels)) {
|
||||
return { attributes, references };
|
||||
const panelMissingType = Object.values(panels).find((panel) => panel.type === undefined);
|
||||
if (panelMissingType) {
|
||||
throw new Error(
|
||||
`"type" attribute is missing from panel "${panelMissingType.explicitInput.id}"`
|
||||
);
|
||||
}
|
||||
|
||||
if ((panels as unknown as Array<Record<string, string>>).some(isPre730Panel)) {
|
||||
return pre730ExtractReferences({ attributes, references }, deps);
|
||||
}
|
||||
|
||||
const missingTypeIndex = panels.findIndex((panel) => panel.type === undefined);
|
||||
if (missingTypeIndex >= 0) {
|
||||
throw new Error(`"type" attribute is missing from panel "${missingTypeIndex}"`);
|
||||
}
|
||||
|
||||
const { references: extractedReferences, state: rawExtractedState } =
|
||||
deps.embeddablePersistableStateService.extract(state);
|
||||
const extractedState = rawExtractedState as DashboardContainerStateWithType;
|
||||
|
||||
const extractedPanels = panelStatesToPanels(extractedState.panels, panels);
|
||||
const { references: extractedReferences, state: extractedState } =
|
||||
deps.embeddablePersistableStateService.extract(parsedAttributes) as {
|
||||
references: Reference[];
|
||||
state: ParsedDashboardAttributesWithType;
|
||||
};
|
||||
const extractedPanels = convertPanelMapToSavedPanels(extractedState.panels);
|
||||
|
||||
const newAttributes = {
|
||||
...attributes,
|
||||
panelsJSON: JSON.stringify(extractedPanels),
|
||||
} as SavedObjectAttributes;
|
||||
} as DashboardAttributes;
|
||||
|
||||
if (extractedState.controlGroupInput) {
|
||||
if (attributes.controlGroupInput && extractedState.controlGroupInput) {
|
||||
newAttributes.controlGroupInput = {
|
||||
...(attributes.controlGroupInput as SavedObjectAttributes),
|
||||
...attributes.controlGroupInput,
|
||||
panelsJSON: JSON.stringify(extractedState.controlGroupInput.panels),
|
||||
};
|
||||
}
|
||||
|
@ -184,14 +118,14 @@ export function extractReferences(
|
|||
};
|
||||
}
|
||||
|
||||
function pre730ExtractReferences(
|
||||
{ attributes, references = [] }: SavedObjectAttributesAndReferences,
|
||||
deps: ExtractDeps
|
||||
): SavedObjectAttributesAndReferences {
|
||||
function pre730ExtractReferences({
|
||||
attributes,
|
||||
references = [],
|
||||
}: DashboardAttributesAndReferences): DashboardAttributesAndReferences {
|
||||
if (typeof attributes.panelsJSON !== 'string') {
|
||||
return { attributes, references };
|
||||
}
|
||||
const panelReferences: SavedObjectReference[] = [];
|
||||
const panelReferences: Reference[] = [];
|
||||
const panels: Array<Record<string, string>> = JSON.parse(String(attributes.panelsJSON));
|
||||
|
||||
panels.forEach((panel, i) => {
|
||||
|
|
|
@ -6,12 +6,7 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
export type {
|
||||
GridData,
|
||||
DashboardOptions,
|
||||
DashboardCapabilities,
|
||||
SharedDashboardState,
|
||||
} from './types';
|
||||
export type { DashboardOptions, DashboardCapabilities, SharedDashboardState } from './types';
|
||||
|
||||
export type {
|
||||
DashboardPanelMap,
|
||||
|
@ -20,11 +15,7 @@ export type {
|
|||
DashboardContainerByReferenceInput,
|
||||
} from './dashboard_container/types';
|
||||
|
||||
export type {
|
||||
DashboardAttributes,
|
||||
ParsedDashboardAttributes,
|
||||
SavedDashboardPanel,
|
||||
} from './dashboard_saved_object/types';
|
||||
export type { DashboardAttributes } from './content_management';
|
||||
|
||||
export {
|
||||
injectReferences,
|
||||
|
|
|
@ -10,9 +10,9 @@ import {
|
|||
convertSavedDashboardPanelToPanelState,
|
||||
convertPanelStateToSavedDashboardPanel,
|
||||
} from './dashboard_panel_converters';
|
||||
import { EmbeddableInput } from '@kbn/embeddable-plugin/common/types';
|
||||
import { SavedDashboardPanel } from '../dashboard_saved_object/types';
|
||||
import { SavedDashboardPanel } from '../content_management';
|
||||
import { DashboardPanelState } from '../dashboard_container/types';
|
||||
import { EmbeddableInput } from '@kbn/embeddable-plugin/common/types';
|
||||
|
||||
test('convertSavedDashboardPanelToPanelState', () => {
|
||||
const savedDashboardPanel: SavedDashboardPanel = {
|
||||
|
@ -46,6 +46,8 @@ test('convertSavedDashboardPanelToPanelState', () => {
|
|||
savedObjectId: 'savedObjectId',
|
||||
},
|
||||
type: 'search',
|
||||
panelRefName: undefined,
|
||||
version: '7.0.0',
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -9,13 +9,15 @@
|
|||
import { omit } from 'lodash';
|
||||
import { EmbeddableInput, SavedObjectEmbeddableInput } from '@kbn/embeddable-plugin/common';
|
||||
|
||||
import { DashboardPanelMap, DashboardPanelState, SavedDashboardPanel } from '..';
|
||||
import { DashboardPanelMap, DashboardPanelState } from '..';
|
||||
import { SavedDashboardPanel } from '../content_management';
|
||||
|
||||
export function convertSavedDashboardPanelToPanelState<
|
||||
TEmbeddableInput extends EmbeddableInput | SavedObjectEmbeddableInput = SavedObjectEmbeddableInput
|
||||
>(savedDashboardPanel: SavedDashboardPanel): DashboardPanelState<TEmbeddableInput> {
|
||||
return {
|
||||
type: savedDashboardPanel.type,
|
||||
version: savedDashboardPanel.version,
|
||||
gridData: savedDashboardPanel.gridData,
|
||||
panelRefName: savedDashboardPanel.panelRefName,
|
||||
explicitInput: {
|
||||
|
@ -29,11 +31,11 @@ export function convertSavedDashboardPanelToPanelState<
|
|||
|
||||
export function convertPanelStateToSavedDashboardPanel(
|
||||
panelState: DashboardPanelState,
|
||||
version: string
|
||||
version?: string
|
||||
): SavedDashboardPanel {
|
||||
const savedObjectId = (panelState.explicitInput as SavedObjectEmbeddableInput).savedObjectId;
|
||||
return {
|
||||
version,
|
||||
version: version ?? (panelState.version as string), // temporary cast. Version will be mandatory at a later date.
|
||||
type: panelState.type,
|
||||
gridData: panelState.gridData,
|
||||
panelIndex: panelState.explicitInput.id,
|
||||
|
@ -52,8 +54,11 @@ export const convertSavedPanelsToPanelMap = (panels?: SavedDashboardPanel[]): Da
|
|||
return panelsMap;
|
||||
};
|
||||
|
||||
export const convertPanelMapToSavedPanels = (panels: DashboardPanelMap, version: string) => {
|
||||
export const convertPanelMapToSavedPanels = (
|
||||
panels: DashboardPanelMap,
|
||||
versionOverride?: string
|
||||
) => {
|
||||
return Object.values(panels).map((panel) =>
|
||||
convertPanelStateToSavedDashboardPanel(panel, version)
|
||||
convertPanelStateToSavedDashboardPanel(panel, versionOverride)
|
||||
);
|
||||
};
|
||||
|
|
|
@ -6,11 +6,12 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { EmbeddableInput, EmbeddableStateWithType } from '@kbn/embeddable-plugin/common';
|
||||
import { Reference } from '@kbn/content-management-utils';
|
||||
import { EmbeddableStateWithType } from '@kbn/embeddable-plugin/common';
|
||||
import { PersistableControlGroupInput } from '@kbn/controls-plugin/common';
|
||||
|
||||
import { SavedDashboardPanel } from './dashboard_saved_object/types';
|
||||
import { DashboardContainerInput, DashboardPanelState } from './dashboard_container/types';
|
||||
import { DashboardAttributes, SavedDashboardPanel } from './content_management';
|
||||
import { DashboardContainerInput, DashboardPanelMap } from './dashboard_container/types';
|
||||
|
||||
export interface DashboardOptions {
|
||||
hidePanelTitles: boolean;
|
||||
|
@ -36,26 +37,15 @@ export type SharedDashboardState = Partial<
|
|||
>;
|
||||
|
||||
/**
|
||||
* Grid type for React Grid Layout
|
||||
* A partially parsed version of the Dashboard Attributes used for inject and extract logic for both the Dashboard Container and the Dashboard Saved Object.
|
||||
*/
|
||||
export interface GridData {
|
||||
w: number;
|
||||
h: number;
|
||||
x: number;
|
||||
y: number;
|
||||
i: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Types below this line are copied here because so many important types are tied up in public. These types should be
|
||||
* moved from public into common.
|
||||
*
|
||||
* TODO replace this type with a type that uses the real Dashboard Input type.
|
||||
* See https://github.com/elastic/kibana/issues/147488 for more information.
|
||||
*/
|
||||
export interface DashboardContainerStateWithType extends EmbeddableStateWithType {
|
||||
panels: {
|
||||
[panelId: string]: DashboardPanelState<EmbeddableInput & { [k: string]: unknown }>;
|
||||
};
|
||||
export type ParsedDashboardAttributesWithType = EmbeddableStateWithType & {
|
||||
controlGroupInput?: PersistableControlGroupInput;
|
||||
panels: DashboardPanelMap;
|
||||
type: 'dashboard';
|
||||
};
|
||||
|
||||
export interface DashboardAttributesAndReferences {
|
||||
attributes: DashboardAttributes;
|
||||
references: Reference[];
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
"savedObjects",
|
||||
"savedObjectsFinder",
|
||||
"savedObjectsManagement",
|
||||
"contentManagement",
|
||||
"share",
|
||||
"screenshotMode",
|
||||
"uiActions",
|
||||
|
@ -34,10 +35,6 @@
|
|||
"usageCollection",
|
||||
"taskManager"
|
||||
],
|
||||
"requiredBundles": [
|
||||
"kibanaReact",
|
||||
"kibanaUtils",
|
||||
"presentationUtil"
|
||||
]
|
||||
"requiredBundles": ["kibanaReact", "kibanaUtils", "presentationUtil"]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,7 +9,9 @@
|
|||
import _ from 'lodash';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
// TODO Remove this usage of the SavedObjectsStart contract.
|
||||
import { SavedObjectsStart } from '@kbn/core/public';
|
||||
|
||||
import {
|
||||
ViewMode,
|
||||
PanelState,
|
||||
|
@ -150,6 +152,7 @@ export class ClonePanelAction implements Action<ClonePanelActionContext> {
|
|||
embeddable: IEmbeddable,
|
||||
objectIdToClone: string
|
||||
): Promise<string> {
|
||||
// TODO: Remove this entire functionality. See https://github.com/elastic/kibana/issues/158632 for more info.
|
||||
const savedObjectToClone = await this.savedObjects.client.get<SavedObject>(
|
||||
embeddable.type,
|
||||
objectIdToClone
|
||||
|
@ -183,6 +186,7 @@ export class ClonePanelAction implements Action<ClonePanelActionContext> {
|
|||
title: newTitle,
|
||||
hidePanelTitles: panelToClone.explicitInput.hidePanelTitles,
|
||||
},
|
||||
version: panelToClone.version,
|
||||
};
|
||||
} else {
|
||||
panelState = {
|
||||
|
@ -191,7 +195,10 @@ export class ClonePanelAction implements Action<ClonePanelActionContext> {
|
|||
...panelToClone.explicitInput,
|
||||
id: uuidv4(),
|
||||
},
|
||||
version: panelToClone.version,
|
||||
};
|
||||
|
||||
// TODO Remove the entire `addCloneToLibrary` section from here.
|
||||
if (panelToClone.explicitInput.savedObjectId) {
|
||||
const clonedSavedObjectId = await this.addCloneToLibrary(
|
||||
embeddable,
|
||||
|
|
|
@ -13,7 +13,7 @@ import { pluginServices } from '../../services/plugin_services';
|
|||
import { createDashboardEditUrl } from '../../dashboard_constants';
|
||||
import { getDashboardURL404String } from '../_dashboard_app_strings';
|
||||
import { useDashboardMountContext } from './dashboard_mount_context';
|
||||
import { LoadDashboardFromSavedObjectReturn } from '../../services/dashboard_saved_object/lib/load_dashboard_state_from_saved_object';
|
||||
import { LoadDashboardReturn } from '../../services/dashboard_content_management/types';
|
||||
|
||||
export const useDashboardOutcomeValidation = ({
|
||||
redirectTo,
|
||||
|
@ -37,7 +37,7 @@ export const useDashboardOutcomeValidation = ({
|
|||
} = pluginServices.getServices();
|
||||
|
||||
const validateOutcome = useCallback(
|
||||
({ dashboardFound, resolveMeta, dashboardId }: LoadDashboardFromSavedObjectReturn) => {
|
||||
({ dashboardFound, resolveMeta, dashboardId }: LoadDashboardReturn) => {
|
||||
if (!dashboardFound) {
|
||||
toasts.addDanger(getDashboardURL404String());
|
||||
redirectTo({ destination: 'listing' });
|
||||
|
@ -45,11 +45,7 @@ export const useDashboardOutcomeValidation = ({
|
|||
}
|
||||
|
||||
if (resolveMeta && dashboardId) {
|
||||
const {
|
||||
outcome: loadOutcome,
|
||||
alias_target_id: alias,
|
||||
alias_purpose: aliasPurpose,
|
||||
} = resolveMeta;
|
||||
const { outcome: loadOutcome, aliasTargetId: alias, aliasPurpose } = resolveMeta;
|
||||
/**
|
||||
* Handle saved object resolve alias outcome by redirecting.
|
||||
*/
|
||||
|
|
|
@ -88,7 +88,7 @@ test('When given a title that matches multiple dashboards, filter on the title',
|
|||
props.title = title;
|
||||
|
||||
(
|
||||
pluginServices.getServices().dashboardSavedObject.findDashboards.findByTitle as jest.Mock
|
||||
pluginServices.getServices().dashboardContentManagement.findDashboards.findByTitle as jest.Mock
|
||||
).mockResolvedValue(undefined);
|
||||
|
||||
let component: ReactWrapper;
|
||||
|
@ -110,7 +110,7 @@ test('When given a title that matches one dashboard, redirect to dashboard', asy
|
|||
const props = makeDefaultProps();
|
||||
props.title = title;
|
||||
(
|
||||
pluginServices.getServices().dashboardSavedObject.findDashboards.findByTitle as jest.Mock
|
||||
pluginServices.getServices().dashboardContentManagement.findDashboards.findByTitle as jest.Mock
|
||||
).mockResolvedValue({ id: 'you_found_me' });
|
||||
|
||||
let component: ReactWrapper;
|
||||
|
|
|
@ -38,7 +38,7 @@ export const DashboardListingPage = ({
|
|||
const {
|
||||
data: { query },
|
||||
chrome: { setBreadcrumbs },
|
||||
dashboardSavedObject: { findDashboards },
|
||||
dashboardContentManagement: { findDashboards },
|
||||
} = pluginServices.getServices();
|
||||
|
||||
const [showNoDataPage, setShowNoDataPage] = useState<boolean>(false);
|
||||
|
|
|
@ -15,8 +15,9 @@ import { SerializableControlGroupInput } from '@kbn/controls-plugin/common';
|
|||
import type { LocatorDefinition, LocatorPublic } from '@kbn/share-plugin/public';
|
||||
import type { GlobalQueryStateFromUrl } from '@kbn/data-plugin/public';
|
||||
|
||||
import type { DashboardContainerInput } from '../../../common';
|
||||
import { SavedDashboardPanel } from '../../../common/content_management';
|
||||
import { DASHBOARD_APP_ID, SEARCH_SESSION_ID } from '../../dashboard_constants';
|
||||
import type { DashboardContainerInput, SavedDashboardPanel } from '../../../common';
|
||||
|
||||
/**
|
||||
* Useful for ensuring that we don't pass any non-serializable values to history.push (for example, functions).
|
||||
|
|
|
@ -19,7 +19,7 @@ import { topNavStrings } from '../_dashboard_app_strings';
|
|||
import { ShowShareModal } from './share/show_share_modal';
|
||||
import { pluginServices } from '../../services/plugin_services';
|
||||
import { CHANGE_CHECK_DEBOUNCE } from '../../dashboard_constants';
|
||||
import { SaveDashboardReturn } from '../../services/dashboard_saved_object/types';
|
||||
import { SaveDashboardReturn } from '../../services/dashboard_content_management/types';
|
||||
import { confirmDiscardUnsavedChanges } from '../../dashboard_listing/confirm_overlays';
|
||||
|
||||
export const useDashboardMenuItems = ({
|
||||
|
|
|
@ -15,7 +15,6 @@ import { replaceUrlHashQuery } from '@kbn/kibana-utils-plugin/common';
|
|||
|
||||
import {
|
||||
DashboardPanelMap,
|
||||
SavedDashboardPanel,
|
||||
SharedDashboardState,
|
||||
convertSavedPanelsToPanelMap,
|
||||
DashboardContainerInput,
|
||||
|
@ -24,7 +23,8 @@ import { DashboardAPI } from '../../dashboard_container';
|
|||
import { pluginServices } from '../../services/plugin_services';
|
||||
import { getPanelTooOldErrorString } from '../_dashboard_app_strings';
|
||||
import { DASHBOARD_STATE_STORAGE_KEY } from '../../dashboard_constants';
|
||||
import { migrateLegacyQuery } from '../../services/dashboard_saved_object/lib/load_dashboard_state_from_saved_object';
|
||||
import { SavedDashboardPanel } from '../../../common/content_management';
|
||||
import { migrateLegacyQuery } from '../../services/dashboard_content_management/lib/load_dashboard_state';
|
||||
|
||||
/**
|
||||
* We no longer support loading panels from a version older than 7.3 in the URL.
|
||||
|
|
|
@ -53,7 +53,6 @@ export const DASHBOARD_UI_METRIC_ID = 'dashboard';
|
|||
export const DASHBOARD_APP_ID = 'dashboards';
|
||||
export const LEGACY_DASHBOARD_APP_ID = 'dashboard';
|
||||
export const SEARCH_SESSION_ID = 'searchSessionId';
|
||||
export const DASHBOARD_SAVED_OBJECT_TYPE = 'dashboard';
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Grid
|
||||
|
@ -66,6 +65,8 @@ export const DEFAULT_PANEL_WIDTH = DASHBOARD_GRID_COLUMN_COUNT / 2;
|
|||
|
||||
export const CHANGE_CHECK_DEBOUNCE = 100;
|
||||
|
||||
export { CONTENT_ID as DASHBOARD_CONTENT_ID } from '../common/content_management/constants';
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Default State
|
||||
// ------------------------------------------------------------------
|
||||
|
|
|
@ -8,7 +8,8 @@
|
|||
|
||||
import _ from 'lodash';
|
||||
import { PanelNotFoundError } from '@kbn/embeddable-plugin/public';
|
||||
import { DashboardPanelState, GridData } from '../../../../common';
|
||||
import { DashboardPanelState } from '../../../../common';
|
||||
import { GridData } from '../../../../common/content_management';
|
||||
import { DASHBOARD_GRID_COLUMN_COUNT } from '../../../dashboard_constants';
|
||||
|
||||
export type PanelPlacementMethod<PlacementArgs extends IPanelPlacementArgs> = (
|
||||
|
|
|
@ -39,7 +39,7 @@ const DUPLICATE_TITLE_CALLOUT_ID = 'duplicateTitleCallout';
|
|||
export const DashboardSettings = ({ onClose }: DashboardSettingsProps) => {
|
||||
const {
|
||||
savedObjectsTagging: { components },
|
||||
dashboardSavedObject: { checkForDuplicateDashboardTitle },
|
||||
dashboardContentManagement: { checkForDuplicateDashboardTitle },
|
||||
} = pluginServices.getServices();
|
||||
|
||||
const dashboard = useDashboardContainer();
|
||||
|
|
|
@ -11,14 +11,14 @@ import { batch } from 'react-redux';
|
|||
import { showSaveModal } from '@kbn/saved-objects-plugin/public';
|
||||
|
||||
import { reportPerformanceMetricEvent } from '@kbn/ebt-tools';
|
||||
import { DASHBOARD_SAVED_OBJECT_TYPE, SAVED_OBJECT_POST_TIME } from '../../../dashboard_constants';
|
||||
import { DASHBOARD_CONTENT_ID, SAVED_OBJECT_POST_TIME } from '../../../dashboard_constants';
|
||||
import { DashboardSaveOptions, DashboardStateFromSaveModal } from '../../types';
|
||||
import { DashboardSaveModal } from './overlays/save_modal';
|
||||
import { DashboardContainer } from '../dashboard_container';
|
||||
import { showCloneModal } from './overlays/show_clone_modal';
|
||||
import { pluginServices } from '../../../services/plugin_services';
|
||||
import { DashboardContainerInput } from '../../../../common';
|
||||
import { SaveDashboardReturn } from '../../../services/dashboard_saved_object/types';
|
||||
import { SaveDashboardReturn } from '../../../services/dashboard_content_management/types';
|
||||
|
||||
export function runSaveAs(this: DashboardContainer) {
|
||||
const {
|
||||
|
@ -28,7 +28,7 @@ export function runSaveAs(this: DashboardContainer) {
|
|||
},
|
||||
},
|
||||
savedObjectsTagging: { hasApi: hasSavedObjectsTagging },
|
||||
dashboardSavedObject: { checkForDuplicateDashboardTitle, saveDashboardStateToSavedObject },
|
||||
dashboardContentManagement: { checkForDuplicateDashboardTitle, saveDashboardState },
|
||||
} = pluginServices.getServices();
|
||||
|
||||
const {
|
||||
|
@ -81,7 +81,7 @@ export function runSaveAs(this: DashboardContainer) {
|
|||
...stateFromSaveModal,
|
||||
};
|
||||
const beforeAddTime = window.performance.now();
|
||||
const saveResult = await saveDashboardStateToSavedObject({
|
||||
const saveResult = await saveDashboardState({
|
||||
currentState: stateToSave,
|
||||
saveOptions,
|
||||
lastSavedId,
|
||||
|
@ -91,7 +91,7 @@ export function runSaveAs(this: DashboardContainer) {
|
|||
eventName: SAVED_OBJECT_POST_TIME,
|
||||
duration: addDuration,
|
||||
meta: {
|
||||
saved_object_type: DASHBOARD_SAVED_OBJECT_TYPE,
|
||||
saved_object_type: DASHBOARD_CONTENT_ID,
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -127,7 +127,7 @@ export function runSaveAs(this: DashboardContainer) {
|
|||
*/
|
||||
export async function runQuickSave(this: DashboardContainer) {
|
||||
const {
|
||||
dashboardSavedObject: { saveDashboardStateToSavedObject },
|
||||
dashboardContentManagement: { saveDashboardState },
|
||||
} = pluginServices.getServices();
|
||||
|
||||
const {
|
||||
|
@ -135,7 +135,7 @@ export async function runQuickSave(this: DashboardContainer) {
|
|||
componentState: { lastSavedId },
|
||||
} = this.getState();
|
||||
|
||||
const saveResult = await saveDashboardStateToSavedObject({
|
||||
const saveResult = await saveDashboardState({
|
||||
lastSavedId,
|
||||
currentState,
|
||||
saveOptions: {},
|
||||
|
@ -147,7 +147,7 @@ export async function runQuickSave(this: DashboardContainer) {
|
|||
|
||||
export async function runClone(this: DashboardContainer) {
|
||||
const {
|
||||
dashboardSavedObject: { saveDashboardStateToSavedObject, checkForDuplicateDashboardTitle },
|
||||
dashboardContentManagement: { saveDashboardState, checkForDuplicateDashboardTitle },
|
||||
} = pluginServices.getServices();
|
||||
|
||||
const { explicitInput: currentState } = this.getState();
|
||||
|
@ -170,7 +170,7 @@ export async function runClone(this: DashboardContainer) {
|
|||
// do not clone if title is duplicate and is unconfirmed
|
||||
return {};
|
||||
}
|
||||
const saveResult = await saveDashboardStateToSavedObject({
|
||||
const saveResult = await saveDashboardState({
|
||||
saveOptions: { saveAsCopy: true },
|
||||
currentState: { ...currentState, title: newTitle },
|
||||
});
|
||||
|
|
|
@ -52,7 +52,7 @@ test('throws error when provided validation function returns invalid', async ()
|
|||
});
|
||||
|
||||
test('pulls state from dashboard saved object when given a saved object id', async () => {
|
||||
pluginServices.getServices().dashboardSavedObject.loadDashboardStateFromSavedObject = jest
|
||||
pluginServices.getServices().dashboardContentManagement.loadDashboardState = jest
|
||||
.fn()
|
||||
.mockResolvedValue({
|
||||
dashboardInput: {
|
||||
|
@ -62,13 +62,13 @@ test('pulls state from dashboard saved object when given a saved object id', asy
|
|||
});
|
||||
const dashboard = await createDashboard({}, 0, 'wow-such-id');
|
||||
expect(
|
||||
pluginServices.getServices().dashboardSavedObject.loadDashboardStateFromSavedObject
|
||||
pluginServices.getServices().dashboardContentManagement.loadDashboardState
|
||||
).toHaveBeenCalledWith({ id: 'wow-such-id' });
|
||||
expect(dashboard.getState().explicitInput.description).toBe(`wow would you look at that? Wow.`);
|
||||
});
|
||||
|
||||
test('pulls state from session storage which overrides state from saved object', async () => {
|
||||
pluginServices.getServices().dashboardSavedObject.loadDashboardStateFromSavedObject = jest
|
||||
pluginServices.getServices().dashboardContentManagement.loadDashboardState = jest
|
||||
.fn()
|
||||
.mockResolvedValue({
|
||||
dashboardInput: {
|
||||
|
@ -86,7 +86,7 @@ test('pulls state from session storage which overrides state from saved object',
|
|||
});
|
||||
|
||||
test('pulls state from creation options initial input which overrides all other state sources', async () => {
|
||||
pluginServices.getServices().dashboardSavedObject.loadDashboardStateFromSavedObject = jest
|
||||
pluginServices.getServices().dashboardContentManagement.loadDashboardState = jest
|
||||
.fn()
|
||||
.mockResolvedValue({
|
||||
dashboardInput: {
|
||||
|
|
|
@ -24,10 +24,10 @@ import { pluginServices } from '../../../services/plugin_services';
|
|||
import { DEFAULT_DASHBOARD_INPUT } from '../../../dashboard_constants';
|
||||
import { DashboardCreationOptions } from '../dashboard_container_factory';
|
||||
import { startSyncingDashboardDataViews } from './data_views/sync_dashboard_data_views';
|
||||
import { LoadDashboardReturn } from '../../../services/dashboard_content_management/types';
|
||||
import { syncUnifiedSearchState } from './unified_search/sync_dashboard_unified_search_state';
|
||||
import { startSyncingDashboardControlGroup } from './controls/dashboard_control_group_integration';
|
||||
import { startDashboardSearchSessionIntegration } from './search_sessions/start_dashboard_search_session_integration';
|
||||
import { LoadDashboardFromSavedObjectReturn } from '../../../services/dashboard_saved_object/lib/load_dashboard_state_from_saved_object';
|
||||
|
||||
/**
|
||||
* Builds a new Dashboard from scratch.
|
||||
|
@ -39,7 +39,7 @@ export const createDashboard = async (
|
|||
): Promise<DashboardContainer> => {
|
||||
const {
|
||||
data: { dataViews },
|
||||
dashboardSavedObject: { loadDashboardStateFromSavedObject },
|
||||
dashboardContentManagement: { loadDashboardState },
|
||||
} = pluginServices.getServices();
|
||||
|
||||
// --------------------------------------------------------------------------------------
|
||||
|
@ -59,7 +59,7 @@ export const createDashboard = async (
|
|||
// --------------------------------------------------------------------------------------
|
||||
const reduxEmbeddablePackagePromise = lazyLoadReduxToolsPackage();
|
||||
const defaultDataViewAssignmentPromise = dataViews.getDefaultDataView();
|
||||
const dashboardSavedObjectPromise = loadDashboardStateFromSavedObject({ id: savedObjectId });
|
||||
const dashboardSavedObjectPromise = loadDashboardState({ id: savedObjectId });
|
||||
|
||||
const [reduxEmbeddablePackage, savedObjectResult, defaultDataView] = await Promise.all([
|
||||
reduxEmbeddablePackagePromise,
|
||||
|
@ -106,7 +106,7 @@ export const initializeDashboard = async ({
|
|||
creationOptions,
|
||||
controlGroup,
|
||||
}: {
|
||||
loadDashboardReturn: LoadDashboardFromSavedObjectReturn;
|
||||
loadDashboardReturn: LoadDashboardReturn;
|
||||
untilDashboardReady: () => Promise<DashboardContainer>;
|
||||
creationOptions?: DashboardCreationOptions;
|
||||
controlGroup?: ControlGroupContainer;
|
||||
|
|
|
@ -355,12 +355,12 @@ export class DashboardContainer extends Container<InheritedChildInput, Dashboard
|
|||
this.stopSyncingWithUnifiedSearch?.();
|
||||
|
||||
const {
|
||||
dashboardSavedObject: { loadDashboardStateFromSavedObject },
|
||||
dashboardContentManagement: { loadDashboardState },
|
||||
} = pluginServices.getServices();
|
||||
if (newCreationOptions) {
|
||||
this.creationOptions = { ...this.creationOptions, ...newCreationOptions };
|
||||
}
|
||||
const loadDashboardReturn = await loadDashboardStateFromSavedObject({ id: newSavedObjectId });
|
||||
const loadDashboardReturn = await loadDashboardState({ id: newSavedObjectId });
|
||||
|
||||
const dashboardContainerReady$ = new Subject<DashboardContainer>();
|
||||
const untilDashboardReady = () =>
|
||||
|
|
|
@ -25,7 +25,7 @@ import { DASHBOARD_CONTAINER_TYPE } from '..';
|
|||
import type { DashboardContainer } from './dashboard_container';
|
||||
import { DEFAULT_DASHBOARD_INPUT } from '../../dashboard_constants';
|
||||
import { createInject, createExtract, DashboardContainerInput } from '../../../common';
|
||||
import { LoadDashboardFromSavedObjectReturn } from '../../services/dashboard_saved_object/lib/load_dashboard_state_from_saved_object';
|
||||
import { LoadDashboardReturn } from '../../services/dashboard_content_management/types';
|
||||
|
||||
export type DashboardContainerFactory = EmbeddableFactory<
|
||||
DashboardContainerInput,
|
||||
|
@ -55,7 +55,7 @@ export interface DashboardCreationOptions {
|
|||
useUnifiedSearchIntegration?: boolean;
|
||||
unifiedSearchSettings?: { kbnUrlStateStorage: IKbnUrlStateStorage };
|
||||
|
||||
validateLoadedSavedObject?: (result: LoadDashboardFromSavedObjectReturn) => boolean;
|
||||
validateLoadedSavedObject?: (result: LoadDashboardReturn) => boolean;
|
||||
}
|
||||
|
||||
export class DashboardContainerFactoryDefinition
|
||||
|
|
|
@ -17,21 +17,21 @@ import {
|
|||
} from '@kbn/content-management-table-list';
|
||||
import { ViewMode } from '@kbn/embeddable-plugin/public';
|
||||
import { reportPerformanceMetricEvent } from '@kbn/ebt-tools';
|
||||
import type { SavedObjectsFindOptionsReference } from '@kbn/core/public';
|
||||
import { toMountPoint, useExecutionContext } from '@kbn/kibana-react-plugin/public';
|
||||
import type { SavedObjectsFindOptionsReference, SimpleSavedObject } from '@kbn/core/public';
|
||||
|
||||
import {
|
||||
DASHBOARD_CONTENT_ID,
|
||||
SAVED_OBJECT_DELETE_TIME,
|
||||
SAVED_OBJECT_LOADED_TIME,
|
||||
DASHBOARD_SAVED_OBJECT_TYPE,
|
||||
} from '../dashboard_constants';
|
||||
import {
|
||||
dashboardListingTableStrings,
|
||||
dashboardListingErrorStrings,
|
||||
} from './_dashboard_listing_strings';
|
||||
import { DashboardAttributes } from '../../common';
|
||||
import { pluginServices } from '../services/plugin_services';
|
||||
import { confirmCreateWithUnsaved } from './confirm_overlays';
|
||||
import { DashboardItem } from '../../common/content_management';
|
||||
import { DashboardUnsavedListing } from './dashboard_unsaved_listing';
|
||||
import { DashboardApplicationService } from '../services/application/types';
|
||||
import { DashboardListingEmptyPrompt } from './dashboard_listing_empty_prompt';
|
||||
|
@ -53,15 +53,13 @@ interface DashboardSavedObjectUserContent extends UserContentCommonSchema {
|
|||
};
|
||||
}
|
||||
|
||||
const toTableListViewSavedObject = (
|
||||
savedObject: SimpleSavedObject<DashboardAttributes>
|
||||
): DashboardSavedObjectUserContent => {
|
||||
const { title, description, timeRestore } = savedObject.attributes;
|
||||
const toTableListViewSavedObject = (hit: DashboardItem): DashboardSavedObjectUserContent => {
|
||||
const { title, description, timeRestore } = hit.attributes;
|
||||
return {
|
||||
type: 'dashboard',
|
||||
id: savedObject.id,
|
||||
updatedAt: savedObject.updatedAt!,
|
||||
references: savedObject.references,
|
||||
id: hit.id,
|
||||
updatedAt: hit.updatedAt!,
|
||||
references: hit.references,
|
||||
attributes: {
|
||||
title,
|
||||
description,
|
||||
|
@ -95,7 +93,7 @@ export const DashboardListing = ({
|
|||
notifications: { toasts },
|
||||
coreContext: { executionContext },
|
||||
dashboardCapabilities: { showWriteControls },
|
||||
dashboardSavedObject: { findDashboards, savedObjectsClient },
|
||||
dashboardContentManagement: { findDashboards, deleteDashboards },
|
||||
} = pluginServices.getServices();
|
||||
|
||||
const [unsavedDashboardIds, setUnsavedDashboardIds] = useState<string[]>(
|
||||
|
@ -133,8 +131,9 @@ export const DashboardListing = ({
|
|||
} = {}
|
||||
) => {
|
||||
const searchStartTime = window.performance.now();
|
||||
|
||||
return findDashboards
|
||||
.findSavedObjects({
|
||||
.search({
|
||||
search: searchTerm,
|
||||
size: listingLimit,
|
||||
hasReference: references,
|
||||
|
@ -147,7 +146,7 @@ export const DashboardListing = ({
|
|||
eventName: SAVED_OBJECT_LOADED_TIME,
|
||||
duration: searchDuration,
|
||||
meta: {
|
||||
saved_object_type: DASHBOARD_SAVED_OBJECT_TYPE,
|
||||
saved_object_type: DASHBOARD_CONTENT_ID,
|
||||
},
|
||||
});
|
||||
return {
|
||||
|
@ -164,10 +163,10 @@ export const DashboardListing = ({
|
|||
try {
|
||||
const deleteStartTime = window.performance.now();
|
||||
|
||||
await Promise.all(
|
||||
await deleteDashboards(
|
||||
dashboardsToDelete.map(({ id }) => {
|
||||
dashboardSessionStorage.clearState(id);
|
||||
return savedObjectsClient.delete(DASHBOARD_SAVED_OBJECT_TYPE, id);
|
||||
return id;
|
||||
})
|
||||
);
|
||||
|
||||
|
@ -176,7 +175,7 @@ export const DashboardListing = ({
|
|||
eventName: SAVED_OBJECT_DELETE_TIME,
|
||||
duration: deleteDuration,
|
||||
meta: {
|
||||
saved_object_type: DASHBOARD_SAVED_OBJECT_TYPE,
|
||||
saved_object_type: DASHBOARD_CONTENT_ID,
|
||||
total: dashboardsToDelete.length,
|
||||
},
|
||||
});
|
||||
|
@ -188,7 +187,7 @@ export const DashboardListing = ({
|
|||
|
||||
setUnsavedDashboardIds(dashboardSessionStorage.getDashboardIdsWithUnsavedChanges());
|
||||
},
|
||||
[savedObjectsClient, dashboardSessionStorage, toasts]
|
||||
[dashboardSessionStorage, deleteDashboards, toasts]
|
||||
);
|
||||
|
||||
const editItem = useCallback(
|
||||
|
|
|
@ -39,7 +39,7 @@ describe('Unsaved listing', () => {
|
|||
mountWith({});
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
pluginServices.getServices().dashboardSavedObject.findDashboards.findByIds
|
||||
pluginServices.getServices().dashboardContentManagement.findDashboards.findByIds
|
||||
).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
@ -50,7 +50,7 @@ describe('Unsaved listing', () => {
|
|||
mountWith({ props });
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
pluginServices.getServices().dashboardSavedObject.findDashboards.findByIds
|
||||
pluginServices.getServices().dashboardContentManagement.findDashboards.findByIds
|
||||
).toHaveBeenCalledWith(['dashboardUnsavedOne']);
|
||||
});
|
||||
});
|
||||
|
@ -99,7 +99,7 @@ describe('Unsaved listing', () => {
|
|||
|
||||
it('removes unsaved changes from any dashboard which errors on fetch', async () => {
|
||||
(
|
||||
pluginServices.getServices().dashboardSavedObject.findDashboards.findByIds as jest.Mock
|
||||
pluginServices.getServices().dashboardContentManagement.findDashboards.findByIds as jest.Mock
|
||||
).mockResolvedValue([
|
||||
{
|
||||
id: 'failCase1',
|
||||
|
|
|
@ -19,9 +19,9 @@ import React, { useCallback, useEffect, useState } from 'react';
|
|||
|
||||
import { ViewMode } from '@kbn/embeddable-plugin/public';
|
||||
|
||||
import { DashboardAttributes } from '../../common';
|
||||
import { pluginServices } from '../services/plugin_services';
|
||||
import { confirmDiscardUnsavedChanges } from './confirm_overlays';
|
||||
import { DashboardAttributes } from '../../common/content_management';
|
||||
import { dashboardUnsavedListingStrings, getNewDashboardTitle } from './_dashboard_listing_strings';
|
||||
import { DASHBOARD_PANELS_UNSAVED_ID } from '../services/dashboard_session_storage/dashboard_session_storage_service';
|
||||
|
||||
|
@ -117,7 +117,7 @@ export const DashboardUnsavedListing = ({
|
|||
}: DashboardUnsavedListingProps) => {
|
||||
const {
|
||||
dashboardSessionStorage,
|
||||
dashboardSavedObject: { savedObjectsClient, findDashboards },
|
||||
dashboardContentManagement: { findDashboards },
|
||||
} = pluginServices.getServices();
|
||||
|
||||
const [items, setItems] = useState<UnsavedItemMap>({});
|
||||
|
@ -173,13 +173,7 @@ export const DashboardUnsavedListing = ({
|
|||
return () => {
|
||||
canceled = true;
|
||||
};
|
||||
}, [
|
||||
refreshUnsavedDashboards,
|
||||
dashboardSessionStorage,
|
||||
unsavedDashboardIds,
|
||||
savedObjectsClient,
|
||||
findDashboards,
|
||||
]);
|
||||
}, [refreshUnsavedDashboards, dashboardSessionStorage, unsavedDashboardIds, findDashboards]);
|
||||
|
||||
return unsavedDashboardIds.length === 0 ? null : (
|
||||
<>
|
||||
|
|
|
@ -20,7 +20,6 @@ import {
|
|||
AppMountParameters,
|
||||
DEFAULT_APP_CATEGORIES,
|
||||
PluginInitializerContext,
|
||||
SavedObjectsClientContract,
|
||||
} from '@kbn/core/public';
|
||||
import type {
|
||||
ScreenshotModePluginSetup,
|
||||
|
@ -45,6 +44,10 @@ import type { UiActionsSetup, UiActionsStart } from '@kbn/ui-actions-plugin/publ
|
|||
import type { EmbeddableSetup, EmbeddableStart } from '@kbn/embeddable-plugin/public';
|
||||
import type { PresentationUtilPluginStart } from '@kbn/presentation-util-plugin/public';
|
||||
import type { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public';
|
||||
import type {
|
||||
ContentManagementPublicSetup,
|
||||
ContentManagementPublicStart,
|
||||
} from '@kbn/content-management-plugin/public';
|
||||
import type { DataPublicPluginSetup, DataPublicPluginStart } from '@kbn/data-plugin/public';
|
||||
import type { UrlForwardingSetup, UrlForwardingStart } from '@kbn/url-forwarding-plugin/public';
|
||||
import type { SavedObjectTaggingOssPluginStart } from '@kbn/saved-objects-tagging-oss-plugin/public';
|
||||
|
@ -64,7 +67,8 @@ import {
|
|||
} from './dashboard_constants';
|
||||
import { DashboardMountContextProps } from './dashboard_app/types';
|
||||
import { PlaceholderEmbeddableFactory } from './placeholder_embeddable';
|
||||
import type { FindDashboardsService } from './services/dashboard_saved_object/types';
|
||||
import type { FindDashboardsService } from './services/dashboard_content_management/types';
|
||||
import { CONTENT_ID, LATEST_VERSION } from '../common/content_management';
|
||||
|
||||
export interface DashboardFeatureFlagConfig {
|
||||
allowByValueEmbeddables: boolean;
|
||||
|
@ -74,6 +78,7 @@ export interface DashboardSetupDependencies {
|
|||
data: DataPublicPluginSetup;
|
||||
embeddable: EmbeddableSetup;
|
||||
home?: HomePublicPluginSetup;
|
||||
contentManagement: ContentManagementPublicSetup;
|
||||
screenshotMode: ScreenshotModePluginSetup;
|
||||
share?: SharePluginSetup;
|
||||
usageCollection?: UsageCollectionSetup;
|
||||
|
@ -90,7 +95,7 @@ export interface DashboardStartDependencies {
|
|||
navigation: NavigationPublicPluginStart;
|
||||
presentationUtil: PresentationUtilPluginStart;
|
||||
savedObjects: SavedObjectsStart;
|
||||
savedObjectsClient: SavedObjectsClientContract;
|
||||
contentManagement: ContentManagementPublicStart;
|
||||
savedObjectsManagement: SavedObjectsManagementPluginStart;
|
||||
savedObjectsTaggingOss?: SavedObjectTaggingOssPluginStart;
|
||||
screenshotMode: ScreenshotModePluginStart;
|
||||
|
@ -141,7 +146,7 @@ export class DashboardPlugin
|
|||
|
||||
public setup(
|
||||
core: CoreSetup<DashboardStartDependencies, DashboardStart>,
|
||||
{ share, embeddable, home, urlForwarding, data }: DashboardSetupDependencies
|
||||
{ share, embeddable, home, urlForwarding, data, contentManagement }: DashboardSetupDependencies
|
||||
): DashboardSetup {
|
||||
this.dashboardFeatureFlagConfig =
|
||||
this.initializerContext.config.get<DashboardFeatureFlagConfig>();
|
||||
|
@ -153,12 +158,9 @@ export class DashboardPlugin
|
|||
getDashboardFilterFields: async (dashboardId: string) => {
|
||||
const { pluginServices } = await import('./services/plugin_services');
|
||||
const {
|
||||
dashboardSavedObject: { loadDashboardStateFromSavedObject },
|
||||
dashboardContentManagement: { loadDashboardState },
|
||||
} = pluginServices.getServices();
|
||||
return (
|
||||
(await loadDashboardStateFromSavedObject({ id: dashboardId })).dashboardInput
|
||||
?.filters ?? []
|
||||
);
|
||||
return (await loadDashboardState({ id: dashboardId })).dashboardInput?.filters ?? [];
|
||||
},
|
||||
})
|
||||
);
|
||||
|
@ -274,13 +276,14 @@ export class DashboardPlugin
|
|||
// persisted dashboard, probably with url state
|
||||
return `#/view/${id}${tail || ''}`;
|
||||
});
|
||||
const dashboardAppTitle = i18n.translate('dashboard.featureCatalogue.dashboardTitle', {
|
||||
defaultMessage: 'Dashboard',
|
||||
});
|
||||
|
||||
if (home) {
|
||||
home.featureCatalogue.register({
|
||||
id: LEGACY_DASHBOARD_APP_ID,
|
||||
title: i18n.translate('dashboard.featureCatalogue.dashboardTitle', {
|
||||
defaultMessage: 'Dashboard',
|
||||
}),
|
||||
title: dashboardAppTitle,
|
||||
subtitle: i18n.translate('dashboard.featureCatalogue.dashboardSubtitle', {
|
||||
defaultMessage: 'Analyze data in dashboards.',
|
||||
}),
|
||||
|
@ -296,6 +299,15 @@ export class DashboardPlugin
|
|||
});
|
||||
}
|
||||
|
||||
// register content management
|
||||
contentManagement.registry.register({
|
||||
id: CONTENT_ID,
|
||||
version: {
|
||||
latest: LATEST_VERSION,
|
||||
},
|
||||
name: dashboardAppTitle,
|
||||
});
|
||||
|
||||
return {
|
||||
locator: this.locator,
|
||||
};
|
||||
|
@ -317,7 +329,7 @@ export class DashboardPlugin
|
|||
findDashboardsService: async () => {
|
||||
const { pluginServices } = await import('./services/plugin_services');
|
||||
const {
|
||||
dashboardSavedObject: { findDashboards },
|
||||
dashboardContentManagement: { findDashboards },
|
||||
} = pluginServices.getServices();
|
||||
return findDashboards;
|
||||
},
|
||||
|
|
|
@ -0,0 +1,77 @@
|
|||
/*
|
||||
* 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 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 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { PluginServiceFactory } from '@kbn/presentation-util-plugin/public';
|
||||
|
||||
import { DashboardContentManagementService, LoadDashboardReturn } from './types';
|
||||
import { DashboardAttributes } from '../../../common/content_management';
|
||||
import { SearchDashboardsResponse } from './lib/find_dashboards';
|
||||
|
||||
export type DashboardContentManagementServiceFactory =
|
||||
PluginServiceFactory<DashboardContentManagementService>;
|
||||
|
||||
export const dashboardContentManagementServiceFactory: DashboardContentManagementServiceFactory =
|
||||
() => {
|
||||
return {
|
||||
loadDashboardState: jest.fn().mockImplementation(() =>
|
||||
Promise.resolve({
|
||||
dashboardInput: {},
|
||||
} as LoadDashboardReturn)
|
||||
),
|
||||
saveDashboardState: jest.fn(),
|
||||
findDashboards: {
|
||||
search: jest.fn().mockImplementation(({ search, size }) => {
|
||||
const sizeToUse = size ?? 10;
|
||||
const hits: SearchDashboardsResponse['hits'] = [];
|
||||
for (let i = 0; i < sizeToUse; i++) {
|
||||
hits.push({
|
||||
type: 'dashboard',
|
||||
id: `dashboard${i}`,
|
||||
attributes: {
|
||||
description: `dashboard${i} desc`,
|
||||
title: `dashboard${i} - ${search} - title`,
|
||||
},
|
||||
references: [] as SearchDashboardsResponse['hits'][0]['references'],
|
||||
} as SearchDashboardsResponse['hits'][0]);
|
||||
}
|
||||
return Promise.resolve({
|
||||
total: sizeToUse,
|
||||
hits,
|
||||
});
|
||||
}),
|
||||
findByIds: jest.fn().mockImplementation(() =>
|
||||
Promise.resolve([
|
||||
{
|
||||
id: `dashboardUnsavedOne`,
|
||||
status: 'success',
|
||||
attributes: {
|
||||
title: `Dashboard Unsaved One`,
|
||||
} as unknown as DashboardAttributes,
|
||||
},
|
||||
{
|
||||
id: `dashboardUnsavedTwo`,
|
||||
status: 'success',
|
||||
attributes: {
|
||||
title: `Dashboard Unsaved Two`,
|
||||
} as unknown as DashboardAttributes,
|
||||
},
|
||||
{
|
||||
id: `dashboardUnsavedThree`,
|
||||
status: 'success',
|
||||
attributes: {
|
||||
title: `Dashboard Unsaved Three`,
|
||||
} as unknown as DashboardAttributes,
|
||||
},
|
||||
])
|
||||
),
|
||||
findByTitle: jest.fn(),
|
||||
},
|
||||
deleteDashboards: jest.fn(),
|
||||
checkForDuplicateDashboardTitle: jest.fn(),
|
||||
};
|
||||
};
|
|
@ -0,0 +1,83 @@
|
|||
/*
|
||||
* 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 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 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import type { KibanaPluginServiceFactory } from '@kbn/presentation-util-plugin/public';
|
||||
|
||||
import type { DashboardStartDependencies } from '../../plugin';
|
||||
import { checkForDuplicateDashboardTitle } from './lib/check_for_duplicate_dashboard_title';
|
||||
|
||||
import {
|
||||
searchDashboards,
|
||||
findDashboardsByIds,
|
||||
findDashboardIdByTitle,
|
||||
} from './lib/find_dashboards';
|
||||
import { saveDashboardState } from './lib/save_dashboard_state';
|
||||
import type {
|
||||
DashboardContentManagementRequiredServices,
|
||||
DashboardContentManagementService,
|
||||
} from './types';
|
||||
import { loadDashboardState } from './lib/load_dashboard_state';
|
||||
import { deleteDashboards } from './lib/delete_dashboards';
|
||||
|
||||
export type DashboardContentManagementServiceFactory = KibanaPluginServiceFactory<
|
||||
DashboardContentManagementService,
|
||||
DashboardStartDependencies,
|
||||
DashboardContentManagementRequiredServices
|
||||
>;
|
||||
|
||||
export const dashboardContentManagementServiceFactory: DashboardContentManagementServiceFactory = (
|
||||
{ startPlugins: { contentManagement } },
|
||||
requiredServices
|
||||
) => {
|
||||
const {
|
||||
data,
|
||||
embeddable,
|
||||
notifications,
|
||||
initializerContext,
|
||||
savedObjectsTagging,
|
||||
dashboardSessionStorage,
|
||||
} = requiredServices;
|
||||
return {
|
||||
loadDashboardState: ({ id }) =>
|
||||
loadDashboardState({
|
||||
id,
|
||||
data,
|
||||
embeddable,
|
||||
contentManagement,
|
||||
savedObjectsTagging,
|
||||
}),
|
||||
saveDashboardState: ({ currentState, saveOptions, lastSavedId }) =>
|
||||
saveDashboardState({
|
||||
data,
|
||||
embeddable,
|
||||
saveOptions,
|
||||
lastSavedId,
|
||||
currentState,
|
||||
notifications,
|
||||
contentManagement,
|
||||
initializerContext,
|
||||
savedObjectsTagging,
|
||||
dashboardSessionStorage,
|
||||
}),
|
||||
findDashboards: {
|
||||
search: ({ hasReference, hasNoReference, search, size }) =>
|
||||
searchDashboards({
|
||||
contentManagement,
|
||||
hasNoReference,
|
||||
hasReference,
|
||||
search,
|
||||
size,
|
||||
}),
|
||||
findByIds: (ids) => findDashboardsByIds(contentManagement, ids),
|
||||
findByTitle: (title) => findDashboardIdByTitle(contentManagement, title),
|
||||
},
|
||||
checkForDuplicateDashboardTitle: (props) =>
|
||||
checkForDuplicateDashboardTitle(props, contentManagement),
|
||||
deleteDashboards: (ids) => deleteDashboards(ids, contentManagement),
|
||||
};
|
||||
};
|
|
@ -6,10 +6,9 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import type { SavedObjectsClientContract } from '@kbn/core/public';
|
||||
|
||||
import { DashboardAttributes } from '../../../../common';
|
||||
import { DASHBOARD_SAVED_OBJECT_TYPE } from '../../../dashboard_constants';
|
||||
import { DashboardStartDependencies } from '../../../plugin';
|
||||
import { DASHBOARD_CONTENT_ID } from '../../../dashboard_constants';
|
||||
import { DashboardCrudTypes } from '../../../../common/content_management';
|
||||
|
||||
export interface DashboardDuplicateTitleCheckProps {
|
||||
title: string;
|
||||
|
@ -32,7 +31,7 @@ export async function checkForDuplicateDashboardTitle(
|
|||
onTitleDuplicate,
|
||||
isTitleDuplicateConfirmed,
|
||||
}: DashboardDuplicateTitleCheckProps,
|
||||
savedObjectsClient: SavedObjectsClientContract
|
||||
contentManagement: DashboardStartDependencies['contentManagement']
|
||||
): Promise<boolean> {
|
||||
// Don't check for duplicates if user has already confirmed save with duplicate title
|
||||
if (isTitleDuplicateConfirmed) {
|
||||
|
@ -44,16 +43,19 @@ export async function checkForDuplicateDashboardTitle(
|
|||
if (title === lastSavedTitle && !copyOnSave) {
|
||||
return true;
|
||||
}
|
||||
const response = await savedObjectsClient.find<DashboardAttributes>({
|
||||
perPage: 10,
|
||||
fields: ['title'],
|
||||
search: `"${title}"`,
|
||||
searchFields: ['title'],
|
||||
type: DASHBOARD_SAVED_OBJECT_TYPE,
|
||||
|
||||
const { hits } = await contentManagement.client.search<
|
||||
DashboardCrudTypes['SearchIn'],
|
||||
DashboardCrudTypes['SearchOut']
|
||||
>({
|
||||
contentTypeId: DASHBOARD_CONTENT_ID,
|
||||
query: {
|
||||
text: title ? `${title}*` : undefined,
|
||||
limit: 10,
|
||||
},
|
||||
options: { onlyTitle: true },
|
||||
});
|
||||
const duplicate = response.savedObjects.find(
|
||||
(obj) => obj.get('title').toLowerCase() === title.toLowerCase()
|
||||
);
|
||||
const duplicate = hits.find((hit) => hit.attributes.title.toLowerCase() === title.toLowerCase());
|
||||
if (!duplicate) {
|
||||
return true;
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* 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 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 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { DashboardStartDependencies } from '../../../plugin';
|
||||
import { DASHBOARD_CONTENT_ID } from '../../../dashboard_constants';
|
||||
import { DashboardCrudTypes } from '../../../../common/content_management';
|
||||
|
||||
export const deleteDashboards = async (
|
||||
ids: string[],
|
||||
contentManagement: DashboardStartDependencies['contentManagement']
|
||||
) => {
|
||||
const deletePromises = ids.map((id) =>
|
||||
contentManagement.client.delete<
|
||||
DashboardCrudTypes['DeleteIn'],
|
||||
DashboardCrudTypes['DeleteOut']
|
||||
>({
|
||||
contentTypeId: DASHBOARD_CONTENT_ID,
|
||||
id,
|
||||
})
|
||||
);
|
||||
|
||||
await Promise.all(deletePromises);
|
||||
};
|
|
@ -0,0 +1,108 @@
|
|||
/*
|
||||
* 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 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 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { SavedObjectError, SavedObjectsFindOptionsReference } from '@kbn/core/public';
|
||||
|
||||
import {
|
||||
DashboardItem,
|
||||
DashboardCrudTypes,
|
||||
DashboardAttributes,
|
||||
} from '../../../../common/content_management';
|
||||
import { DashboardStartDependencies } from '../../../plugin';
|
||||
import { DASHBOARD_CONTENT_ID } from '../../../dashboard_constants';
|
||||
|
||||
export interface SearchDashboardsArgs {
|
||||
contentManagement: DashboardStartDependencies['contentManagement'];
|
||||
hasNoReference?: SavedObjectsFindOptionsReference[];
|
||||
hasReference?: SavedObjectsFindOptionsReference[];
|
||||
search: string;
|
||||
size: number;
|
||||
}
|
||||
|
||||
export interface SearchDashboardsResponse {
|
||||
total: number;
|
||||
hits: DashboardItem[];
|
||||
}
|
||||
|
||||
export async function searchDashboards({
|
||||
contentManagement,
|
||||
hasNoReference,
|
||||
hasReference,
|
||||
search,
|
||||
size,
|
||||
}: SearchDashboardsArgs): Promise<SearchDashboardsResponse> {
|
||||
const {
|
||||
hits,
|
||||
pagination: { total },
|
||||
} = await contentManagement.client.search<
|
||||
DashboardCrudTypes['SearchIn'],
|
||||
DashboardCrudTypes['SearchOut']
|
||||
>({
|
||||
contentTypeId: DASHBOARD_CONTENT_ID,
|
||||
query: {
|
||||
text: search ? `${search}*` : undefined,
|
||||
limit: size,
|
||||
tags: {
|
||||
included: (hasReference ?? []).map(({ id }) => id),
|
||||
excluded: (hasNoReference ?? []).map(({ id }) => id),
|
||||
},
|
||||
},
|
||||
});
|
||||
return {
|
||||
total,
|
||||
hits,
|
||||
};
|
||||
}
|
||||
|
||||
export type FindDashboardsByIdResponse = { id: string } & (
|
||||
| { status: 'success'; attributes: DashboardAttributes }
|
||||
| { status: 'error'; error: SavedObjectError }
|
||||
);
|
||||
|
||||
export async function findDashboardsByIds(
|
||||
contentManagement: DashboardStartDependencies['contentManagement'],
|
||||
ids: string[]
|
||||
): Promise<FindDashboardsByIdResponse[]> {
|
||||
const findPromises = ids.map((id) =>
|
||||
contentManagement.client.get<DashboardCrudTypes['GetIn'], DashboardCrudTypes['GetOut']>({
|
||||
contentTypeId: DASHBOARD_CONTENT_ID,
|
||||
id,
|
||||
})
|
||||
);
|
||||
const results = await Promise.all(findPromises);
|
||||
|
||||
return results.map((result) => {
|
||||
if (result.item.error) return { status: 'error', error: result.item.error, id: result.item.id };
|
||||
const { attributes, id } = result.item;
|
||||
return { id, status: 'success', attributes };
|
||||
});
|
||||
}
|
||||
|
||||
export async function findDashboardIdByTitle(
|
||||
contentManagement: DashboardStartDependencies['contentManagement'],
|
||||
title: string
|
||||
): Promise<{ id: string } | undefined> {
|
||||
const { hits } = await contentManagement.client.search<
|
||||
DashboardCrudTypes['SearchIn'],
|
||||
DashboardCrudTypes['SearchOut']
|
||||
>({
|
||||
contentTypeId: DASHBOARD_CONTENT_ID,
|
||||
query: {
|
||||
text: title ? `${title}*` : undefined,
|
||||
limit: 10,
|
||||
},
|
||||
options: { onlyTitle: true },
|
||||
});
|
||||
// The search isn't an exact match, lets see if we can find a single exact match to use
|
||||
const matchingDashboards = hits.filter(
|
||||
(hit) => hit.attributes.title.toLowerCase() === title.toLowerCase()
|
||||
);
|
||||
if (matchingDashboards.length === 1) {
|
||||
return { id: matchingDashboards[0].id };
|
||||
}
|
||||
}
|
|
@ -8,11 +8,6 @@
|
|||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { has } from 'lodash';
|
||||
|
||||
import {
|
||||
ResolvedSimpleSavedObject,
|
||||
SavedObjectAttributes,
|
||||
SavedObjectsClientContract,
|
||||
} from '@kbn/core/public';
|
||||
import { Filter, Query } from '@kbn/es-query';
|
||||
import { ViewMode } from '@kbn/embeddable-plugin/public';
|
||||
import { cleanFiltersForSerialize } from '@kbn/presentation-util-plugin/public';
|
||||
|
@ -20,14 +15,13 @@ import { rawControlGroupAttributesToControlGroupInput } from '@kbn/controls-plug
|
|||
import { parseSearchSourceJSON, injectSearchSourceReferences } from '@kbn/data-plugin/public';
|
||||
|
||||
import {
|
||||
convertSavedPanelsToPanelMap,
|
||||
DashboardContainerInput,
|
||||
DashboardAttributes,
|
||||
DashboardOptions,
|
||||
injectReferences,
|
||||
type DashboardOptions,
|
||||
convertSavedPanelsToPanelMap,
|
||||
} from '../../../../common';
|
||||
import { DashboardSavedObjectRequiredServices } from '../types';
|
||||
import { DASHBOARD_SAVED_OBJECT_TYPE, DEFAULT_DASHBOARD_INPUT } from '../../../dashboard_constants';
|
||||
import { DashboardCrudTypes } from '../../../../common/content_management';
|
||||
import type { LoadDashboardFromSavedObjectProps, LoadDashboardReturn } from '../types';
|
||||
import { DASHBOARD_CONTENT_ID, DEFAULT_DASHBOARD_INPUT } from '../../../dashboard_constants';
|
||||
|
||||
export function migrateLegacyQuery(query: Query | { [key: string]: any } | string): Query {
|
||||
// Lucene was the only option before, so language-less queries are all lucene
|
||||
|
@ -38,25 +32,13 @@ export function migrateLegacyQuery(query: Query | { [key: string]: any } | strin
|
|||
return query as Query;
|
||||
}
|
||||
|
||||
export type LoadDashboardFromSavedObjectProps = DashboardSavedObjectRequiredServices & {
|
||||
id?: string;
|
||||
savedObjectsClient: SavedObjectsClientContract;
|
||||
};
|
||||
|
||||
export interface LoadDashboardFromSavedObjectReturn {
|
||||
dashboardFound: boolean;
|
||||
dashboardId?: string;
|
||||
resolveMeta?: Omit<ResolvedSimpleSavedObject, 'saved_object'>;
|
||||
dashboardInput: DashboardContainerInput;
|
||||
}
|
||||
|
||||
export const loadDashboardStateFromSavedObject = async ({
|
||||
savedObjectsTagging,
|
||||
savedObjectsClient,
|
||||
embeddable,
|
||||
data,
|
||||
export const loadDashboardState = async ({
|
||||
id,
|
||||
}: LoadDashboardFromSavedObjectProps): Promise<LoadDashboardFromSavedObjectReturn> => {
|
||||
data,
|
||||
embeddable,
|
||||
contentManagement,
|
||||
savedObjectsTagging,
|
||||
}: LoadDashboardFromSavedObjectProps): Promise<LoadDashboardReturn> => {
|
||||
const {
|
||||
search: dataSearchService,
|
||||
query: { queryString },
|
||||
|
@ -73,29 +55,31 @@ export const loadDashboardStateFromSavedObject = async ({
|
|||
if (!savedObjectId) return { dashboardInput: newDashboardState, dashboardFound: true };
|
||||
|
||||
/**
|
||||
* Load the saved object
|
||||
* Load the saved object from Content Management
|
||||
*/
|
||||
const { saved_object: rawDashboardSavedObject, ...resolveMeta } =
|
||||
await savedObjectsClient.resolve<DashboardAttributes>(
|
||||
DASHBOARD_SAVED_OBJECT_TYPE,
|
||||
savedObjectId
|
||||
);
|
||||
if (!rawDashboardSavedObject._version) {
|
||||
return { dashboardInput: newDashboardState, dashboardFound: false, dashboardId: savedObjectId };
|
||||
const { item: rawDashboardContent, meta: resolveMeta } = await contentManagement.client.get<
|
||||
DashboardCrudTypes['GetIn'],
|
||||
DashboardCrudTypes['GetOut']
|
||||
>({ contentTypeId: DASHBOARD_CONTENT_ID, id });
|
||||
if (!rawDashboardContent.version) {
|
||||
return {
|
||||
dashboardInput: newDashboardState,
|
||||
dashboardFound: false,
|
||||
dashboardId: savedObjectId,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Inject saved object references back into the saved object attributes
|
||||
*/
|
||||
const { references, attributes: rawAttributes } = rawDashboardSavedObject;
|
||||
const { references, attributes: rawAttributes } = rawDashboardContent;
|
||||
const attributes = (() => {
|
||||
if (!references || references.length === 0) return rawAttributes;
|
||||
return injectReferences(
|
||||
{ references, attributes: rawAttributes as unknown as SavedObjectAttributes },
|
||||
{ references, attributes: rawAttributes },
|
||||
{
|
||||
embeddablePersistableStateService: embeddable,
|
||||
}
|
||||
) as unknown as DashboardAttributes;
|
||||
);
|
||||
})();
|
||||
|
||||
/**
|
|
@ -15,30 +15,23 @@ import {
|
|||
controlGroupInputToRawControlGroupAttributes,
|
||||
} from '@kbn/controls-plugin/common';
|
||||
import { isFilterPinned } from '@kbn/es-query';
|
||||
import { SavedObjectsClientContract } from '@kbn/core/public';
|
||||
import { SavedObjectAttributes } from '@kbn/core-saved-objects-common';
|
||||
import { SavedObjectSaveOpts } from '@kbn/saved-objects-plugin/public';
|
||||
import { extractSearchSourceReferences, RefreshInterval } from '@kbn/data-plugin/public';
|
||||
|
||||
import {
|
||||
extractReferences,
|
||||
DashboardAttributes,
|
||||
convertPanelMapToSavedPanels,
|
||||
DashboardContainerInput,
|
||||
convertPanelMapToSavedPanels,
|
||||
} from '../../../../common';
|
||||
import { DashboardSavedObjectRequiredServices } from '../types';
|
||||
import { DASHBOARD_SAVED_OBJECT_TYPE } from '../../../dashboard_constants';
|
||||
import {
|
||||
SaveDashboardProps,
|
||||
SaveDashboardReturn,
|
||||
DashboardContentManagementRequiredServices,
|
||||
} from '../types';
|
||||
import { DashboardStartDependencies } from '../../../plugin';
|
||||
import { DASHBOARD_CONTENT_ID } from '../../../dashboard_constants';
|
||||
import { DashboardCrudTypes, DashboardAttributes } from '../../../../common/content_management';
|
||||
import { dashboardSaveToastStrings } from '../../../dashboard_container/_dashboard_container_strings';
|
||||
|
||||
export type SavedDashboardSaveOpts = SavedObjectSaveOpts & { saveAsCopy?: boolean };
|
||||
|
||||
export type SaveDashboardProps = DashboardSavedObjectRequiredServices & {
|
||||
savedObjectsClient: SavedObjectsClientContract;
|
||||
currentState: DashboardContainerInput;
|
||||
saveOptions: SavedDashboardSaveOpts;
|
||||
lastSavedId?: string;
|
||||
};
|
||||
|
||||
export const serializeControlGroupInput = (
|
||||
controlGroupInput: DashboardContainerInput['controlGroupInput']
|
||||
) => {
|
||||
|
@ -62,24 +55,28 @@ export const convertTimeToUTCString = (time?: string | Moment): undefined | stri
|
|||
}
|
||||
};
|
||||
|
||||
export interface SaveDashboardReturn {
|
||||
id?: string;
|
||||
error?: string;
|
||||
redirectRequired?: boolean;
|
||||
}
|
||||
type SaveDashboardStateProps = SaveDashboardProps & {
|
||||
data: DashboardContentManagementRequiredServices['data'];
|
||||
contentManagement: DashboardStartDependencies['contentManagement'];
|
||||
embeddable: DashboardContentManagementRequiredServices['embeddable'];
|
||||
notifications: DashboardContentManagementRequiredServices['notifications'];
|
||||
initializerContext: DashboardContentManagementRequiredServices['initializerContext'];
|
||||
savedObjectsTagging: DashboardContentManagementRequiredServices['savedObjectsTagging'];
|
||||
dashboardSessionStorage: DashboardContentManagementRequiredServices['dashboardSessionStorage'];
|
||||
};
|
||||
|
||||
export const saveDashboardStateToSavedObject = async ({
|
||||
export const saveDashboardState = async ({
|
||||
data,
|
||||
embeddable,
|
||||
lastSavedId,
|
||||
saveOptions,
|
||||
currentState,
|
||||
savedObjectsClient,
|
||||
contentManagement,
|
||||
savedObjectsTagging,
|
||||
dashboardSessionStorage,
|
||||
notifications: { toasts },
|
||||
initializerContext: { kibanaVersion },
|
||||
}: SaveDashboardProps): Promise<SaveDashboardReturn> => {
|
||||
}: SaveDashboardStateProps): Promise<SaveDashboardReturn> => {
|
||||
const {
|
||||
search: dataSearchService,
|
||||
query: {
|
||||
|
@ -167,7 +164,7 @@ export const saveDashboardStateToSavedObject = async ({
|
|||
*/
|
||||
const { attributes, references: dashboardReferences } = extractReferences(
|
||||
{
|
||||
attributes: rawDashboardAttributes as unknown as SavedObjectAttributes,
|
||||
attributes: rawDashboardAttributes,
|
||||
references: searchSourceReferences,
|
||||
},
|
||||
{ embeddablePersistableStateService: embeddable }
|
||||
|
@ -177,15 +174,19 @@ export const saveDashboardStateToSavedObject = async ({
|
|||
: dashboardReferences;
|
||||
|
||||
/**
|
||||
* Save the saved object using the saved objects client
|
||||
* Save the saved object using the content management
|
||||
*/
|
||||
const idToSaveTo = saveOptions.saveAsCopy ? undefined : lastSavedId;
|
||||
try {
|
||||
const { id: newId } = await savedObjectsClient.create(DASHBOARD_SAVED_OBJECT_TYPE, attributes, {
|
||||
id: idToSaveTo,
|
||||
overwrite: true,
|
||||
references,
|
||||
const result = await contentManagement.client.create<
|
||||
DashboardCrudTypes['CreateIn'],
|
||||
DashboardCrudTypes['CreateOut']
|
||||
>({
|
||||
contentTypeId: DASHBOARD_CONTENT_ID,
|
||||
data: attributes,
|
||||
options: { id: idToSaveTo, references, overwrite: true },
|
||||
});
|
||||
const newId = result.item.id;
|
||||
|
||||
if (newId) {
|
||||
toasts.addSuccess({
|
|
@ -0,0 +1,94 @@
|
|||
/*
|
||||
* 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 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 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { SavedObjectSaveOpts } from '@kbn/saved-objects-plugin/public';
|
||||
|
||||
import {
|
||||
FindDashboardsByIdResponse,
|
||||
SearchDashboardsArgs,
|
||||
SearchDashboardsResponse,
|
||||
} from './lib/find_dashboards';
|
||||
import { DashboardDataService } from '../data/types';
|
||||
import { DashboardSpacesService } from '../spaces/types';
|
||||
import { DashboardContainerInput } from '../../../common';
|
||||
import { DashboardStartDependencies } from '../../plugin';
|
||||
import { DashboardEmbeddableService } from '../embeddable/types';
|
||||
import { DashboardNotificationsService } from '../notifications/types';
|
||||
import { DashboardCrudTypes } from '../../../common/content_management';
|
||||
import { DashboardScreenshotModeService } from '../screenshot_mode/types';
|
||||
import { DashboardInitializerContextService } from '../initializer_context/types';
|
||||
import { DashboardSavedObjectsTaggingService } from '../saved_objects_tagging/types';
|
||||
import { DashboardSessionStorageServiceType } from '../dashboard_session_storage/types';
|
||||
import { DashboardDuplicateTitleCheckProps } from './lib/check_for_duplicate_dashboard_title';
|
||||
|
||||
export interface DashboardContentManagementRequiredServices {
|
||||
data: DashboardDataService;
|
||||
spaces: DashboardSpacesService;
|
||||
embeddable: DashboardEmbeddableService;
|
||||
notifications: DashboardNotificationsService;
|
||||
screenshotMode: DashboardScreenshotModeService;
|
||||
initializerContext: DashboardInitializerContextService;
|
||||
savedObjectsTagging: DashboardSavedObjectsTaggingService;
|
||||
dashboardSessionStorage: DashboardSessionStorageServiceType;
|
||||
}
|
||||
|
||||
export interface DashboardContentManagementService {
|
||||
findDashboards: FindDashboardsService;
|
||||
deleteDashboards: (ids: string[]) => void;
|
||||
loadDashboardState: (props: { id?: string }) => Promise<LoadDashboardReturn>;
|
||||
saveDashboardState: (props: SaveDashboardProps) => Promise<SaveDashboardReturn>;
|
||||
checkForDuplicateDashboardTitle: (meta: DashboardDuplicateTitleCheckProps) => Promise<boolean>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Types for Loading Dashboards
|
||||
*/
|
||||
export interface LoadDashboardFromSavedObjectProps {
|
||||
id?: string;
|
||||
data: DashboardContentManagementRequiredServices['data'];
|
||||
contentManagement: DashboardStartDependencies['contentManagement'];
|
||||
embeddable: DashboardContentManagementRequiredServices['embeddable'];
|
||||
savedObjectsTagging: DashboardContentManagementRequiredServices['savedObjectsTagging'];
|
||||
}
|
||||
|
||||
type DashboardResolveMeta = DashboardCrudTypes['GetOut']['meta'];
|
||||
|
||||
export interface LoadDashboardReturn {
|
||||
dashboardFound: boolean;
|
||||
dashboardId?: string;
|
||||
resolveMeta?: DashboardResolveMeta;
|
||||
dashboardInput: DashboardContainerInput;
|
||||
}
|
||||
|
||||
/**
|
||||
* Types for Saving Dashboards
|
||||
*/
|
||||
export type SavedDashboardSaveOpts = SavedObjectSaveOpts & { saveAsCopy?: boolean };
|
||||
|
||||
export interface SaveDashboardProps {
|
||||
currentState: DashboardContainerInput;
|
||||
saveOptions: SavedDashboardSaveOpts;
|
||||
lastSavedId?: string;
|
||||
}
|
||||
|
||||
export interface SaveDashboardReturn {
|
||||
id?: string;
|
||||
error?: string;
|
||||
redirectRequired?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Types for Finding Dashboards
|
||||
*/
|
||||
export interface FindDashboardsService {
|
||||
search: (
|
||||
props: Pick<SearchDashboardsArgs, 'hasReference' | 'hasNoReference' | 'search' | 'size'>
|
||||
) => Promise<SearchDashboardsResponse>;
|
||||
findByIds: (ids: string[]) => Promise<FindDashboardsByIdResponse[]>;
|
||||
findByTitle: (title: string) => Promise<{ id: string } | undefined>;
|
||||
}
|
|
@ -1,78 +0,0 @@
|
|||
/*
|
||||
* 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 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 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { savedObjectsServiceMock } from '@kbn/core/public/mocks';
|
||||
import { PluginServiceFactory } from '@kbn/presentation-util-plugin/public';
|
||||
import { FindDashboardSavedObjectsResponse } from './lib/find_dashboard_saved_objects';
|
||||
|
||||
import { DashboardSavedObjectService } from './types';
|
||||
import { DashboardAttributes } from '../../../common';
|
||||
import { LoadDashboardFromSavedObjectReturn } from './lib/load_dashboard_state_from_saved_object';
|
||||
|
||||
type DashboardSavedObjectServiceFactory = PluginServiceFactory<DashboardSavedObjectService>;
|
||||
|
||||
export const dashboardSavedObjectServiceFactory: DashboardSavedObjectServiceFactory = () => {
|
||||
const { client: savedObjectsClient } = savedObjectsServiceMock.createStartContract();
|
||||
return {
|
||||
loadDashboardStateFromSavedObject: jest.fn().mockImplementation(() =>
|
||||
Promise.resolve({
|
||||
dashboardInput: {},
|
||||
} as LoadDashboardFromSavedObjectReturn)
|
||||
),
|
||||
saveDashboardStateToSavedObject: jest.fn(),
|
||||
findDashboards: {
|
||||
findSavedObjects: jest.fn().mockImplementation(({ search, size }) => {
|
||||
const sizeToUse = size ?? 10;
|
||||
const hits: FindDashboardSavedObjectsResponse['hits'] = [];
|
||||
for (let i = 0; i < sizeToUse; i++) {
|
||||
hits.push({
|
||||
type: 'dashboard',
|
||||
id: `dashboard${i}`,
|
||||
attributes: {
|
||||
description: `dashboard${i} desc`,
|
||||
title: `dashboard${i} - ${search} - title`,
|
||||
},
|
||||
references: [] as FindDashboardSavedObjectsResponse['hits'][0]['references'],
|
||||
} as FindDashboardSavedObjectsResponse['hits'][0]);
|
||||
}
|
||||
return Promise.resolve({
|
||||
total: sizeToUse,
|
||||
hits,
|
||||
});
|
||||
}),
|
||||
findByIds: jest.fn().mockImplementation(() =>
|
||||
Promise.resolve([
|
||||
{
|
||||
id: `dashboardUnsavedOne`,
|
||||
status: 'success',
|
||||
attributes: {
|
||||
title: `Dashboard Unsaved One`,
|
||||
} as unknown as DashboardAttributes,
|
||||
},
|
||||
{
|
||||
id: `dashboardUnsavedTwo`,
|
||||
status: 'success',
|
||||
attributes: {
|
||||
title: `Dashboard Unsaved Two`,
|
||||
} as unknown as DashboardAttributes,
|
||||
},
|
||||
{
|
||||
id: `dashboardUnsavedThree`,
|
||||
status: 'success',
|
||||
attributes: {
|
||||
title: `Dashboard Unsaved Three`,
|
||||
} as unknown as DashboardAttributes,
|
||||
},
|
||||
])
|
||||
),
|
||||
findByTitle: jest.fn(),
|
||||
},
|
||||
checkForDuplicateDashboardTitle: jest.fn(),
|
||||
savedObjectsClient,
|
||||
};
|
||||
};
|
|
@ -1,67 +0,0 @@
|
|||
/*
|
||||
* 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 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 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import type { KibanaPluginServiceFactory } from '@kbn/presentation-util-plugin/public';
|
||||
|
||||
import type { DashboardStartDependencies } from '../../plugin';
|
||||
import { checkForDuplicateDashboardTitle } from './lib/check_for_duplicate_dashboard_title';
|
||||
import {
|
||||
findDashboardIdByTitle,
|
||||
findDashboardSavedObjects,
|
||||
findDashboardSavedObjectsByIds,
|
||||
} from './lib/find_dashboard_saved_objects';
|
||||
import { saveDashboardStateToSavedObject } from './lib/save_dashboard_state_to_saved_object';
|
||||
import { loadDashboardStateFromSavedObject } from './lib/load_dashboard_state_from_saved_object';
|
||||
import type { DashboardSavedObjectRequiredServices, DashboardSavedObjectService } from './types';
|
||||
|
||||
export type DashboardSavedObjectServiceFactory = KibanaPluginServiceFactory<
|
||||
DashboardSavedObjectService,
|
||||
DashboardStartDependencies,
|
||||
DashboardSavedObjectRequiredServices
|
||||
>;
|
||||
|
||||
export const dashboardSavedObjectServiceFactory: DashboardSavedObjectServiceFactory = (
|
||||
{ coreStart },
|
||||
requiredServices
|
||||
) => {
|
||||
const {
|
||||
savedObjects: { client: savedObjectsClient },
|
||||
} = coreStart;
|
||||
|
||||
return {
|
||||
loadDashboardStateFromSavedObject: ({ id }) =>
|
||||
loadDashboardStateFromSavedObject({
|
||||
id,
|
||||
savedObjectsClient,
|
||||
...requiredServices,
|
||||
}),
|
||||
saveDashboardStateToSavedObject: ({ currentState, saveOptions, lastSavedId }) =>
|
||||
saveDashboardStateToSavedObject({
|
||||
saveOptions,
|
||||
lastSavedId,
|
||||
currentState,
|
||||
savedObjectsClient,
|
||||
...requiredServices,
|
||||
}),
|
||||
findDashboards: {
|
||||
findSavedObjects: ({ hasReference, hasNoReference, search, size }) =>
|
||||
findDashboardSavedObjects({
|
||||
hasReference,
|
||||
hasNoReference,
|
||||
search,
|
||||
size,
|
||||
savedObjectsClient,
|
||||
}),
|
||||
findByIds: (ids) => findDashboardSavedObjectsByIds(savedObjectsClient, ids),
|
||||
findByTitle: (title) => findDashboardIdByTitle(title, savedObjectsClient),
|
||||
},
|
||||
checkForDuplicateDashboardTitle: (props) =>
|
||||
checkForDuplicateDashboardTitle(props, savedObjectsClient),
|
||||
savedObjectsClient,
|
||||
};
|
||||
};
|
|
@ -1,96 +0,0 @@
|
|||
/*
|
||||
* 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 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 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import {
|
||||
SavedObjectError,
|
||||
SavedObjectsClientContract,
|
||||
SavedObjectsFindOptionsReference,
|
||||
SimpleSavedObject,
|
||||
} from '@kbn/core/public';
|
||||
|
||||
import { DashboardAttributes } from '../../../../common';
|
||||
import { DASHBOARD_SAVED_OBJECT_TYPE } from '../../../dashboard_constants';
|
||||
|
||||
export interface FindDashboardSavedObjectsArgs {
|
||||
hasReference?: SavedObjectsFindOptionsReference[];
|
||||
hasNoReference?: SavedObjectsFindOptionsReference[];
|
||||
savedObjectsClient: SavedObjectsClientContract;
|
||||
search: string;
|
||||
size: number;
|
||||
}
|
||||
|
||||
export interface FindDashboardSavedObjectsResponse {
|
||||
total: number;
|
||||
hits: Array<SimpleSavedObject<DashboardAttributes>>;
|
||||
}
|
||||
|
||||
export async function findDashboardSavedObjects({
|
||||
savedObjectsClient,
|
||||
hasReference,
|
||||
hasNoReference,
|
||||
search,
|
||||
size,
|
||||
}: FindDashboardSavedObjectsArgs): Promise<FindDashboardSavedObjectsResponse> {
|
||||
const { total, savedObjects } = await savedObjectsClient.find<DashboardAttributes>({
|
||||
type: DASHBOARD_SAVED_OBJECT_TYPE,
|
||||
search: search ? `${search}*` : undefined,
|
||||
searchFields: ['title^3', 'description'],
|
||||
defaultSearchOperator: 'AND' as 'AND',
|
||||
perPage: size,
|
||||
hasReference,
|
||||
hasNoReference,
|
||||
page: 1,
|
||||
});
|
||||
return {
|
||||
total,
|
||||
hits: savedObjects,
|
||||
};
|
||||
}
|
||||
|
||||
export type FindDashboardBySavedObjectIdsResult = { id: string } & (
|
||||
| { status: 'success'; attributes: DashboardAttributes }
|
||||
| { status: 'error'; error: SavedObjectError }
|
||||
);
|
||||
|
||||
export async function findDashboardSavedObjectsByIds(
|
||||
savedObjectsClient: SavedObjectsClientContract,
|
||||
ids: string[]
|
||||
): Promise<FindDashboardBySavedObjectIdsResult[]> {
|
||||
const { savedObjects } = await savedObjectsClient.bulkGet(
|
||||
ids.map((id) => ({ id, type: DASHBOARD_SAVED_OBJECT_TYPE }))
|
||||
);
|
||||
|
||||
return savedObjects.map((savedObjectResult) => {
|
||||
if (savedObjectResult.error)
|
||||
return { status: 'error', error: savedObjectResult.error, id: savedObjectResult.id };
|
||||
const { attributes, id } = savedObjectResult;
|
||||
return {
|
||||
id,
|
||||
status: 'success',
|
||||
attributes: attributes as DashboardAttributes,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
export async function findDashboardIdByTitle(
|
||||
title: string,
|
||||
savedObjectsClient: SavedObjectsClientContract
|
||||
): Promise<{ id: string } | undefined> {
|
||||
const results = await savedObjectsClient.find<DashboardAttributes>({
|
||||
search: `"${title}"`,
|
||||
searchFields: ['title'],
|
||||
type: 'dashboard',
|
||||
});
|
||||
// The search isn't an exact match, lets see if we can find a single exact match to use
|
||||
const matchingDashboards = results.savedObjects.filter(
|
||||
(dashboard) => dashboard.attributes.title.toLowerCase() === title.toLowerCase()
|
||||
);
|
||||
if (matchingDashboards.length === 1) {
|
||||
return { id: matchingDashboards[0].id };
|
||||
}
|
||||
}
|
|
@ -1,70 +0,0 @@
|
|||
/*
|
||||
* 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 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 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { SavedObjectsClientContract } from '@kbn/core/public';
|
||||
|
||||
import { DashboardDataService } from '../data/types';
|
||||
import { DashboardSpacesService } from '../spaces/types';
|
||||
import { DashboardEmbeddableService } from '../embeddable/types';
|
||||
import { DashboardNotificationsService } from '../notifications/types';
|
||||
import { DashboardScreenshotModeService } from '../screenshot_mode/types';
|
||||
import { DashboardInitializerContextService } from '../initializer_context/types';
|
||||
import { DashboardSavedObjectsTaggingService } from '../saved_objects_tagging/types';
|
||||
import { DashboardSessionStorageServiceType } from '../dashboard_session_storage/types';
|
||||
|
||||
import {
|
||||
LoadDashboardFromSavedObjectProps,
|
||||
LoadDashboardFromSavedObjectReturn,
|
||||
} from './lib/load_dashboard_state_from_saved_object';
|
||||
import {
|
||||
SaveDashboardProps,
|
||||
SaveDashboardReturn,
|
||||
} from './lib/save_dashboard_state_to_saved_object';
|
||||
import {
|
||||
FindDashboardBySavedObjectIdsResult,
|
||||
FindDashboardSavedObjectsArgs,
|
||||
FindDashboardSavedObjectsResponse,
|
||||
} from './lib/find_dashboard_saved_objects';
|
||||
import { DashboardDuplicateTitleCheckProps } from './lib/check_for_duplicate_dashboard_title';
|
||||
|
||||
export interface DashboardSavedObjectRequiredServices {
|
||||
screenshotMode: DashboardScreenshotModeService;
|
||||
embeddable: DashboardEmbeddableService;
|
||||
spaces: DashboardSpacesService;
|
||||
data: DashboardDataService;
|
||||
initializerContext: DashboardInitializerContextService;
|
||||
notifications: DashboardNotificationsService;
|
||||
savedObjectsTagging: DashboardSavedObjectsTaggingService;
|
||||
dashboardSessionStorage: DashboardSessionStorageServiceType;
|
||||
}
|
||||
|
||||
export interface FindDashboardsService {
|
||||
findSavedObjects: (
|
||||
props: Pick<
|
||||
FindDashboardSavedObjectsArgs,
|
||||
'hasReference' | 'hasNoReference' | 'search' | 'size'
|
||||
>
|
||||
) => Promise<FindDashboardSavedObjectsResponse>;
|
||||
findByIds: (ids: string[]) => Promise<FindDashboardBySavedObjectIdsResult[]>;
|
||||
findByTitle: (title: string) => Promise<{ id: string } | undefined>;
|
||||
}
|
||||
|
||||
export interface DashboardSavedObjectService {
|
||||
loadDashboardStateFromSavedObject: (
|
||||
props: Pick<LoadDashboardFromSavedObjectProps, 'id'>
|
||||
) => Promise<LoadDashboardFromSavedObjectReturn>;
|
||||
|
||||
saveDashboardStateToSavedObject: (
|
||||
props: Pick<SaveDashboardProps, 'currentState' | 'saveOptions' | 'lastSavedId'>
|
||||
) => Promise<SaveDashboardReturn>;
|
||||
findDashboards: FindDashboardsService;
|
||||
checkForDuplicateDashboardTitle: (meta: DashboardDuplicateTitleCheckProps) => Promise<boolean>;
|
||||
savedObjectsClient: SavedObjectsClientContract;
|
||||
}
|
||||
|
||||
export type { SaveDashboardReturn };
|
|
@ -37,12 +37,12 @@ import { usageCollectionServiceFactory } from './usage_collection/usage_collecti
|
|||
import { spacesServiceFactory } from './spaces/spaces.stub';
|
||||
import { urlForwardingServiceFactory } from './url_forwarding/url_fowarding.stub';
|
||||
import { visualizationsServiceFactory } from './visualizations/visualizations.stub';
|
||||
import { dashboardSavedObjectServiceFactory } from './dashboard_saved_object/dashboard_saved_object.stub';
|
||||
import { dashboardContentManagementServiceFactory } from './dashboard_content_management/dashboard_content_management.stub';
|
||||
import { customBrandingServiceFactory } from './custom_branding/custom_branding.stub';
|
||||
import { savedObjectsManagementServiceFactory } from './saved_objects_management/saved_objects_management_service.stub';
|
||||
|
||||
export const providers: PluginServiceProviders<DashboardServices> = {
|
||||
dashboardSavedObject: new PluginServiceProvider(dashboardSavedObjectServiceFactory),
|
||||
dashboardContentManagement: new PluginServiceProvider(dashboardContentManagementServiceFactory),
|
||||
analytics: new PluginServiceProvider(analyticsServiceFactory),
|
||||
application: new PluginServiceProvider(applicationServiceFactory),
|
||||
chrome: new PluginServiceProvider(chromeServiceFactory),
|
||||
|
|
|
@ -38,12 +38,12 @@ import { urlForwardingServiceFactory } from './url_forwarding/url_forwarding_ser
|
|||
import { visualizationsServiceFactory } from './visualizations/visualizations_service';
|
||||
import { usageCollectionServiceFactory } from './usage_collection/usage_collection_service';
|
||||
import { analyticsServiceFactory } from './analytics/analytics_service';
|
||||
import { dashboardSavedObjectServiceFactory } from './dashboard_saved_object/dashboard_saved_object_service';
|
||||
import { customBrandingServiceFactory } from './custom_branding/custom_branding_service';
|
||||
import { savedObjectsManagementServiceFactory } from './saved_objects_management/saved_objects_management_service';
|
||||
import { dashboardContentManagementServiceFactory } from './dashboard_content_management/dashboard_content_management_service';
|
||||
|
||||
const providers: PluginServiceProviders<DashboardServices, DashboardPluginServiceParams> = {
|
||||
dashboardSavedObject: new PluginServiceProvider(dashboardSavedObjectServiceFactory, [
|
||||
dashboardContentManagement: new PluginServiceProvider(dashboardContentManagementServiceFactory, [
|
||||
'dashboardSessionStorage',
|
||||
'savedObjectsTagging',
|
||||
'initializerContext',
|
||||
|
|
|
@ -17,7 +17,7 @@ import { DashboardChromeService } from './chrome/types';
|
|||
import { DashboardCoreContextService } from './core_context/types';
|
||||
import { DashboardCustomBrandingService } from './custom_branding/types';
|
||||
import { DashboardCapabilitiesService } from './dashboard_capabilities/types';
|
||||
import { DashboardSavedObjectService } from './dashboard_saved_object/types';
|
||||
import { DashboardContentManagementService } from './dashboard_content_management/types';
|
||||
import { DashboardSessionStorageServiceType } from './dashboard_session_storage/types';
|
||||
import { DashboardDataService } from './data/types';
|
||||
import { DashboardDataViewEditorService } from './data_view_editor/types';
|
||||
|
@ -41,8 +41,8 @@ export type DashboardPluginServiceParams = KibanaPluginServiceParams<DashboardSt
|
|||
initContext: PluginInitializerContext; // need a custom type so that initContext is a required parameter for initializerContext
|
||||
};
|
||||
export interface DashboardServices {
|
||||
dashboardSavedObject: DashboardSavedObjectService;
|
||||
dashboardSessionStorage: DashboardSessionStorageServiceType;
|
||||
dashboardContentManagement: DashboardContentManagementService;
|
||||
|
||||
analytics: DashboardAnalyticsService;
|
||||
application: DashboardApplicationService;
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* 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 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 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { SOContentStorage, tagsToFindOptions } from '@kbn/content-management-utils';
|
||||
import { SavedObjectsFindOptions } from '@kbn/core-saved-objects-api-server';
|
||||
|
||||
import { CONTENT_ID } from '../../common/content_management';
|
||||
import { cmServicesDefinition } from '../../common/content_management/cm_services';
|
||||
import type { DashboardCrudTypes } from '../../common/content_management';
|
||||
|
||||
const searchArgsToSOFindOptions = (
|
||||
args: DashboardCrudTypes['SearchIn']
|
||||
): SavedObjectsFindOptions => {
|
||||
const { query, contentTypeId, options } = args;
|
||||
|
||||
return {
|
||||
type: contentTypeId,
|
||||
searchFields: options?.onlyTitle ? ['title'] : ['title^3', 'description'],
|
||||
fields: ['description', 'title', 'timeRestore'],
|
||||
search: query.text,
|
||||
perPage: query.limit,
|
||||
page: query.cursor ? +query.cursor : undefined,
|
||||
defaultSearchOperator: 'AND',
|
||||
...tagsToFindOptions(query.tags),
|
||||
};
|
||||
};
|
||||
|
||||
export class DashboardStorage extends SOContentStorage<DashboardCrudTypes> {
|
||||
constructor() {
|
||||
super({
|
||||
savedObjectType: CONTENT_ID,
|
||||
cmServicesDefinition,
|
||||
searchArgsToSOFindOptions,
|
||||
enableMSearch: true,
|
||||
allowedSavedObjectAttributes: [
|
||||
'kibanaSavedObjectMeta',
|
||||
'controlGroupInput',
|
||||
'refreshInterval',
|
||||
'description',
|
||||
'timeRestore',
|
||||
'optionsJSON',
|
||||
'panelsJSON',
|
||||
'timeFrom',
|
||||
'timeTo',
|
||||
'title',
|
||||
],
|
||||
});
|
||||
}
|
||||
}
|
9
src/plugins/dashboard/server/content_management/index.ts
Normal file
9
src/plugins/dashboard/server/content_management/index.ts
Normal file
|
@ -0,0 +1,9 @@
|
|||
/*
|
||||
* 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 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 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
export { DashboardStorage } from './dashboard_storage';
|
|
@ -6,6 +6,7 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { schema } from '@kbn/config-schema';
|
||||
import { ANALYTICS_SAVED_OBJECT_INDEX } from '@kbn/core-saved-objects-server';
|
||||
import { SavedObjectsType } from '@kbn/core/server';
|
||||
import {
|
||||
|
@ -69,5 +70,46 @@ export const createDashboardSavedObjectType = ({
|
|||
version: { type: 'integer' },
|
||||
},
|
||||
},
|
||||
schemas: {
|
||||
'8.9.0': schema.object({
|
||||
// General
|
||||
title: schema.string(),
|
||||
description: schema.string({ defaultValue: '' }),
|
||||
|
||||
// Search
|
||||
kibanaSavedObjectMeta: schema.object({
|
||||
searchSourceJSON: schema.maybe(schema.string()),
|
||||
}),
|
||||
|
||||
// Time
|
||||
timeRestore: schema.maybe(schema.boolean()),
|
||||
timeFrom: schema.maybe(schema.string()),
|
||||
timeTo: schema.maybe(schema.string()),
|
||||
refreshInterval: schema.maybe(
|
||||
schema.object({
|
||||
pause: schema.boolean(),
|
||||
value: schema.number(),
|
||||
display: schema.maybe(schema.string()),
|
||||
section: schema.maybe(schema.number()),
|
||||
})
|
||||
),
|
||||
|
||||
// Dashboard Content
|
||||
controlGroupInput: schema.maybe(
|
||||
schema.object({
|
||||
panelsJSON: schema.maybe(schema.string()),
|
||||
controlStyle: schema.maybe(schema.string()),
|
||||
chainingSystem: schema.maybe(schema.string()),
|
||||
ignoreParentSettingsJSON: schema.maybe(schema.string()),
|
||||
})
|
||||
),
|
||||
panelsJSON: schema.string({ defaultValue: '[]' }),
|
||||
optionsJSON: schema.string({ defaultValue: '{}' }),
|
||||
|
||||
// Legacy
|
||||
hits: schema.maybe(schema.number()),
|
||||
version: schema.maybe(schema.number()),
|
||||
}),
|
||||
},
|
||||
migrations: () => createDashboardSavedObjectTypeMigrations(migrationDeps),
|
||||
});
|
||||
|
|
|
@ -19,8 +19,8 @@ import { SavedObjectEmbeddableInput } from '@kbn/embeddable-plugin/common';
|
|||
import {
|
||||
convertPanelStateToSavedDashboardPanel,
|
||||
convertSavedDashboardPanelToPanelState,
|
||||
SavedDashboardPanel,
|
||||
} from '../../../common';
|
||||
import { SavedDashboardPanel } from '../../../common/content_management';
|
||||
|
||||
type ValueOrReferenceInput = SavedObjectEmbeddableInput & {
|
||||
attributes?: Serializable;
|
||||
|
|
|
@ -6,9 +6,10 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { SavedObjectAttributes, SavedObjectMigrationFn } from '@kbn/core/server';
|
||||
import { SavedObjectMigrationFn } from '@kbn/core/server';
|
||||
|
||||
import { DashboardAttributes, extractReferences, injectReferences } from '../../../common';
|
||||
import { extractReferences, injectReferences } from '../../../common';
|
||||
import { DashboardAttributes } from '../../../common/content_management';
|
||||
import { DashboardSavedObjectTypeMigrationsDeps } from './dashboard_saved_object_migrations';
|
||||
|
||||
/**
|
||||
|
@ -36,7 +37,7 @@ export function createExtractPanelReferencesMigration(
|
|||
|
||||
const injectedAttributes = injectReferences(
|
||||
{
|
||||
attributes: doc.attributes as unknown as SavedObjectAttributes,
|
||||
attributes: doc.attributes,
|
||||
references,
|
||||
},
|
||||
{ embeddablePersistableStateService: deps.embeddable }
|
||||
|
|
|
@ -12,8 +12,8 @@ import { EmbeddableInput } from '@kbn/embeddable-plugin/common';
|
|||
import {
|
||||
convertSavedDashboardPanelToPanelState,
|
||||
convertPanelStateToSavedDashboardPanel,
|
||||
SavedDashboardPanel,
|
||||
} from '../../../common';
|
||||
import { SavedDashboardPanel } from '../../../common/content_management';
|
||||
|
||||
/**
|
||||
* Before 7.10, hidden panel titles were stored as a blank string on the title attribute. In 7.10, this was replaced
|
||||
|
|
|
@ -24,7 +24,7 @@ import {
|
|||
RawSavedDashboardPanel640To720,
|
||||
RawSavedDashboardPanel730ToLatest,
|
||||
} from './types';
|
||||
import { GridData } from '../../../../common';
|
||||
import { GridData } from '../../../../common/content_management';
|
||||
|
||||
const PANEL_HEIGHT_SCALE_FACTOR = 5;
|
||||
const PANEL_HEIGHT_SCALE_FACTOR_WITH_MARGINS = 4;
|
||||
|
|
|
@ -12,28 +12,28 @@ import { SavedObjectReference } from '@kbn/core/server';
|
|||
import type {
|
||||
GridData,
|
||||
DashboardAttributes as CurrentDashboardAttributes, // Dashboard attributes from common are the source of truth for the current version.
|
||||
} from '../../../../common';
|
||||
} from '../../../../common/content_management';
|
||||
|
||||
interface SavedObjectAttributes {
|
||||
interface KibanaAttributes {
|
||||
kibanaSavedObjectMeta: {
|
||||
searchSourceJSON: string;
|
||||
};
|
||||
}
|
||||
|
||||
interface Doc<Attributes extends SavedObjectAttributes = SavedObjectAttributes> {
|
||||
interface Doc<Attributes extends KibanaAttributes = KibanaAttributes> {
|
||||
references: SavedObjectReference[];
|
||||
attributes: Attributes;
|
||||
id: string;
|
||||
type: string;
|
||||
}
|
||||
|
||||
interface DocPre700<Attributes extends SavedObjectAttributes = SavedObjectAttributes> {
|
||||
interface DocPre700<Attributes extends KibanaAttributes = KibanaAttributes> {
|
||||
attributes: Attributes;
|
||||
id: string;
|
||||
type: string;
|
||||
}
|
||||
|
||||
interface DashboardAttributesTo720 extends SavedObjectAttributes {
|
||||
interface DashboardAttributesTo720 extends KibanaAttributes {
|
||||
panelsJSON: string;
|
||||
description: string;
|
||||
uiStateJSON?: string;
|
||||
|
|
|
@ -25,4 +25,3 @@ export function plugin(initializerContext: PluginInitializerContext) {
|
|||
}
|
||||
|
||||
export type { DashboardPluginSetup, DashboardPluginStart } from './types';
|
||||
export { findByValueEmbeddables } from './usage/find_by_value_embeddables';
|
||||
|
|
|
@ -6,32 +6,34 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { PluginInitializerContext, CoreSetup, CoreStart, Plugin, Logger } from '@kbn/core/server';
|
||||
|
||||
import { EmbeddableSetup } from '@kbn/embeddable-plugin/server';
|
||||
import { UsageCollectionSetup } from '@kbn/usage-collection-plugin/server';
|
||||
import {
|
||||
TaskManagerSetupContract,
|
||||
TaskManagerStartContract,
|
||||
} from '@kbn/task-manager-plugin/server';
|
||||
import { createDashboardSavedObjectType } from './dashboard_saved_object';
|
||||
import { capabilitiesProvider } from './capabilities_provider';
|
||||
|
||||
import { DashboardPluginSetup, DashboardPluginStart } from './types';
|
||||
import { registerDashboardUsageCollector } from './usage/register_collector';
|
||||
import { dashboardPersistableStateServiceFactory } from './dashboard_container/dashboard_container_embeddable_factory';
|
||||
import { getUISettings } from './ui_settings';
|
||||
import { EmbeddableSetup } from '@kbn/embeddable-plugin/server';
|
||||
import { UsageCollectionSetup } from '@kbn/usage-collection-plugin/server';
|
||||
import { ContentManagementServerSetup } from '@kbn/content-management-plugin/server';
|
||||
import { PluginInitializerContext, CoreSetup, CoreStart, Plugin, Logger } from '@kbn/core/server';
|
||||
|
||||
import {
|
||||
initializeDashboardTelemetryTask,
|
||||
scheduleDashboardTelemetry,
|
||||
TASK_ID,
|
||||
} from './usage/dashboard_telemetry_collection_task';
|
||||
import { getUISettings } from './ui_settings';
|
||||
import { DashboardStorage } from './content_management';
|
||||
import { capabilitiesProvider } from './capabilities_provider';
|
||||
import { DashboardPluginSetup, DashboardPluginStart } from './types';
|
||||
import { createDashboardSavedObjectType } from './dashboard_saved_object';
|
||||
import { CONTENT_ID, LATEST_VERSION } from '../common/content_management';
|
||||
import { registerDashboardUsageCollector } from './usage/register_collector';
|
||||
import { dashboardPersistableStateServiceFactory } from './dashboard_container/dashboard_container_embeddable_factory';
|
||||
|
||||
interface SetupDeps {
|
||||
embeddable: EmbeddableSetup;
|
||||
usageCollection: UsageCollectionSetup;
|
||||
taskManager: TaskManagerSetupContract;
|
||||
contentManagement: ContentManagementServerSetup;
|
||||
}
|
||||
|
||||
interface StartDeps {
|
||||
|
@ -58,6 +60,14 @@ export class DashboardPlugin
|
|||
})
|
||||
);
|
||||
|
||||
plugins.contentManagement.register({
|
||||
id: CONTENT_ID,
|
||||
storage: new DashboardStorage(),
|
||||
version: {
|
||||
latest: LATEST_VERSION,
|
||||
},
|
||||
});
|
||||
|
||||
if (plugins.taskManager) {
|
||||
initializeDashboardTelemetryTask(this.logger, core, plugins.taskManager, plugins.embeddable);
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { SavedDashboardPanel } from '../../common';
|
||||
import { SavedDashboardPanel } from '../../common/content_management';
|
||||
import { getEmptyDashboardData, collectPanelsByType } from './dashboard_telemetry';
|
||||
import { EmbeddableStateWithType } from '@kbn/embeddable-plugin/common';
|
||||
import { createEmbeddablePersistableStateServiceMock } from '@kbn/embeddable-plugin/common/mocks';
|
||||
|
|
|
@ -7,16 +7,13 @@
|
|||
*/
|
||||
|
||||
import { isEmpty } from 'lodash';
|
||||
import { SavedObjectAttributes } from '@kbn/core/server';
|
||||
import { EmbeddablePersistableStateService } from '@kbn/embeddable-plugin/common';
|
||||
import {
|
||||
type ControlGroupTelemetry,
|
||||
CONTROL_GROUP_TYPE,
|
||||
RawControlGroupAttributes,
|
||||
} from '@kbn/controls-plugin/common';
|
||||
import { initializeControlGroupTelemetry } from '@kbn/controls-plugin/server';
|
||||
|
||||
import { TaskManagerStartContract } from '@kbn/task-manager-plugin/server';
|
||||
import type { SavedDashboardPanel } from '../../common';
|
||||
import { initializeControlGroupTelemetry } from '@kbn/controls-plugin/server';
|
||||
import { EmbeddablePersistableStateService } from '@kbn/embeddable-plugin/common';
|
||||
import { type ControlGroupTelemetry, CONTROL_GROUP_TYPE } from '@kbn/controls-plugin/common';
|
||||
|
||||
import { DashboardAttributes, SavedDashboardPanel } from '../../common/content_management';
|
||||
import { TASK_ID, DashboardTelemetryTaskState } from './dashboard_telemetry_collection_task';
|
||||
export interface DashboardCollectorData {
|
||||
panels: {
|
||||
|
@ -90,13 +87,11 @@ export const collectPanelsByType = (
|
|||
|
||||
export const controlsCollectorFactory =
|
||||
(embeddableService: EmbeddablePersistableStateService) =>
|
||||
(attributes: SavedObjectAttributes, collectorData: DashboardCollectorData) => {
|
||||
const controlGroupAttributes: RawControlGroupAttributes | undefined =
|
||||
attributes.controlGroupInput as unknown as RawControlGroupAttributes;
|
||||
if (!isEmpty(controlGroupAttributes)) {
|
||||
(attributes: DashboardAttributes, collectorData: DashboardCollectorData) => {
|
||||
if (!isEmpty(attributes.controlGroupInput)) {
|
||||
collectorData.controls = embeddableService.telemetry(
|
||||
{
|
||||
...controlGroupAttributes,
|
||||
...attributes.controlGroupInput,
|
||||
type: CONTROL_GROUP_TYPE,
|
||||
id: `DASHBOARD_${CONTROL_GROUP_TYPE}`,
|
||||
},
|
||||
|
|
|
@ -6,21 +6,25 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { CoreSetup, Logger, SavedObjectAttributes, SavedObjectReference } from '@kbn/core/server';
|
||||
import moment from 'moment';
|
||||
|
||||
import {
|
||||
RunContext,
|
||||
TaskManagerSetupContract,
|
||||
TaskManagerStartContract,
|
||||
} from '@kbn/task-manager-plugin/server';
|
||||
import { EmbeddableSetup } from '@kbn/embeddable-plugin/server';
|
||||
import { CoreSetup, Logger, SavedObjectReference } from '@kbn/core/server';
|
||||
|
||||
import {
|
||||
controlsCollectorFactory,
|
||||
collectPanelsByType,
|
||||
getEmptyDashboardData,
|
||||
DashboardCollectorData,
|
||||
} from './dashboard_telemetry';
|
||||
import { injectReferences, SavedDashboardPanel } from '../../common';
|
||||
import { injectReferences } from '../../common';
|
||||
import { DashboardAttributesAndReferences } from '../../common/types';
|
||||
import { DashboardAttributes, SavedDashboardPanel } from '../../common/content_management';
|
||||
|
||||
// This task is responsible for running daily and aggregating all the Dashboard telemerty data
|
||||
// into a single document. This is an effort to make sure the load of fetching/parsing all of the
|
||||
|
@ -28,11 +32,6 @@ import { injectReferences, SavedDashboardPanel } from '../../common';
|
|||
const TELEMETRY_TASK_TYPE = 'dashboard_telemetry';
|
||||
export const TASK_ID = `Dashboard-${TELEMETRY_TASK_TYPE}`;
|
||||
|
||||
interface SavedObjectAttributesAndReferences {
|
||||
attributes: SavedObjectAttributes;
|
||||
references: SavedObjectReference[];
|
||||
}
|
||||
|
||||
export interface DashboardTelemetryTaskState {
|
||||
runs: number;
|
||||
telemetry: DashboardCollectorData;
|
||||
|
@ -92,7 +91,7 @@ export function dashboardTaskRunner(logger: Logger, core: CoreSetup, embeddable:
|
|||
async run() {
|
||||
let dashboardData = getEmptyDashboardData();
|
||||
const controlsCollector = controlsCollectorFactory(embeddable);
|
||||
const processDashboards = (dashboards: SavedObjectAttributesAndReferences[]) => {
|
||||
const processDashboards = (dashboards: DashboardAttributesAndReferences[]) => {
|
||||
for (const dashboard of dashboards) {
|
||||
const attributes = injectReferences(dashboard, {
|
||||
embeddablePersistableStateService: embeddable,
|
||||
|
@ -133,7 +132,7 @@ export function dashboardTaskRunner(logger: Logger, core: CoreSetup, embeddable:
|
|||
const esClient = await getEsClient();
|
||||
|
||||
let result = await esClient.search<{
|
||||
dashboard: SavedObjectAttributes;
|
||||
dashboard: DashboardAttributes;
|
||||
references: SavedObjectReference[];
|
||||
}>(searchParams);
|
||||
|
||||
|
@ -148,8 +147,8 @@ export function dashboardTaskRunner(logger: Logger, core: CoreSetup, embeddable:
|
|||
}
|
||||
return undefined;
|
||||
})
|
||||
.filter<SavedObjectAttributesAndReferences>(
|
||||
(s): s is SavedObjectAttributesAndReferences => s !== undefined
|
||||
.filter<DashboardAttributesAndReferences>(
|
||||
(s): s is DashboardAttributesAndReferences => s !== undefined
|
||||
)
|
||||
);
|
||||
|
||||
|
@ -167,8 +166,8 @@ export function dashboardTaskRunner(logger: Logger, core: CoreSetup, embeddable:
|
|||
}
|
||||
return undefined;
|
||||
})
|
||||
.filter<SavedObjectAttributesAndReferences>(
|
||||
(s): s is SavedObjectAttributesAndReferences => s !== undefined
|
||||
.filter<DashboardAttributesAndReferences>(
|
||||
(s): s is DashboardAttributesAndReferences => s !== undefined
|
||||
)
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,60 +0,0 @@
|
|||
/*
|
||||
* 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 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 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import type { SavedDashboardPanel } from '../../common';
|
||||
import { findByValueEmbeddables } from './find_by_value_embeddables';
|
||||
|
||||
const visualizationByValue = {
|
||||
embeddableConfig: {
|
||||
value: 'visualization-by-value',
|
||||
},
|
||||
type: 'visualization',
|
||||
} as unknown as SavedDashboardPanel;
|
||||
|
||||
const mapByValue = {
|
||||
embeddableConfig: {
|
||||
value: 'map-by-value',
|
||||
},
|
||||
type: 'map',
|
||||
} as unknown as SavedDashboardPanel;
|
||||
|
||||
const embeddableByRef = {
|
||||
panelRefName: 'panel_ref_1',
|
||||
} as unknown as SavedDashboardPanel;
|
||||
|
||||
describe('findByValueEmbeddables', () => {
|
||||
it('finds the by value embeddables for the given type', async () => {
|
||||
const savedObjectsResult = {
|
||||
saved_objects: [
|
||||
{
|
||||
attributes: {
|
||||
panelsJSON: JSON.stringify([visualizationByValue, mapByValue, embeddableByRef]),
|
||||
},
|
||||
},
|
||||
{
|
||||
attributes: {
|
||||
panelsJSON: JSON.stringify([embeddableByRef, mapByValue, visualizationByValue]),
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
const savedObjectClient = { find: jest.fn().mockResolvedValue(savedObjectsResult) };
|
||||
|
||||
const maps = await findByValueEmbeddables(savedObjectClient, 'map');
|
||||
|
||||
expect(maps.length).toBe(2);
|
||||
expect(maps[0]).toEqual(mapByValue.embeddableConfig);
|
||||
expect(maps[1]).toEqual(mapByValue.embeddableConfig);
|
||||
|
||||
const visualizations = await findByValueEmbeddables(savedObjectClient, 'visualization');
|
||||
|
||||
expect(visualizations.length).toBe(2);
|
||||
expect(visualizations[0]).toEqual(visualizationByValue.embeddableConfig);
|
||||
expect(visualizations[1]).toEqual(visualizationByValue.embeddableConfig);
|
||||
});
|
||||
});
|
|
@ -1,34 +0,0 @@
|
|||
/*
|
||||
* 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 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 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { ISavedObjectsRepository, SavedObjectAttributes } from '@kbn/core/server';
|
||||
import type { SavedDashboardPanel } from '../../common';
|
||||
|
||||
export const findByValueEmbeddables = async (
|
||||
savedObjectClient: Pick<ISavedObjectsRepository, 'find'>,
|
||||
embeddableType: string
|
||||
) => {
|
||||
const dashboards = await savedObjectClient.find<SavedObjectAttributes>({
|
||||
type: 'dashboard',
|
||||
});
|
||||
|
||||
return dashboards.saved_objects
|
||||
.map((dashboard) => {
|
||||
try {
|
||||
return JSON.parse(
|
||||
dashboard.attributes.panelsJSON as string
|
||||
) as unknown as SavedDashboardPanel[];
|
||||
} catch (exception) {
|
||||
return [];
|
||||
}
|
||||
})
|
||||
.flat()
|
||||
.filter((panel) => (panel as Record<string, any>).panelRefName === undefined)
|
||||
.filter((panel) => panel.type === embeddableType)
|
||||
.map((panel) => panel.embeddableConfig);
|
||||
};
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"extends": "../../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "target/types",
|
||||
"outDir": "target/types"
|
||||
},
|
||||
"include": ["*.ts", ".storybook/**/*.ts", "common/**/*", "public/**/*", "server/**/*"],
|
||||
"kbn_references": [
|
||||
|
@ -33,6 +33,8 @@
|
|||
"@kbn/unified-search-plugin",
|
||||
"@kbn/shared-ux-page-analytics-no-data",
|
||||
"@kbn/content-management-table-list",
|
||||
"@kbn/content-management-plugin",
|
||||
"@kbn/content-management-utils",
|
||||
"@kbn/i18n-react",
|
||||
"@kbn/expressions-plugin",
|
||||
"@kbn/field-formats-plugin",
|
||||
|
@ -48,7 +50,6 @@
|
|||
"@kbn/core-overlays-browser-mocks",
|
||||
"@kbn/core-theme-browser-mocks",
|
||||
"@kbn/core-ui-settings-browser-mocks",
|
||||
"@kbn/core-saved-objects-common",
|
||||
"@kbn/task-manager-plugin",
|
||||
"@kbn/core-execution-context-common",
|
||||
"@kbn/core-custom-branding-browser",
|
||||
|
@ -58,8 +59,8 @@
|
|||
"@kbn/shared-ux-button-toolbar",
|
||||
"@kbn/core-saved-objects-server",
|
||||
"@kbn/core-saved-objects-utils-server",
|
||||
"@kbn/object-versioning",
|
||||
"@kbn/core-saved-objects-api-server"
|
||||
],
|
||||
"exclude": [
|
||||
"target/**/*",
|
||||
]
|
||||
"exclude": ["target/**/*"]
|
||||
}
|
||||
|
|
|
@ -80,6 +80,9 @@ export interface PanelState<E extends EmbeddableInput & { id: string } = { id: s
|
|||
// Stores input for this embeddable that is specific to this embeddable. Other parts of embeddable input
|
||||
// will be derived from the container's input. **State in here will override state derived from the container.**
|
||||
explicitInput: Partial<E> & { id: string };
|
||||
|
||||
// allows individual embeddable panels to maintain versioning information separate from the main Kibana version
|
||||
version?: string;
|
||||
}
|
||||
|
||||
export type EmbeddableStateWithType = EmbeddableInput & { type: string };
|
||||
|
|
|
@ -12,7 +12,13 @@ export class AuditTrailTestPlugin implements Plugin {
|
|||
const router = core.http.createRouter();
|
||||
router.get({ path: '/audit_log', validate: false }, async (context, request, response) => {
|
||||
const soClient = (await context.core).savedObjects.client;
|
||||
await soClient.create('dashboard', {});
|
||||
await soClient.create('dashboard', {
|
||||
title: '',
|
||||
optionsJSON: '',
|
||||
description: '',
|
||||
panelsJSON: '{}',
|
||||
kibanaSavedObjectMeta: {},
|
||||
});
|
||||
await soClient.find({ type: 'dashboard' });
|
||||
return response.noContent();
|
||||
});
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue