mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[Exploratory view] Allow ability add extra actions in lens embeddable (#123713)
This commit is contained in:
parent
9f6c78139e
commit
5b8af6c1ea
20 changed files with 401 additions and 44 deletions
|
@ -32,6 +32,7 @@ import type {
|
|||
} from '../../../plugins/lens/public';
|
||||
|
||||
import { ViewMode } from '../../../../src/plugins/embeddable/public';
|
||||
import { ActionExecutionContext } from '../../../../src/plugins/ui_actions/public';
|
||||
|
||||
// Generate a Lens state based on some app-specific input parameters.
|
||||
// `TypedLensByValueInput` can be used for type-safety - it uses the same interfaces as Lens-internal code.
|
||||
|
@ -126,6 +127,9 @@ export const App = (props: {
|
|||
to: 'now',
|
||||
});
|
||||
|
||||
const [enableExtraAction, setEnableExtraAction] = useState(false);
|
||||
const [enableDefaultAction, setEnableDefaultAction] = useState(false);
|
||||
|
||||
const LensComponent = props.plugins.lens.EmbeddableComponent;
|
||||
const LensSaveModalComponent = props.plugins.lens.SaveModalComponent;
|
||||
|
||||
|
@ -153,7 +157,7 @@ export const App = (props: {
|
|||
configuration and navigate to a prefilled editor.
|
||||
</p>
|
||||
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexGroup wrap>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton
|
||||
data-test-subj="lns-example-change-color"
|
||||
|
@ -238,10 +242,34 @@ export const App = (props: {
|
|||
Change time range
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton
|
||||
aria-label="Enable extra action"
|
||||
data-test-subj="lns-example-extra-action"
|
||||
isDisabled={!attributes}
|
||||
onClick={() => {
|
||||
setEnableExtraAction((prevState) => !prevState);
|
||||
}}
|
||||
>
|
||||
{enableExtraAction ? 'Disable extra action' : 'Enable extra action'}
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton
|
||||
aria-label="Enable default actions"
|
||||
data-test-subj="lns-example-default-action"
|
||||
isDisabled={!attributes}
|
||||
onClick={() => {
|
||||
setEnableDefaultAction((prevState) => !prevState);
|
||||
}}
|
||||
>
|
||||
{enableDefaultAction ? 'Disable default action' : 'Enable default action'}
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<LensComponent
|
||||
id=""
|
||||
withActions
|
||||
withDefaultActions={enableDefaultAction}
|
||||
style={{ height: 500 }}
|
||||
timeRange={time}
|
||||
attributes={attributes}
|
||||
|
@ -261,6 +289,27 @@ export const App = (props: {
|
|||
// call back event for on table row click event
|
||||
}}
|
||||
viewMode={ViewMode.VIEW}
|
||||
extraActions={
|
||||
enableExtraAction
|
||||
? [
|
||||
{
|
||||
id: 'testAction',
|
||||
type: 'link',
|
||||
getIconType: () => 'save',
|
||||
async isCompatible(
|
||||
context: ActionExecutionContext<object>
|
||||
): Promise<boolean> {
|
||||
return true;
|
||||
},
|
||||
execute: async (context: ActionExecutionContext<object>) => {
|
||||
alert('I am an extra action');
|
||||
return;
|
||||
},
|
||||
getDisplayName: () => 'Extra action',
|
||||
},
|
||||
]
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
{isSaveModalVisible && (
|
||||
<LensSaveModalComponent
|
||||
|
|
|
@ -6,13 +6,14 @@
|
|||
"server": false,
|
||||
"ui": true,
|
||||
"requiredPlugins": [
|
||||
"observability",
|
||||
"cases",
|
||||
"data",
|
||||
"developerExamples",
|
||||
"embeddable",
|
||||
"developerExamples"
|
||||
"observability"
|
||||
],
|
||||
"optionalPlugins": [],
|
||||
"requiredBundles": [],
|
||||
"requiredBundles": ["kibanaReact"],
|
||||
"owner": {
|
||||
"name": "`Synthetics team`",
|
||||
"githubTeam": "uptime"
|
||||
|
|
|
@ -80,6 +80,7 @@ export const App = (props: {
|
|||
attributes={seriesList}
|
||||
reportType="kpi-over-time"
|
||||
title={'Monitor response duration'}
|
||||
withActions={['save', 'explore']}
|
||||
/>
|
||||
</EuiPageContentBody>
|
||||
</EuiPageContent>
|
||||
|
|
|
@ -7,9 +7,12 @@
|
|||
|
||||
import * as React from 'react';
|
||||
import { render, unmountComponentAtNode } from 'react-dom';
|
||||
import { CoreSetup, AppMountParameters } from 'kibana/public';
|
||||
import { CoreSetup, AppMountParameters, APP_WRAPPER_CLASS } from '../../../../src/core/public';
|
||||
import { StartDependencies } from './plugin';
|
||||
|
||||
import {
|
||||
KibanaContextProvider,
|
||||
RedirectAppLinks,
|
||||
} from '../../../../src/plugins/kibana_react/public';
|
||||
export const mount =
|
||||
(coreSetup: CoreSetup<StartDependencies>) =>
|
||||
async ({ element }: AppMountParameters) => {
|
||||
|
@ -26,9 +29,13 @@ export const mount =
|
|||
const i18nCore = core.i18n;
|
||||
|
||||
const reactElement = (
|
||||
<i18nCore.Context>
|
||||
<App {...deps} defaultIndexPattern={defaultIndexPattern} />
|
||||
</i18nCore.Context>
|
||||
<KibanaContextProvider services={{ ...coreSetup, ...core, ...plugins }}>
|
||||
<i18nCore.Context>
|
||||
<RedirectAppLinks application={core.application} className={APP_WRAPPER_CLASS}>
|
||||
<App {...deps} defaultIndexPattern={defaultIndexPattern} />
|
||||
</RedirectAppLinks>
|
||||
</i18nCore.Context>
|
||||
</KibanaContextProvider>
|
||||
);
|
||||
render(reactElement, element);
|
||||
return () => unmountComponentAtNode(element);
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
import React, { FC, useEffect } from 'react';
|
||||
import type { CoreStart, ThemeServiceStart } from 'kibana/public';
|
||||
import type { UiActionsStart } from 'src/plugins/ui_actions/public';
|
||||
import type { Action, UiActionsStart } from 'src/plugins/ui_actions/public';
|
||||
import type { Start as InspectorStartContract } from 'src/plugins/inspector/public';
|
||||
import { EuiLoadingChart } from '@elastic/eui';
|
||||
import {
|
||||
|
@ -52,7 +52,8 @@ export type TypedLensByValueInput = Omit<LensByValueInput, 'attributes'> & {
|
|||
};
|
||||
|
||||
export type EmbeddableComponentProps = (TypedLensByValueInput | LensByReferenceInput) & {
|
||||
withActions?: boolean;
|
||||
withDefaultActions?: boolean;
|
||||
extraActions?: Action[];
|
||||
};
|
||||
|
||||
interface PluginsStartDependencies {
|
||||
|
@ -67,7 +68,9 @@ export function getEmbeddableComponent(core: CoreStart, plugins: PluginsStartDep
|
|||
const factory = embeddableStart.getEmbeddableFactory('lens')!;
|
||||
const input = { ...props };
|
||||
const [embeddable, loading, error] = useEmbeddableFactory({ factory, input });
|
||||
const hasActions = props.withActions === true;
|
||||
const hasActions =
|
||||
Boolean(props.withDefaultActions) || (props.extraActions && props.extraActions?.length > 0);
|
||||
|
||||
const theme = core.theme;
|
||||
|
||||
if (loading) {
|
||||
|
@ -83,6 +86,8 @@ export function getEmbeddableComponent(core: CoreStart, plugins: PluginsStartDep
|
|||
actionPredicate={() => hasActions}
|
||||
input={input}
|
||||
theme={theme}
|
||||
extraActions={props.extraActions}
|
||||
withDefaultActions={props.withDefaultActions}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -98,6 +103,8 @@ interface EmbeddablePanelWrapperProps {
|
|||
actionPredicate: (id: string) => boolean;
|
||||
input: EmbeddableComponentProps;
|
||||
theme: ThemeServiceStart;
|
||||
extraActions?: Action[];
|
||||
withDefaultActions?: boolean;
|
||||
}
|
||||
|
||||
const EmbeddablePanelWrapper: FC<EmbeddablePanelWrapperProps> = ({
|
||||
|
@ -107,6 +114,8 @@ const EmbeddablePanelWrapper: FC<EmbeddablePanelWrapperProps> = ({
|
|||
inspector,
|
||||
input,
|
||||
theme,
|
||||
extraActions,
|
||||
withDefaultActions,
|
||||
}) => {
|
||||
useEffect(() => {
|
||||
embeddable.updateInput(input);
|
||||
|
@ -116,7 +125,13 @@ const EmbeddablePanelWrapper: FC<EmbeddablePanelWrapperProps> = ({
|
|||
<EmbeddablePanel
|
||||
hideHeader={false}
|
||||
embeddable={embeddable as IEmbeddable<EmbeddableInput, EmbeddableOutput>}
|
||||
getActions={uiActions.getTriggerCompatibleActions}
|
||||
getActions={async (triggerId, context) => {
|
||||
const actions = withDefaultActions
|
||||
? await uiActions.getTriggerCompatibleActions(triggerId, context)
|
||||
: [];
|
||||
|
||||
return [...(extraActions ?? []), ...actions];
|
||||
}}
|
||||
inspector={inspector}
|
||||
actionPredicate={actionPredicate}
|
||||
showShadow={false}
|
||||
|
|
|
@ -783,7 +783,7 @@ export class LensAttributes {
|
|||
};
|
||||
}
|
||||
|
||||
getJSON(refresh?: number): TypedLensByValueInput['attributes'] {
|
||||
getJSON(): TypedLensByValueInput['attributes'] {
|
||||
const uniqueIndexPatternsIds = Array.from(
|
||||
new Set([...this.layerConfigs.map(({ indexPattern }) => indexPattern.id)])
|
||||
);
|
||||
|
@ -792,7 +792,7 @@ export class LensAttributes {
|
|||
|
||||
return {
|
||||
title: 'Prefilled from exploratory view app',
|
||||
description: String(refresh),
|
||||
description: '',
|
||||
visualizationType: 'lnsXY',
|
||||
references: [
|
||||
...uniqueIndexPatternsIds.map((patternId) => ({
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
export const sampleAttribute = {
|
||||
description: 'undefined',
|
||||
description: '',
|
||||
references: [
|
||||
{
|
||||
id: 'apm-*',
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
export const sampleAttributeCoreWebVital = {
|
||||
description: 'undefined',
|
||||
description: '',
|
||||
references: [
|
||||
{
|
||||
id: 'apm-*',
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
export const sampleAttributeKpi = {
|
||||
description: 'undefined',
|
||||
description: '',
|
||||
references: [
|
||||
{
|
||||
id: 'apm-*',
|
||||
|
|
|
@ -49,15 +49,30 @@ export function convertToShortUrl(series: SeriesUrl) {
|
|||
};
|
||||
}
|
||||
|
||||
export function createExploratoryViewRoutePath({
|
||||
reportType,
|
||||
allSeries,
|
||||
}: {
|
||||
reportType: ReportViewType;
|
||||
allSeries: AllSeries;
|
||||
}) {
|
||||
const allShortSeries: AllShortSeries = allSeries.map((series) => convertToShortUrl(series));
|
||||
|
||||
return `/exploratory-view/#?reportType=${reportType}&sr=${rison.encode(
|
||||
allShortSeries as unknown as RisonValue
|
||||
)}`;
|
||||
}
|
||||
|
||||
export function createExploratoryViewUrl(
|
||||
{ reportType, allSeries }: { reportType: ReportViewType; allSeries: AllSeries },
|
||||
baseHref = ''
|
||||
baseHref = '',
|
||||
appId = 'observability'
|
||||
) {
|
||||
const allShortSeries: AllShortSeries = allSeries.map((series) => convertToShortUrl(series));
|
||||
|
||||
return (
|
||||
baseHref +
|
||||
`/app/observability/exploratory-view/#?reportType=${reportType}&sr=${rison.encode(
|
||||
`/app/${appId}/exploratory-view/#?reportType=${reportType}&sr=${rison.encode(
|
||||
allShortSeries as unknown as RisonValue
|
||||
)}`
|
||||
);
|
||||
|
|
|
@ -12,11 +12,13 @@ import { AllSeries, useTheme } from '../../../..';
|
|||
import { LayerConfig, LensAttributes } from '../configurations/lens_attributes';
|
||||
import { AppDataType, ReportViewType } from '../types';
|
||||
import { getLayerConfigs } from '../hooks/use_lens_attributes';
|
||||
import { LensPublicStart, XYState } from '../../../../../../lens/public';
|
||||
import { LensEmbeddableInput, LensPublicStart, XYState } from '../../../../../../lens/public';
|
||||
import { OperationTypeComponent } from '../series_editor/columns/operation_type_select';
|
||||
import { IndexPatternState } from '../hooks/use_app_index_pattern';
|
||||
import { ReportConfigMap } from '../contexts/exploratory_view_config';
|
||||
import { obsvReportConfigMap } from '../obsv_exploratory_view';
|
||||
import { ActionTypes, useActions } from './use_actions';
|
||||
import { AddToCaseAction } from '../header/add_to_case_action';
|
||||
|
||||
export interface ExploratoryEmbeddableProps {
|
||||
reportType: ReportViewType;
|
||||
|
@ -28,6 +30,8 @@ export interface ExploratoryEmbeddableProps {
|
|||
legendIsVisible?: boolean;
|
||||
dataTypesIndexPatterns?: Partial<Record<AppDataType, string>>;
|
||||
reportConfigMap?: ReportConfigMap;
|
||||
withActions?: boolean | ActionTypes[];
|
||||
appId?: 'security' | 'observability';
|
||||
}
|
||||
|
||||
export interface ExploratoryEmbeddableComponentProps extends ExploratoryEmbeddableProps {
|
||||
|
@ -43,17 +47,31 @@ export default function Embeddable({
|
|||
appendTitle,
|
||||
indexPatterns,
|
||||
lens,
|
||||
appId,
|
||||
axisTitlesVisibility,
|
||||
legendIsVisible,
|
||||
withActions = true,
|
||||
reportConfigMap = {},
|
||||
showCalculationMethod = false,
|
||||
}: ExploratoryEmbeddableComponentProps) {
|
||||
const LensComponent = lens?.EmbeddableComponent;
|
||||
const LensSaveModalComponent = lens?.SaveModalComponent;
|
||||
|
||||
const [isSaveOpen, setIsSaveOpen] = useState(false);
|
||||
const [isAddToCaseOpen, setAddToCaseOpen] = useState(false);
|
||||
|
||||
const series = Object.entries(attributes)[0][1];
|
||||
|
||||
const [operationType, setOperationType] = useState(series?.operationType);
|
||||
const theme = useTheme();
|
||||
const actions = useActions({
|
||||
withActions,
|
||||
attributes,
|
||||
reportType,
|
||||
appId,
|
||||
setIsSaveOpen,
|
||||
setAddToCaseOpen,
|
||||
});
|
||||
|
||||
const layerConfigs: LayerConfig[] = getLayerConfigs(
|
||||
attributes,
|
||||
|
@ -107,6 +125,24 @@ export default function Embeddable({
|
|||
timeRange={series?.time}
|
||||
attributes={attributesJSON}
|
||||
onBrushEnd={({ range }) => {}}
|
||||
withDefaultActions={Boolean(withActions)}
|
||||
extraActions={actions}
|
||||
/>
|
||||
{isSaveOpen && attributesJSON && (
|
||||
<LensSaveModalComponent
|
||||
initialInput={attributesJSON as unknown as LensEmbeddableInput}
|
||||
onClose={() => setIsSaveOpen(false)}
|
||||
// if we want to do anything after the viz is saved
|
||||
// right now there is no action, so an empty function
|
||||
onSave={() => {}}
|
||||
/>
|
||||
)}
|
||||
<AddToCaseAction
|
||||
lensAttributes={attributesJSON}
|
||||
timeRange={series?.time}
|
||||
autoOpen={isAddToCaseOpen}
|
||||
setAutoOpen={setAddToCaseOpen}
|
||||
appId={appId}
|
||||
/>
|
||||
</Wrapper>
|
||||
);
|
||||
|
@ -118,5 +154,23 @@ const Wrapper = styled.div`
|
|||
> :nth-child(2) {
|
||||
height: calc(100% - 32px);
|
||||
}
|
||||
.embPanel--editing {
|
||||
border-style: initial !important;
|
||||
:hover {
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
.embPanel__title {
|
||||
display: none;
|
||||
}
|
||||
.embPanel__optionsMenuPopover {
|
||||
visibility: collapse;
|
||||
}
|
||||
|
||||
&&&:hover {
|
||||
.embPanel__optionsMenuPopover {
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
|
|
@ -0,0 +1,150 @@
|
|||
/*
|
||||
* 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; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { createExploratoryViewRoutePath, createExploratoryViewUrl } from '../configurations/utils';
|
||||
import { ReportViewType } from '../types';
|
||||
import { AllSeries } from '../hooks/use_series_storage';
|
||||
import { useKibana } from '../../../../../../../../src/plugins/kibana_react/public';
|
||||
import {
|
||||
Action,
|
||||
ActionExecutionContext,
|
||||
} from '../../../../../../../../src/plugins/ui_actions/public';
|
||||
|
||||
export type ActionTypes = 'explore' | 'save' | 'addToCase';
|
||||
|
||||
export function useActions({
|
||||
withActions,
|
||||
attributes,
|
||||
reportType,
|
||||
setIsSaveOpen,
|
||||
setAddToCaseOpen,
|
||||
appId = 'observability',
|
||||
}: {
|
||||
withActions?: boolean | ActionTypes[];
|
||||
reportType: ReportViewType;
|
||||
attributes: AllSeries;
|
||||
appId?: 'security' | 'observability';
|
||||
setIsSaveOpen: (val: boolean) => void;
|
||||
setAddToCaseOpen: (val: boolean) => void;
|
||||
}) {
|
||||
const [defaultActions, setDefaultActions] = useState(['explore', 'save', 'addToCase']);
|
||||
|
||||
useEffect(() => {
|
||||
if (withActions === false) {
|
||||
setDefaultActions([]);
|
||||
}
|
||||
if (Array.isArray(withActions)) {
|
||||
setDefaultActions(withActions);
|
||||
}
|
||||
}, [withActions]);
|
||||
|
||||
const { http, application } = useKibana().services;
|
||||
|
||||
const href = createExploratoryViewUrl(
|
||||
{ reportType, allSeries: attributes },
|
||||
http?.basePath.get(),
|
||||
appId
|
||||
);
|
||||
|
||||
const routePath = createExploratoryViewRoutePath({ reportType, allSeries: attributes });
|
||||
|
||||
const exploreCallback = useCallback(() => {
|
||||
application?.navigateToApp(appId, { path: routePath });
|
||||
}, [appId, application, routePath]);
|
||||
|
||||
const saveCallback = useCallback(() => {
|
||||
setIsSaveOpen(true);
|
||||
}, [setIsSaveOpen]);
|
||||
|
||||
const addToCaseCallback = useCallback(() => {
|
||||
setAddToCaseOpen(true);
|
||||
}, [setAddToCaseOpen]);
|
||||
|
||||
return defaultActions.map<Action>((action) => {
|
||||
if (action === 'save') {
|
||||
return getSaveAction({ callback: saveCallback });
|
||||
}
|
||||
if (action === 'addToCase') {
|
||||
return getAddToCaseAction({ callback: addToCaseCallback });
|
||||
}
|
||||
return getExploreAction({ href, callback: exploreCallback });
|
||||
});
|
||||
}
|
||||
|
||||
const getExploreAction = ({ href, callback }: { href: string; callback: () => void }): Action => {
|
||||
return {
|
||||
id: 'expViewExplore',
|
||||
getDisplayName(context: ActionExecutionContext<object>): string {
|
||||
return i18n.translate('xpack.observability.expView.explore', {
|
||||
defaultMessage: 'Explore',
|
||||
});
|
||||
},
|
||||
getIconType(context: ActionExecutionContext<object>): string | undefined {
|
||||
return 'visArea';
|
||||
},
|
||||
type: 'link',
|
||||
async isCompatible(context: ActionExecutionContext<object>): Promise<boolean> {
|
||||
return true;
|
||||
},
|
||||
async getHref(context: ActionExecutionContext<object>): Promise<string | undefined> {
|
||||
return href;
|
||||
},
|
||||
async execute(context: ActionExecutionContext<object>): Promise<void> {
|
||||
callback();
|
||||
return;
|
||||
},
|
||||
order: 50,
|
||||
};
|
||||
};
|
||||
|
||||
const getSaveAction = ({ callback }: { callback: () => void }): Action => {
|
||||
return {
|
||||
id: 'expViewSave',
|
||||
getDisplayName(context: ActionExecutionContext<object>): string {
|
||||
return i18n.translate('xpack.observability.expView.save', {
|
||||
defaultMessage: 'Save visualization',
|
||||
});
|
||||
},
|
||||
getIconType(context: ActionExecutionContext<object>): string | undefined {
|
||||
return 'save';
|
||||
},
|
||||
type: 'actionButton',
|
||||
async isCompatible(context: ActionExecutionContext<object>): Promise<boolean> {
|
||||
return true;
|
||||
},
|
||||
async execute(context: ActionExecutionContext<object>): Promise<void> {
|
||||
callback();
|
||||
return;
|
||||
},
|
||||
order: 49,
|
||||
};
|
||||
};
|
||||
|
||||
const getAddToCaseAction = ({ callback }: { callback: () => void }): Action => {
|
||||
return {
|
||||
id: 'expViewAddToCase',
|
||||
getDisplayName(context: ActionExecutionContext<object>): string {
|
||||
return i18n.translate('xpack.observability.expView.addToCase', {
|
||||
defaultMessage: 'Add to case',
|
||||
});
|
||||
},
|
||||
getIconType(context: ActionExecutionContext<object>): string | undefined {
|
||||
return 'link';
|
||||
},
|
||||
type: 'actionButton',
|
||||
async isCompatible(context: ActionExecutionContext<object>): Promise<boolean> {
|
||||
return true;
|
||||
},
|
||||
async execute(context: ActionExecutionContext<object>): Promise<void> {
|
||||
callback();
|
||||
return;
|
||||
},
|
||||
order: 48,
|
||||
};
|
||||
};
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
import { EuiButtonEmpty, EuiFlexGroup, EuiFlexItem, EuiLink } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import React, { useCallback } from 'react';
|
||||
import React, { useCallback, useEffect } from 'react';
|
||||
import { toMountPoint, useKibana } from '../../../../../../../../src/plugins/kibana_react/public';
|
||||
import { ObservabilityAppServices } from '../../../../application/types';
|
||||
import {
|
||||
|
@ -21,11 +21,20 @@ import { observabilityFeatureId, observabilityAppId } from '../../../../../commo
|
|||
import { parseRelativeDate } from '../components/date_range_picker';
|
||||
|
||||
export interface AddToCaseProps {
|
||||
autoOpen?: boolean;
|
||||
setAutoOpen?: (val: boolean) => void;
|
||||
timeRange: { from: string; to: string };
|
||||
appId?: 'security' | 'observability';
|
||||
lensAttributes: TypedLensByValueInput['attributes'] | null;
|
||||
}
|
||||
|
||||
export function AddToCaseAction({ lensAttributes, timeRange }: AddToCaseProps) {
|
||||
export function AddToCaseAction({
|
||||
lensAttributes,
|
||||
timeRange,
|
||||
autoOpen,
|
||||
setAutoOpen,
|
||||
appId,
|
||||
}: AddToCaseProps) {
|
||||
const kServices = useKibana<ObservabilityAppServices>().services;
|
||||
|
||||
const {
|
||||
|
@ -58,6 +67,7 @@ export function AddToCaseAction({ lensAttributes, timeRange }: AddToCaseProps) {
|
|||
from: absoluteFromDate?.toISOString() ?? '',
|
||||
to: absoluteToDate?.toISOString() ?? '',
|
||||
},
|
||||
appId,
|
||||
});
|
||||
|
||||
const getAllCasesSelectorModalProps: GetAllCasesSelectorModalProps = {
|
||||
|
@ -69,22 +79,36 @@ export function AddToCaseAction({ lensAttributes, timeRange }: AddToCaseProps) {
|
|||
},
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (autoOpen) {
|
||||
setIsCasesOpen(true);
|
||||
}
|
||||
}, [autoOpen, setIsCasesOpen]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isCasesOpen) {
|
||||
setAutoOpen?.(false);
|
||||
}
|
||||
}, [isCasesOpen, setAutoOpen]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<EuiButtonEmpty
|
||||
size="s"
|
||||
isLoading={isSaving}
|
||||
isDisabled={lensAttributes === null}
|
||||
onClick={() => {
|
||||
if (lensAttributes) {
|
||||
setIsCasesOpen(true);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{i18n.translate('xpack.observability.expView.heading.addToCase', {
|
||||
defaultMessage: 'Add to case',
|
||||
})}
|
||||
</EuiButtonEmpty>
|
||||
{typeof autoOpen === 'undefined' && (
|
||||
<EuiButtonEmpty
|
||||
size="s"
|
||||
isLoading={isSaving}
|
||||
isDisabled={lensAttributes === null}
|
||||
onClick={() => {
|
||||
if (lensAttributes) {
|
||||
setIsCasesOpen(true);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{i18n.translate('xpack.observability.expView.heading.addToCase', {
|
||||
defaultMessage: 'Add to case',
|
||||
})}
|
||||
</EuiButtonEmpty>
|
||||
)}
|
||||
{isCasesOpen &&
|
||||
lensAttributes &&
|
||||
cases.getAllCasesSelectorModal(getAllCasesSelectorModalProps)}
|
||||
|
|
|
@ -41,7 +41,11 @@ export const useAddToCase = ({
|
|||
lensAttributes,
|
||||
getToastText,
|
||||
timeRange,
|
||||
}: AddToCaseProps & { getToastText: (thaCase: Case | SubCase) => MountPoint<HTMLElement> }) => {
|
||||
appId,
|
||||
}: AddToCaseProps & {
|
||||
appId?: 'security' | 'observability';
|
||||
getToastText: (thaCase: Case | SubCase) => MountPoint<HTMLElement>;
|
||||
}) => {
|
||||
const [isSaving, setIsSaving] = useState(false);
|
||||
const [isCasesOpen, setIsCasesOpen] = useState(false);
|
||||
|
||||
|
@ -87,13 +91,13 @@ export const useAddToCase = ({
|
|||
}
|
||||
);
|
||||
} else {
|
||||
navigateToApp(observabilityFeatureId, {
|
||||
navigateToApp(appId || observabilityFeatureId, {
|
||||
deepLinkId: CasesDeepLinkId.casesCreate,
|
||||
openInNewTab: true,
|
||||
});
|
||||
}
|
||||
},
|
||||
[getToastText, http, lensAttributes, navigateToApp, timeRange, toasts]
|
||||
[appId, getToastText, http, lensAttributes, navigateToApp, timeRange, toasts]
|
||||
);
|
||||
|
||||
return {
|
||||
|
|
|
@ -118,7 +118,7 @@ export const useLensAttributes = (): TypedLensByValueInput['attributes'] | null
|
|||
|
||||
const lensAttributes = new LensAttributes(layerConfigs);
|
||||
|
||||
return lensAttributes.getJSON(lastRefresh);
|
||||
return lensAttributes.getJSON();
|
||||
// we also want to check the state on allSeries changes
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [indexPatterns, reportType, storage, theme, lastRefresh, allSeries]);
|
||||
|
|
|
@ -6,9 +6,9 @@
|
|||
*/
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import React, { Dispatch, SetStateAction, useCallback } from 'react';
|
||||
import React, { Dispatch, SetStateAction, useCallback, useState } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { TypedLensByValueInput } from '../../../../../lens/public';
|
||||
import { LensEmbeddableInput, TypedLensByValueInput } from '../../../../../lens/public';
|
||||
import { useUiTracker } from '../../../hooks/use_track_metric';
|
||||
import { useSeriesStorage } from './hooks/use_series_storage';
|
||||
import { ObservabilityPublicPluginsStart } from '../../../plugin';
|
||||
|
@ -30,9 +30,12 @@ export function LensEmbeddable(props: Props) {
|
|||
} = useKibana<ObservabilityPublicPluginsStart>();
|
||||
|
||||
const LensComponent = lens?.EmbeddableComponent;
|
||||
const LensSaveModalComponent = lens?.SaveModalComponent;
|
||||
|
||||
const { firstSeries, setSeries, reportType, lastRefresh } = useSeriesStorage();
|
||||
|
||||
const [isSaveOpen, setIsSaveOpen] = useState(false);
|
||||
|
||||
const firstSeriesId = 0;
|
||||
|
||||
const timeRange = useExpViewTimeRange();
|
||||
|
@ -90,6 +93,15 @@ export function LensEmbeddable(props: Props) {
|
|||
onLoad={onLensLoad}
|
||||
onBrushEnd={onBrushEnd}
|
||||
/>
|
||||
{isSaveOpen && lensAttributes && (
|
||||
<LensSaveModalComponent
|
||||
initialInput={lensAttributes as unknown as LensEmbeddableInput}
|
||||
onClose={() => setIsSaveOpen(false)}
|
||||
// if we want to do anything after the viz is saved
|
||||
// right now there is no action, so an empty function
|
||||
onSave={() => {}}
|
||||
/>
|
||||
)}
|
||||
</LensWrapper>
|
||||
);
|
||||
}
|
||||
|
@ -97,6 +109,26 @@ export function LensEmbeddable(props: Props) {
|
|||
const LensWrapper = styled.div`
|
||||
height: 100%;
|
||||
|
||||
.embPanel__optionsMenuPopover {
|
||||
visibility: collapse;
|
||||
}
|
||||
|
||||
&&&:hover {
|
||||
.embPanel__optionsMenuPopover {
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
|
||||
&& .embPanel--editing {
|
||||
border-style: initial !important;
|
||||
:hover {
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
.embPanel__title {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&&& > div {
|
||||
height: 100%;
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
"optionalPlugins": ["cloud", "data", "fleet", "home", "ml"],
|
||||
"requiredPlugins": [
|
||||
"alerting",
|
||||
"cases",
|
||||
"embeddable",
|
||||
"encryptedSavedObjects",
|
||||
"features",
|
||||
|
|
|
@ -48,6 +48,7 @@ import {
|
|||
import { LazySyntheticsCustomAssetsExtension } from '../components/fleet_package/lazy_synthetics_custom_assets_extension';
|
||||
import { Start as InspectorPluginStart } from '../../../../../src/plugins/inspector/public';
|
||||
import { UptimeUiConfig } from '../../common/config';
|
||||
import { CasesUiStart } from '../../../cases/public';
|
||||
|
||||
export interface ClientPluginsSetup {
|
||||
home?: HomePublicPluginSetup;
|
||||
|
@ -65,6 +66,7 @@ export interface ClientPluginsStart {
|
|||
observability: ObservabilityPublicStart;
|
||||
share: SharePluginStart;
|
||||
triggersActionsUi: TriggersAndActionsUIPublicPluginStart;
|
||||
cases: CasesUiStart;
|
||||
}
|
||||
|
||||
export interface UptimePluginServices extends Partial<CoreStart> {
|
||||
|
|
|
@ -120,6 +120,7 @@ const Application = (props: UptimeAppProps) => {
|
|||
inspector: startPlugins.inspector,
|
||||
triggersActionsUi: startPlugins.triggersActionsUi,
|
||||
observability: startPlugins.observability,
|
||||
cases: startPlugins.cases,
|
||||
}}
|
||||
>
|
||||
<Router history={appMountParameters.history}>
|
||||
|
|
|
@ -88,6 +88,7 @@ export function StepFieldTrend({
|
|||
}
|
||||
: undefined
|
||||
}
|
||||
withActions={false}
|
||||
/>
|
||||
</Wrapper>
|
||||
);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue