Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Controls] TEMPORARY Integrate on dashboard #115477

Closed
wants to merge 18 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
33d4017
Ported options list to use Redux. added selection management and sele…
ThomThomson Oct 14, 2021
bb3624d
remove unused import
ThomThomson Oct 15, 2021
a65eb4e
Code cleanups
ThomThomson Oct 15, 2021
879b4ed
add single select functionality
ThomThomson Oct 15, 2021
5af6bd5
Move single select check to optionslist instead of popover
ThomThomson Oct 15, 2021
7dcb01c
Update src/plugins/presentation_util/public/components/controls/contr…
ThomThomson Oct 15, 2021
b99d296
add empty state and cleanup
andreadelrio Oct 15, 2021
324bf11
remove setAllWidths button
andreadelrio Oct 15, 2021
e446e58
Merge branch 'master' of github.com:elastic/kibana into controls/crea…
ThomThomson Oct 16, 2021
0232bbe
Merge branch 'master' into controls/optionsListSelectionManagement2
kibanamachine Oct 18, 2021
e4f5711
Merge pull request #13 from andreadelrio/controls-empty-state
ThomThomson Oct 18, 2021
7c5ba0c
fix types, update copy to match suggestions
ThomThomson Oct 19, 2021
a7a0fbf
fix i18n
ThomThomson Oct 19, 2021
f6602c6
changed to more specific id
ThomThomson Oct 19, 2021
66b3582
Create filters when controls change. Integrated controls on dashboard…
ThomThomson Oct 18, 2021
188fd58
Merge branch 'master' of github.com:elastic/kibana into controls/crea…
ThomThomson Oct 19, 2021
32bf652
Merge branch 'controls/optionsListSelectionManagement2' of https://gi…
ThomThomson Oct 19, 2021
a1b4a12
Inject and Extract, tracking of index pattern changes in control group
ThomThomson Oct 21, 2021
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 @@ -39,6 +39,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,
createAndSyncDashboardControlGroup,
} from '../lib/dashboard_control_group';
import { ControlGroupContainer } from '../../../../presentation_util/public';

export interface DashboardContainerServices {
ExitFullScreenButton: React.ComponentType<any>;
Expand Down Expand Up @@ -88,6 +93,9 @@ 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;
};
Expand All @@ -106,6 +114,20 @@ export class DashboardContainer extends Container<InheritedChildInput, Dashboard
services.embeddable.getEmbeddableFactory,
parent
);

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

protected createNewPanelState<
Expand Down Expand Up @@ -232,14 +254,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 +279,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
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,11 @@
.dshDashboardViewport-withMargins {
width: 100%;
}

.dshDashboardViewport-controlGroup {
margin: 0 $euiSizeS 0 $euiSizeS;
}

.dshDashboardEmptyScreen {
margin-top: $euiSizeS;
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,16 @@ import { DashboardContainer, DashboardReactContextValue } from '../dashboard_con
import { DashboardGrid } from '../grid';
import { context } from '../../../services/kibana_react';
import { DashboardEmptyScreen } from '../empty_screen/dashboard_empty_screen';
import { ControlGroupContainer } from '../../../../../presentation_util/public';

export interface DashboardViewportProps {
container: DashboardContainer;
controlGroup?: ControlGroupContainer;
}

interface State {
isFullScreenMode: boolean;
controlGroupReady: boolean;
useMargins: boolean;
title: string;
description?: string;
Expand All @@ -29,16 +32,21 @@ interface State {

export class DashboardViewport extends React.Component<DashboardViewportProps, State> {
static contextType = context;

public readonly context!: DashboardReactContextValue;

private controlsRoot: React.RefObject<HTMLDivElement>;

private subscription?: Subscription;
private mounted: boolean = false;
constructor(props: DashboardViewportProps) {
super(props);
const { isFullScreenMode, panels, useMargins, title, isEmbeddedExternally } =
this.props.container.getInput();

this.controlsRoot = React.createRef();

this.state = {
controlGroupReady: !this.props.controlGroup,
isFullScreenMode,
panels,
useMargins,
Expand All @@ -62,6 +70,12 @@ export class DashboardViewport extends React.Component<DashboardViewportProps, S
});
}
});
if (this.props.controlGroup && this.controlsRoot.current) {
this.props.controlGroup.render(this.controlsRoot.current);
}
if (this.props.controlGroup) {
this.props.controlGroup?.untilReady().then(() => this.setState({ controlGroupReady: true }));
}
}

public componentWillUnmount() {
Expand All @@ -83,7 +97,8 @@ export class DashboardViewport extends React.Component<DashboardViewportProps, S
const { isEmbeddedExternally, isFullScreenMode, panels, title, description, useMargins } =
this.state;
return (
<React.Fragment>
<>
<div className="dshDashboardViewport-controlGroup" ref={this.controlsRoot} />
<div
data-shared-items-count={Object.values(panels).length}
data-shared-items-container
Expand All @@ -109,9 +124,9 @@ export class DashboardViewport extends React.Component<DashboardViewportProps, S
/>
</div>
)}
<DashboardGrid container={container} />
{this.state.controlGroupReady && <DashboardGrid container={container} />}
</div>
</React.Fragment>
</>
);
}
}
Loading