[Analyst Experience Components] Dashboard & Control Group APIs (#150121)

Aligns the Portable Dashboard renderer and the Control Group renderer to a new API structure using `useImperativeHandle` rather than the overcomplicated and mostly unused wrapper provider system.
This commit is contained in:
Devon Thomson 2023-04-19 15:34:39 -04:00 committed by GitHub
parent befd429f4f
commit ffc349225e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
146 changed files with 2772 additions and 2854 deletions

View file

@ -20,7 +20,6 @@
"unifiedSearch",
"developerExamples",
"embeddableExamples"
],
"requiredBundles": ["presentationUtil"]
]
}
}

View file

@ -6,19 +6,37 @@
* Side Public License, v 1.
*/
import React from 'react';
import React, { useEffect, useState } from 'react';
import { withSuspense } from '@kbn/shared-ux-utility';
import { ViewMode } from '@kbn/embeddable-plugin/public';
import type { DataView } from '@kbn/data-views-plugin/public';
import { controlGroupInputBuilder } from '@kbn/controls-plugin/public';
import { EuiPanel, EuiSpacer, EuiText, EuiTitle } from '@elastic/eui';
import { controlGroupInputBuilder } from '@kbn/controls-plugin/public';
import { getDefaultControlGroupInput } from '@kbn/controls-plugin/common';
import { FILTER_DEBUGGER_EMBEDDABLE } from '@kbn/embeddable-examples-plugin/public';
import { LazyDashboardContainerRenderer } from '@kbn/dashboard-plugin/public';
const DashboardContainerRenderer = withSuspense(LazyDashboardContainerRenderer);
import { AwaitingDashboardAPI, DashboardRenderer } from '@kbn/dashboard-plugin/public';
export const DashboardWithControlsExample = ({ dataView }: { dataView: DataView }) => {
const [dashboard, setDashboard] = useState<AwaitingDashboardAPI>();
// add a filter debugger panel as soon as the dashboard becomes available
useEffect(() => {
if (!dashboard) return;
(async () => {
const embeddable = await dashboard.addNewEmbeddable(FILTER_DEBUGGER_EMBEDDABLE, {});
const prevPanelState = dashboard.getExplicitInput().panels[embeddable.id];
// resize the new panel so that it fills up the entire width of the dashboard
dashboard.updateInput({
panels: {
[embeddable.id]: {
...prevPanelState,
gridData: { i: embeddable.id, x: 0, y: 0, w: 48, h: 12 },
},
},
});
})();
}, [dashboard]);
return (
<>
<EuiTitle>
@ -29,10 +47,10 @@ export const DashboardWithControlsExample = ({ dataView }: { dataView: DataView
</EuiText>
<EuiSpacer size="m" />
<EuiPanel hasBorder={true}>
<DashboardContainerRenderer
<DashboardRenderer
getCreationOptions={async () => {
const builder = controlGroupInputBuilder;
const controlGroupInput = {};
const controlGroupInput = getDefaultControlGroupInput();
await builder.addDataControlFromField(controlGroupInput, {
dataViewId: dataView.id ?? '',
title: 'Destintion country',
@ -57,22 +75,7 @@ export const DashboardWithControlsExample = ({ dataView }: { dataView: DataView
},
};
}}
onDashboardContainerLoaded={(container) => {
const addFilterEmbeddable = async () => {
const embeddable = await container.addNewEmbeddable(FILTER_DEBUGGER_EMBEDDABLE, {});
const prevPanelState = container.getExplicitInput().panels[embeddable.id];
// resize the new panel so that it fills up the entire width of the dashboard
container.updateInput({
panels: {
[embeddable.id]: {
...prevPanelState,
gridData: { i: embeddable.id, x: 0, y: 0, w: 48, h: 12 },
},
},
});
};
addFilterEmbeddable();
}}
ref={setDashboard}
/>
</EuiPanel>
</>

View file

@ -6,9 +6,8 @@
* Side Public License, v 1.
*/
import React, { useMemo, useState } from 'react';
import React, { useState } from 'react';
import { DashboardContainer, LazyDashboardContainerRenderer } from '@kbn/dashboard-plugin/public';
import {
EuiButtonGroup,
EuiFlexGroup,
@ -18,35 +17,19 @@ import {
EuiText,
EuiTitle,
} from '@elastic/eui';
import {
AwaitingDashboardAPI,
DashboardAPI,
DashboardRenderer,
} from '@kbn/dashboard-plugin/public';
import { ViewMode } from '@kbn/embeddable-plugin/public';
import { withSuspense } from '@kbn/presentation-util-plugin/public';
import { useDashboardContainerContext } from '@kbn/dashboard-plugin/public';
const DashboardContainerRenderer = withSuspense(LazyDashboardContainerRenderer);
export const DualReduxExample = () => {
const [firstDashboardContainer, setFirstDashboardContainer] = useState<
DashboardContainer | undefined
>();
const [secondDashboardContainer, setSecondDashboardContainer] = useState<
DashboardContainer | undefined
>();
const [firstDashboardContainer, setFirstDashboardContainer] = useState<AwaitingDashboardAPI>();
const [secondDashboardContainer, setSecondDashboardContainer] = useState<AwaitingDashboardAPI>();
const FirstDashboardReduxWrapper = useMemo(() => {
if (firstDashboardContainer) return firstDashboardContainer.getReduxEmbeddableTools().Wrapper;
}, [firstDashboardContainer]);
const SecondDashboardReduxWrapper = useMemo(() => {
if (secondDashboardContainer) return secondDashboardContainer.getReduxEmbeddableTools().Wrapper;
}, [secondDashboardContainer]);
const ButtonControls = () => {
const {
useEmbeddableDispatch,
useEmbeddableSelector: select,
actions: { setViewMode },
} = useDashboardContainerContext();
const dispatch = useEmbeddableDispatch();
const viewMode = select((state) => state.explicitInput.viewMode);
const ButtonControls = ({ dashboard }: { dashboard: DashboardAPI }) => {
const viewMode = dashboard.select((state) => state.explicitInput.viewMode);
return (
<EuiButtonGroup
@ -64,9 +47,7 @@ export const DualReduxExample = () => {
},
]}
idSelected={viewMode}
onChange={(id, value) => {
dispatch(setViewMode(value));
}}
onChange={(id, value) => dashboard.dispatch.setViewMode(value)}
type="single"
/>
);
@ -91,34 +72,18 @@ export const DualReduxExample = () => {
<h3>Dashboard #1</h3>
</EuiTitle>
<EuiSpacer size="m" />
{FirstDashboardReduxWrapper && (
<FirstDashboardReduxWrapper>
<ButtonControls />
</FirstDashboardReduxWrapper>
)}
{firstDashboardContainer && <ButtonControls dashboard={firstDashboardContainer} />}
<EuiSpacer size="m" />
<DashboardContainerRenderer
onDashboardContainerLoaded={(container) => {
setFirstDashboardContainer(container);
}}
/>
<DashboardRenderer ref={setFirstDashboardContainer} />
</EuiFlexItem>
<EuiFlexItem>
<EuiTitle size="xs">
<h3>Dashboard #2</h3>
</EuiTitle>
<EuiSpacer size="m" />
{SecondDashboardReduxWrapper && (
<SecondDashboardReduxWrapper>
<ButtonControls />
</SecondDashboardReduxWrapper>
)}
{secondDashboardContainer && <ButtonControls dashboard={secondDashboardContainer} />}
<EuiSpacer size="m" />
<DashboardContainerRenderer
onDashboardContainerLoaded={(container) => {
setSecondDashboardContainer(container);
}}
/>
<DashboardRenderer ref={setSecondDashboardContainer} />
</EuiFlexItem>
</EuiFlexGroup>
</EuiPanel>

View file

@ -8,7 +8,7 @@
import React, { useMemo, useState } from 'react';
import { DashboardContainer, LazyDashboardContainerRenderer } from '@kbn/dashboard-plugin/public';
import { AwaitingDashboardAPI, DashboardRenderer } from '@kbn/dashboard-plugin/public';
import {
EuiButton,
EuiFlexGroup,
@ -23,19 +23,17 @@ import {
VisualizeInput,
VisualizeOutput,
} from '@kbn/visualizations-plugin/public/embeddable/visualize_embeddable';
import { withSuspense } from '@kbn/presentation-util-plugin/public';
const INPUT_KEY = 'portableDashboard:saveExample:input';
const DashboardContainerRenderer = withSuspense(LazyDashboardContainerRenderer); // make this so we don't have two loading states - loading in the dashboard plugin instead
export const DynamicByReferenceExample = () => {
const [isSaving, setIsSaving] = useState(false);
const [dashboardContainer, setDashboardContainer] = useState<DashboardContainer | undefined>();
const [dashboard, setdashboard] = useState<AwaitingDashboardAPI>();
const onSave = async () => {
if (!dashboard) return;
setIsSaving(true);
localStorage.setItem(INPUT_KEY, JSON.stringify(dashboardContainer!.getInput()));
localStorage.setItem(INPUT_KEY, JSON.stringify(dashboard.getInput()));
// simulated async save await
await new Promise((resolve) => setTimeout(resolve, 1000));
setIsSaving(false);
@ -56,44 +54,37 @@ export const DynamicByReferenceExample = () => {
const resetPersistableInput = () => {
localStorage.removeItem(INPUT_KEY);
if (dashboardContainer) {
const children = dashboardContainer.getChildIds();
if (dashboard) {
const children = dashboard.getChildIds();
children.map((childId) => {
dashboardContainer.removeEmbeddable(childId);
dashboard.removeEmbeddable(childId);
});
}
};
const addByReference = () => {
if (dashboardContainer) {
dashboardContainer.addFromLibrary();
}
};
const addByValue = async () => {
if (dashboardContainer) {
dashboardContainer.addNewEmbeddable<VisualizeInput, VisualizeOutput, VisualizeEmbeddable>(
'visualization',
{
title: 'Sample Markdown Vis',
savedVis: {
type: 'markdown',
title: '',
data: { aggs: [], searchSource: {} },
params: {
fontSize: 12,
openLinksInNewTab: false,
markdown: '### By Value Visualization\nThis is a sample by value panel.',
},
if (!dashboard) return;
dashboard.addNewEmbeddable<VisualizeInput, VisualizeOutput, VisualizeEmbeddable>(
'visualization',
{
title: 'Sample Markdown Vis',
savedVis: {
type: 'markdown',
title: '',
data: { aggs: [], searchSource: {} },
params: {
fontSize: 12,
openLinksInNewTab: false,
markdown: '### By Value Visualization\nThis is a sample by value panel.',
},
}
);
}
},
}
);
};
const disableButtons = useMemo(() => {
return dashboardContainer === undefined || isSaving;
}, [dashboardContainer, isSaving]);
return !dashboard || isSaving;
}, [dashboard, isSaving]);
return (
<>
@ -114,7 +105,7 @@ export const DynamicByReferenceExample = () => {
</EuiButton>
</EuiFlexItem>
<EuiFlexItem>
<EuiButton onClick={addByReference} isDisabled={disableButtons}>
<EuiButton onClick={() => dashboard?.addFromLibrary()} isDisabled={disableButtons}>
Add visualization from library
</EuiButton>
</EuiFlexItem>
@ -141,7 +132,7 @@ export const DynamicByReferenceExample = () => {
</EuiFlexGroup>
<EuiSpacer size="m" />
<DashboardContainerRenderer
<DashboardRenderer
getCreationOptions={async () => {
const persistedInput = getPersistableInput();
return {
@ -151,9 +142,7 @@ export const DynamicByReferenceExample = () => {
},
};
}}
onDashboardContainerLoaded={(container) => {
setDashboardContainer(container);
}}
ref={setdashboard}
/>
</EuiPanel>
</>

View file

@ -11,15 +11,9 @@ import { css } from '@emotion/react';
import { buildPhraseFilter, Filter } from '@kbn/es-query';
import type { DataView } from '@kbn/data-views-plugin/public';
import {
LazyDashboardContainerRenderer,
DashboardCreationOptions,
} from '@kbn/dashboard-plugin/public';
import { DashboardRenderer, DashboardCreationOptions } from '@kbn/dashboard-plugin/public';
import { EuiCode, EuiPanel, EuiSpacer, EuiText, EuiTitle } from '@elastic/eui';
import { ViewMode } from '@kbn/embeddable-plugin/public';
import { withSuspense } from '@kbn/presentation-util-plugin/public';
const DashboardContainerRenderer = withSuspense(LazyDashboardContainerRenderer);
export const StaticByReferenceExample = ({
dashboardId,
@ -50,7 +44,7 @@ export const StaticByReferenceExample = ({
overflow-y: auto;
`}
>
<DashboardContainerRenderer
<DashboardRenderer
savedObjectId={dashboardId}
getCreationOptions={async () => {
const field = dataView.getFieldByName('machine.os.keyword');
@ -61,13 +55,10 @@ export const StaticByReferenceExample = ({
if (field) {
filter = buildPhraseFilter(field, 'win xp', dataView);
filter.meta.negate = true;
creationOptions = { ...creationOptions, overrideInput: { filters: [filter] } };
creationOptions = { ...creationOptions, initialInput: { filters: [filter] } };
}
return creationOptions; // if can't find the field, then just return no special creation options
}}
onDashboardContainerLoaded={(container) => {
return; // this example is static, so don't need to do anything with the dashboard container
}}
/>
</EuiPanel>
</>

View file

@ -11,13 +11,10 @@ import React from 'react';
import { ViewMode } from '@kbn/embeddable-plugin/public';
import { EuiPanel, EuiSpacer, EuiText, EuiTitle } from '@elastic/eui';
import type { DashboardPanelMap } from '@kbn/dashboard-plugin/common';
import { LazyDashboardContainerRenderer } from '@kbn/dashboard-plugin/public';
import { withSuspense } from '@kbn/presentation-util-plugin/public';
import { DashboardRenderer } from '@kbn/dashboard-plugin/public';
import panelsJson from './static_by_value_example_panels.json';
const DashboardContainerRenderer = withSuspense(LazyDashboardContainerRenderer);
export const StaticByValueExample = () => {
return (
<>
@ -29,7 +26,7 @@ export const StaticByValueExample = () => {
</EuiText>
<EuiSpacer size="m" />
<EuiPanel hasBorder={true}>
<DashboardContainerRenderer
<DashboardRenderer
getCreationOptions={async () => {
return {
initialInput: {
@ -39,9 +36,6 @@ export const StaticByValueExample = () => {
},
};
}}
onDashboardContainerLoaded={(container) => {
return; // this example is static, so don't need to do anything with the dashboard container
}}
/>
</EuiPanel>
</>

View file

@ -20,11 +20,9 @@
"@kbn/embeddable-plugin",
"@kbn/data-views-plugin",
"@kbn/visualizations-plugin",
"@kbn/presentation-util-plugin",
"@kbn/developer-examples-plugin",
"@kbn/embeddable-examples-plugin",
"@kbn/shared-ux-page-kibana-template",
"@kbn/shared-ux-utility",
"@kbn/controls-plugin",
"@kbn/shared-ux-router"
]