Skip to content

Commit

Permalink
[Controls] Dashboard Integration (#115991)
Browse files Browse the repository at this point in the history
Create filters when controls change. Integrated controls on dashboard behind lab
  • Loading branch information
ThomThomson authored Oct 27, 2021
1 parent 910a5ca commit 0b2dcdf
Show file tree
Hide file tree
Showing 102 changed files with 1,968 additions and 922 deletions.
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';
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

0 comments on commit 0b2dcdf

Please sign in to comment.