Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,17 @@ import {
EmbeddableStateWithType,
} from '../../../embeddable/common';
import { SavedObjectReference } from '../../../../core/types';
import { DashboardContainerStateWithType, DashboardPanelState } from '../types';
import {
DashboardContainerControlGroupInput,
DashboardContainerStateWithType,
DashboardPanelState,
} from '../types';
import { CONTROL_GROUP_TYPE } from '../../../presentation_util/common/lib';

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

const controlGroupReferencePrefix = 'controlGroup_';

export const createInject = (
persistableStateService: EmbeddablePersistableStateService
): EmbeddablePersistableStateService['inject'] => {
Expand Down Expand Up @@ -69,6 +76,26 @@ export const createInject = (
}
}

// since the controlGroup is not part of the panels array, its references need to be injected separately
if ('controlGroupInput' in workingState && workingState.controlGroupInput) {
const controlGroupReferences = references
.filter((reference) => reference.name.indexOf(controlGroupReferencePrefix) === 0)
.map((reference) => ({
...reference,
name: reference.name.replace(controlGroupReferencePrefix, ''),
}));

const { type, ...injectedControlGroupState } = persistableStateService.inject(
{
...workingState.controlGroupInput,
type: CONTROL_GROUP_TYPE,
},
controlGroupReferences
);
workingState.controlGroupInput =
injectedControlGroupState as DashboardContainerControlGroupInput;
}

return workingState as EmbeddableStateWithType;
};
};
Expand Down Expand Up @@ -120,6 +147,22 @@ export const createExtract = (
}
}

// since the controlGroup is not part of the panels array, its references need to be extracted separately
if ('controlGroupInput' in workingState && workingState.controlGroupInput) {
const { state: extractedControlGroupState, references: controlGroupReferences } =
persistableStateService.extract({
...workingState.controlGroupInput,
type: CONTROL_GROUP_TYPE,
});
workingState.controlGroupInput =
extractedControlGroupState as DashboardContainerControlGroupInput;
const prefixedControlGroupReferences = controlGroupReferences.map((reference) => ({
...reference,
name: `${controlGroupReferencePrefix}${reference.name}`,
}));
references.push(...prefixedControlGroupReferences);
}

return { state: workingState as EmbeddableStateWithType, references };
};
};
74 changes: 57 additions & 17 deletions src/plugins/dashboard/common/saved_dashboard_references.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,20 @@
*/
import semverGt from 'semver/functions/gt';
import { SavedObjectAttributes, SavedObjectReference } from '../../../core/types';
import { DashboardContainerStateWithType, DashboardPanelState } from './types';
import {
DashboardContainerControlGroupInput,
DashboardContainerStateWithType,
DashboardPanelState,
RawControlGroupAttributes,
} from './types';
import { EmbeddablePersistableStateService } from '../../embeddable/common/types';
import {
convertPanelStateToSavedDashboardPanel,
convertSavedDashboardPanelToPanelState,
} from './embeddable/embeddable_saved_object_converters';
import { SavedDashboardPanel } from './types';
import { CONTROL_GROUP_TYPE } from '../../presentation_util/common/lib';

export interface ExtractDeps {
embeddablePersistableStateService: EmbeddablePersistableStateService;
}
Expand All @@ -35,10 +42,27 @@ function dashboardAttributesToState(attributes: SavedObjectAttributes): {
inputPanels = JSON.parse(attributes.panelsJSON) as SavedDashboardPanel[];
}

let controlGroupInput: DashboardContainerControlGroupInput | undefined;
if (attributes.controlGroupInput) {
const rawControlGroupInput =
attributes.controlGroupInput as unknown as RawControlGroupAttributes;
if (rawControlGroupInput.panelsJSON && typeof rawControlGroupInput.panelsJSON === 'string') {
const controlGroupPanels = JSON.parse(rawControlGroupInput.panelsJSON);
if (controlGroupPanels && typeof controlGroupPanels === 'object') {
controlGroupInput = {
...rawControlGroupInput,
type: CONTROL_GROUP_TYPE,
panels: controlGroupPanels,
};
}
}
}

return {
panels: inputPanels,
state: {
id: attributes.id as string,
controlGroupInput,
type: 'dashboard',
panels: inputPanels.reduce<Record<string, DashboardPanelState>>((current, panel, index) => {
const panelIndex = panel.panelIndex || `${index}`;
Expand Down Expand Up @@ -92,20 +116,27 @@ export function extractReferences(
throw new Error(`"type" attribute is missing from panel "${missingTypeIndex}"`);
}

const { state: extractedState, references: extractedReferences } =
const { references: extractedReferences, state: rawExtractedState } =
deps.embeddablePersistableStateService.extract(state);
const extractedState = rawExtractedState as DashboardContainerStateWithType;

const extractedPanels = panelStatesToPanels(extractedState.panels, panels);

const extractedPanels = panelStatesToPanels(
(extractedState as DashboardContainerStateWithType).panels,
panels
);
const newAttributes = {
...attributes,
panelsJSON: JSON.stringify(extractedPanels),
} as SavedObjectAttributes;

if (extractedState.controlGroupInput) {
newAttributes.controlGroupInput = {
...(attributes.controlGroupInput as SavedObjectAttributes),
panelsJSON: JSON.stringify(extractedState.controlGroupInput.panels),
};
}

return {
references: [...references, ...extractedReferences],
attributes: {
...attributes,
panelsJSON: JSON.stringify(extractedPanels),
},
attributes: newAttributes,
};
}

Expand All @@ -131,16 +162,25 @@ export function injectReferences(

const { panels, state } = dashboardAttributesToState(attributes);

const injectedState = deps.embeddablePersistableStateService.inject(state, references);
const injectedPanels = panelStatesToPanels(
(injectedState as DashboardContainerStateWithType).panels,
panels
);
const injectedState = deps.embeddablePersistableStateService.inject(
state,
references
) as DashboardContainerStateWithType;
const injectedPanels = panelStatesToPanels(injectedState.panels, panels);

return {
const newAttributes = {
...attributes,
panelsJSON: JSON.stringify(injectedPanels),
};
} as SavedObjectAttributes;

if (injectedState.controlGroupInput) {
newAttributes.controlGroupInput = {
...(attributes.controlGroupInput as SavedObjectAttributes),
panelsJSON: JSON.stringify(injectedState.controlGroupInput.panels),
};
}

return newAttributes;
}

function pre730ExtractReferences(
Expand Down
15 changes: 15 additions & 0 deletions src/plugins/dashboard/common/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
} from './bwc/types';

import { GridData } from './embeddable/types';
import { ControlGroupInput } from '../../presentation_util/common/controls/control_group/types';
export type PanelId = string;
export type SavedObjectId = string;

Expand Down Expand Up @@ -96,8 +97,22 @@ export type SavedDashboardPanel730ToLatest = Pick<

// Making this interface because so much of the Container type from embeddable is tied up in public
// Once that is all available from common, we should be able to move the dashboard_container type to our common as well

export interface DashboardContainerControlGroupInput extends EmbeddableStateWithType {
panels: ControlGroupInput['panels'];
controlStyle: ControlGroupInput['controlStyle'];
id: string;
}

export interface RawControlGroupAttributes {
controlStyle: ControlGroupInput['controlStyle'];
panelsJSON: string;
id: string;
}

export interface DashboardContainerStateWithType extends EmbeddableStateWithType {
panels: {
[panelId: string]: DashboardPanelState<EmbeddableInput & { [k: string]: unknown }>;
};
controlGroupInput?: DashboardContainerControlGroupInput;
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
*/

import { AddToLibraryAction } from '.';
import { DashboardContainer } from '../embeddable';
import { DashboardContainer } from '../embeddable/dashboard_container';
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the reason for only exporting the DashboardContainer type and not the entire class, thus having to change all of these imports to a deeper path?

Copy link
Copy Markdown
Contributor Author

@ThomThomson ThomThomson Oct 26, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the secret to how I made the initial dashboard plugin bundle less than 100kb. The dashboard embeddable contains a whole bunch of code and resources, so I imported it async in the factory.

That alone cut the bundle size from 230kb to 80kb.

The only places that used the dashboard container class itself without going through the factory were the actions/tests so I just updated them all to import from elsewhere.

import { getSampleDashboardInput } from '../test_helpers';

import { CoreStart } from 'kibana/public';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
* Side Public License, v 1.
*/

import { DashboardContainer, DashboardPanelState } from '../embeddable';
import { DashboardPanelState } from '../embeddable';
import { DashboardContainer } from '../embeddable/dashboard_container';
import { getSampleDashboardInput, getSampleDashboardPanel } from '../test_helpers';

import { coreMock, uiSettingsServiceMock } from '../../../../../core/public/mocks';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
*/

import { ExpandPanelAction } from './expand_panel_action';
import { DashboardContainer } from '../embeddable';
import { DashboardContainer } from '../embeddable/dashboard_container';
import { getSampleDashboardInput, getSampleDashboardPanel } from '../test_helpers';

import { embeddablePluginMock } from 'src/plugins/embeddable/public/mocks';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import { CoreStart } from 'kibana/public';

import { isErrorEmbeddable, IContainer, ErrorEmbeddable } from '../../services/embeddable';
import { DashboardContainer } from '../../application/embeddable';
import { DashboardContainer } from '../../application/embeddable/dashboard_container';
import { getSampleDashboardInput, getSampleDashboardPanel } from '../../application/test_helpers';
import {
ContactCardEmbeddable,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
* Side Public License, v 1.
*/

import { DashboardContainer } from '../embeddable';
import { getSampleDashboardInput } from '../test_helpers';
import { DashboardContainer } from '../embeddable/dashboard_container';

import { coreMock, uiSettingsServiceMock } from '../../../../../core/public/mocks';
import { CoreStart } from 'kibana/public';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@
*/

import React from 'react';
import { DashboardContainer } from '..';
import { mountWithIntl } from '@kbn/test/jest';

import { DashboardContainer } from '../embeddable/dashboard_container';
import { embeddablePluginMock } from '../../../../embeddable/public/mocks';
import { getSampleDashboardInput } from '../test_helpers';
import {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
*/

import { ReplacePanelAction } from './replace_panel_action';
import { DashboardContainer } from '../embeddable';
import { DashboardContainer } from '../embeddable/dashboard_container';
import { getSampleDashboardInput, getSampleDashboardPanel } from '../test_helpers';

import { coreMock, uiSettingsServiceMock } from '../../../../../core/public/mocks';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ import {
SavedObjectEmbeddableInput,
} from '../../services/embeddable';
import { UnlinkFromLibraryAction } from '.';
import { DashboardContainer } from '../embeddable';
import { getSampleDashboardInput } from '../test_helpers';
import { DashboardContainer } from '../embeddable/dashboard_container';
import { coreMock, uiSettingsServiceMock } from '../../../../../core/public/mocks';

import { embeddablePluginMock } from 'src/plugins/embeddable/public/mocks';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ import {
EmbeddableStart,
EmbeddableOutput,
EmbeddableFactory,
ErrorEmbeddable,
isErrorEmbeddable,
} from '../../services/embeddable';
import { DASHBOARD_CONTAINER_TYPE } from './dashboard_constants';
import { createPanelState } from './panel';
Expand All @@ -39,6 +41,11 @@ import { PLACEHOLDER_EMBEDDABLE } from './placeholder';
import { DashboardAppCapabilities, DashboardContainerInput } from '../../types';
import { PresentationUtilPluginStart } from '../../services/presentation_util';
import { PanelPlacementMethod, IPanelPlacementArgs } from './panel/dashboard_panel_placement';
import {
combineDashboardFiltersWithControlGroupFilters,
syncDashboardControlGroup,
} from '../lib/dashboard_control_group';
import { ControlGroupContainer } from '../../../../presentation_util/public';

export interface DashboardContainerServices {
ExitFullScreenButton: React.ComponentType<any>;
Expand Down Expand Up @@ -88,14 +95,18 @@ const defaultCapabilities: DashboardAppCapabilities = {
export class DashboardContainer extends Container<InheritedChildInput, DashboardContainerInput> {
public readonly type = DASHBOARD_CONTAINER_TYPE;

private onDestroyControlGroup?: () => void;
public controlGroup?: ControlGroupContainer;

public getPanelCount = () => {
return Object.keys(this.getInput().panels).length;
};

constructor(
initialInput: DashboardContainerInput,
private readonly services: DashboardContainerServices,
parent?: Container
parent?: Container,
controlGroup?: ControlGroupContainer | ErrorEmbeddable
) {
super(
{
Expand All @@ -106,6 +117,21 @@ export class DashboardContainer extends Container<InheritedChildInput, Dashboard
services.embeddable.getEmbeddableFactory,
parent
);

if (
controlGroup &&
!isErrorEmbeddable(controlGroup) &&
services.presentationUtil.labsService.isProjectEnabled('labs:dashboard:dashboardControls')
) {
this.controlGroup = controlGroup;
syncDashboardControlGroup({ dashboardContainer: this, controlGroup: this.controlGroup }).then(
(result) => {
if (!result) return;
const { onDestroyControlGroup } = result;
this.onDestroyControlGroup = onDestroyControlGroup;
}
);
}
}

protected createNewPanelState<
Expand Down Expand Up @@ -232,14 +258,19 @@ export class DashboardContainer extends Container<InheritedChildInput, Dashboard
<I18nProvider>
<KibanaContextProvider services={this.services}>
<this.services.presentationUtil.ContextProvider>
<DashboardViewport container={this} />
<DashboardViewport container={this} controlGroup={this.controlGroup} />
</this.services.presentationUtil.ContextProvider>
</KibanaContextProvider>
</I18nProvider>,
dom
);
}

public destroy() {
super.destroy();
this.onDestroyControlGroup?.();
}

protected getInheritedInput(id: string): InheritedChildInput {
const {
viewMode,
Expand All @@ -252,8 +283,12 @@ export class DashboardContainer extends Container<InheritedChildInput, Dashboard
syncColors,
executionContext,
} = this.input;
let combinedFilters = filters;
if (this.controlGroup) {
combinedFilters = combineDashboardFiltersWithControlGroupFilters(filters, this.controlGroup);
}
return {
filters,
filters: combinedFilters,
hidePanelTitles,
query,
timeRange,
Expand Down
Loading