Skip to content

Commit

Permalink
[Dashboard] Move all dashboard extract/inject into persistable state (#…
Browse files Browse the repository at this point in the history
…96095)

* Move all dashboard inject/extract to be part of embeddable persistable state

* Fixes typescript errors

* Remove comments

* Fixes test

* API Doc changes

* Fix integration tests

* Fix functional testS

* Fix unit tests

* Update Dashboard plugin API to get dashboard embeddable renderer

* Fix Types

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
  • Loading branch information
Corey Robertson and kibanamachine authored Apr 12, 2021
1 parent f544d8d commit b645fec
Show file tree
Hide file tree
Showing 21 changed files with 964 additions and 345 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->

[Home](./index.md) &gt; [kibana-plugin-plugins-embeddable-server](./kibana-plugin-plugins-embeddable-server.md) &gt; [EmbeddableStart](./kibana-plugin-plugins-embeddable-server.embeddablestart.md)

## EmbeddableStart type

<b>Signature:</b>

```typescript
export declare type EmbeddableStart = PersistableStateService<EmbeddableStateWithType>;
```
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,9 @@
| --- | --- |
| [plugin](./kibana-plugin-plugins-embeddable-server.plugin.md) | |

## Type Aliases

| Type Alias | Description |
| --- | --- |
| [EmbeddableStart](./kibana-plugin-plugins-embeddable-server.embeddablestart.md) | |

4 changes: 3 additions & 1 deletion examples/dashboard_embeddable_examples/public/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,9 @@ const Nav = withRouter(({ history, pages }: NavProps) => {

interface Props {
basename: string;
DashboardContainerByValueRenderer: DashboardStart['DashboardContainerByValueRenderer'];
DashboardContainerByValueRenderer: ReturnType<
DashboardStart['getDashboardContainerByValueRenderer']
>;
}

const DashboardEmbeddableExplorerApp = ({ basename, DashboardContainerByValueRenderer }: Props) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,9 @@ const initialInput: DashboardContainerInput = {
export const DashboardEmbeddableByValue = ({
DashboardContainerByValueRenderer,
}: {
DashboardContainerByValueRenderer: DashboardStart['DashboardContainerByValueRenderer'];
DashboardContainerByValueRenderer: ReturnType<
DashboardStart['getDashboardContainerByValueRenderer']
>;
}) => {
const [input, setInput] = useState(initialInput);

Expand Down
3 changes: 1 addition & 2 deletions examples/dashboard_embeddable_examples/public/plugin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,7 @@ export class DashboardEmbeddableExamples implements Plugin<void, void, {}, Start
return renderApp(
{
basename: params.appBasePath,
DashboardContainerByValueRenderer:
depsStart.dashboard.DashboardContainerByValueRenderer,
DashboardContainerByValueRenderer: depsStart.dashboard.getDashboardContainerByValueRenderer(),
},
params.element
);
Expand Down
1 change: 1 addition & 0 deletions src/plugins/dashboard/common/bwc/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ export type RawSavedDashboardPanel730ToLatest = Pick<
readonly name?: string;

panelIndex: string;
panelRefName?: string;
};

// NOTE!!
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

import { createExtract, createInject } from './dashboard_container_persistable_state';
import { createEmbeddablePersistableStateServiceMock } from '../../../embeddable/common/mocks';
import { DashboardContainerStateWithType } from '../types';

const persistableStateService = createEmbeddablePersistableStateServiceMock();

const dashboardWithExtractedPanel: DashboardContainerStateWithType = {
id: 'id',
type: 'dashboard',
panels: {
panel_1: {
type: 'panel_type',
gridData: { w: 0, h: 0, x: 0, y: 0, i: '0' },
panelRefName: 'panel_panel_1',
explicitInput: {
id: 'panel_1',
},
},
},
};

const extractedSavedObjectPanelRef = {
name: 'panel_1:panel_panel_1',
type: 'panel_type',
id: 'object-id',
};

const unextractedDashboardState: DashboardContainerStateWithType = {
id: 'id',
type: 'dashboard',
panels: {
panel_1: {
type: 'panel_type',
gridData: { w: 0, h: 0, x: 0, y: 0, i: '0' },
explicitInput: {
id: 'panel_1',
savedObjectId: 'object-id',
},
},
},
};

describe('inject/extract by reference panel', () => {
it('should inject the extracted saved object panel', () => {
const inject = createInject(persistableStateService);
const references = [extractedSavedObjectPanelRef];

const injected = inject(
dashboardWithExtractedPanel,
references
) as DashboardContainerStateWithType;

expect(injected).toEqual(unextractedDashboardState);
});

it('should extract the saved object panel', () => {
const extract = createExtract(persistableStateService);
const { state: extractedState, references: extractedReferences } = extract(
unextractedDashboardState
);

expect(extractedState).toEqual(dashboardWithExtractedPanel);
expect(extractedReferences[0]).toEqual(extractedSavedObjectPanelRef);
});
});

const dashboardWithExtractedByValuePanel: DashboardContainerStateWithType = {
id: 'id',
type: 'dashboard',
panels: {
panel_1: {
type: 'panel_type',
gridData: { w: 0, h: 0, x: 0, y: 0, i: '0' },
explicitInput: {
id: 'panel_1',
extracted_reference: 'ref',
},
},
},
};

const extractedByValueRef = {
id: 'id',
name: 'panel_1:ref',
type: 'panel_type',
};

const unextractedDashboardByValueState: DashboardContainerStateWithType = {
id: 'id',
type: 'dashboard',
panels: {
panel_1: {
type: 'panel_type',
gridData: { w: 0, h: 0, x: 0, y: 0, i: '0' },
explicitInput: {
id: 'panel_1',
value: 'id',
},
},
},
};

describe('inject/extract by value panels', () => {
it('should inject the extracted references', () => {
const inject = createInject(persistableStateService);

persistableStateService.inject.mockImplementationOnce((state, references) => {
const ref = references.find((r) => r.name === 'ref');
if (!ref) {
return state;
}

if (('extracted_reference' in state) as any) {
(state as any).value = ref.id;
delete (state as any).extracted_reference;
}

return state;
});

const injectedState = inject(dashboardWithExtractedByValuePanel, [extractedByValueRef]);

expect(injectedState).toEqual(unextractedDashboardByValueState);
});

it('should extract references using persistable state', () => {
const extract = createExtract(persistableStateService);

persistableStateService.extract.mockImplementationOnce((state) => {
if ((state as any).value === 'id') {
delete (state as any).value;
(state as any).extracted_reference = 'ref';

return {
state,
references: [{ id: extractedByValueRef.id, name: 'ref', type: extractedByValueRef.type }],
};
}

return { state, references: [] };
});

const { state: extractedState, references: extractedReferences } = extract(
unextractedDashboardByValueState
);

expect(extractedState).toEqual(dashboardWithExtractedByValuePanel);
expect(extractedReferences).toEqual([extractedByValueRef]);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

import {
EmbeddableInput,
EmbeddablePersistableStateService,
EmbeddableStateWithType,
} from '../../../embeddable/common';
import { SavedObjectReference } from '../../../../core/types';
import { DashboardContainerStateWithType, DashboardPanelState } from '../types';

const getPanelStatePrefix = (state: DashboardPanelState) => `${state.explicitInput.id}:`;

export const createInject = (
persistableStateService: EmbeddablePersistableStateService
): EmbeddablePersistableStateService['inject'] => {
return (state: EmbeddableStateWithType, references: SavedObjectReference[]) => {
const workingState = { ...state } as EmbeddableStateWithType | DashboardContainerStateWithType;

if ('panels' in workingState) {
workingState.panels = { ...workingState.panels };

for (const [key, panel] of Object.entries(workingState.panels)) {
workingState.panels[key] = { ...panel };
// Find the references for this panel
const prefix = getPanelStatePrefix(panel);

const filteredReferences = references
.filter((reference) => reference.name.indexOf(prefix) === 0)
.map((reference) => ({ ...reference, name: reference.name.replace(prefix, '') }));

const panelReferences = filteredReferences.length === 0 ? references : filteredReferences;

// Inject dashboard references back in
if (panel.panelRefName !== undefined) {
const matchingReference = panelReferences.find(
(reference) => reference.name === panel.panelRefName
);

if (!matchingReference) {
throw new Error(`Could not find reference "${panel.panelRefName}"`);
}

if (matchingReference !== undefined) {
workingState.panels[key] = {
...panel,
type: matchingReference.type,
explicitInput: {
...workingState.panels[key].explicitInput,
savedObjectId: matchingReference.id,
},
};

delete workingState.panels[key].panelRefName;
}
}

const { type, ...injectedState } = persistableStateService.inject(
{ ...workingState.panels[key].explicitInput, type: workingState.panels[key].type },
panelReferences
);

workingState.panels[key].explicitInput = injectedState as EmbeddableInput;
}
}

return workingState as EmbeddableStateWithType;
};
};

export const createExtract = (
persistableStateService: EmbeddablePersistableStateService
): EmbeddablePersistableStateService['extract'] => {
return (state: EmbeddableStateWithType) => {
const workingState = { ...state } as EmbeddableStateWithType | DashboardContainerStateWithType;

const references: SavedObjectReference[] = [];

if ('panels' in workingState) {
workingState.panels = { ...workingState.panels };

// Run every panel through the state service to get the nested references
for (const [key, panel] of Object.entries(workingState.panels)) {
const prefix = getPanelStatePrefix(panel);

// If the panel is a saved object, then we will make the reference for that saved object and change the explicit input
if (panel.explicitInput.savedObjectId) {
panel.panelRefName = `panel_${key}`;

references.push({
name: `${prefix}panel_${key}`,
type: panel.type,
id: panel.explicitInput.savedObjectId as string,
});

delete panel.explicitInput.savedObjectId;
delete panel.explicitInput.type;
}

const { state: panelState, references: panelReferences } = persistableStateService.extract({
...panel.explicitInput,
type: panel.type,
});

// We're going to prefix the names of the references so that we don't end up with dupes (from visualizations for instance)
const prefixedReferences = panelReferences.map((reference) => ({
...reference,
name: `${prefix}${reference.name}`,
}));

references.push(...prefixedReferences);

const { type, ...restOfState } = panelState;
workingState.panels[key].explicitInput = restOfState as EmbeddableInput;
}
}

return { state: workingState as EmbeddableStateWithType, references };
};
};
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export function convertSavedDashboardPanelToPanelState(
return {
type: savedDashboardPanel.type,
gridData: savedDashboardPanel.gridData,
panelRefName: savedDashboardPanel.panelRefName,
explicitInput: {
id: savedDashboardPanel.panelIndex,
...(savedDashboardPanel.id !== undefined && { savedObjectId: savedDashboardPanel.id }),
Expand All @@ -38,5 +39,6 @@ export function convertPanelStateToSavedDashboardPanel(
embeddableConfig: omit(panelState.explicitInput, ['id', 'savedObjectId', 'title']),
...(panelState.explicitInput.title !== undefined && { title: panelState.explicitInput.title }),
...(savedObjectId !== undefined && { id: savedObjectId }),
...(panelState.panelRefName !== undefined && { panelRefName: panelState.panelRefName }),
};
}
1 change: 1 addition & 0 deletions src/plugins/dashboard/common/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export {
DashboardDocPre700,
} from './bwc/types';
export {
DashboardContainerStateWithType,
SavedDashboardPanelTo60,
SavedDashboardPanel610,
SavedDashboardPanel620,
Expand Down
Loading

0 comments on commit b645fec

Please sign in to comment.