[SLO] Move overview embeddable to new react registry (#181930)

Fixes https://github.com/elastic/kibana/issues/179296

## 🍒 Summary

This PR converts the SLO Overview embeddable (Single SLO & Grouped SLOs)
to the new React Embeddable framework. There are no UI changes to the
current embeddable and the behavior should be the same. I have a video
of how the embeddable should work.



d37c3f99-3140-4a09-a223-36a1d2242649

## ✔️ Acceptance criteria
- A new `create_overview_panel_action` action is created, which adds the
`SLO Overview` option in the Add panel section
- The `SLO Overview` menu option is grouped under SLOs menu
- Clicking on the single SLO card should open a Flyout with the SLO
details
- Clicking on the Grouped SLOs should expand the accordion and show the
SLOs that belong to this group
- The Grouped SLO have the `Edit criteria` option
- `Edit criteria` option appears under `More` panel options only for the
Grouped SLOs
- Kibana screenshot tool should report no timeout error.
- Clicking on the Refresh button should reload the embeddable
- We should be able to set default dimensions to the embeddable panels
cc @nickpeihl
  - Depends on https://github.com/elastic/kibana/pull/182120

---------

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Panagiota Mitsopoulou 2024-05-03 10:35:29 +02:00 committed by GitHub
parent cd22c673ef
commit 79b4d5e963
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
22 changed files with 457 additions and 615 deletions

View file

@ -49,7 +49,8 @@
"kibanaUtils",
"unifiedSearch",
"embeddable",
"ingestPipelines"
"ingestPipelines",
"dashboard"
]
}
}

View file

@ -13,7 +13,7 @@ import {
ErrorEmbeddable,
IContainer,
} from '@kbn/embeddable-plugin/public';
import { COMMON_SLO_GROUPING } from '../overview/slo_embeddable_factory';
import { COMMON_SLO_GROUPING } from '../common/constants';
import { SLO_ALERTS_EMBEDDABLE, SLOAlertsEmbeddable } from './slo_alerts_embeddable';
import { SloPublicPluginsStart, SloPublicStart } from '../../..';
import { SloAlertsEmbeddableInput } from './types';

View file

@ -28,7 +28,7 @@ import type { EmbeddableSloProps, SloAlertsEmbeddableInput, SloItem } from './ty
interface SloConfigurationProps {
initialInput?: Partial<SloAlertsEmbeddableInput>;
onCreate: (props: EmbeddableSloProps) => void; // TODO check change point detection
onCreate: (props: EmbeddableSloProps) => void;
onCancel: () => void;
}

View file

@ -0,0 +1,16 @@
/*
* 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.
*/
export const COMMON_SLO_GROUPING = [
{
id: 'slos',
getDisplayName: () => 'SLOs',
getIconType: () => {
return 'visGauge';
},
},
];

View file

@ -5,4 +5,5 @@
* 2.0.
*/
export { SloOverviewEmbeddableFactoryDefinition } from './slo_embeddable_factory';
export const SLO_OVERVIEW_EMBEDDABLE_ID = 'SLO_EMBEDDABLE';
export const ADD_SLO_OVERVIEW_ACTION_ID = 'CREATE_SLO_OVERVIEW_EMBEDDABLE';

View file

@ -9,7 +9,6 @@ import React, { useState, useEffect } from 'react';
import { Filter } from '@kbn/es-query';
import { Subject } from 'rxjs';
import { SLOView } from '../../../../pages/slos/components/toggle_slo_view';
import { SloEmbeddableInput } from '../types';
import { GroupView } from '../../../../pages/slos/components/grouped_slos/group_view';
import { buildCombinedKqlQuery } from './helpers/build_kql_query';
@ -20,45 +19,29 @@ interface Props {
sloView: SLOView;
sort?: string;
filters?: Filter[];
reloadGroupSubject: Subject<SloEmbeddableInput | undefined>;
reloadSubject: Subject<boolean>;
}
export function GroupSloView({
sloView,
groupBy: initialGroupBy = 'status',
groups: initialGroups = [],
kqlQuery: initialKqlQuery = '',
filters: initialFilters = [],
reloadGroupSubject,
groupBy = 'status',
groups = [],
kqlQuery = '',
filters = [],
reloadSubject,
}: Props) {
const [lastRefreshTime, setLastRefreshTime] = useState<number | undefined>(undefined);
const [groupBy, setGroupBy] = useState(initialGroupBy);
const [kqlQuery, setKqlQuery] = useState(initialKqlQuery);
const [filters, setFilters] = useState(initialFilters);
const [groups, setGroups] = useState(initialGroups);
const combinedKqlQuery = buildCombinedKqlQuery({ groups, groupBy, kqlQuery });
useEffect(() => {
const subs = reloadGroupSubject?.subscribe((input) => {
if (input) {
const nGroupBy = input?.groupFilters?.groupBy ?? groupBy;
setGroupBy(nGroupBy);
const nKqlInput = input?.groupFilters?.kqlQuery ?? kqlQuery;
setKqlQuery(nKqlInput);
const nFilters = input?.groupFilters?.filters ?? filters;
setFilters(nFilters);
const nGroups = input?.groupFilters?.groups ?? groups;
setGroups(nGroups);
}
reloadSubject?.subscribe(() => {
setLastRefreshTime(Date.now());
});
return () => {
subs?.unsubscribe();
reloadSubject?.unsubscribe();
};
}, [filters, groupBy, groups, kqlQuery, reloadGroupSubject]);
}, [reloadSubject]);
return (
<GroupView
sloView={sloView}

View file

@ -25,9 +25,8 @@ import { i18n } from '@kbn/i18n';
import { SloSelector } from '../alerts/slo_selector';
import type {
SingleSloProps,
GroupSloProps,
SloEmbeddableInput,
SingleSloCustomInput,
GroupSloCustomInput,
GroupFilters,
OverviewMode,
} from './types';
@ -35,26 +34,26 @@ import { SloGroupFilters } from './group_view/slo_group_filters';
import { OverviewModeSelector } from './overview_mode_selector';
interface SloConfigurationProps {
initialInput?: Partial<SloEmbeddableInput> | undefined;
onCreate: (props: SingleSloProps | GroupSloProps) => void;
initialInput?: GroupSloCustomInput;
onCreate: (props: SingleSloCustomInput | GroupSloCustomInput) => void;
onCancel: () => void;
}
interface SingleConfigurationProps {
onCreate: (props: SingleSloProps) => void;
onCreate: (props: SingleSloCustomInput) => void;
onCancel: () => void;
overviewMode: OverviewMode;
}
interface GroupConfigurationProps {
onCreate: (props: GroupSloProps) => void;
onCreate: (props: GroupSloCustomInput) => void;
onCancel: () => void;
overviewMode: OverviewMode;
initialInput?: GroupSloProps;
initialInput?: GroupSloCustomInput;
}
function SingleSloConfiguration({ overviewMode, onCreate, onCancel }: SingleConfigurationProps) {
const [selectedSlo, setSelectedSlo] = useState<SingleSloProps>();
const [selectedSlo, setSelectedSlo] = useState<SingleSloCustomInput>();
const [showAllGroupByInstances, setShowAllGroupByInstances] = useState(false);
const [hasError, setHasError] = useState(false);
const hasGroupBy = selectedSlo && selectedSlo.sloInstanceId !== ALL_VALUE;
@ -144,10 +143,10 @@ function GroupSloConfiguration({
initialInput,
}: GroupConfigurationProps) {
const [selectedGroupFilters, setSelectedGroupFilters] = useState<GroupFilters>({
groupBy: initialInput?.groupFilters.groupBy ?? 'status',
filters: initialInput?.groupFilters.filters ?? [],
kqlQuery: initialInput?.groupFilters.kqlQuery ?? '',
groups: initialInput?.groupFilters.groups ?? [],
groupBy: initialInput?.groupFilters?.groupBy ?? 'status',
filters: initialInput?.groupFilters?.filters ?? [],
kqlQuery: initialInput?.groupFilters?.kqlQuery ?? '',
groups: initialInput?.groupFilters?.groups ?? [],
});
const onConfirmClick = () =>
@ -226,7 +225,7 @@ export function SloConfiguration({ initialInput, onCreate, onCancel }: SloConfig
</EuiFlyoutHeader>
{overviewMode === 'groups' ? (
<GroupSloConfiguration
initialInput={initialInput as GroupSloProps}
initialInput={initialInput as GroupSloCustomInput}
overviewMode={overviewMode}
onCreate={onCreate}
onCancel={onCancel}

View file

@ -1,173 +0,0 @@
/*
* 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 { nextTick } from '@kbn/test-jest-helpers';
import { ReactWrapper } from 'enzyme';
import { ReactElement } from 'react';
import { SLOEmbeddable, SloEmbeddableDeps } from './slo_embeddable';
import type { SloEmbeddableInput, OverviewMode, GroupFilters } from './types';
import { uiSettingsServiceMock } from '@kbn/core-ui-settings-browser-mocks';
import { i18nServiceMock } from '@kbn/core-i18n-browser-mocks';
import { themeServiceMock } from '@kbn/core-theme-browser-mocks';
import { notificationServiceMock } from '@kbn/core-notifications-browser-mocks';
import { applicationServiceMock } from '@kbn/core/public/mocks';
import { httpServiceMock } from '@kbn/core-http-browser-mocks';
import { createObservabilityRuleTypeRegistryMock } from '@kbn/observability-plugin/public';
import { SloOverview } from './slo_overview';
import { useFetchSloDetails } from '../../../hooks/use_fetch_slo_details';
import { useFetchActiveAlerts } from '../../../hooks/use_fetch_active_alerts';
import { useFetchHistoricalSummary } from '../../../hooks/use_fetch_historical_summary';
import { useFetchRulesForSlo } from '../../../hooks/use_fetch_rules_for_slo';
import { ActiveAlerts } from '../../../hooks/active_alerts';
import { buildSlo } from '../../../data/slo/slo';
import { historicalSummaryData } from '../../../data/slo/historical_summary_data';
import { uiActionsPluginMock } from '@kbn/ui-actions-plugin/public/mocks';
import { GroupListView } from '../../../pages/slos/components/grouped_slos/group_list_view';
import { render } from 'react-dom';
import { act } from 'react-dom/test-utils';
let mockWrapper: ReactWrapper;
jest.mock('react-dom', () => {
const { mount } = jest.requireActual('enzyme');
return {
...jest.requireActual('react-dom'),
render: jest.fn((component: ReactElement) => {
mockWrapper = mount(component);
}),
};
});
jest.mock('../../../hooks/use_fetch_slo_details');
jest.mock('../../../hooks/use_fetch_active_alerts');
jest.mock('../../../hooks/use_fetch_historical_summary');
jest.mock('../../../hooks/use_fetch_rules_for_slo');
const useFetchSloDetailsMock = useFetchSloDetails as jest.Mock;
const useFetchActiveAlertsMock = useFetchActiveAlerts as jest.Mock;
const useFetchHistoricalSummaryMock = useFetchHistoricalSummary as jest.Mock;
const useFetchRulesForSloMock = useFetchRulesForSlo as jest.Mock;
function createSloEmbeddableDepsMock(): SloEmbeddableDeps {
return {
application: applicationServiceMock.createStartContract(),
uiSettings: uiSettingsServiceMock.createSetupContract(),
i18n: i18nServiceMock.createStartContract(),
theme: themeServiceMock.createStartContract(),
notifications: notificationServiceMock.createStartContract(),
http: httpServiceMock.createStartContract(),
uiActions: uiActionsPluginMock.createStartContract(),
observability: {
config: {
unsafe: {
alertDetails: {
observability: { enabled: true },
metrics: { enabled: false },
uptime: { enabled: false },
},
},
},
useRulesLink: () => ({ href: 'newRuleLink' }),
observabilityRuleTypeRegistry: createObservabilityRuleTypeRegistryMock(),
},
};
}
const waitOneTick = () => act(() => new Promise((resolve) => setTimeout(resolve, 0)));
describe('SLO Overview embeddable', () => {
let mountpoint: HTMLDivElement;
let depsMock: jest.Mocked<SloEmbeddableDeps>;
const createEmbeddable = ({
overviewMode,
sloId,
sloInstanceId,
groupFilters,
}: {
overviewMode: OverviewMode;
sloId?: string;
sloInstanceId?: string;
groupFilters?: GroupFilters;
}) => {
const baseInput: SloEmbeddableInput = {
id: 'mock-embeddable-id',
overviewMode,
};
let initialInput = baseInput;
initialInput =
overviewMode === 'single'
? {
...baseInput,
sloId,
sloInstanceId,
}
: {
...baseInput,
groupFilters,
};
return new SLOEmbeddable(depsMock, initialInput);
};
beforeEach(() => {
mountpoint = document.createElement('div');
depsMock = createSloEmbeddableDepsMock() as unknown as jest.Mocked<SloEmbeddableDeps>;
});
afterEach(() => {
mountpoint.remove();
jest.restoreAllMocks();
});
it('should render single Overview', async () => {
const slo = buildSlo();
useFetchSloDetailsMock.mockReturnValue({ isLoading: false, data: slo, refetch: () => {} });
useFetchActiveAlertsMock.mockReturnValue({ isLoading: false, data: new ActiveAlerts() });
useFetchHistoricalSummaryMock.mockReturnValue({
isLoading: false,
data: historicalSummaryData,
});
useFetchRulesForSloMock.mockReturnValue({
isLoading: false,
data: [],
});
const embeddable = createEmbeddable({
overviewMode: 'single',
sloId: 'sloId',
sloInstanceId: 'sloInstanceId',
});
await waitOneTick();
expect(render).toHaveBeenCalledTimes(0);
embeddable.render(mountpoint);
expect(render).toHaveBeenCalledTimes(1);
expect(mockWrapper.find(SloOverview).exists()).toBe(true);
expect(mockWrapper.find(GroupListView).exists()).toBe(false);
});
it('should show SLOs grouped by tags', async () => {
await act(async () => {
await nextTick();
mockWrapper.unmount();
mockWrapper.update();
});
const embeddable = createEmbeddable({
overviewMode: 'groups',
groupFilters: {
groupBy: 'slo.tags',
groups: ['production'],
},
});
await waitOneTick();
expect(render).toHaveBeenCalledTimes(0);
embeddable.render(mountpoint);
mockWrapper.update();
expect(render).toHaveBeenCalledTimes(1);
expect(mockWrapper.find(SloOverview).exists()).toBe(false);
});
});

View file

@ -1,217 +0,0 @@
/*
* 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 React from 'react';
import ReactDOM from 'react-dom';
import { EuiFlexItem, EuiLink, EuiFlexGroup } from '@elastic/eui';
import styled from 'styled-components';
import { i18n } from '@kbn/i18n';
import { Router } from '@kbn/shared-ux-router';
import {
Embeddable as AbstractEmbeddable,
EmbeddableOutput,
IContainer,
} from '@kbn/embeddable-plugin/public';
import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import {
type CoreStart,
IUiSettingsClient,
ApplicationStart,
NotificationsStart,
} from '@kbn/core/public';
import { Subject, Subscription } from 'rxjs';
import { ObservabilityPublicStart } from '@kbn/observability-plugin/public';
import { EuiThemeProvider } from '@kbn/kibana-react-plugin/common';
import { createBrowserHistory } from 'history';
import { KibanaThemeProvider } from '@kbn/react-kibana-context-theme';
import { CONTEXT_MENU_TRIGGER } from '@kbn/embeddable-plugin/public';
import { ActionExecutionContext } from '@kbn/ui-actions-plugin/public';
import type { UiActionsStart } from '@kbn/ui-actions-plugin/public';
import { PluginContext } from '../../../context/plugin_context';
import { SloCardChartList } from './slo_overview_grid';
import { SloOverview } from './slo_overview';
import { GroupSloView } from './group_view/group_view';
import type { SloEmbeddableInput } from './types';
import { EDIT_SLO_OVERVIEW_ACTION } from '../../../ui_actions/edit_slo_overview_panel';
export const SLO_EMBEDDABLE = 'SLO_EMBEDDABLE';
export interface SloEmbeddableDeps {
uiSettings: IUiSettingsClient;
http: CoreStart['http'];
i18n: CoreStart['i18n'];
theme: CoreStart['theme'];
application: ApplicationStart;
notifications: NotificationsStart;
observability: ObservabilityPublicStart;
uiActions: UiActionsStart;
}
export class SLOEmbeddable extends AbstractEmbeddable<SloEmbeddableInput, EmbeddableOutput> {
public readonly type = SLO_EMBEDDABLE;
private node?: HTMLElement;
private reloadSubject: Subject<boolean>;
private reloadGroupSubject: Subject<SloEmbeddableInput | undefined>;
private subscription: Subscription;
constructor(
private readonly deps: SloEmbeddableDeps,
initialInput: SloEmbeddableInput,
parent?: IContainer
) {
super(initialInput, {}, parent);
this.reloadSubject = new Subject<boolean>();
this.reloadGroupSubject = new Subject<SloEmbeddableInput | undefined>();
this.setTitle(
this.input.title ||
i18n.translate('xpack.slo.sloEmbeddable.displayTitle', {
defaultMessage: 'SLO Overview',
})
);
this.subscription = this.getInput$().subscribe((input) => {
this.reloadGroupSubject.next(input);
});
}
setTitle(title: string) {
this.updateInput({ title });
}
public onRenderComplete() {
this.renderComplete.dispatchComplete();
}
public render(node: HTMLElement) {
super.render(node);
this.node = node;
// required for the export feature to work
this.node.setAttribute('data-shared-item', '');
const {
sloId,
sloInstanceId,
showAllGroupByInstances,
overviewMode,
groupFilters,
remoteName,
} = this.getInput();
const queryClient = new QueryClient();
const { observabilityRuleTypeRegistry } = this.deps.observability;
const I18nContext = this.deps.i18n.Context;
const renderOverview = () => {
if (overviewMode === 'groups') {
const groupBy = groupFilters?.groupBy ?? 'status';
const kqlQuery = groupFilters?.kqlQuery ?? '';
const groups = groupFilters?.groups ?? [];
return (
<Wrapper>
<EuiFlexGroup
justifyContent="flexEnd"
wrap
css={`
margin-bottom: 20px;
`}
>
<EuiFlexItem grow={false}>
<EuiLink
onClick={() => {
const trigger = this.deps.uiActions.getTrigger(CONTEXT_MENU_TRIGGER);
this.deps.uiActions.getAction(EDIT_SLO_OVERVIEW_ACTION).execute({
trigger,
embeddable: this,
} as ActionExecutionContext);
}}
data-test-subj="o11ySloAlertsWrapperSlOsIncludedLink"
>
{i18n.translate('xpack.slo.overviewEmbeddable.editCriteriaLabel', {
defaultMessage: 'Edit criteria',
})}
</EuiLink>
</EuiFlexItem>
</EuiFlexGroup>
<EuiFlexItem grow={false}>
<GroupSloView
sloView="cardView"
groupBy={groupBy}
groups={groups}
kqlQuery={kqlQuery}
filters={groupFilters?.filters}
reloadGroupSubject={this.reloadGroupSubject}
/>
</EuiFlexItem>
</Wrapper>
);
} else {
return (
<SloOverview
onRenderComplete={() => this.onRenderComplete()}
sloId={sloId}
sloInstanceId={sloInstanceId}
reloadSubject={this.reloadSubject}
showAllGroupByInstances={showAllGroupByInstances}
remoteName={remoteName}
/>
);
}
};
ReactDOM.render(
<I18nContext>
<Router history={createBrowserHistory()}>
<EuiThemeProvider darkMode={true}>
<KibanaThemeProvider theme={this.deps.theme}>
<KibanaContextProvider services={this.deps}>
<PluginContext.Provider value={{ observabilityRuleTypeRegistry }}>
<QueryClientProvider client={queryClient}>
{showAllGroupByInstances ? (
<SloCardChartList sloId={sloId!} />
) : (
renderOverview()
)}
</QueryClientProvider>
</PluginContext.Provider>
</KibanaContextProvider>
</KibanaThemeProvider>
</EuiThemeProvider>
</Router>
</I18nContext>,
node
);
}
public getSloOverviewConfig() {
return this.getInput();
}
public updateSloOverviewConfig(next: SloEmbeddableInput) {
this.updateInput(next);
}
public reload() {
this.reloadSubject.next(true);
this.reloadGroupSubject?.next(undefined);
}
public destroy() {
super.destroy();
this.subscription.unsubscribe();
if (this.node) {
ReactDOM.unmountComponentAtNode(this.node);
}
}
}
const Wrapper = styled.div`
width: 100%;
padding: 5px 15px;
overflow: scroll;
.euiAccordion__buttonContent {
min-width: 100px;
}
`;

View file

@ -1,90 +0,0 @@
/*
* 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 { i18n } from '@kbn/i18n';
import type { CoreSetup } from '@kbn/core/public';
import {
IContainer,
EmbeddableFactoryDefinition,
EmbeddableFactory,
ErrorEmbeddable,
} from '@kbn/embeddable-plugin/public';
import { IProvidesPanelPlacementSettings } from '@kbn/dashboard-plugin/public/dashboard_container/component/panel_placement/types';
import { SLOEmbeddable, SLO_EMBEDDABLE } from './slo_embeddable';
import { SloPublicPluginsStart, SloPublicStart } from '../../..';
import type { SloEmbeddableInput } from './types';
export const COMMON_SLO_GROUPING = [
{
id: 'slos',
getDisplayName: () => 'SLOs',
getIconType: () => {
return 'visGauge';
},
},
];
export type SloOverviewEmbeddableFactory = EmbeddableFactory;
export class SloOverviewEmbeddableFactoryDefinition
implements EmbeddableFactoryDefinition, IProvidesPanelPlacementSettings<SloEmbeddableInput>
{
public readonly type = SLO_EMBEDDABLE;
public readonly grouping = COMMON_SLO_GROUPING;
constructor(
private getStartServices: CoreSetup<SloPublicPluginsStart, SloPublicStart>['getStartServices']
) {}
public async isEditable() {
return true;
}
public async getExplicitInput(): Promise<Partial<SloEmbeddableInput>> {
const [coreStart, pluginStart] = await this.getStartServices();
try {
const { resolveEmbeddableSloUserInput } = await import('./handle_explicit_input');
return await resolveEmbeddableSloUserInput(coreStart, pluginStart);
} catch (e) {
return Promise.reject();
}
}
public getPanelPlacementSettings: IProvidesPanelPlacementSettings<
SloEmbeddableInput,
unknown
>['getPanelPlacementSettings'] = (input) => {
if (input.showAllGroupByInstances || input.groupFilters) {
return { width: 24, height: 8 };
}
return { width: 12, height: 8 };
};
public async create(initialInput: SloEmbeddableInput, parent?: IContainer) {
try {
const [coreStart, pluginStart] = await this.getStartServices();
return new SLOEmbeddable({ ...coreStart, ...pluginStart }, initialInput, parent);
} catch (e) {
return new ErrorEmbeddable(e, initialInput, parent);
}
}
public getDescription() {
return i18n.translate('xpack.slo.sloEmbeddable.description', {
defaultMessage: 'Get an overview of your SLO health',
});
}
public getDisplayName() {
return i18n.translate('xpack.slo.sloEmbeddable.displayName', {
defaultMessage: 'SLO Overview',
});
}
public getIconType() {
return 'visGauge';
}
}

View file

@ -0,0 +1,221 @@
/*
* 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 { i18n } from '@kbn/i18n';
import React, { useEffect } from 'react';
import styled from 'styled-components';
import { EuiFlexItem, EuiLink, EuiFlexGroup } from '@elastic/eui';
import { Router } from '@kbn/shared-ux-router';
import { ReactEmbeddableFactory } from '@kbn/embeddable-plugin/public';
import {
initializeTitles,
useBatchedPublishingSubjects,
fetch$,
} from '@kbn/presentation-publishing';
import { BehaviorSubject, Subject } from 'rxjs';
import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { EuiThemeProvider } from '@kbn/kibana-react-plugin/common';
import { createBrowserHistory } from 'history';
import { CONTEXT_MENU_TRIGGER } from '@kbn/embeddable-plugin/public';
import { ActionExecutionContext } from '@kbn/ui-actions-plugin/public';
import { SLO_OVERVIEW_EMBEDDABLE_ID } from './constants';
import { SloCardChartList } from './slo_overview_grid';
import { SloOverview } from './slo_overview';
import { GroupSloView } from './group_view/group_view';
import {
SloOverviewEmbeddableState,
SloEmbeddableDeps,
SloOverviewApi,
GroupSloCustomInput,
} from './types';
import { EDIT_SLO_OVERVIEW_ACTION } from '../../../ui_actions/edit_slo_overview_panel';
import { PluginContext } from '../../../context/plugin_context';
const queryClient = new QueryClient();
export const getOverviewPanelTitle = () =>
i18n.translate('xpack.slo.sloEmbeddable.displayName', {
defaultMessage: 'SLO Overview',
});
export const getOverviewEmbeddableFactory = (deps: SloEmbeddableDeps) => {
const factory: ReactEmbeddableFactory<SloOverviewEmbeddableState, SloOverviewApi> = {
type: SLO_OVERVIEW_EMBEDDABLE_ID,
deserializeState: (state) => {
return state.rawState as SloOverviewEmbeddableState;
},
buildEmbeddable: async (state, buildApi, uuid, parentApi) => {
const { titlesApi, titleComparators, serializeTitles } = initializeTitles(state);
const defaultTitle$ = new BehaviorSubject<string | undefined>(getOverviewPanelTitle());
const sloId$ = new BehaviorSubject(state.sloId);
const sloInstanceId$ = new BehaviorSubject(state.sloInstanceId);
const showAllGroupByInstances$ = new BehaviorSubject(state.showAllGroupByInstances);
const overviewMode$ = new BehaviorSubject(state.overviewMode);
const groupFilters$ = new BehaviorSubject(state.groupFilters);
const remoteName$ = new BehaviorSubject(state.remoteName);
const reload$ = new Subject<boolean>();
const api = buildApi(
{
...titlesApi,
defaultPanelTitle: defaultTitle$,
serializeState: () => {
return {
rawState: {
...serializeTitles(),
sloId: sloId$.getValue(),
sloInstanceId: sloInstanceId$.getValue(),
showAllGroupByInstances: showAllGroupByInstances$.getValue(),
overviewMode: overviewMode$.getValue(),
groupFilters: groupFilters$.getValue(),
remoteName: remoteName$.getValue(),
},
};
},
getSloGroupOverviewConfig: () => {
return {
groupFilters: groupFilters$.getValue(),
overviewMode: overviewMode$.getValue(),
};
},
updateSloGroupOverviewConfig: (update: GroupSloCustomInput) => {
groupFilters$.next(update.groupFilters);
},
},
{
sloId: [sloId$, (value) => sloId$.next(value)],
sloInstanceId: [sloInstanceId$, (value) => sloInstanceId$.next(value)],
groupFilters: [groupFilters$, (value) => groupFilters$.next(value)],
showAllGroupByInstances: [
showAllGroupByInstances$,
(value) => showAllGroupByInstances$.next(value),
],
remoteName: [remoteName$, (value) => remoteName$.next(value)],
overviewMode: [overviewMode$, (value) => overviewMode$.next(value)],
...titleComparators,
}
);
const fetchSubscription = fetch$(api)
.pipe()
.subscribe((next) => {
reload$.next(next.isReload);
});
return {
api,
Component: () => {
const [
sloId,
sloInstanceId,
showAllGroupByInstances,
overviewMode,
groupFilters,
remoteName,
] = useBatchedPublishingSubjects(
sloId$,
sloInstanceId$,
showAllGroupByInstances$,
overviewMode$,
groupFilters$,
remoteName$
);
const { observabilityRuleTypeRegistry } = deps.observability;
useEffect(() => {
return () => {
fetchSubscription.unsubscribe();
};
}, []);
const renderOverview = () => {
if (overviewMode === 'groups') {
const groupBy = groupFilters?.groupBy ?? 'status';
const kqlQuery = groupFilters?.kqlQuery ?? '';
const groups = groupFilters?.groups ?? [];
return (
<Wrapper>
<EuiFlexGroup
data-shared-item=""
justifyContent="flexEnd"
wrap
css={`
margin-bottom: 20px;
`}
>
<EuiFlexItem grow={false}>
<EuiLink
onClick={() => {
const trigger = deps.uiActions.getTrigger(CONTEXT_MENU_TRIGGER);
deps.uiActions.getAction(EDIT_SLO_OVERVIEW_ACTION).execute({
trigger,
embeddable: api,
} as ActionExecutionContext);
}}
data-test-subj="o11ySloAlertsWrapperSlOsIncludedLink"
>
{i18n.translate('xpack.slo.overviewEmbeddable.editCriteriaLabel', {
defaultMessage: 'Edit criteria',
})}
</EuiLink>
</EuiFlexItem>
</EuiFlexGroup>
<EuiFlexItem grow={false}>
<GroupSloView
sloView="cardView"
groupBy={groupBy}
groups={groups}
kqlQuery={kqlQuery}
filters={groupFilters?.filters}
reloadSubject={reload$}
/>
</EuiFlexItem>
</Wrapper>
);
} else {
return (
<SloOverview
sloId={sloId}
sloInstanceId={sloInstanceId}
reloadSubject={reload$}
showAllGroupByInstances={showAllGroupByInstances}
remoteName={remoteName}
/>
);
}
};
return (
<Router history={createBrowserHistory()}>
<EuiThemeProvider darkMode={true}>
<KibanaContextProvider services={deps}>
<PluginContext.Provider value={{ observabilityRuleTypeRegistry }}>
<QueryClientProvider client={queryClient}>
{showAllGroupByInstances ? (
<SloCardChartList sloId={sloId!} />
) : (
renderOverview()
)}
</QueryClientProvider>
</PluginContext.Provider>
</KibanaContextProvider>
</EuiThemeProvider>
</Router>
);
},
};
},
};
return factory;
};
const Wrapper = styled.div`
width: 100%;
padding: 5px 15px;
overflow: scroll;
.euiAccordion__buttonContent {
min-width: 100px;
}
`;

View file

@ -10,6 +10,7 @@ import { i18n } from '@kbn/i18n';
import { EuiLoadingChart } from '@elastic/eui';
import { euiStyled } from '@kbn/kibana-react-plugin/common';
import { ALL_VALUE, SLOWithSummaryResponse } from '@kbn/slo-schema';
import { Subject } from 'rxjs';
import { SloOverviewDetails } from '../common/slo_overview_details';
import { formatHistoricalData } from '../../../utils/slo/chart_data_formatter';
import { useFetchHistoricalSummary } from '../../../hooks/use_fetch_historical_summary';
@ -19,15 +20,13 @@ import { SloCardItemBadges } from '../../../pages/slos/components/card_view/slo_
import { SloCardChart } from '../../../pages/slos/components/card_view/slo_card_item';
import { useFetchSloDetails } from '../../../hooks/use_fetch_slo_details';
import { SingleSloProps } from './types';
import { SingleSloCustomInput } from './types';
export function SloOverview({
sloId,
sloInstanceId,
remoteName,
onRenderComplete,
reloadSubject,
}: SingleSloProps) {
interface Props extends SingleSloCustomInput {
reloadSubject?: Subject<boolean>;
}
export function SloOverview({ sloId, sloInstanceId, remoteName, reloadSubject }: Props) {
const [lastRefreshTime, setLastRefreshTime] = useState<number | undefined>(undefined);
useEffect(() => {
@ -67,13 +66,6 @@ export function SloOverview({
useEffect(() => {
refetch();
}, [lastRefreshTime, refetch]);
useEffect(() => {
if (!onRenderComplete) return;
if (!isLoading) {
onRenderComplete();
}
}, [isLoading, onRenderComplete]);
const isSloNotFound = !isLoading && slo === undefined;
@ -113,7 +105,7 @@ export function SloOverview({
const historicalSliData = formatHistoricalData(historicalSummary, 'sli_value');
return (
<div style={{ width: '100%' }}>
<div data-shared-item="" style={{ width: '100%' }}>
<SloCardChart
slo={slo}
historicalSliData={historicalSliData ?? []}

View file

@ -153,7 +153,7 @@ export function SloCardChartList({ sloId }: { sloId: string }) {
return (
<>
<div style={{ width: '100%' }}>
<div data-shared-item="" style={{ width: '100%' }}>
<Chart>
<Settings
baseTheme={DARK_THEME}

View file

@ -6,25 +6,23 @@
*/
import React from 'react';
import { toMountPoint } from '@kbn/react-kibana-mount';
import type { CoreStart } from '@kbn/core/public';
import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import type { SloEmbeddableInput, EmbeddableSloProps } from './types';
import { toMountPoint } from '@kbn/react-kibana-mount';
import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public';
import type { GroupSloCustomInput, SingleSloCustomInput } from './types';
import { SloPublicPluginsStart } from '../../..';
import { SloConfiguration } from './slo_configuration';
export async function resolveEmbeddableSloUserInput(
export async function openSloConfiguration(
coreStart: CoreStart,
pluginStart: SloPublicPluginsStart,
input?: SloEmbeddableInput
): Promise<EmbeddableSloProps> {
initialState?: GroupSloCustomInput
): Promise<GroupSloCustomInput | SingleSloCustomInput> {
const { overlays } = coreStart;
const queryClient = new QueryClient();
return new Promise(async (resolve, reject) => {
try {
const flyoutSession = overlays.openFlyout(
const modalSession = overlays.openFlyout(
toMountPoint(
<KibanaContextProvider
services={{
@ -34,13 +32,13 @@ export async function resolveEmbeddableSloUserInput(
>
<QueryClientProvider client={queryClient}>
<SloConfiguration
initialInput={input}
onCreate={(update: EmbeddableSloProps) => {
flyoutSession.close();
initialInput={initialState}
onCreate={(update: GroupSloCustomInput | SingleSloCustomInput) => {
modalSession.close();
resolve(update);
}}
onCancel={() => {
flyoutSession.close();
modalSession.close();
reject();
}}
/>

View file

@ -4,9 +4,23 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { EmbeddableInput } from '@kbn/embeddable-plugin/public';
import { Subject } from 'rxjs';
import {
SerializedTitles,
PublishesWritablePanelTitle,
PublishesPanelTitle,
} from '@kbn/presentation-publishing';
import type { EmbeddableApiContext } from '@kbn/presentation-publishing';
import { DefaultEmbeddableApi } from '@kbn/embeddable-plugin/public';
import { Filter } from '@kbn/es-query';
import {
type CoreStart,
IUiSettingsClient,
ApplicationStart,
NotificationsStart,
} from '@kbn/core/public';
import { ObservabilityPublicStart } from '@kbn/observability-plugin/public';
import type { UiActionsStart } from '@kbn/ui-actions-plugin/public';
export type OverviewMode = 'single' | 'groups';
export type GroupBy = 'slo.tags' | 'status' | 'slo.indicator.type';
export interface GroupFilters {
@ -16,36 +30,56 @@ export interface GroupFilters {
kqlQuery?: string;
}
export type SingleSloProps = EmbeddableSloProps & {
sloId: string | undefined;
sloInstanceId: string | undefined;
showAllGroupByInstances?: boolean;
};
export type GroupSloProps = EmbeddableSloProps & {
groupFilters: GroupFilters;
};
export interface EmbeddableSloProps {
sloId?: string;
sloInstanceId?: string;
remoteName?: string;
reloadSubject?: Subject<boolean>;
onRenderComplete?: () => void;
export interface SloConfigurationProps {
overviewMode?: OverviewMode;
}
export type SloEmbeddableInput = EmbeddableInput & Partial<GroupSloProps> & Partial<SingleSloProps>;
export type SingleSloCustomInput = SloConfigurationProps & {
sloId: string | undefined;
sloInstanceId: string | undefined;
remoteName?: string;
showAllGroupByInstances?: boolean;
};
export interface HasSloOverviewConfig {
getSloOverviewConfig: () => SloEmbeddableInput;
updateSloOverviewConfig: (next: SloEmbeddableInput) => void;
export type GroupSloCustomInput = SloConfigurationProps & {
groupFilters: GroupFilters | undefined;
};
export type SloOverviewEmbeddableState = SerializedTitles &
Partial<GroupSloCustomInput> &
Partial<SingleSloCustomInput>;
export type SloOverviewApi = DefaultEmbeddableApi<SloOverviewEmbeddableState> &
PublishesWritablePanelTitle &
PublishesPanelTitle &
HasSloGroupOverviewConfig;
export interface HasSloGroupOverviewConfig {
getSloGroupOverviewConfig: () => GroupSloCustomInput;
updateSloGroupOverviewConfig: (next: GroupSloCustomInput) => void;
}
export const apiHasSloOverviewConfig = (api: unknown | null): api is HasSloOverviewConfig => {
export const apiHasSloGroupOverviewConfig = (
api: unknown | null
): api is HasSloGroupOverviewConfig => {
return Boolean(
api &&
typeof (api as HasSloOverviewConfig).getSloOverviewConfig === 'function' &&
typeof (api as HasSloOverviewConfig).updateSloOverviewConfig === 'function'
typeof (api as HasSloGroupOverviewConfig).getSloGroupOverviewConfig === 'function' &&
typeof (api as HasSloGroupOverviewConfig).updateSloGroupOverviewConfig === 'function'
);
};
export interface SloEmbeddableDeps {
uiSettings: IUiSettingsClient;
http: CoreStart['http'];
i18n: CoreStart['i18n'];
theme: CoreStart['theme'];
application: ApplicationStart;
notifications: NotificationsStart;
observability: ObservabilityPublicStart;
uiActions: UiActionsStart;
}
export type SloOverviewEmbeddableActionContext = EmbeddableApiContext & {
embeddable: SloOverviewApi;
};

View file

@ -10,7 +10,7 @@ import { SaveModalDashboardProps } from '@kbn/presentation-util-plugin/public';
import { useCallback } from 'react';
import { useKibana } from '../../../utils/kibana_react';
import { useDeleteSlo } from '../../../hooks/use_delete_slo';
import { SLO_EMBEDDABLE } from '../../../embeddable/slo/overview/slo_embeddable';
import { SLO_OVERVIEW_EMBEDDABLE_ID } from '../../../embeddable/slo/overview/constants';
export function useSloListActions({
slo,
@ -52,7 +52,7 @@ export function useSloListActions({
const state = {
input: embeddableInput,
type: SLO_EMBEDDABLE,
type: SLO_OVERVIEW_EMBEDDABLE_ID,
};
const path = dashboardId === 'new' ? '#/create' : `#/view/${dashboardId}`;

View file

@ -16,6 +16,7 @@ import {
} from '@kbn/core/public';
import { BehaviorSubject, firstValueFrom } from 'rxjs';
import { registerReactEmbeddableFactory } from '@kbn/embeddable-plugin/public';
import { registerDashboardPanelPlacementSetting } from '@kbn/dashboard-plugin/public';
import { SloPublicPluginsSetup, SloPublicPluginsStart } from './types';
import { PLUGIN_NAME, sloAppId } from '../common';
import type { SloPublicSetup, SloPublicStart } from './types';
@ -26,8 +27,9 @@ import { SLOS_BASE_PATH } from '../common/locators/paths';
import { getCreateSLOFlyoutLazy } from './pages/slo_edit/shared_flyout/get_create_slo_flyout';
import { registerBurnRateRuleType } from './rules/register_burn_rate_rule_type';
import { ExperimentalFeatures, SloConfig } from '../common/config';
import { SLO_OVERVIEW_EMBEDDABLE_ID } from './embeddable/slo/overview/constants';
import { SloOverviewEmbeddableState } from './embeddable/slo/overview/types';
import { SLO_ERROR_BUDGET_ID } from './embeddable/slo/error_budget/constants';
export class SloPlugin
implements Plugin<SloPublicSetup, SloPublicStart, SloPublicPluginsSetup, SloPublicPluginsStart>
{
@ -93,14 +95,25 @@ export class SloPlugin
const hasPlatinumLicense = license.hasAtLeast('platinum');
if (hasPlatinumLicense) {
const registerSloOverviewEmbeddableFactory = async () => {
const { SloOverviewEmbeddableFactoryDefinition } = await import(
registerDashboardPanelPlacementSetting(
SLO_OVERVIEW_EMBEDDABLE_ID,
(serializedState: SloOverviewEmbeddableState | undefined) => {
if (serializedState?.showAllGroupByInstances || serializedState?.groupFilters) {
return { width: 24, height: 8 };
}
return { width: 12, height: 8 };
}
);
registerReactEmbeddableFactory(SLO_OVERVIEW_EMBEDDABLE_ID, async () => {
const [coreStart, pluginsStart] = await coreSetup.getStartServices();
const deps = { ...coreStart, ...pluginsStart };
const { getOverviewEmbeddableFactory } = await import(
'./embeddable/slo/overview/slo_embeddable_factory'
);
const factory = new SloOverviewEmbeddableFactoryDefinition(coreSetup.getStartServices);
pluginsSetup.embeddable.registerEmbeddableFactory(factory.type, factory);
};
registerSloOverviewEmbeddableFactory();
return getOverviewEmbeddableFactory(deps);
});
const registerSloAlertsEmbeddableFactory = async () => {
const { SloAlertsEmbeddableFactoryDefinition } = await import(
'./embeddable/slo/alerts/slo_alerts_embeddable_factory'

View file

@ -17,7 +17,7 @@ import {
SLO_ERROR_BUDGET_ID,
} from '../embeddable/slo/error_budget/constants';
import { SloPublicPluginsStart, SloPublicStart } from '..';
import { COMMON_SLO_GROUPING } from '../embeddable/slo/overview/slo_embeddable_factory';
import { COMMON_SLO_GROUPING } from '../embeddable/slo/common/constants';
export function createAddErrorBudgetPanelAction(
getStartServices: CoreSetup<SloPublicPluginsStart, SloPublicStart>['getStartServices']
): UiActionsActionDefinition<EmbeddableApiContext> {

View file

@ -0,0 +1,56 @@
/*
* 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 { i18n } from '@kbn/i18n';
import type { CoreSetup } from '@kbn/core/public';
import { apiIsPresentationContainer } from '@kbn/presentation-containers';
import {
IncompatibleActionError,
type UiActionsActionDefinition,
} from '@kbn/ui-actions-plugin/public';
import { EmbeddableApiContext } from '@kbn/presentation-publishing';
import {
ADD_SLO_OVERVIEW_ACTION_ID,
SLO_OVERVIEW_EMBEDDABLE_ID,
} from '../embeddable/slo/overview/constants';
import { SloPublicPluginsStart, SloPublicStart } from '..';
import { COMMON_SLO_GROUPING } from '../embeddable/slo/common/constants';
export function createOverviewPanelAction(
getStartServices: CoreSetup<SloPublicPluginsStart, SloPublicStart>['getStartServices']
): UiActionsActionDefinition<EmbeddableApiContext> {
return {
id: ADD_SLO_OVERVIEW_ACTION_ID,
grouping: COMMON_SLO_GROUPING,
getIconType: () => 'visGauge',
isCompatible: async ({ embeddable }) => {
return apiIsPresentationContainer(embeddable);
},
execute: async ({ embeddable }) => {
if (!apiIsPresentationContainer(embeddable)) throw new IncompatibleActionError();
const [coreStart, deps] = await getStartServices();
try {
const { openSloConfiguration } = await import(
'../embeddable/slo/overview/slo_overview_open_configuration'
);
const initialState = await openSloConfiguration(coreStart, deps);
embeddable.addNewPanel(
{
panelType: SLO_OVERVIEW_EMBEDDABLE_ID,
initialState,
},
true
);
} catch (e) {
return Promise.reject();
}
},
getDisplayName: () =>
i18n.translate('xpack.slo.sloEmbeddable.ariaLabel', {
defaultMessage: 'SLO Overview',
}),
};
}

View file

@ -17,14 +17,21 @@ import {
CanAccessViewMode,
HasType,
} from '@kbn/presentation-publishing';
import { createAction } from '@kbn/ui-actions-plugin/public';
import type { SLOEmbeddable } from '../embeddable/slo/overview/slo_embeddable';
import {
type UiActionsActionDefinition,
IncompatibleActionError,
} from '@kbn/ui-actions-plugin/public';
import { SLO_EMBEDDABLE } from '../embeddable/slo/constants';
import { SloPublicPluginsStart, SloPublicStart } from '..';
import { HasSloOverviewConfig } from '../embeddable/slo/overview/types';
import {
GroupSloCustomInput,
HasSloGroupOverviewConfig,
SloOverviewEmbeddableActionContext,
apiHasSloGroupOverviewConfig,
} from '../embeddable/slo/overview/types';
export const EDIT_SLO_OVERVIEW_ACTION = 'editSloOverviewPanelAction';
type EditSloOverviewPanelApi = CanAccessViewMode & HasType & HasSloOverviewConfig;
type EditSloOverviewPanelApi = CanAccessViewMode & HasType & HasSloGroupOverviewConfig;
const isEditSloOverviewPanelApi = (api: unknown): api is EditSloOverviewPanelApi =>
Boolean(
apiHasType(api) &&
@ -35,8 +42,8 @@ const isEditSloOverviewPanelApi = (api: unknown): api is EditSloOverviewPanelApi
export function createEditSloOverviewPanelAction(
getStartServices: CoreSetup<SloPublicPluginsStart, SloPublicStart>['getStartServices']
) {
return createAction<EmbeddableApiContext>({
): UiActionsActionDefinition<SloOverviewEmbeddableActionContext> {
return {
id: EDIT_SLO_OVERVIEW_ACTION,
type: EDIT_SLO_OVERVIEW_ACTION,
getIconType(): string {
@ -46,30 +53,34 @@ export function createEditSloOverviewPanelAction(
i18n.translate('xpack.slo.actions.editSloOverviewEmbeddableTitle', {
defaultMessage: 'Edit criteria',
}),
async execute({ embeddable }: EmbeddableApiContext) {
if (!embeddable) {
throw new Error('Not possible to execute an action without the embeddable context');
async execute(context) {
const { embeddable } = context;
if (!apiHasSloGroupOverviewConfig(embeddable)) {
throw new IncompatibleActionError();
}
const [coreStart, pluginStart] = await getStartServices();
try {
const { resolveEmbeddableSloUserInput } = await import(
'../embeddable/slo/overview/handle_explicit_input'
const { openSloConfiguration } = await import(
'../embeddable/slo/overview/slo_overview_open_configuration'
);
const result = await resolveEmbeddableSloUserInput(
const result = await openSloConfiguration(
coreStart,
pluginStart,
(embeddable as SLOEmbeddable).getSloOverviewConfig()
embeddable.getSloGroupOverviewConfig()
);
(embeddable as SLOEmbeddable).updateInput(result);
embeddable.updateSloGroupOverviewConfig(result as GroupSloCustomInput);
} catch (e) {
return Promise.reject();
}
},
isCompatible: async ({ embeddable }: EmbeddableApiContext) =>
isEditSloOverviewPanelApi(embeddable) &&
embeddable.getSloOverviewConfig().overviewMode === 'groups',
});
isCompatible: async ({ embeddable }: EmbeddableApiContext) => {
return (
isEditSloOverviewPanelApi(embeddable) &&
embeddable.getSloGroupOverviewConfig().overviewMode === 'groups'
);
},
};
}

View file

@ -10,6 +10,7 @@ import { CONTEXT_MENU_TRIGGER } from '@kbn/embeddable-plugin/public';
import type { CoreSetup } from '@kbn/core/public';
import { createEditSloAlertsPanelAction } from './edit_slo_alerts_panel';
import { createEditSloOverviewPanelAction } from './edit_slo_overview_panel';
import { createOverviewPanelAction } from './create_overview_panel_action';
import { createAddErrorBudgetPanelAction } from './create_error_budget_action';
import { SloPublicPluginsStart, SloPublicStart } from '..';
@ -20,10 +21,12 @@ export function registerSloUiActions(
// Initialize actions
const editSloAlertsPanelAction = createEditSloAlertsPanelAction(core.getStartServices);
const editSloOverviewPanelAction = createEditSloOverviewPanelAction(core.getStartServices);
const addOverviewPanelAction = createOverviewPanelAction(core.getStartServices);
const addErrorBudgetPanelAction = createAddErrorBudgetPanelAction(core.getStartServices);
// Assign triggers
uiActions.addTriggerAction(CONTEXT_MENU_TRIGGER, editSloAlertsPanelAction);
uiActions.addTriggerAction(CONTEXT_MENU_TRIGGER, editSloOverviewPanelAction);
uiActions.addTriggerAction('ADD_PANEL_TRIGGER', addOverviewPanelAction);
uiActions.addTriggerAction('ADD_PANEL_TRIGGER', addErrorBudgetPanelAction);
}

View file

@ -43,7 +43,6 @@
"@kbn/charts-plugin",
"@kbn/ui-actions-plugin",
"@kbn/serverless",
"@kbn/dashboard-plugin",
"@kbn/data-views-plugin",
"@kbn/rule-registry-plugin",
"@kbn/licensing-plugin",
@ -88,16 +87,11 @@
"@kbn/aiops-plugin",
"@kbn/presentation-publishing",
"@kbn/aiops-log-rate-analysis",
"@kbn/test-jest-helpers",
"@kbn/core-ui-settings-browser-mocks",
"@kbn/core-i18n-browser-mocks",
"@kbn/core-theme-browser-mocks",
"@kbn/core-notifications-browser-mocks",
"@kbn/core-http-browser-mocks",
"@kbn/data-view-field-editor-plugin",
"@kbn/securitysolution-io-ts-utils",
"@kbn/core-elasticsearch-server-mocks",
"@kbn/datemath",
"@kbn/presentation-containers",
"@kbn/dashboard-plugin",
]
}