parent
ae3d844872
commit
aec05c49a8
|
@ -1,11 +0,0 @@
|
||||||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
|
||||||
|
|
||||||
[Home](./index.md) > [kibana-plugin-plugins-embeddable-server](./kibana-plugin-plugins-embeddable-server.md) > [EmbeddableSetup](./kibana-plugin-plugins-embeddable-server.embeddablesetup.md) > [getAttributeService](./kibana-plugin-plugins-embeddable-server.embeddablesetup.getattributeservice.md)
|
|
||||||
|
|
||||||
## EmbeddableSetup.getAttributeService property
|
|
||||||
|
|
||||||
<b>Signature:</b>
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
getAttributeService: any;
|
|
||||||
```
|
|
|
@ -7,14 +7,13 @@
|
||||||
<b>Signature:</b>
|
<b>Signature:</b>
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
export interface EmbeddableSetup
|
export interface EmbeddableSetup extends PersistableStateService<EmbeddableStateWithType>
|
||||||
```
|
```
|
||||||
|
|
||||||
## Properties
|
## Properties
|
||||||
|
|
||||||
| Property | Type | Description |
|
| Property | Type | Description |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| [getAttributeService](./kibana-plugin-plugins-embeddable-server.embeddablesetup.getattributeservice.md) | <code>any</code> | |
|
|
||||||
| [registerEmbeddableFactory](./kibana-plugin-plugins-embeddable-server.embeddablesetup.registerembeddablefactory.md) | <code>(factory: EmbeddableRegistryDefinition) => void</code> | |
|
| [registerEmbeddableFactory](./kibana-plugin-plugins-embeddable-server.embeddablesetup.registerembeddablefactory.md) | <code>(factory: EmbeddableRegistryDefinition) => void</code> | |
|
||||||
| [registerEnhancement](./kibana-plugin-plugins-embeddable-server.embeddablesetup.registerenhancement.md) | <code>(enhancement: EnhancementRegistryDefinition) => void</code> | |
|
| [registerEnhancement](./kibana-plugin-plugins-embeddable-server.embeddablesetup.registerenhancement.md) | <code>(enhancement: EnhancementRegistryDefinition) => void</code> | |
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,87 @@
|
||||||
|
/*
|
||||||
|
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||||
|
* license agreements. See the NOTICE file distributed with
|
||||||
|
* this work for additional information regarding copyright
|
||||||
|
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||||
|
* the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
* not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {
|
||||||
|
ExtractDeps,
|
||||||
|
extractPanelsReferences,
|
||||||
|
InjectDeps,
|
||||||
|
injectPanelsReferences,
|
||||||
|
} from './embeddable_references';
|
||||||
|
import { createEmbeddablePersistableStateServiceMock } from '../../../embeddable/common/mocks';
|
||||||
|
import { SavedDashboardPanel } from '../types';
|
||||||
|
import { EmbeddableStateWithType } from '../../../embeddable/common';
|
||||||
|
|
||||||
|
const embeddablePersistableStateService = createEmbeddablePersistableStateServiceMock();
|
||||||
|
const deps: InjectDeps & ExtractDeps = {
|
||||||
|
embeddablePersistableStateService,
|
||||||
|
};
|
||||||
|
|
||||||
|
test('inject/extract panel references', () => {
|
||||||
|
embeddablePersistableStateService.extract.mockImplementationOnce((state) => {
|
||||||
|
const { HARDCODED_ID, ...restOfState } = (state as unknown) as Record<string, unknown>;
|
||||||
|
return {
|
||||||
|
state: restOfState as EmbeddableStateWithType,
|
||||||
|
references: [{ id: HARDCODED_ID as string, name: 'refName', type: 'type' }],
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
embeddablePersistableStateService.inject.mockImplementationOnce((state, references) => {
|
||||||
|
const ref = references.find((r) => r.name === 'refName');
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
HARDCODED_ID: ref!.id,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const savedDashboardPanel: SavedDashboardPanel = {
|
||||||
|
type: 'search',
|
||||||
|
embeddableConfig: {
|
||||||
|
HARDCODED_ID: 'IMPORTANT_HARDCODED_ID',
|
||||||
|
},
|
||||||
|
id: 'savedObjectId',
|
||||||
|
panelIndex: '123',
|
||||||
|
gridData: {
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
h: 15,
|
||||||
|
w: 15,
|
||||||
|
i: '123',
|
||||||
|
},
|
||||||
|
version: '7.0.0',
|
||||||
|
};
|
||||||
|
|
||||||
|
const [{ panel: extractedPanel, references }] = extractPanelsReferences(
|
||||||
|
[savedDashboardPanel],
|
||||||
|
deps
|
||||||
|
);
|
||||||
|
expect(extractedPanel.embeddableConfig).toEqual({});
|
||||||
|
expect(references).toMatchInlineSnapshot(`
|
||||||
|
Array [
|
||||||
|
Object {
|
||||||
|
"id": "IMPORTANT_HARDCODED_ID",
|
||||||
|
"name": "refName",
|
||||||
|
"type": "type",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
`);
|
||||||
|
|
||||||
|
const [injectedPanel] = injectPanelsReferences([extractedPanel], references, deps);
|
||||||
|
|
||||||
|
expect(injectedPanel).toEqual(savedDashboardPanel);
|
||||||
|
});
|
|
@ -0,0 +1,82 @@
|
||||||
|
/*
|
||||||
|
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||||
|
* license agreements. See the NOTICE file distributed with
|
||||||
|
* this work for additional information regarding copyright
|
||||||
|
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||||
|
* the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
* not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { omit } from 'lodash';
|
||||||
|
import {
|
||||||
|
convertSavedDashboardPanelToPanelState,
|
||||||
|
convertPanelStateToSavedDashboardPanel,
|
||||||
|
} from './embeddable_saved_object_converters';
|
||||||
|
import { SavedDashboardPanel } from '../types';
|
||||||
|
import { SavedObjectReference } from '../../../../core/types';
|
||||||
|
import { EmbeddablePersistableStateService } from '../../../embeddable/common/types';
|
||||||
|
|
||||||
|
export interface InjectDeps {
|
||||||
|
embeddablePersistableStateService: EmbeddablePersistableStateService;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function injectPanelsReferences(
|
||||||
|
panels: SavedDashboardPanel[],
|
||||||
|
references: SavedObjectReference[],
|
||||||
|
deps: InjectDeps
|
||||||
|
): SavedDashboardPanel[] {
|
||||||
|
const result: SavedDashboardPanel[] = [];
|
||||||
|
for (const panel of panels) {
|
||||||
|
const embeddableState = convertSavedDashboardPanelToPanelState(panel);
|
||||||
|
embeddableState.explicitInput = omit(
|
||||||
|
deps.embeddablePersistableStateService.inject(
|
||||||
|
{ ...embeddableState.explicitInput, type: panel.type },
|
||||||
|
references
|
||||||
|
),
|
||||||
|
'type'
|
||||||
|
);
|
||||||
|
result.push(convertPanelStateToSavedDashboardPanel(embeddableState, panel.version));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ExtractDeps {
|
||||||
|
embeddablePersistableStateService: EmbeddablePersistableStateService;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function extractPanelsReferences(
|
||||||
|
panels: SavedDashboardPanel[],
|
||||||
|
deps: ExtractDeps
|
||||||
|
): Array<{ panel: SavedDashboardPanel; references: SavedObjectReference[] }> {
|
||||||
|
const result: Array<{ panel: SavedDashboardPanel; references: SavedObjectReference[] }> = [];
|
||||||
|
|
||||||
|
for (const panel of panels) {
|
||||||
|
const embeddable = convertSavedDashboardPanelToPanelState(panel);
|
||||||
|
const {
|
||||||
|
state: embeddableInputWithExtractedReferences,
|
||||||
|
references,
|
||||||
|
} = deps.embeddablePersistableStateService.extract({
|
||||||
|
...embeddable.explicitInput,
|
||||||
|
type: embeddable.type,
|
||||||
|
});
|
||||||
|
embeddable.explicitInput = omit(embeddableInputWithExtractedReferences, 'type');
|
||||||
|
|
||||||
|
const newPanel = convertPanelStateToSavedDashboardPanel(embeddable, panel.version);
|
||||||
|
result.push({
|
||||||
|
panel: newPanel,
|
||||||
|
references,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
|
@ -21,9 +21,8 @@ import {
|
||||||
convertSavedDashboardPanelToPanelState,
|
convertSavedDashboardPanelToPanelState,
|
||||||
convertPanelStateToSavedDashboardPanel,
|
convertPanelStateToSavedDashboardPanel,
|
||||||
} from './embeddable_saved_object_converters';
|
} from './embeddable_saved_object_converters';
|
||||||
import { SavedDashboardPanel } from '../../types';
|
import { SavedDashboardPanel, DashboardPanelState } from '../types';
|
||||||
import { DashboardPanelState } from '../embeddable';
|
import { EmbeddableInput } from '../../../embeddable/common/types';
|
||||||
import { EmbeddableInput } from '../../../../embeddable/public';
|
|
||||||
|
|
||||||
test('convertSavedDashboardPanelToPanelState', () => {
|
test('convertSavedDashboardPanelToPanelState', () => {
|
||||||
const savedDashboardPanel: SavedDashboardPanel = {
|
const savedDashboardPanel: SavedDashboardPanel = {
|
||||||
|
@ -135,3 +134,24 @@ test('convertPanelStateToSavedDashboardPanel will not add an undefined id when n
|
||||||
const converted = convertPanelStateToSavedDashboardPanel(dashboardPanel, '8.0.0');
|
const converted = convertPanelStateToSavedDashboardPanel(dashboardPanel, '8.0.0');
|
||||||
expect(converted.hasOwnProperty('id')).toBe(false);
|
expect(converted.hasOwnProperty('id')).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('convertPanelStateToSavedDashboardPanel will not leave title as part of embeddable config', () => {
|
||||||
|
const dashboardPanel: DashboardPanelState = {
|
||||||
|
gridData: {
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
h: 15,
|
||||||
|
w: 15,
|
||||||
|
i: '123',
|
||||||
|
},
|
||||||
|
explicitInput: {
|
||||||
|
id: '123',
|
||||||
|
title: 'title',
|
||||||
|
} as EmbeddableInput,
|
||||||
|
type: 'search',
|
||||||
|
};
|
||||||
|
|
||||||
|
const converted = convertPanelStateToSavedDashboardPanel(dashboardPanel, '8.0.0');
|
||||||
|
expect(converted.embeddableConfig.hasOwnProperty('title')).toBe(false);
|
||||||
|
expect(converted.title).toBe('title');
|
||||||
|
});
|
|
@ -17,9 +17,8 @@
|
||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
import { omit } from 'lodash';
|
import { omit } from 'lodash';
|
||||||
import { SavedDashboardPanel } from '../../types';
|
import { DashboardPanelState, SavedDashboardPanel } from '../types';
|
||||||
import { DashboardPanelState } from '../embeddable';
|
import { SavedObjectEmbeddableInput } from '../../../embeddable/common/';
|
||||||
import { SavedObjectEmbeddableInput } from '../../embeddable_plugin';
|
|
||||||
|
|
||||||
export function convertSavedDashboardPanelToPanelState(
|
export function convertSavedDashboardPanelToPanelState(
|
||||||
savedDashboardPanel: SavedDashboardPanel
|
savedDashboardPanel: SavedDashboardPanel
|
||||||
|
@ -49,7 +48,7 @@ export function convertPanelStateToSavedDashboardPanel(
|
||||||
type: panelState.type,
|
type: panelState.type,
|
||||||
gridData: panelState.gridData,
|
gridData: panelState.gridData,
|
||||||
panelIndex: panelState.explicitInput.id,
|
panelIndex: panelState.explicitInput.id,
|
||||||
embeddableConfig: omit(panelState.explicitInput, ['id', 'savedObjectId']),
|
embeddableConfig: omit(panelState.explicitInput, ['id', 'savedObjectId', 'title']),
|
||||||
...(customTitle && { title: customTitle }),
|
...(customTitle && { title: customTitle }),
|
||||||
...(savedObjectId !== undefined && { id: savedObjectId }),
|
...(savedObjectId !== undefined && { id: savedObjectId }),
|
||||||
};
|
};
|
|
@ -17,8 +17,18 @@
|
||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { extractReferences, injectReferences } from './saved_dashboard_references';
|
import {
|
||||||
import { SavedObjectDashboard } from './saved_dashboard';
|
extractReferences,
|
||||||
|
injectReferences,
|
||||||
|
InjectDeps,
|
||||||
|
ExtractDeps,
|
||||||
|
} from './saved_dashboard_references';
|
||||||
|
import { createEmbeddablePersistableStateServiceMock } from '../../embeddable/common/mocks';
|
||||||
|
|
||||||
|
const embeddablePersistableStateServiceMock = createEmbeddablePersistableStateServiceMock();
|
||||||
|
const deps: InjectDeps & ExtractDeps = {
|
||||||
|
embeddablePersistableStateService: embeddablePersistableStateServiceMock,
|
||||||
|
};
|
||||||
|
|
||||||
describe('extractReferences', () => {
|
describe('extractReferences', () => {
|
||||||
test('extracts references from panelsJSON', () => {
|
test('extracts references from panelsJSON', () => {
|
||||||
|
@ -41,28 +51,28 @@ describe('extractReferences', () => {
|
||||||
},
|
},
|
||||||
references: [],
|
references: [],
|
||||||
};
|
};
|
||||||
const updatedDoc = extractReferences(doc);
|
const updatedDoc = extractReferences(doc, deps);
|
||||||
|
|
||||||
expect(updatedDoc).toMatchInlineSnapshot(`
|
expect(updatedDoc).toMatchInlineSnapshot(`
|
||||||
Object {
|
Object {
|
||||||
"attributes": Object {
|
"attributes": Object {
|
||||||
"foo": true,
|
"foo": true,
|
||||||
"panelsJSON": "[{\\"title\\":\\"Title 1\\",\\"panelRefName\\":\\"panel_0\\"},{\\"title\\":\\"Title 2\\",\\"panelRefName\\":\\"panel_1\\"}]",
|
"panelsJSON": "[{\\"embeddableConfig\\":{},\\"title\\":\\"Title 1\\",\\"panelRefName\\":\\"panel_0\\"},{\\"embeddableConfig\\":{},\\"title\\":\\"Title 2\\",\\"panelRefName\\":\\"panel_1\\"}]",
|
||||||
},
|
},
|
||||||
"references": Array [
|
"references": Array [
|
||||||
Object {
|
Object {
|
||||||
"id": "1",
|
"id": "1",
|
||||||
"name": "panel_0",
|
"name": "panel_0",
|
||||||
"type": "visualization",
|
"type": "visualization",
|
||||||
},
|
},
|
||||||
Object {
|
Object {
|
||||||
"id": "2",
|
"id": "2",
|
||||||
"name": "panel_1",
|
"name": "panel_1",
|
||||||
"type": "visualization",
|
"type": "visualization",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
`);
|
`);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('fails when "type" attribute is missing from a panel', () => {
|
test('fails when "type" attribute is missing from a panel', () => {
|
||||||
|
@ -79,7 +89,7 @@ Object {
|
||||||
},
|
},
|
||||||
references: [],
|
references: [],
|
||||||
};
|
};
|
||||||
expect(() => extractReferences(doc)).toThrowErrorMatchingInlineSnapshot(
|
expect(() => extractReferences(doc, deps)).toThrowErrorMatchingInlineSnapshot(
|
||||||
`"\\"type\\" attribute is missing from panel \\"0\\""`
|
`"\\"type\\" attribute is missing from panel \\"0\\""`
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -98,21 +108,21 @@ Object {
|
||||||
},
|
},
|
||||||
references: [],
|
references: [],
|
||||||
};
|
};
|
||||||
expect(extractReferences(doc)).toMatchInlineSnapshot(`
|
expect(extractReferences(doc, deps)).toMatchInlineSnapshot(`
|
||||||
Object {
|
Object {
|
||||||
"attributes": Object {
|
"attributes": Object {
|
||||||
"foo": true,
|
"foo": true,
|
||||||
"panelsJSON": "[{\\"type\\":\\"visualization\\",\\"title\\":\\"Title 1\\"}]",
|
"panelsJSON": "[{\\"type\\":\\"visualization\\",\\"embeddableConfig\\":{},\\"title\\":\\"Title 1\\"}]",
|
||||||
},
|
},
|
||||||
"references": Array [],
|
"references": Array [],
|
||||||
}
|
}
|
||||||
`);
|
`);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('injectReferences', () => {
|
describe('injectReferences', () => {
|
||||||
test('injects references into context', () => {
|
test('returns injected attributes', () => {
|
||||||
const context = {
|
const attributes = {
|
||||||
id: '1',
|
id: '1',
|
||||||
title: 'test',
|
title: 'test',
|
||||||
panelsJSON: JSON.stringify([
|
panelsJSON: JSON.stringify([
|
||||||
|
@ -125,7 +135,7 @@ describe('injectReferences', () => {
|
||||||
title: 'Title 2',
|
title: 'Title 2',
|
||||||
},
|
},
|
||||||
]),
|
]),
|
||||||
} as SavedObjectDashboard;
|
};
|
||||||
const references = [
|
const references = [
|
||||||
{
|
{
|
||||||
name: 'panel_0',
|
name: 'panel_0',
|
||||||
|
@ -138,49 +148,49 @@ describe('injectReferences', () => {
|
||||||
id: '2',
|
id: '2',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
injectReferences(context, references);
|
const newAttributes = injectReferences({ attributes, references }, deps);
|
||||||
|
|
||||||
expect(context).toMatchInlineSnapshot(`
|
expect(newAttributes).toMatchInlineSnapshot(`
|
||||||
Object {
|
Object {
|
||||||
"id": "1",
|
"id": "1",
|
||||||
"panelsJSON": "[{\\"title\\":\\"Title 1\\",\\"id\\":\\"1\\",\\"type\\":\\"visualization\\"},{\\"title\\":\\"Title 2\\",\\"id\\":\\"2\\",\\"type\\":\\"visualization\\"}]",
|
"panelsJSON": "[{\\"type\\":\\"visualization\\",\\"embeddableConfig\\":{},\\"title\\":\\"Title 1\\",\\"id\\":\\"1\\"},{\\"type\\":\\"visualization\\",\\"embeddableConfig\\":{},\\"title\\":\\"Title 2\\",\\"id\\":\\"2\\"}]",
|
||||||
"title": "test",
|
"title": "test",
|
||||||
}
|
}
|
||||||
`);
|
`);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('skips when panelsJSON is missing', () => {
|
test('skips when panelsJSON is missing', () => {
|
||||||
const context = {
|
const attributes = {
|
||||||
id: '1',
|
id: '1',
|
||||||
title: 'test',
|
title: 'test',
|
||||||
} as SavedObjectDashboard;
|
};
|
||||||
injectReferences(context, []);
|
const newAttributes = injectReferences({ attributes, references: [] }, deps);
|
||||||
expect(context).toMatchInlineSnapshot(`
|
expect(newAttributes).toMatchInlineSnapshot(`
|
||||||
Object {
|
Object {
|
||||||
"id": "1",
|
"id": "1",
|
||||||
"title": "test",
|
"title": "test",
|
||||||
}
|
}
|
||||||
`);
|
`);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('skips when panelsJSON is not an array', () => {
|
test('skips when panelsJSON is not an array', () => {
|
||||||
const context = {
|
const attributes = {
|
||||||
id: '1',
|
id: '1',
|
||||||
panelsJSON: '{}',
|
panelsJSON: '{}',
|
||||||
title: 'test',
|
title: 'test',
|
||||||
} as SavedObjectDashboard;
|
};
|
||||||
injectReferences(context, []);
|
const newAttributes = injectReferences({ attributes, references: [] }, deps);
|
||||||
expect(context).toMatchInlineSnapshot(`
|
expect(newAttributes).toMatchInlineSnapshot(`
|
||||||
Object {
|
Object {
|
||||||
"id": "1",
|
"id": "1",
|
||||||
"panelsJSON": "{}",
|
"panelsJSON": "{}",
|
||||||
"title": "test",
|
"title": "test",
|
||||||
}
|
}
|
||||||
`);
|
`);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('skips a panel when panelRefName is missing', () => {
|
test('skips a panel when panelRefName is missing', () => {
|
||||||
const context = {
|
const attributes = {
|
||||||
id: '1',
|
id: '1',
|
||||||
title: 'test',
|
title: 'test',
|
||||||
panelsJSON: JSON.stringify([
|
panelsJSON: JSON.stringify([
|
||||||
|
@ -192,7 +202,7 @@ Object {
|
||||||
title: 'Title 2',
|
title: 'Title 2',
|
||||||
},
|
},
|
||||||
]),
|
]),
|
||||||
} as SavedObjectDashboard;
|
};
|
||||||
const references = [
|
const references = [
|
||||||
{
|
{
|
||||||
name: 'panel_0',
|
name: 'panel_0',
|
||||||
|
@ -200,18 +210,18 @@ Object {
|
||||||
id: '1',
|
id: '1',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
injectReferences(context, references);
|
const newAttributes = injectReferences({ attributes, references }, deps);
|
||||||
expect(context).toMatchInlineSnapshot(`
|
expect(newAttributes).toMatchInlineSnapshot(`
|
||||||
Object {
|
Object {
|
||||||
"id": "1",
|
"id": "1",
|
||||||
"panelsJSON": "[{\\"title\\":\\"Title 1\\",\\"id\\":\\"1\\",\\"type\\":\\"visualization\\"},{\\"title\\":\\"Title 2\\"}]",
|
"panelsJSON": "[{\\"type\\":\\"visualization\\",\\"embeddableConfig\\":{},\\"title\\":\\"Title 1\\",\\"id\\":\\"1\\"},{\\"embeddableConfig\\":{},\\"title\\":\\"Title 2\\"}]",
|
||||||
"title": "test",
|
"title": "test",
|
||||||
}
|
}
|
||||||
`);
|
`);
|
||||||
});
|
});
|
||||||
|
|
||||||
test(`fails when it can't find the reference in the array`, () => {
|
test(`fails when it can't find the reference in the array`, () => {
|
||||||
const context = {
|
const attributes = {
|
||||||
id: '1',
|
id: '1',
|
||||||
title: 'test',
|
title: 'test',
|
||||||
panelsJSON: JSON.stringify([
|
panelsJSON: JSON.stringify([
|
||||||
|
@ -220,9 +230,9 @@ Object {
|
||||||
title: 'Title 1',
|
title: 'Title 1',
|
||||||
},
|
},
|
||||||
]),
|
]),
|
||||||
} as SavedObjectDashboard;
|
};
|
||||||
expect(() => injectReferences(context, [])).toThrowErrorMatchingInlineSnapshot(
|
expect(() =>
|
||||||
`"Could not find reference \\"panel_0\\""`
|
injectReferences({ attributes, references: [] }, deps)
|
||||||
);
|
).toThrowErrorMatchingInlineSnapshot(`"Could not find reference \\"panel_0\\""`);
|
||||||
});
|
});
|
||||||
});
|
});
|
|
@ -17,18 +17,47 @@
|
||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { SavedObjectAttributes, SavedObjectReference } from 'kibana/public';
|
import { SavedObjectAttributes, SavedObjectReference } from '../../../core/types';
|
||||||
import { SavedObjectDashboard } from './saved_dashboard';
|
import {
|
||||||
|
extractPanelsReferences,
|
||||||
|
injectPanelsReferences,
|
||||||
|
} from './embeddable/embeddable_references';
|
||||||
|
import { SavedDashboardPanel730ToLatest } from './types';
|
||||||
|
import { EmbeddablePersistableStateService } from '../../embeddable/common/types';
|
||||||
|
|
||||||
export function extractReferences({
|
export interface ExtractDeps {
|
||||||
attributes,
|
embeddablePersistableStateService: EmbeddablePersistableStateService;
|
||||||
references = [],
|
}
|
||||||
}: {
|
|
||||||
|
export interface SavedObjectAttributesAndReferences {
|
||||||
attributes: SavedObjectAttributes;
|
attributes: SavedObjectAttributes;
|
||||||
references: SavedObjectReference[];
|
references: SavedObjectReference[];
|
||||||
}) {
|
}
|
||||||
|
|
||||||
|
export function extractReferences(
|
||||||
|
{ attributes, references = [] }: SavedObjectAttributesAndReferences,
|
||||||
|
deps: ExtractDeps
|
||||||
|
): SavedObjectAttributesAndReferences {
|
||||||
|
if (typeof attributes.panelsJSON !== 'string') {
|
||||||
|
return { attributes, references };
|
||||||
|
}
|
||||||
const panelReferences: SavedObjectReference[] = [];
|
const panelReferences: SavedObjectReference[] = [];
|
||||||
const panels: Array<Record<string, string>> = JSON.parse(String(attributes.panelsJSON));
|
let panels: Array<Record<string, string>> = JSON.parse(String(attributes.panelsJSON));
|
||||||
|
|
||||||
|
const extractedReferencesResult = extractPanelsReferences(
|
||||||
|
(panels as unknown) as SavedDashboardPanel730ToLatest[],
|
||||||
|
deps
|
||||||
|
);
|
||||||
|
|
||||||
|
panels = (extractedReferencesResult.map((res) => res.panel) as unknown) as Array<
|
||||||
|
Record<string, string>
|
||||||
|
>;
|
||||||
|
extractedReferencesResult.forEach((res) => {
|
||||||
|
panelReferences.push(...res.references);
|
||||||
|
});
|
||||||
|
|
||||||
|
// TODO: This extraction should be done by EmbeddablePersistableStateService
|
||||||
|
// https://github.com/elastic/kibana/issues/82830
|
||||||
panels.forEach((panel, i) => {
|
panels.forEach((panel, i) => {
|
||||||
if (!panel.type) {
|
if (!panel.type) {
|
||||||
throw new Error(`"type" attribute is missing from panel "${i}"`);
|
throw new Error(`"type" attribute is missing from panel "${i}"`);
|
||||||
|
@ -46,6 +75,7 @@ export function extractReferences({
|
||||||
delete panel.type;
|
delete panel.type;
|
||||||
delete panel.id;
|
delete panel.id;
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
references: [...references, ...panelReferences],
|
references: [...references, ...panelReferences],
|
||||||
attributes: {
|
attributes: {
|
||||||
|
@ -55,21 +85,28 @@ export function extractReferences({
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface InjectDeps {
|
||||||
|
embeddablePersistableStateService: EmbeddablePersistableStateService;
|
||||||
|
}
|
||||||
|
|
||||||
export function injectReferences(
|
export function injectReferences(
|
||||||
savedObject: SavedObjectDashboard,
|
{ attributes, references = [] }: SavedObjectAttributesAndReferences,
|
||||||
references: SavedObjectReference[]
|
deps: InjectDeps
|
||||||
) {
|
): SavedObjectAttributes {
|
||||||
// Skip if panelsJSON is missing otherwise this will cause saved object import to fail when
|
// 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
|
// importing objects without panelsJSON. At development time of this, there is no guarantee each saved
|
||||||
// object has panelsJSON in all previous versions of kibana.
|
// object has panelsJSON in all previous versions of kibana.
|
||||||
if (typeof savedObject.panelsJSON !== 'string') {
|
if (typeof attributes.panelsJSON !== 'string') {
|
||||||
return;
|
return attributes;
|
||||||
}
|
}
|
||||||
const panels = JSON.parse(savedObject.panelsJSON);
|
let panels = JSON.parse(attributes.panelsJSON);
|
||||||
// Same here, prevent failing saved object import if ever panels aren't an array.
|
// Same here, prevent failing saved object import if ever panels aren't an array.
|
||||||
if (!Array.isArray(panels)) {
|
if (!Array.isArray(panels)) {
|
||||||
return;
|
return attributes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: This injection should be done by EmbeddablePersistableStateService
|
||||||
|
// https://github.com/elastic/kibana/issues/82830
|
||||||
panels.forEach((panel) => {
|
panels.forEach((panel) => {
|
||||||
if (!panel.panelRefName) {
|
if (!panel.panelRefName) {
|
||||||
return;
|
return;
|
||||||
|
@ -84,5 +121,11 @@ export function injectReferences(
|
||||||
panel.type = reference.type;
|
panel.type = reference.type;
|
||||||
delete panel.panelRefName;
|
delete panel.panelRefName;
|
||||||
});
|
});
|
||||||
savedObject.panelsJSON = JSON.stringify(panels);
|
|
||||||
|
panels = injectPanelsReferences(panels, references, deps);
|
||||||
|
|
||||||
|
return {
|
||||||
|
...attributes,
|
||||||
|
panelsJSON: JSON.stringify(panels),
|
||||||
|
};
|
||||||
}
|
}
|
|
@ -17,6 +17,8 @@
|
||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { EmbeddableInput, PanelState } from '../../../../src/plugins/embeddable/common/types';
|
||||||
|
import { SavedObjectEmbeddableInput } from '../../../../src/plugins/embeddable/common/lib/saved_object_embeddable';
|
||||||
import {
|
import {
|
||||||
RawSavedDashboardPanelTo60,
|
RawSavedDashboardPanelTo60,
|
||||||
RawSavedDashboardPanel610,
|
RawSavedDashboardPanel610,
|
||||||
|
@ -26,6 +28,21 @@ import {
|
||||||
RawSavedDashboardPanel730ToLatest,
|
RawSavedDashboardPanel730ToLatest,
|
||||||
} from './bwc/types';
|
} from './bwc/types';
|
||||||
|
|
||||||
|
import { GridData } from './embeddable/types';
|
||||||
|
export type PanelId = string;
|
||||||
|
export type SavedObjectId = string;
|
||||||
|
|
||||||
|
export interface DashboardPanelState<
|
||||||
|
TEmbeddableInput extends EmbeddableInput | SavedObjectEmbeddableInput = SavedObjectEmbeddableInput
|
||||||
|
> extends PanelState<TEmbeddableInput> {
|
||||||
|
readonly gridData: GridData;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This should always represent the latest dashboard panel shape, after all possible migrations.
|
||||||
|
*/
|
||||||
|
export type SavedDashboardPanel = SavedDashboardPanel730ToLatest;
|
||||||
|
|
||||||
export type SavedDashboardPanel640To720 = Pick<
|
export type SavedDashboardPanel640To720 = Pick<
|
||||||
RawSavedDashboardPanel640To720,
|
RawSavedDashboardPanel640To720,
|
||||||
Exclude<keyof RawSavedDashboardPanel640To720, 'name'>
|
Exclude<keyof RawSavedDashboardPanel640To720, 'name'>
|
||||||
|
|
|
@ -81,7 +81,6 @@ import { getTopNavConfig } from './top_nav/get_top_nav_config';
|
||||||
import { TopNavIds } from './top_nav/top_nav_ids';
|
import { TopNavIds } from './top_nav/top_nav_ids';
|
||||||
import { getDashboardTitle } from './dashboard_strings';
|
import { getDashboardTitle } from './dashboard_strings';
|
||||||
import { DashboardAppScope } from './dashboard_app';
|
import { DashboardAppScope } from './dashboard_app';
|
||||||
import { convertSavedDashboardPanelToPanelState } from './lib/embeddable_saved_object_converters';
|
|
||||||
import { RenderDeps } from './application';
|
import { RenderDeps } from './application';
|
||||||
import {
|
import {
|
||||||
IKbnUrlStateStorage,
|
IKbnUrlStateStorage,
|
||||||
|
@ -97,6 +96,7 @@ import {
|
||||||
subscribeWithScope,
|
subscribeWithScope,
|
||||||
} from '../../../kibana_legacy/public';
|
} from '../../../kibana_legacy/public';
|
||||||
import { migrateLegacyQuery } from './lib/migrate_legacy_query';
|
import { migrateLegacyQuery } from './lib/migrate_legacy_query';
|
||||||
|
import { convertSavedDashboardPanelToPanelState } from '../../common/embeddable/embeddable_saved_object_converters';
|
||||||
|
|
||||||
export interface DashboardAppControllerDependencies extends RenderDeps {
|
export interface DashboardAppControllerDependencies extends RenderDeps {
|
||||||
$scope: DashboardAppScope;
|
$scope: DashboardAppScope;
|
||||||
|
|
|
@ -30,7 +30,6 @@ import { migrateLegacyQuery } from './lib/migrate_legacy_query';
|
||||||
|
|
||||||
import { ViewMode } from '../embeddable_plugin';
|
import { ViewMode } from '../embeddable_plugin';
|
||||||
import { getAppStateDefaults, migrateAppState, getDashboardIdFromUrl } from './lib';
|
import { getAppStateDefaults, migrateAppState, getDashboardIdFromUrl } from './lib';
|
||||||
import { convertPanelStateToSavedDashboardPanel } from './lib/embeddable_saved_object_converters';
|
|
||||||
import { FilterUtils } from './lib/filter_utils';
|
import { FilterUtils } from './lib/filter_utils';
|
||||||
import {
|
import {
|
||||||
DashboardAppState,
|
DashboardAppState,
|
||||||
|
@ -48,6 +47,7 @@ import {
|
||||||
} from '../../../kibana_utils/public';
|
} from '../../../kibana_utils/public';
|
||||||
import { SavedObjectDashboard } from '../saved_dashboards';
|
import { SavedObjectDashboard } from '../saved_dashboards';
|
||||||
import { DashboardContainer } from './embeddable';
|
import { DashboardContainer } from './embeddable';
|
||||||
|
import { convertPanelStateToSavedDashboardPanel } from '../../common/embeddable/embeddable_saved_object_converters';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Dashboard state manager handles connecting angular and redux state between the angular and react portions of the
|
* Dashboard state manager handles connecting angular and redux state between the angular and react portions of the
|
||||||
|
|
|
@ -16,14 +16,4 @@
|
||||||
* specific language governing permissions and limitations
|
* specific language governing permissions and limitations
|
||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
import { SavedObjectEmbeddableInput } from 'src/plugins/embeddable/public';
|
export * from '../../../common/types';
|
||||||
import { GridData } from '../../../common';
|
|
||||||
import { PanelState, EmbeddableInput } from '../../embeddable_plugin';
|
|
||||||
export type PanelId = string;
|
|
||||||
export type SavedObjectId = string;
|
|
||||||
|
|
||||||
export interface DashboardPanelState<
|
|
||||||
TEmbeddableInput extends EmbeddableInput | SavedObjectEmbeddableInput = SavedObjectEmbeddableInput
|
|
||||||
> extends PanelState<TEmbeddableInput> {
|
|
||||||
readonly gridData: GridData;
|
|
||||||
}
|
|
||||||
|
|
|
@ -450,6 +450,7 @@ export class DashboardPlugin
|
||||||
const savedDashboardLoader = createSavedDashboardLoader({
|
const savedDashboardLoader = createSavedDashboardLoader({
|
||||||
savedObjectsClient: core.savedObjects.client,
|
savedObjectsClient: core.savedObjects.client,
|
||||||
savedObjects: plugins.savedObjects,
|
savedObjects: plugins.savedObjects,
|
||||||
|
embeddableStart: plugins.embeddable,
|
||||||
});
|
});
|
||||||
const dashboardContainerFactory = plugins.embeddable.getEmbeddableFactory(
|
const dashboardContainerFactory = plugins.embeddable.getEmbeddableFactory(
|
||||||
DASHBOARD_CONTAINER_TYPE
|
DASHBOARD_CONTAINER_TYPE
|
||||||
|
|
|
@ -16,6 +16,6 @@
|
||||||
* specific language governing permissions and limitations
|
* specific language governing permissions and limitations
|
||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
export * from './saved_dashboard_references';
|
export * from '../../common/saved_dashboard_references';
|
||||||
export * from './saved_dashboard';
|
export * from './saved_dashboard';
|
||||||
export * from './saved_dashboards';
|
export * from './saved_dashboards';
|
||||||
|
|
|
@ -17,10 +17,12 @@
|
||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
import { SavedObject, SavedObjectsStart } from '../../../../plugins/saved_objects/public';
|
import { SavedObject, SavedObjectsStart } from '../../../../plugins/saved_objects/public';
|
||||||
import { extractReferences, injectReferences } from './saved_dashboard_references';
|
|
||||||
|
|
||||||
import { Filter, ISearchSource, Query, RefreshInterval } from '../../../../plugins/data/public';
|
import { Filter, ISearchSource, Query, RefreshInterval } from '../../../../plugins/data/public';
|
||||||
import { createDashboardEditUrl } from '../dashboard_constants';
|
import { createDashboardEditUrl } from '../dashboard_constants';
|
||||||
|
import { EmbeddableStart } from '../../../embeddable/public';
|
||||||
|
import { SavedObjectAttributes, SavedObjectReference } from '../../../../core/types';
|
||||||
|
import { extractReferences, injectReferences } from '../../common/saved_dashboard_references';
|
||||||
|
|
||||||
export interface SavedObjectDashboard extends SavedObject {
|
export interface SavedObjectDashboard extends SavedObject {
|
||||||
id?: string;
|
id?: string;
|
||||||
|
@ -41,7 +43,8 @@ export interface SavedObjectDashboard extends SavedObject {
|
||||||
|
|
||||||
// Used only by the savedDashboards service, usually no reason to change this
|
// Used only by the savedDashboards service, usually no reason to change this
|
||||||
export function createSavedDashboardClass(
|
export function createSavedDashboardClass(
|
||||||
savedObjectStart: SavedObjectsStart
|
savedObjectStart: SavedObjectsStart,
|
||||||
|
embeddableStart: EmbeddableStart
|
||||||
): new (id: string) => SavedObjectDashboard {
|
): new (id: string) => SavedObjectDashboard {
|
||||||
class SavedDashboard extends savedObjectStart.SavedObjectClass {
|
class SavedDashboard extends savedObjectStart.SavedObjectClass {
|
||||||
// save these objects with the 'dashboard' type
|
// save these objects with the 'dashboard' type
|
||||||
|
@ -77,8 +80,19 @@ export function createSavedDashboardClass(
|
||||||
type: SavedDashboard.type,
|
type: SavedDashboard.type,
|
||||||
mapping: SavedDashboard.mapping,
|
mapping: SavedDashboard.mapping,
|
||||||
searchSource: SavedDashboard.searchSource,
|
searchSource: SavedDashboard.searchSource,
|
||||||
extractReferences,
|
extractReferences: (opts: {
|
||||||
injectReferences,
|
attributes: SavedObjectAttributes;
|
||||||
|
references: SavedObjectReference[];
|
||||||
|
}) => extractReferences(opts, { embeddablePersistableStateService: embeddableStart }),
|
||||||
|
injectReferences: (so: SavedObjectDashboard, references: SavedObjectReference[]) => {
|
||||||
|
const newAttributes = injectReferences(
|
||||||
|
{ attributes: so._serialize().attributes, references },
|
||||||
|
{
|
||||||
|
embeddablePersistableStateService: embeddableStart,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
Object.assign(so, newAttributes);
|
||||||
|
},
|
||||||
|
|
||||||
// if this is null/undefined then the SavedObject will be assigned the defaults
|
// if this is null/undefined then the SavedObject will be assigned the defaults
|
||||||
id,
|
id,
|
||||||
|
|
|
@ -20,16 +20,22 @@
|
||||||
import { SavedObjectsClientContract } from 'kibana/public';
|
import { SavedObjectsClientContract } from 'kibana/public';
|
||||||
import { SavedObjectLoader, SavedObjectsStart } from '../../../../plugins/saved_objects/public';
|
import { SavedObjectLoader, SavedObjectsStart } from '../../../../plugins/saved_objects/public';
|
||||||
import { createSavedDashboardClass } from './saved_dashboard';
|
import { createSavedDashboardClass } from './saved_dashboard';
|
||||||
|
import { EmbeddableStart } from '../../../embeddable/public';
|
||||||
|
|
||||||
interface Services {
|
interface Services {
|
||||||
savedObjectsClient: SavedObjectsClientContract;
|
savedObjectsClient: SavedObjectsClientContract;
|
||||||
savedObjects: SavedObjectsStart;
|
savedObjects: SavedObjectsStart;
|
||||||
|
embeddableStart: EmbeddableStart;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param services
|
* @param services
|
||||||
*/
|
*/
|
||||||
export function createSavedDashboardLoader({ savedObjects, savedObjectsClient }: Services) {
|
export function createSavedDashboardLoader({
|
||||||
const SavedDashboard = createSavedDashboardClass(savedObjects);
|
savedObjects,
|
||||||
|
savedObjectsClient,
|
||||||
|
embeddableStart,
|
||||||
|
}: Services) {
|
||||||
|
const SavedDashboard = createSavedDashboardClass(savedObjects, embeddableStart);
|
||||||
return new SavedObjectLoader(SavedDashboard, savedObjectsClient);
|
return new SavedObjectLoader(SavedDashboard, savedObjectsClient);
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,9 +19,12 @@
|
||||||
|
|
||||||
import { Query, Filter } from 'src/plugins/data/public';
|
import { Query, Filter } from 'src/plugins/data/public';
|
||||||
import { SavedObject as SavedObjectType, SavedObjectAttributes } from 'src/core/public';
|
import { SavedObject as SavedObjectType, SavedObjectAttributes } from 'src/core/public';
|
||||||
import { SavedDashboardPanel730ToLatest } from '../common';
|
|
||||||
import { ViewMode } from './embeddable_plugin';
|
import { ViewMode } from './embeddable_plugin';
|
||||||
|
|
||||||
|
import { SavedDashboardPanel } from '../common/types';
|
||||||
|
export { SavedDashboardPanel };
|
||||||
|
|
||||||
export interface DashboardCapabilities {
|
export interface DashboardCapabilities {
|
||||||
showWriteControls: boolean;
|
showWriteControls: boolean;
|
||||||
createNew: boolean;
|
createNew: boolean;
|
||||||
|
@ -71,11 +74,6 @@ export interface Field {
|
||||||
|
|
||||||
export type NavAction = (anchorElement?: any) => void;
|
export type NavAction = (anchorElement?: any) => void;
|
||||||
|
|
||||||
/**
|
|
||||||
* This should always represent the latest dashboard panel shape, after all possible migrations.
|
|
||||||
*/
|
|
||||||
export type SavedDashboardPanel = SavedDashboardPanel730ToLatest;
|
|
||||||
|
|
||||||
export interface DashboardAppState {
|
export interface DashboardAppState {
|
||||||
panels: SavedDashboardPanel[];
|
panels: SavedDashboardPanel[];
|
||||||
fullScreenMode: boolean;
|
fullScreenMode: boolean;
|
||||||
|
|
|
@ -25,22 +25,34 @@ import {
|
||||||
Logger,
|
Logger,
|
||||||
} from '../../../core/server';
|
} from '../../../core/server';
|
||||||
|
|
||||||
import { dashboardSavedObjectType } from './saved_objects';
|
import { createDashboardSavedObjectType } from './saved_objects';
|
||||||
import { capabilitiesProvider } from './capabilities_provider';
|
import { capabilitiesProvider } from './capabilities_provider';
|
||||||
|
|
||||||
import { DashboardPluginSetup, DashboardPluginStart } from './types';
|
import { DashboardPluginSetup, DashboardPluginStart } from './types';
|
||||||
|
import { EmbeddableSetup } from '../../embeddable/server';
|
||||||
|
|
||||||
export class DashboardPlugin implements Plugin<DashboardPluginSetup, DashboardPluginStart> {
|
interface SetupDeps {
|
||||||
|
embeddable: EmbeddableSetup;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class DashboardPlugin
|
||||||
|
implements Plugin<DashboardPluginSetup, DashboardPluginStart, SetupDeps> {
|
||||||
private readonly logger: Logger;
|
private readonly logger: Logger;
|
||||||
|
|
||||||
constructor(initializerContext: PluginInitializerContext) {
|
constructor(initializerContext: PluginInitializerContext) {
|
||||||
this.logger = initializerContext.logger.get();
|
this.logger = initializerContext.logger.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
public setup(core: CoreSetup) {
|
public setup(core: CoreSetup, plugins: SetupDeps) {
|
||||||
this.logger.debug('dashboard: Setup');
|
this.logger.debug('dashboard: Setup');
|
||||||
|
|
||||||
core.savedObjects.registerType(dashboardSavedObjectType);
|
core.savedObjects.registerType(
|
||||||
|
createDashboardSavedObjectType({
|
||||||
|
migrationDeps: {
|
||||||
|
embeddable: plugins.embeddable,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
core.capabilities.registerProvider(capabilitiesProvider);
|
core.capabilities.registerProvider(capabilitiesProvider);
|
||||||
|
|
||||||
return {};
|
return {};
|
||||||
|
|
|
@ -18,9 +18,16 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { SavedObjectsType } from 'kibana/server';
|
import { SavedObjectsType } from 'kibana/server';
|
||||||
import { dashboardSavedObjectTypeMigrations } from './dashboard_migrations';
|
import {
|
||||||
|
createDashboardSavedObjectTypeMigrations,
|
||||||
|
DashboardSavedObjectTypeMigrationsDeps,
|
||||||
|
} from './dashboard_migrations';
|
||||||
|
|
||||||
export const dashboardSavedObjectType: SavedObjectsType = {
|
export const createDashboardSavedObjectType = ({
|
||||||
|
migrationDeps,
|
||||||
|
}: {
|
||||||
|
migrationDeps: DashboardSavedObjectTypeMigrationsDeps;
|
||||||
|
}): SavedObjectsType => ({
|
||||||
name: 'dashboard',
|
name: 'dashboard',
|
||||||
hidden: false,
|
hidden: false,
|
||||||
namespaceType: 'single',
|
namespaceType: 'single',
|
||||||
|
@ -65,5 +72,5 @@ export const dashboardSavedObjectType: SavedObjectsType = {
|
||||||
version: { type: 'integer' },
|
version: { type: 'integer' },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
migrations: dashboardSavedObjectTypeMigrations,
|
migrations: createDashboardSavedObjectTypeMigrations(migrationDeps),
|
||||||
};
|
});
|
||||||
|
|
|
@ -19,7 +19,14 @@
|
||||||
|
|
||||||
import { SavedObjectUnsanitizedDoc } from 'kibana/server';
|
import { SavedObjectUnsanitizedDoc } from 'kibana/server';
|
||||||
import { savedObjectsServiceMock } from '../../../../core/server/mocks';
|
import { savedObjectsServiceMock } from '../../../../core/server/mocks';
|
||||||
import { dashboardSavedObjectTypeMigrations as migrations } from './dashboard_migrations';
|
import { createEmbeddableSetupMock } from '../../../embeddable/server/mocks';
|
||||||
|
import { createDashboardSavedObjectTypeMigrations } from './dashboard_migrations';
|
||||||
|
import { DashboardDoc730ToLatest } from '../../common';
|
||||||
|
|
||||||
|
const embeddableSetupMock = createEmbeddableSetupMock();
|
||||||
|
const migrations = createDashboardSavedObjectTypeMigrations({
|
||||||
|
embeddable: embeddableSetupMock,
|
||||||
|
});
|
||||||
|
|
||||||
const contextMock = savedObjectsServiceMock.createMigrationContext();
|
const contextMock = savedObjectsServiceMock.createMigrationContext();
|
||||||
|
|
||||||
|
@ -448,4 +455,50 @@ Object {
|
||||||
`);
|
`);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('7.11.0 - embeddable persistable state extraction', () => {
|
||||||
|
const migration = migrations['7.11.0'];
|
||||||
|
const doc: DashboardDoc730ToLatest = {
|
||||||
|
attributes: {
|
||||||
|
description: '',
|
||||||
|
kibanaSavedObjectMeta: {
|
||||||
|
searchSourceJSON:
|
||||||
|
'{"query":{"language":"kuery","query":""},"filter":[{"query":{"match_phrase":{"machine.os.keyword":"osx"}},"$state":{"store":"appState"},"meta":{"type":"phrase","key":"machine.os.keyword","params":{"query":"osx"},"disabled":false,"negate":false,"alias":null,"indexRefName":"kibanaSavedObjectMeta.searchSourceJSON.filter[0].meta.index"}}]}',
|
||||||
|
},
|
||||||
|
optionsJSON: '{"useMargins":true,"hidePanelTitles":false}',
|
||||||
|
panelsJSON:
|
||||||
|
'[{"version":"7.9.3","gridData":{"x":0,"y":0,"w":24,"h":15,"i":"82fa0882-9f9e-476a-bbb9-03555e5ced91"},"panelIndex":"82fa0882-9f9e-476a-bbb9-03555e5ced91","embeddableConfig":{"enhancements":{"dynamicActions":{"events":[]}}},"panelRefName":"panel_0"}]',
|
||||||
|
timeRestore: false,
|
||||||
|
title: 'Dashboard A',
|
||||||
|
version: 1,
|
||||||
|
},
|
||||||
|
id: '376e6260-1f5e-11eb-91aa-7b6d5f8a61d6',
|
||||||
|
references: [
|
||||||
|
{
|
||||||
|
id: '90943e30-9a47-11e8-b64d-95841ca0b247',
|
||||||
|
name: 'kibanaSavedObjectMeta.searchSourceJSON.filter[0].meta.index',
|
||||||
|
type: 'index-pattern',
|
||||||
|
},
|
||||||
|
{ id: '14e2e710-4258-11e8-b3aa-73fdaf54bfc9', name: 'panel_0', type: 'visualization' },
|
||||||
|
],
|
||||||
|
type: 'dashboard',
|
||||||
|
};
|
||||||
|
|
||||||
|
test('should migrate 7.3.0 doc without embeddable state to extract', () => {
|
||||||
|
const newDoc = migration(doc, contextMock);
|
||||||
|
expect(newDoc).toEqual(doc);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should migrate 7.3.0 doc and extract embeddable state', () => {
|
||||||
|
embeddableSetupMock.extract.mockImplementationOnce((state) => ({
|
||||||
|
state: { ...state, __extracted: true },
|
||||||
|
references: [{ id: '__new', name: '__newRefName', type: '__newType' }],
|
||||||
|
}));
|
||||||
|
|
||||||
|
const newDoc = migration(doc, contextMock);
|
||||||
|
expect(newDoc).not.toEqual(doc);
|
||||||
|
expect(newDoc.references).toHaveLength(doc.references.length + 1);
|
||||||
|
expect(JSON.parse(newDoc.attributes.panelsJSON)[0].embeddableConfig.__extracted).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -18,11 +18,12 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { get, flow } from 'lodash';
|
import { get, flow } from 'lodash';
|
||||||
|
import { SavedObjectAttributes, SavedObjectMigrationFn } from 'kibana/server';
|
||||||
import { SavedObjectMigrationFn } from 'kibana/server';
|
|
||||||
import { migrations730 } from './migrations_730';
|
import { migrations730 } from './migrations_730';
|
||||||
import { migrateMatchAllQuery } from './migrate_match_all_query';
|
import { migrateMatchAllQuery } from './migrate_match_all_query';
|
||||||
import { DashboardDoc700To720 } from '../../common';
|
import { DashboardDoc700To720, DashboardDoc730ToLatest } from '../../common';
|
||||||
|
import { EmbeddableSetup } from '../../../embeddable/server';
|
||||||
|
import { injectReferences, extractReferences } from '../../common/saved_dashboard_references';
|
||||||
|
|
||||||
function migrateIndexPattern(doc: DashboardDoc700To720) {
|
function migrateIndexPattern(doc: DashboardDoc700To720) {
|
||||||
const searchSourceJSON = get(doc, 'attributes.kibanaSavedObjectMeta.searchSourceJSON');
|
const searchSourceJSON = get(doc, 'attributes.kibanaSavedObjectMeta.searchSourceJSON');
|
||||||
|
@ -100,7 +101,57 @@ const migrations700: SavedObjectMigrationFn<any, any> = (doc): DashboardDoc700To
|
||||||
return doc as DashboardDoc700To720;
|
return doc as DashboardDoc700To720;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const dashboardSavedObjectTypeMigrations = {
|
/**
|
||||||
|
* In 7.8.0 we introduced dashboard drilldowns which are stored inside dashboard saved object as part of embeddable state
|
||||||
|
* In 7.11.0 we created an embeddable references/migrations system that allows to properly extract embeddable persistable state
|
||||||
|
* https://github.com/elastic/kibana/issues/71409
|
||||||
|
* The idea of this migration is to inject all the embeddable panel references and then run the extraction again.
|
||||||
|
* As the result of the extraction:
|
||||||
|
* 1. In addition to regular `panel_` we will get new references which are extracted by `embeddablePersistableStateService` (dashboard drilldown references)
|
||||||
|
* 2. `panel_` references will be regenerated
|
||||||
|
* All other references like index-patterns are forwarded non touched
|
||||||
|
* @param deps
|
||||||
|
*/
|
||||||
|
function createExtractPanelReferencesMigration(
|
||||||
|
deps: DashboardSavedObjectTypeMigrationsDeps
|
||||||
|
): SavedObjectMigrationFn<DashboardDoc730ToLatest['attributes']> {
|
||||||
|
return (doc) => {
|
||||||
|
const references = doc.references ?? [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remembering this because dashboard's extractReferences won't return those
|
||||||
|
* All other references like `panel_` will be overwritten
|
||||||
|
*/
|
||||||
|
const oldNonPanelReferences = references.filter((ref) => !ref.name.startsWith('panel_'));
|
||||||
|
|
||||||
|
const injectedAttributes = injectReferences(
|
||||||
|
{
|
||||||
|
attributes: (doc.attributes as unknown) as SavedObjectAttributes,
|
||||||
|
references,
|
||||||
|
},
|
||||||
|
{ embeddablePersistableStateService: deps.embeddable }
|
||||||
|
);
|
||||||
|
|
||||||
|
const { attributes, references: newPanelReferences } = extractReferences(
|
||||||
|
{ attributes: injectedAttributes, references: [] },
|
||||||
|
{ embeddablePersistableStateService: deps.embeddable }
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
...doc,
|
||||||
|
references: [...oldNonPanelReferences, ...newPanelReferences],
|
||||||
|
attributes,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DashboardSavedObjectTypeMigrationsDeps {
|
||||||
|
embeddable: EmbeddableSetup;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const createDashboardSavedObjectTypeMigrations = (
|
||||||
|
deps: DashboardSavedObjectTypeMigrationsDeps
|
||||||
|
) => ({
|
||||||
/**
|
/**
|
||||||
* We need to have this migration twice, once with a version prior to 7.0.0 once with a version
|
* We need to have this migration twice, once with a version prior to 7.0.0 once with a version
|
||||||
* after it. The reason for that is, that this migration has been introduced once 7.0.0 was already
|
* after it. The reason for that is, that this migration has been introduced once 7.0.0 was already
|
||||||
|
@ -115,4 +166,5 @@ export const dashboardSavedObjectTypeMigrations = {
|
||||||
'7.0.0': flow(migrations700),
|
'7.0.0': flow(migrations700),
|
||||||
'7.3.0': flow(migrations730),
|
'7.3.0': flow(migrations730),
|
||||||
'7.9.3': flow(migrateMatchAllQuery),
|
'7.9.3': flow(migrateMatchAllQuery),
|
||||||
};
|
'7.11.0': flow(createExtractPanelReferencesMigration(deps)),
|
||||||
|
});
|
||||||
|
|
|
@ -17,4 +17,4 @@
|
||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export { dashboardSavedObjectType } from './dashboard';
|
export { createDashboardSavedObjectType } from './dashboard';
|
||||||
|
|
|
@ -18,12 +18,16 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { savedObjectsServiceMock } from '../../../../core/server/mocks';
|
import { savedObjectsServiceMock } from '../../../../core/server/mocks';
|
||||||
import { dashboardSavedObjectTypeMigrations as migrations } from './dashboard_migrations';
|
import { createDashboardSavedObjectTypeMigrations } from './dashboard_migrations';
|
||||||
import { migrations730 } from './migrations_730';
|
import { migrations730 } from './migrations_730';
|
||||||
import { DashboardDoc700To720, DashboardDoc730ToLatest, DashboardDocPre700 } from '../../common';
|
import { DashboardDoc700To720, DashboardDoc730ToLatest, DashboardDocPre700 } from '../../common';
|
||||||
import { RawSavedDashboardPanel730ToLatest } from '../../common';
|
import { RawSavedDashboardPanel730ToLatest } from '../../common';
|
||||||
|
import { createEmbeddableSetupMock } from '../../../embeddable/server/mocks';
|
||||||
|
|
||||||
const mockContext = savedObjectsServiceMock.createMigrationContext();
|
const mockContext = savedObjectsServiceMock.createMigrationContext();
|
||||||
|
const migrations = createDashboardSavedObjectTypeMigrations({
|
||||||
|
embeddable: createEmbeddableSetupMock(),
|
||||||
|
});
|
||||||
|
|
||||||
test('dashboard migration 7.3.0 migrates filters to query on search source', () => {
|
test('dashboard migration 7.3.0 migrates filters to query on search source', () => {
|
||||||
const doc: DashboardDoc700To720 = {
|
const doc: DashboardDoc700To720 = {
|
||||||
|
|
21
src/plugins/embeddable/common/index.ts
Normal file
21
src/plugins/embeddable/common/index.ts
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
/*
|
||||||
|
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||||
|
* license agreements. See the NOTICE file distributed with
|
||||||
|
* this work for additional information regarding copyright
|
||||||
|
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||||
|
* the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
* not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export * from './types';
|
||||||
|
export * from './lib';
|
|
@ -22,3 +22,4 @@ export * from './inject';
|
||||||
export * from './migrate';
|
export * from './migrate';
|
||||||
export * from './migrate_base_input';
|
export * from './migrate_base_input';
|
||||||
export * from './telemetry';
|
export * from './telemetry';
|
||||||
|
export * from './saved_object_embeddable';
|
||||||
|
|
|
@ -17,7 +17,7 @@
|
||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { EmbeddableInput } from '..';
|
import { EmbeddableInput } from '../types';
|
||||||
|
|
||||||
export interface SavedObjectEmbeddableInput extends EmbeddableInput {
|
export interface SavedObjectEmbeddableInput extends EmbeddableInput {
|
||||||
savedObjectId: string;
|
savedObjectId: string;
|
31
src/plugins/embeddable/common/mocks.ts
Normal file
31
src/plugins/embeddable/common/mocks.ts
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
/*
|
||||||
|
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||||
|
* license agreements. See the NOTICE file distributed with
|
||||||
|
* this work for additional information regarding copyright
|
||||||
|
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||||
|
* the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
* not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { EmbeddablePersistableStateService } from './types';
|
||||||
|
|
||||||
|
export const createEmbeddablePersistableStateServiceMock = (): jest.Mocked<
|
||||||
|
EmbeddablePersistableStateService
|
||||||
|
> => {
|
||||||
|
return {
|
||||||
|
inject: jest.fn((state, references) => state),
|
||||||
|
extract: jest.fn((state) => ({ state, references: [] })),
|
||||||
|
migrate: jest.fn((state, version) => state),
|
||||||
|
telemetry: jest.fn((state, collector) => ({})),
|
||||||
|
};
|
||||||
|
};
|
|
@ -17,7 +17,7 @@
|
||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { SerializableState } from '../../kibana_utils/common';
|
import { PersistableStateService, SerializableState } from '../../kibana_utils/common';
|
||||||
import { Query, TimeRange } from '../../data/common/query';
|
import { Query, TimeRange } from '../../data/common/query';
|
||||||
import { Filter } from '../../data/common/es_query/filters';
|
import { Filter } from '../../data/common/es_query/filters';
|
||||||
|
|
||||||
|
@ -74,8 +74,21 @@ export type EmbeddableInput = {
|
||||||
searchSessionId?: string;
|
searchSessionId?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export interface PanelState<E extends EmbeddableInput & { id: string } = { id: string }> {
|
||||||
|
// The type of embeddable in this panel. Will be used to find the factory in which to
|
||||||
|
// load the embeddable.
|
||||||
|
type: string;
|
||||||
|
|
||||||
|
// Stores input for this embeddable that is specific to this embeddable. Other parts of embeddable input
|
||||||
|
// will be derived from the container's input. **Any state in here will override any state derived from
|
||||||
|
// the container.**
|
||||||
|
explicitInput: Partial<E> & { id: string };
|
||||||
|
}
|
||||||
|
|
||||||
export type EmbeddableStateWithType = EmbeddableInput & { type: string };
|
export type EmbeddableStateWithType = EmbeddableInput & { type: string };
|
||||||
|
|
||||||
|
export type EmbeddablePersistableStateService = PersistableStateService<EmbeddableStateWithType>;
|
||||||
|
|
||||||
export interface CommonEmbeddableStartContract {
|
export interface CommonEmbeddableStartContract {
|
||||||
getEmbeddableFactory: (embeddableFactoryId: string) => any;
|
getEmbeddableFactory: (embeddableFactoryId: string) => any;
|
||||||
getEnhancement: (enhancementId: string) => any;
|
getEnhancement: (enhancementId: string) => any;
|
||||||
|
|
|
@ -31,7 +31,7 @@ import {
|
||||||
import { IContainer, ContainerInput, ContainerOutput, PanelState } from './i_container';
|
import { IContainer, ContainerInput, ContainerOutput, PanelState } from './i_container';
|
||||||
import { PanelNotFoundError, EmbeddableFactoryNotFoundError } from '../errors';
|
import { PanelNotFoundError, EmbeddableFactoryNotFoundError } from '../errors';
|
||||||
import { EmbeddableStart } from '../../plugin';
|
import { EmbeddableStart } from '../../plugin';
|
||||||
import { isSavedObjectEmbeddableInput } from '../embeddables/saved_object_embeddable';
|
import { isSavedObjectEmbeddableInput } from '../../../common/lib/saved_object_embeddable';
|
||||||
|
|
||||||
const getKeys = <T extends {}>(o: T): Array<keyof T> => Object.keys(o) as Array<keyof T>;
|
const getKeys = <T extends {}>(o: T): Array<keyof T> => Object.keys(o) as Array<keyof T>;
|
||||||
|
|
||||||
|
|
|
@ -24,17 +24,9 @@ import {
|
||||||
ErrorEmbeddable,
|
ErrorEmbeddable,
|
||||||
IEmbeddable,
|
IEmbeddable,
|
||||||
} from '../embeddables';
|
} from '../embeddables';
|
||||||
|
import { PanelState } from '../../../common/types';
|
||||||
|
|
||||||
export interface PanelState<E extends EmbeddableInput & { id: string } = { id: string }> {
|
export { PanelState };
|
||||||
// The type of embeddable in this panel. Will be used to find the factory in which to
|
|
||||||
// load the embeddable.
|
|
||||||
type: string;
|
|
||||||
|
|
||||||
// Stores input for this embeddable that is specific to this embeddable. Other parts of embeddable input
|
|
||||||
// will be derived from the container's input. **Any state in here will override any state derived from
|
|
||||||
// the container.**
|
|
||||||
explicitInput: Partial<E> & { id: string };
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ContainerOutput extends EmbeddableOutput {
|
export interface ContainerOutput extends EmbeddableOutput {
|
||||||
embeddableLoaded: { [key: string]: boolean };
|
embeddableLoaded: { [key: string]: boolean };
|
||||||
|
|
|
@ -24,5 +24,5 @@ export * from './default_embeddable_factory_provider';
|
||||||
export { ErrorEmbeddable, isErrorEmbeddable } from './error_embeddable';
|
export { ErrorEmbeddable, isErrorEmbeddable } from './error_embeddable';
|
||||||
export { withEmbeddableSubscription } from './with_subscription';
|
export { withEmbeddableSubscription } from './with_subscription';
|
||||||
export { EmbeddableRoot } from './embeddable_root';
|
export { EmbeddableRoot } from './embeddable_root';
|
||||||
export * from './saved_object_embeddable';
|
export * from '../../../common/lib/saved_object_embeddable';
|
||||||
export { EmbeddableRenderer, EmbeddableRendererProps } from './embeddable_renderer';
|
export { EmbeddableRenderer, EmbeddableRendererProps } from './embeddable_renderer';
|
||||||
|
|
30
src/plugins/embeddable/server/mocks.ts
Normal file
30
src/plugins/embeddable/server/mocks.ts
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
/*
|
||||||
|
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||||
|
* license agreements. See the NOTICE file distributed with
|
||||||
|
* this work for additional information regarding copyright
|
||||||
|
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||||
|
* the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
* not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { createEmbeddablePersistableStateServiceMock } from '../common/mocks';
|
||||||
|
import { EmbeddableSetup, EmbeddableStart } from './plugin';
|
||||||
|
|
||||||
|
export const createEmbeddableSetupMock = (): jest.Mocked<EmbeddableSetup> => ({
|
||||||
|
...createEmbeddablePersistableStateServiceMock(),
|
||||||
|
registerEmbeddableFactory: jest.fn(),
|
||||||
|
registerEnhancement: jest.fn(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const createEmbeddableStartMock = (): jest.Mocked<EmbeddableStart> =>
|
||||||
|
createEmbeddablePersistableStateServiceMock();
|
|
@ -32,23 +32,32 @@ import {
|
||||||
getMigrateFunction,
|
getMigrateFunction,
|
||||||
getTelemetryFunction,
|
getTelemetryFunction,
|
||||||
} from '../common/lib';
|
} from '../common/lib';
|
||||||
import { SerializableState } from '../../kibana_utils/common';
|
import { PersistableStateService, SerializableState } from '../../kibana_utils/common';
|
||||||
import { EmbeddableStateWithType } from '../common/types';
|
import { EmbeddableStateWithType } from '../common/types';
|
||||||
|
|
||||||
export interface EmbeddableSetup {
|
export interface EmbeddableSetup extends PersistableStateService<EmbeddableStateWithType> {
|
||||||
getAttributeService: any;
|
|
||||||
registerEmbeddableFactory: (factory: EmbeddableRegistryDefinition) => void;
|
registerEmbeddableFactory: (factory: EmbeddableRegistryDefinition) => void;
|
||||||
registerEnhancement: (enhancement: EnhancementRegistryDefinition) => void;
|
registerEnhancement: (enhancement: EnhancementRegistryDefinition) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class EmbeddableServerPlugin implements Plugin<object, object> {
|
export type EmbeddableStart = PersistableStateService<EmbeddableStateWithType>;
|
||||||
|
|
||||||
|
export class EmbeddableServerPlugin implements Plugin<EmbeddableSetup, EmbeddableStart> {
|
||||||
private readonly embeddableFactories: EmbeddableFactoryRegistry = new Map();
|
private readonly embeddableFactories: EmbeddableFactoryRegistry = new Map();
|
||||||
private readonly enhancements: EnhancementsRegistry = new Map();
|
private readonly enhancements: EnhancementsRegistry = new Map();
|
||||||
|
|
||||||
public setup(core: CoreSetup) {
|
public setup(core: CoreSetup) {
|
||||||
|
const commonContract = {
|
||||||
|
getEmbeddableFactory: this.getEmbeddableFactory,
|
||||||
|
getEnhancement: this.getEnhancement,
|
||||||
|
};
|
||||||
return {
|
return {
|
||||||
registerEmbeddableFactory: this.registerEmbeddableFactory,
|
registerEmbeddableFactory: this.registerEmbeddableFactory,
|
||||||
registerEnhancement: this.registerEnhancement,
|
registerEnhancement: this.registerEnhancement,
|
||||||
|
telemetry: getTelemetryFunction(commonContract),
|
||||||
|
extract: getExtractFunction(commonContract),
|
||||||
|
inject: getInjectFunction(commonContract),
|
||||||
|
migrate: getMigrateFunction(commonContract),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -18,12 +18,11 @@ export interface EmbeddableRegistryDefinition<P extends EmbeddableStateWithType
|
||||||
id: string;
|
id: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Warning: (ae-forgotten-export) The symbol "PersistableStateService" needs to be exported by the entry point index.d.ts
|
||||||
// Warning: (ae-missing-release-tag) "EmbeddableSetup" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
// Warning: (ae-missing-release-tag) "EmbeddableSetup" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||||
//
|
//
|
||||||
// @public (undocumented)
|
// @public (undocumented)
|
||||||
export interface EmbeddableSetup {
|
export interface EmbeddableSetup extends PersistableStateService<EmbeddableStateWithType> {
|
||||||
// (undocumented)
|
|
||||||
getAttributeService: any;
|
|
||||||
// (undocumented)
|
// (undocumented)
|
||||||
registerEmbeddableFactory: (factory: EmbeddableRegistryDefinition) => void;
|
registerEmbeddableFactory: (factory: EmbeddableRegistryDefinition) => void;
|
||||||
// (undocumented)
|
// (undocumented)
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
/*
|
||||||
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||||
|
* or more contributor license agreements. Licensed under the Elastic License;
|
||||||
|
* you may not use this file except in compliance with the Elastic License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* NOTE: DO NOT CHANGE THIS STRING WITHOUT CAREFUL CONSIDERATOIN, BECAUSE IT IS
|
||||||
|
* STORED IN SAVED OBJECTS.
|
||||||
|
*
|
||||||
|
* Also temporary dashboard drilldown migration code inside embeddable plugin relies on it
|
||||||
|
* x-pack/plugins/embeddable_enhanced/public/embeddables/embeddable_action_storage.ts
|
||||||
|
*/
|
||||||
|
export const EMBEDDABLE_TO_DASHBOARD_DRILLDOWN = 'DASHBOARD_TO_DASHBOARD_DRILLDOWN';
|
|
@ -0,0 +1,48 @@
|
||||||
|
/*
|
||||||
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||||
|
* or more contributor license agreements. Licensed under the Elastic License;
|
||||||
|
* you may not use this file except in compliance with the Elastic License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { createExtract, createInject } from './dashboard_drilldown_persistable_state';
|
||||||
|
import { SerializedEvent } from '../../../../ui_actions_enhanced/common';
|
||||||
|
|
||||||
|
const drilldownId = 'test_id';
|
||||||
|
const extract = createExtract({ drilldownId });
|
||||||
|
const inject = createInject({ drilldownId });
|
||||||
|
|
||||||
|
const state: SerializedEvent = {
|
||||||
|
eventId: 'event_id',
|
||||||
|
triggers: [],
|
||||||
|
action: {
|
||||||
|
factoryId: drilldownId,
|
||||||
|
name: 'name',
|
||||||
|
config: {
|
||||||
|
dashboardId: 'dashboardId_1',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
test('should extract and injected dashboard reference', () => {
|
||||||
|
const { state: extractedState, references } = extract(state);
|
||||||
|
expect(extractedState).not.toEqual(state);
|
||||||
|
expect(extractedState.action.config.dashboardId).toBeUndefined();
|
||||||
|
expect(references).toMatchInlineSnapshot(`
|
||||||
|
Array [
|
||||||
|
Object {
|
||||||
|
"id": "dashboardId_1",
|
||||||
|
"name": "drilldown:test_id:event_id:dashboardId",
|
||||||
|
"type": "dashboard",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
`);
|
||||||
|
|
||||||
|
let injectedState = inject(extractedState, references);
|
||||||
|
expect(injectedState).toEqual(state);
|
||||||
|
|
||||||
|
references[0].id = 'dashboardId_2';
|
||||||
|
|
||||||
|
injectedState = inject(extractedState, references);
|
||||||
|
expect(injectedState).not.toEqual(extractedState);
|
||||||
|
expect(injectedState.action.config.dashboardId).toBe('dashboardId_2');
|
||||||
|
});
|
|
@ -0,0 +1,75 @@
|
||||||
|
/*
|
||||||
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||||
|
* or more contributor license agreements. Licensed under the Elastic License;
|
||||||
|
* you may not use this file except in compliance with the Elastic License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { SavedObjectReference } from '../../../../../../src/core/types';
|
||||||
|
import { PersistableStateService } from '../../../../../../src/plugins/kibana_utils/common';
|
||||||
|
import { SerializedAction, SerializedEvent } from '../../../../ui_actions_enhanced/common';
|
||||||
|
import { DrilldownConfig } from './types';
|
||||||
|
|
||||||
|
type DashboardDrilldownPersistableState = PersistableStateService<SerializedEvent>;
|
||||||
|
|
||||||
|
const generateRefName = (state: SerializedEvent, id: string) =>
|
||||||
|
`drilldown:${id}:${state.eventId}:dashboardId`;
|
||||||
|
|
||||||
|
const injectDashboardId = (state: SerializedEvent, dashboardId: string): SerializedEvent => {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
action: {
|
||||||
|
...state.action,
|
||||||
|
config: {
|
||||||
|
...state.action.config,
|
||||||
|
dashboardId,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const createInject = ({
|
||||||
|
drilldownId,
|
||||||
|
}: {
|
||||||
|
drilldownId: string;
|
||||||
|
}): DashboardDrilldownPersistableState['inject'] => {
|
||||||
|
return (state: SerializedEvent, references: SavedObjectReference[]) => {
|
||||||
|
const action = state.action as SerializedAction<DrilldownConfig>;
|
||||||
|
const refName = generateRefName(state, drilldownId);
|
||||||
|
const ref = references.find((r) => r.name === refName);
|
||||||
|
if (!ref) return state;
|
||||||
|
if (ref.id && ref.id === action.config.dashboardId) return state;
|
||||||
|
return injectDashboardId(state, ref.id);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const createExtract = ({
|
||||||
|
drilldownId,
|
||||||
|
}: {
|
||||||
|
drilldownId: string;
|
||||||
|
}): DashboardDrilldownPersistableState['extract'] => {
|
||||||
|
return (state: SerializedEvent) => {
|
||||||
|
const action = state.action as SerializedAction<DrilldownConfig>;
|
||||||
|
const references: SavedObjectReference[] = action.config.dashboardId
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
name: generateRefName(state, drilldownId),
|
||||||
|
type: 'dashboard',
|
||||||
|
id: action.config.dashboardId,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
: [];
|
||||||
|
|
||||||
|
const { dashboardId, ...restOfConfig } = action.config;
|
||||||
|
|
||||||
|
return {
|
||||||
|
state: {
|
||||||
|
...state,
|
||||||
|
action: ({
|
||||||
|
...state.action,
|
||||||
|
config: restOfConfig,
|
||||||
|
} as unknown) as SerializedAction,
|
||||||
|
},
|
||||||
|
references,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
|
@ -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;
|
||||||
|
* you may not use this file except in compliance with the Elastic License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export { createExtract, createInject } from './dashboard_drilldown_persistable_state';
|
||||||
|
export { EMBEDDABLE_TO_DASHBOARD_DRILLDOWN } from './constants';
|
||||||
|
export { DrilldownConfig } from './types';
|
|
@ -0,0 +1,12 @@
|
||||||
|
/*
|
||||||
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||||
|
* or more contributor license agreements. Licensed under the Elastic License;
|
||||||
|
* you may not use this file except in compliance with the Elastic License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/consistent-type-definitions
|
||||||
|
export type DrilldownConfig = {
|
||||||
|
dashboardId?: string;
|
||||||
|
useCurrentFilters: boolean;
|
||||||
|
useCurrentDateRange: boolean;
|
||||||
|
};
|
|
@ -0,0 +1,7 @@
|
||||||
|
/*
|
||||||
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||||
|
* or more contributor license agreements. Licensed under the Elastic License;
|
||||||
|
* you may not use this file except in compliance with the Elastic License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export * from './dashboard_drilldown';
|
7
x-pack/plugins/dashboard_enhanced/common/index.ts
Normal file
7
x-pack/plugins/dashboard_enhanced/common/index.ts
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
/*
|
||||||
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||||
|
* or more contributor license agreements. Licensed under the Elastic License;
|
||||||
|
* you may not use this file except in compliance with the Elastic License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export * from './drilldowns';
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"id": "dashboardEnhanced",
|
"id": "dashboardEnhanced",
|
||||||
"version": "kibana",
|
"version": "kibana",
|
||||||
"server": false,
|
"server": true,
|
||||||
"ui": true,
|
"ui": true,
|
||||||
"requiredPlugins": ["data", "uiActionsEnhanced", "embeddable", "dashboard", "share"],
|
"requiredPlugins": ["data", "uiActionsEnhanced", "embeddable", "dashboard", "share"],
|
||||||
"configPath": ["xpack", "dashboardEnhanced"],
|
"configPath": ["xpack", "dashboardEnhanced"],
|
||||||
|
|
|
@ -9,19 +9,19 @@ import { DataPublicPluginStart } from 'src/plugins/data/public';
|
||||||
import { DashboardStart } from 'src/plugins/dashboard/public';
|
import { DashboardStart } from 'src/plugins/dashboard/public';
|
||||||
import { reactToUiComponent } from '../../../../../../../src/plugins/kibana_react/public';
|
import { reactToUiComponent } from '../../../../../../../src/plugins/kibana_react/public';
|
||||||
import {
|
import {
|
||||||
TriggerId,
|
|
||||||
TriggerContextMapping,
|
TriggerContextMapping,
|
||||||
|
TriggerId,
|
||||||
} from '../../../../../../../src/plugins/ui_actions/public';
|
} from '../../../../../../../src/plugins/ui_actions/public';
|
||||||
import { CollectConfigContainer } from './components';
|
import { CollectConfigContainer } from './components';
|
||||||
import {
|
import {
|
||||||
UiActionsEnhancedDrilldownDefinition as Drilldown,
|
|
||||||
UiActionsEnhancedBaseActionFactoryContext as BaseActionFactoryContext,
|
|
||||||
AdvancedUiActionsStart,
|
AdvancedUiActionsStart,
|
||||||
|
UiActionsEnhancedBaseActionFactoryContext as BaseActionFactoryContext,
|
||||||
|
UiActionsEnhancedDrilldownDefinition as Drilldown,
|
||||||
} from '../../../../../ui_actions_enhanced/public';
|
} from '../../../../../ui_actions_enhanced/public';
|
||||||
import { txtGoToDashboard } from './i18n';
|
import { txtGoToDashboard } from './i18n';
|
||||||
import {
|
import {
|
||||||
StartServicesGetter,
|
|
||||||
CollectConfigProps,
|
CollectConfigProps,
|
||||||
|
StartServicesGetter,
|
||||||
} from '../../../../../../../src/plugins/kibana_utils/public';
|
} from '../../../../../../../src/plugins/kibana_utils/public';
|
||||||
import { KibanaURL } from '../../../../../../../src/plugins/share/public';
|
import { KibanaURL } from '../../../../../../../src/plugins/share/public';
|
||||||
import { Config } from './types';
|
import { Config } from './types';
|
||||||
|
|
|
@ -6,12 +6,8 @@
|
||||||
|
|
||||||
import { UiActionsEnhancedBaseActionFactoryContext } from '../../../../../ui_actions_enhanced/public';
|
import { UiActionsEnhancedBaseActionFactoryContext } from '../../../../../ui_actions_enhanced/public';
|
||||||
import { APPLY_FILTER_TRIGGER } from '../../../../../../../src/plugins/ui_actions/public';
|
import { APPLY_FILTER_TRIGGER } from '../../../../../../../src/plugins/ui_actions/public';
|
||||||
|
import { DrilldownConfig } from '../../../../common';
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/consistent-type-definitions
|
export type Config = DrilldownConfig;
|
||||||
export type Config = {
|
|
||||||
dashboardId?: string;
|
|
||||||
useCurrentFilters: boolean;
|
|
||||||
useCurrentDateRange: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type FactoryContext = UiActionsEnhancedBaseActionFactoryContext<typeof APPLY_FILTER_TRIGGER>;
|
export type FactoryContext = UiActionsEnhancedBaseActionFactoryContext<typeof APPLY_FILTER_TRIGGER>;
|
||||||
|
|
|
@ -65,6 +65,12 @@ test('getHref is defined', () => {
|
||||||
expect(drilldown.getHref).toBeDefined();
|
expect(drilldown.getHref).toBeDefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('inject/extract are defined', () => {
|
||||||
|
const drilldown = new EmbeddableToDashboardDrilldown({} as any);
|
||||||
|
expect(drilldown.extract).toBeDefined();
|
||||||
|
expect(drilldown.inject).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
describe('.execute() & getHref', () => {
|
describe('.execute() & getHref', () => {
|
||||||
/**
|
/**
|
||||||
* A convenience test setup helper
|
* A convenience test setup helper
|
||||||
|
|
|
@ -22,6 +22,7 @@ import {
|
||||||
} from '../abstract_dashboard_drilldown';
|
} from '../abstract_dashboard_drilldown';
|
||||||
import { KibanaURL } from '../../../../../../../src/plugins/share/public';
|
import { KibanaURL } from '../../../../../../../src/plugins/share/public';
|
||||||
import { EMBEDDABLE_TO_DASHBOARD_DRILLDOWN } from './constants';
|
import { EMBEDDABLE_TO_DASHBOARD_DRILLDOWN } from './constants';
|
||||||
|
import { createExtract, createInject } from '../../../../common';
|
||||||
|
|
||||||
type Trigger = typeof APPLY_FILTER_TRIGGER;
|
type Trigger = typeof APPLY_FILTER_TRIGGER;
|
||||||
type Context = TriggerContextMapping[Trigger];
|
type Context = TriggerContextMapping[Trigger];
|
||||||
|
@ -80,4 +81,8 @@ export class EmbeddableToDashboardDrilldown extends AbstractDashboardDrilldown<T
|
||||||
|
|
||||||
return url;
|
return url;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public readonly inject = createInject({ drilldownId: this.id });
|
||||||
|
|
||||||
|
public readonly extract = createExtract({ drilldownId: this.id });
|
||||||
}
|
}
|
||||||
|
|
19
x-pack/plugins/dashboard_enhanced/server/index.ts
Normal file
19
x-pack/plugins/dashboard_enhanced/server/index.ts
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||||
|
* or more contributor license agreements. Licensed under the Elastic License;
|
||||||
|
* you may not use this file except in compliance with the Elastic License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { PluginInitializerContext } from 'src/core/server';
|
||||||
|
import { DashboardEnhancedPlugin } from './plugin';
|
||||||
|
|
||||||
|
export {
|
||||||
|
SetupContract as DashboardEnhancedSetupContract,
|
||||||
|
SetupDependencies as DashboardEnhancedSetupDependencies,
|
||||||
|
StartContract as DashboardEnhancedStartContract,
|
||||||
|
StartDependencies as DashboardEnhancedStartDependencies,
|
||||||
|
} from './plugin';
|
||||||
|
|
||||||
|
export function plugin(context: PluginInitializerContext) {
|
||||||
|
return new DashboardEnhancedPlugin(context);
|
||||||
|
}
|
44
x-pack/plugins/dashboard_enhanced/server/plugin.ts
Normal file
44
x-pack/plugins/dashboard_enhanced/server/plugin.ts
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
/*
|
||||||
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||||
|
* or more contributor license agreements. Licensed under the Elastic License;
|
||||||
|
* you may not use this file except in compliance with the Elastic License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { CoreSetup, CoreStart, Plugin, PluginInitializerContext } from 'src/core/server';
|
||||||
|
import { AdvancedUiActionsSetup, AdvancedUiActionsStart } from '../../ui_actions_enhanced/server';
|
||||||
|
import { EMBEDDABLE_TO_DASHBOARD_DRILLDOWN, createExtract, createInject } from '../common';
|
||||||
|
|
||||||
|
export interface SetupDependencies {
|
||||||
|
uiActionsEnhanced: AdvancedUiActionsSetup;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface StartDependencies {
|
||||||
|
uiActionsEnhanced: AdvancedUiActionsStart;
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line
|
||||||
|
export interface SetupContract {}
|
||||||
|
|
||||||
|
// eslint-disable-next-line
|
||||||
|
export interface StartContract {}
|
||||||
|
|
||||||
|
export class DashboardEnhancedPlugin
|
||||||
|
implements Plugin<SetupContract, StartContract, SetupDependencies, StartDependencies> {
|
||||||
|
constructor(protected readonly context: PluginInitializerContext) {}
|
||||||
|
|
||||||
|
public setup(core: CoreSetup<StartDependencies>, plugins: SetupDependencies): SetupContract {
|
||||||
|
plugins.uiActionsEnhanced.registerActionFactory({
|
||||||
|
id: EMBEDDABLE_TO_DASHBOARD_DRILLDOWN,
|
||||||
|
inject: createInject({ drilldownId: EMBEDDABLE_TO_DASHBOARD_DRILLDOWN }),
|
||||||
|
extract: createExtract({ drilldownId: EMBEDDABLE_TO_DASHBOARD_DRILLDOWN }),
|
||||||
|
});
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
public start(core: CoreStart, plugins: StartDependencies): StartContract {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
public stop() {}
|
||||||
|
}
|
7
x-pack/plugins/ui_actions_enhanced/common/index.ts
Normal file
7
x-pack/plugins/ui_actions_enhanced/common/index.ts
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
/*
|
||||||
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||||
|
* or more contributor license agreements. Licensed under the Elastic License;
|
||||||
|
* you may not use this file except in compliance with the Elastic License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export * from './types';
|
|
@ -7,11 +7,11 @@
|
||||||
import { EnhancementRegistryDefinition } from '../../../../src/plugins/embeddable/server';
|
import { EnhancementRegistryDefinition } from '../../../../src/plugins/embeddable/server';
|
||||||
import { SavedObjectReference } from '../../../../src/core/types';
|
import { SavedObjectReference } from '../../../../src/core/types';
|
||||||
import { DynamicActionsState, SerializedEvent } from './types';
|
import { DynamicActionsState, SerializedEvent } from './types';
|
||||||
import { AdvancedUiActionsPublicPlugin } from './plugin';
|
import { AdvancedUiActionsServerPlugin } from './plugin';
|
||||||
import { SerializableState } from '../../../../src/plugins/kibana_utils/common';
|
import { SerializableState } from '../../../../src/plugins/kibana_utils/common';
|
||||||
|
|
||||||
export const dynamicActionEnhancement = (
|
export const dynamicActionEnhancement = (
|
||||||
uiActionsEnhanced: AdvancedUiActionsPublicPlugin
|
uiActionsEnhanced: AdvancedUiActionsServerPlugin
|
||||||
): EnhancementRegistryDefinition => {
|
): EnhancementRegistryDefinition => {
|
||||||
return {
|
return {
|
||||||
id: 'dynamicActions',
|
id: 'dynamicActions',
|
||||||
|
|
|
@ -4,13 +4,13 @@
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
* you may not use this file except in compliance with the Elastic License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { AdvancedUiActionsPublicPlugin } from './plugin';
|
import { AdvancedUiActionsServerPlugin } from './plugin';
|
||||||
|
|
||||||
export function plugin() {
|
export function plugin() {
|
||||||
return new AdvancedUiActionsPublicPlugin();
|
return new AdvancedUiActionsServerPlugin();
|
||||||
}
|
}
|
||||||
|
|
||||||
export { AdvancedUiActionsPublicPlugin as Plugin };
|
export { AdvancedUiActionsServerPlugin as Plugin };
|
||||||
export {
|
export {
|
||||||
SetupContract as AdvancedUiActionsSetup,
|
SetupContract as AdvancedUiActionsSetup,
|
||||||
StartContract as AdvancedUiActionsStart,
|
StartContract as AdvancedUiActionsStart,
|
||||||
|
|
|
@ -16,7 +16,7 @@ import {
|
||||||
} from './types';
|
} from './types';
|
||||||
|
|
||||||
export interface SetupContract {
|
export interface SetupContract {
|
||||||
registerActionFactory: any;
|
registerActionFactory: (definition: ActionFactoryDefinition) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type StartContract = void;
|
export type StartContract = void;
|
||||||
|
@ -25,7 +25,7 @@ interface SetupDependencies {
|
||||||
embeddable: EmbeddableSetup; // Embeddable are needed because they register basic triggers/actions.
|
embeddable: EmbeddableSetup; // Embeddable are needed because they register basic triggers/actions.
|
||||||
}
|
}
|
||||||
|
|
||||||
export class AdvancedUiActionsPublicPlugin
|
export class AdvancedUiActionsServerPlugin
|
||||||
implements Plugin<SetupContract, StartContract, SetupDependencies> {
|
implements Plugin<SetupContract, StartContract, SetupDependencies> {
|
||||||
protected readonly actionFactories: ActionFactoryRegistry = new Map();
|
protected readonly actionFactories: ActionFactoryRegistry = new Map();
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,14 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
||||||
const dashboardPanelActions = getService('dashboardPanelActions');
|
const dashboardPanelActions = getService('dashboardPanelActions');
|
||||||
const dashboardDrilldownPanelActions = getService('dashboardDrilldownPanelActions');
|
const dashboardDrilldownPanelActions = getService('dashboardDrilldownPanelActions');
|
||||||
const dashboardDrilldownsManage = getService('dashboardDrilldownsManage');
|
const dashboardDrilldownsManage = getService('dashboardDrilldownsManage');
|
||||||
const PageObjects = getPageObjects(['dashboard', 'common', 'header', 'timePicker']);
|
const PageObjects = getPageObjects([
|
||||||
|
'dashboard',
|
||||||
|
'common',
|
||||||
|
'header',
|
||||||
|
'timePicker',
|
||||||
|
'settings',
|
||||||
|
'copySavedObjectsToSpace',
|
||||||
|
]);
|
||||||
const pieChart = getService('pieChart');
|
const pieChart = getService('pieChart');
|
||||||
const log = getService('log');
|
const log = getService('log');
|
||||||
const browser = getService('browser');
|
const browser = getService('browser');
|
||||||
|
@ -22,120 +29,188 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
||||||
const testSubjects = getService('testSubjects');
|
const testSubjects = getService('testSubjects');
|
||||||
const filterBar = getService('filterBar');
|
const filterBar = getService('filterBar');
|
||||||
const security = getService('security');
|
const security = getService('security');
|
||||||
|
const spaces = getService('spaces');
|
||||||
|
|
||||||
describe('Dashboard to dashboard drilldown', function () {
|
describe('Dashboard to dashboard drilldown', function () {
|
||||||
before(async () => {
|
describe('Create & use drilldowns', () => {
|
||||||
log.debug('Dashboard Drilldowns:initTests');
|
before(async () => {
|
||||||
await security.testUser.setRoles(['test_logstash_reader', 'global_dashboard_all']);
|
log.debug('Dashboard Drilldowns:initTests');
|
||||||
await PageObjects.common.navigateToApp('dashboard');
|
await security.testUser.setRoles(['test_logstash_reader', 'global_dashboard_all']);
|
||||||
await PageObjects.dashboard.preserveCrossAppState();
|
await PageObjects.common.navigateToApp('dashboard');
|
||||||
|
await PageObjects.dashboard.preserveCrossAppState();
|
||||||
|
});
|
||||||
|
|
||||||
|
after(async () => {
|
||||||
|
await security.testUser.restoreDefaults();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create dashboard to dashboard drilldown, use it, and then delete it', async () => {
|
||||||
|
await PageObjects.dashboard.gotoDashboardEditMode(
|
||||||
|
dashboardDrilldownsManage.DASHBOARD_WITH_PIE_CHART_NAME
|
||||||
|
);
|
||||||
|
|
||||||
|
// create drilldown
|
||||||
|
await dashboardPanelActions.openContextMenu();
|
||||||
|
await dashboardDrilldownPanelActions.expectExistsCreateDrilldownAction();
|
||||||
|
await dashboardDrilldownPanelActions.clickCreateDrilldown();
|
||||||
|
await dashboardDrilldownsManage.expectsCreateDrilldownFlyoutOpen();
|
||||||
|
await dashboardDrilldownsManage.fillInDashboardToDashboardDrilldownWizard({
|
||||||
|
drilldownName: DRILLDOWN_TO_AREA_CHART_NAME,
|
||||||
|
destinationDashboardTitle: dashboardDrilldownsManage.DASHBOARD_WITH_AREA_CHART_NAME,
|
||||||
|
});
|
||||||
|
await dashboardDrilldownsManage.saveChanges();
|
||||||
|
await dashboardDrilldownsManage.expectsCreateDrilldownFlyoutClose();
|
||||||
|
|
||||||
|
// check that drilldown notification badge is shown
|
||||||
|
expect(await PageObjects.dashboard.getPanelDrilldownCount()).to.be(1);
|
||||||
|
|
||||||
|
// save dashboard, navigate to view mode
|
||||||
|
await PageObjects.dashboard.saveDashboard(
|
||||||
|
dashboardDrilldownsManage.DASHBOARD_WITH_PIE_CHART_NAME,
|
||||||
|
{
|
||||||
|
saveAsNew: false,
|
||||||
|
waitDialogIsClosed: true,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// trigger drilldown action by clicking on a pie and picking drilldown action by it's name
|
||||||
|
await pieChart.clickOnPieSlice('40,000');
|
||||||
|
await dashboardDrilldownPanelActions.expectMultipleActionsMenuOpened();
|
||||||
|
|
||||||
|
const href = await dashboardDrilldownPanelActions.getActionHrefByText(
|
||||||
|
DRILLDOWN_TO_AREA_CHART_NAME
|
||||||
|
);
|
||||||
|
expect(typeof href).to.be('string'); // checking that action has a href
|
||||||
|
const dashboardIdFromHref = PageObjects.dashboard.getDashboardIdFromUrl(href);
|
||||||
|
|
||||||
|
await navigateWithinDashboard(async () => {
|
||||||
|
await dashboardDrilldownPanelActions.clickActionByText(DRILLDOWN_TO_AREA_CHART_NAME);
|
||||||
|
});
|
||||||
|
// checking that href is at least pointing to the same dashboard that we are navigated to by regular click
|
||||||
|
expect(dashboardIdFromHref).to.be(
|
||||||
|
await PageObjects.dashboard.getDashboardIdFromCurrentUrl()
|
||||||
|
);
|
||||||
|
|
||||||
|
// check that we drilled-down with filter from pie chart
|
||||||
|
expect(await filterBar.getFilterCount()).to.be(1);
|
||||||
|
|
||||||
|
const originalTimeRangeDurationHours = await PageObjects.timePicker.getTimeDurationInHours();
|
||||||
|
|
||||||
|
// brush area chart and drilldown back to pie chat dashboard
|
||||||
|
await brushAreaChart();
|
||||||
|
await dashboardDrilldownPanelActions.expectMultipleActionsMenuOpened();
|
||||||
|
|
||||||
|
await navigateWithinDashboard(async () => {
|
||||||
|
await dashboardDrilldownPanelActions.clickActionByText(DRILLDOWN_TO_PIE_CHART_NAME);
|
||||||
|
});
|
||||||
|
|
||||||
|
// because filters are preserved during navigation, we expect that only one slice is displayed (filter is still applied)
|
||||||
|
expect(await filterBar.getFilterCount()).to.be(1);
|
||||||
|
await pieChart.expectPieSliceCount(1);
|
||||||
|
|
||||||
|
// check that new time range duration was applied
|
||||||
|
const newTimeRangeDurationHours = await PageObjects.timePicker.getTimeDurationInHours();
|
||||||
|
expect(newTimeRangeDurationHours).to.be.lessThan(originalTimeRangeDurationHours);
|
||||||
|
|
||||||
|
// delete drilldown
|
||||||
|
await PageObjects.dashboard.switchToEditMode();
|
||||||
|
await dashboardPanelActions.openContextMenu();
|
||||||
|
await dashboardDrilldownPanelActions.expectExistsManageDrilldownsAction();
|
||||||
|
await dashboardDrilldownPanelActions.clickManageDrilldowns();
|
||||||
|
await dashboardDrilldownsManage.expectsManageDrilldownsFlyoutOpen();
|
||||||
|
|
||||||
|
await dashboardDrilldownsManage.deleteDrilldownsByTitles([DRILLDOWN_TO_AREA_CHART_NAME]);
|
||||||
|
await dashboardDrilldownsManage.closeFlyout();
|
||||||
|
|
||||||
|
// check that drilldown notification badge is shown
|
||||||
|
expect(await PageObjects.dashboard.getPanelDrilldownCount()).to.be(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('browser back/forward navigation works after drilldown navigation', async () => {
|
||||||
|
await PageObjects.dashboard.loadSavedDashboard(
|
||||||
|
dashboardDrilldownsManage.DASHBOARD_WITH_AREA_CHART_NAME
|
||||||
|
);
|
||||||
|
const originalTimeRangeDurationHours = await PageObjects.timePicker.getTimeDurationInHours();
|
||||||
|
await brushAreaChart();
|
||||||
|
await dashboardDrilldownPanelActions.expectMultipleActionsMenuOpened();
|
||||||
|
await navigateWithinDashboard(async () => {
|
||||||
|
await dashboardDrilldownPanelActions.clickActionByText(DRILLDOWN_TO_PIE_CHART_NAME);
|
||||||
|
});
|
||||||
|
// check that new time range duration was applied
|
||||||
|
const newTimeRangeDurationHours = await PageObjects.timePicker.getTimeDurationInHours();
|
||||||
|
expect(newTimeRangeDurationHours).to.be.lessThan(originalTimeRangeDurationHours);
|
||||||
|
|
||||||
|
await navigateWithinDashboard(async () => {
|
||||||
|
await browser.goBack();
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(await PageObjects.timePicker.getTimeDurationInHours()).to.be(
|
||||||
|
originalTimeRangeDurationHours
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
after(async () => {
|
describe('Copy to space', () => {
|
||||||
await security.testUser.restoreDefaults();
|
const destinationSpaceId = 'custom_space';
|
||||||
});
|
before(async () => {
|
||||||
|
await spaces.create({
|
||||||
it('should create dashboard to dashboard drilldown, use it, and then delete it', async () => {
|
id: destinationSpaceId,
|
||||||
await PageObjects.dashboard.gotoDashboardEditMode(
|
name: 'custom_space',
|
||||||
dashboardDrilldownsManage.DASHBOARD_WITH_PIE_CHART_NAME
|
disabledFeatures: [],
|
||||||
);
|
});
|
||||||
|
await PageObjects.settings.navigateTo();
|
||||||
// create drilldown
|
await PageObjects.settings.clickKibanaSavedObjects();
|
||||||
await dashboardPanelActions.openContextMenu();
|
|
||||||
await dashboardDrilldownPanelActions.expectExistsCreateDrilldownAction();
|
|
||||||
await dashboardDrilldownPanelActions.clickCreateDrilldown();
|
|
||||||
await dashboardDrilldownsManage.expectsCreateDrilldownFlyoutOpen();
|
|
||||||
await dashboardDrilldownsManage.fillInDashboardToDashboardDrilldownWizard({
|
|
||||||
drilldownName: DRILLDOWN_TO_AREA_CHART_NAME,
|
|
||||||
destinationDashboardTitle: dashboardDrilldownsManage.DASHBOARD_WITH_AREA_CHART_NAME,
|
|
||||||
});
|
|
||||||
await dashboardDrilldownsManage.saveChanges();
|
|
||||||
await dashboardDrilldownsManage.expectsCreateDrilldownFlyoutClose();
|
|
||||||
|
|
||||||
// check that drilldown notification badge is shown
|
|
||||||
expect(await PageObjects.dashboard.getPanelDrilldownCount()).to.be(1);
|
|
||||||
|
|
||||||
// save dashboard, navigate to view mode
|
|
||||||
await PageObjects.dashboard.saveDashboard(
|
|
||||||
dashboardDrilldownsManage.DASHBOARD_WITH_PIE_CHART_NAME,
|
|
||||||
{
|
|
||||||
saveAsNew: false,
|
|
||||||
waitDialogIsClosed: true,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
// trigger drilldown action by clicking on a pie and picking drilldown action by it's name
|
|
||||||
await pieChart.clickOnPieSlice('40,000');
|
|
||||||
await dashboardDrilldownPanelActions.expectMultipleActionsMenuOpened();
|
|
||||||
|
|
||||||
const href = await dashboardDrilldownPanelActions.getActionHrefByText(
|
|
||||||
DRILLDOWN_TO_AREA_CHART_NAME
|
|
||||||
);
|
|
||||||
expect(typeof href).to.be('string'); // checking that action has a href
|
|
||||||
const dashboardIdFromHref = PageObjects.dashboard.getDashboardIdFromUrl(href);
|
|
||||||
|
|
||||||
await navigateWithinDashboard(async () => {
|
|
||||||
await dashboardDrilldownPanelActions.clickActionByText(DRILLDOWN_TO_AREA_CHART_NAME);
|
|
||||||
});
|
|
||||||
// checking that href is at least pointing to the same dashboard that we are navigated to by regular click
|
|
||||||
expect(dashboardIdFromHref).to.be(await PageObjects.dashboard.getDashboardIdFromCurrentUrl());
|
|
||||||
|
|
||||||
// check that we drilled-down with filter from pie chart
|
|
||||||
expect(await filterBar.getFilterCount()).to.be(1);
|
|
||||||
|
|
||||||
const originalTimeRangeDurationHours = await PageObjects.timePicker.getTimeDurationInHours();
|
|
||||||
|
|
||||||
// brush area chart and drilldown back to pie chat dashboard
|
|
||||||
await brushAreaChart();
|
|
||||||
await dashboardDrilldownPanelActions.expectMultipleActionsMenuOpened();
|
|
||||||
|
|
||||||
await navigateWithinDashboard(async () => {
|
|
||||||
await dashboardDrilldownPanelActions.clickActionByText(DRILLDOWN_TO_PIE_CHART_NAME);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// because filters are preserved during navigation, we expect that only one slice is displayed (filter is still applied)
|
after(async () => {
|
||||||
expect(await filterBar.getFilterCount()).to.be(1);
|
await spaces.delete(destinationSpaceId);
|
||||||
await pieChart.expectPieSliceCount(1);
|
|
||||||
|
|
||||||
// check that new time range duration was applied
|
|
||||||
const newTimeRangeDurationHours = await PageObjects.timePicker.getTimeDurationInHours();
|
|
||||||
expect(newTimeRangeDurationHours).to.be.lessThan(originalTimeRangeDurationHours);
|
|
||||||
|
|
||||||
// delete drilldown
|
|
||||||
await PageObjects.dashboard.switchToEditMode();
|
|
||||||
await dashboardPanelActions.openContextMenu();
|
|
||||||
await dashboardDrilldownPanelActions.expectExistsManageDrilldownsAction();
|
|
||||||
await dashboardDrilldownPanelActions.clickManageDrilldowns();
|
|
||||||
await dashboardDrilldownsManage.expectsManageDrilldownsFlyoutOpen();
|
|
||||||
|
|
||||||
await dashboardDrilldownsManage.deleteDrilldownsByTitles([DRILLDOWN_TO_AREA_CHART_NAME]);
|
|
||||||
await dashboardDrilldownsManage.closeFlyout();
|
|
||||||
|
|
||||||
// check that drilldown notification badge is shown
|
|
||||||
expect(await PageObjects.dashboard.getPanelDrilldownCount()).to.be(0);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('browser back/forward navigation works after drilldown navigation', async () => {
|
|
||||||
await PageObjects.dashboard.loadSavedDashboard(
|
|
||||||
dashboardDrilldownsManage.DASHBOARD_WITH_AREA_CHART_NAME
|
|
||||||
);
|
|
||||||
const originalTimeRangeDurationHours = await PageObjects.timePicker.getTimeDurationInHours();
|
|
||||||
await brushAreaChart();
|
|
||||||
await dashboardDrilldownPanelActions.expectMultipleActionsMenuOpened();
|
|
||||||
await navigateWithinDashboard(async () => {
|
|
||||||
await dashboardDrilldownPanelActions.clickActionByText(DRILLDOWN_TO_PIE_CHART_NAME);
|
|
||||||
});
|
|
||||||
// check that new time range duration was applied
|
|
||||||
const newTimeRangeDurationHours = await PageObjects.timePicker.getTimeDurationInHours();
|
|
||||||
expect(newTimeRangeDurationHours).to.be.lessThan(originalTimeRangeDurationHours);
|
|
||||||
|
|
||||||
await navigateWithinDashboard(async () => {
|
|
||||||
await browser.goBack();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(await PageObjects.timePicker.getTimeDurationInHours()).to.be(
|
it('Dashboards linked by a drilldown are both copied to a space', async () => {
|
||||||
originalTimeRangeDurationHours
|
await PageObjects.copySavedObjectsToSpace.openCopyToSpaceFlyoutForObject(
|
||||||
);
|
dashboardDrilldownsManage.DASHBOARD_WITH_AREA_CHART_NAME
|
||||||
|
);
|
||||||
|
await PageObjects.copySavedObjectsToSpace.setupForm({
|
||||||
|
destinationSpaceId,
|
||||||
|
});
|
||||||
|
await PageObjects.copySavedObjectsToSpace.startCopy();
|
||||||
|
|
||||||
|
// Wait for successful copy
|
||||||
|
await testSubjects.waitForDeleted(`cts-summary-indicator-loading-${destinationSpaceId}`);
|
||||||
|
await testSubjects.existOrFail(`cts-summary-indicator-success-${destinationSpaceId}`);
|
||||||
|
|
||||||
|
const summaryCounts = await PageObjects.copySavedObjectsToSpace.getSummaryCounts();
|
||||||
|
|
||||||
|
expect(summaryCounts).to.eql({
|
||||||
|
success: 5, // 2 dashboards (linked by a drilldown) + 2 visualizations + 1 index pattern
|
||||||
|
pending: 0,
|
||||||
|
skipped: 0,
|
||||||
|
errors: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
await PageObjects.copySavedObjectsToSpace.finishCopy();
|
||||||
|
|
||||||
|
// Actually use copied dashboards in a new space:
|
||||||
|
|
||||||
|
await PageObjects.common.navigateToApp('dashboard', {
|
||||||
|
basePath: `/s/${destinationSpaceId}`,
|
||||||
|
});
|
||||||
|
await PageObjects.dashboard.preserveCrossAppState();
|
||||||
|
await PageObjects.dashboard.loadSavedDashboard(
|
||||||
|
dashboardDrilldownsManage.DASHBOARD_WITH_AREA_CHART_NAME
|
||||||
|
);
|
||||||
|
await PageObjects.header.waitUntilLoadingHasFinished();
|
||||||
|
await PageObjects.dashboard.waitForRenderComplete();
|
||||||
|
|
||||||
|
// brush area chart and drilldown back to pie chat dashboard
|
||||||
|
await brushAreaChart();
|
||||||
|
await dashboardDrilldownPanelActions.expectMultipleActionsMenuOpened();
|
||||||
|
|
||||||
|
await navigateWithinDashboard(async () => {
|
||||||
|
await dashboardDrilldownPanelActions.clickActionByText(DRILLDOWN_TO_PIE_CHART_NAME);
|
||||||
|
});
|
||||||
|
await pieChart.expectPieSliceCount(10);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
Binary file not shown.
|
@ -126,7 +126,7 @@
|
||||||
"title": "Dashboard Foo",
|
"title": "Dashboard Foo",
|
||||||
"hits": 0,
|
"hits": 0,
|
||||||
"description": "",
|
"description": "",
|
||||||
"panelsJSON": "[{}]",
|
"panelsJSON": "[]",
|
||||||
"optionsJSON": "{}",
|
"optionsJSON": "{}",
|
||||||
"version": 1,
|
"version": 1,
|
||||||
"timeRestore": false,
|
"timeRestore": false,
|
||||||
|
@ -156,7 +156,7 @@
|
||||||
"title": "Dashboard Bar",
|
"title": "Dashboard Bar",
|
||||||
"hits": 0,
|
"hits": 0,
|
||||||
"description": "",
|
"description": "",
|
||||||
"panelsJSON": "[{}]",
|
"panelsJSON": "[]",
|
||||||
"optionsJSON": "{}",
|
"optionsJSON": "{}",
|
||||||
"version": 1,
|
"version": 1,
|
||||||
"timeRestore": false,
|
"timeRestore": false,
|
||||||
|
|
|
@ -11,6 +11,12 @@
|
||||||
"title": "[Logs Sample] Overview ECS",
|
"title": "[Logs Sample] Overview ECS",
|
||||||
"version": 1
|
"version": 1
|
||||||
},
|
},
|
||||||
|
"references": [
|
||||||
|
{ "id": "sample_visualization", "name": "panel_0", "type": "visualization" },
|
||||||
|
{ "id": "sample_search", "name": "panel_1", "type": "search" },
|
||||||
|
{ "id": "sample_search", "name": "panel_2", "type": "search" },
|
||||||
|
{ "id": "sample_visualization", "name": "panel_3", "type": "visualization" }
|
||||||
|
],
|
||||||
"id": "sample_dashboard",
|
"id": "sample_dashboard",
|
||||||
"type": "dashboard"
|
"type": "dashboard"
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,12 @@
|
||||||
"title": "[Logs Sample2] Overview ECS",
|
"title": "[Logs Sample2] Overview ECS",
|
||||||
"version": 1
|
"version": 1
|
||||||
},
|
},
|
||||||
|
"references": [
|
||||||
|
{ "id": "sample_visualization", "name": "panel_0", "type": "visualization" },
|
||||||
|
{ "id": "sample_search", "name": "panel_1", "type": "search" },
|
||||||
|
{ "id": "sample_search", "name": "panel_2", "type": "search" },
|
||||||
|
{ "id": "sample_visualization", "name": "panel_3", "type": "visualization" }
|
||||||
|
],
|
||||||
"id": "sample_dashboard2",
|
"id": "sample_dashboard2",
|
||||||
"type": "dashboard"
|
"type": "dashboard"
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,12 @@
|
||||||
"title": "[Logs Sample] Overview ECS",
|
"title": "[Logs Sample] Overview ECS",
|
||||||
"version": 1
|
"version": 1
|
||||||
},
|
},
|
||||||
|
"references": [
|
||||||
|
{ "id": "sample_visualization", "name": "panel_0", "type": "visualization" },
|
||||||
|
{ "id": "sample_search2", "name": "panel_1", "type": "search" },
|
||||||
|
{ "id": "sample_search2", "name": "panel_2", "type": "search" },
|
||||||
|
{ "id": "sample_visualization", "name": "panel_3", "type": "visualization" }
|
||||||
|
],
|
||||||
"id": "sample_dashboard",
|
"id": "sample_dashboard",
|
||||||
"type": "dashboard"
|
"type": "dashboard"
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue