mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[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:
parent
cd22c673ef
commit
79b4d5e963
22 changed files with 457 additions and 615 deletions
|
@ -49,7 +49,8 @@
|
|||
"kibanaUtils",
|
||||
"unifiedSearch",
|
||||
"embeddable",
|
||||
"ingestPipelines"
|
||||
"ingestPipelines",
|
||||
"dashboard"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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';
|
||||
},
|
||||
},
|
||||
];
|
|
@ -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';
|
|
@ -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}
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
});
|
|
@ -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;
|
||||
}
|
||||
`;
|
|
@ -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';
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
`;
|
|
@ -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 ?? []}
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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();
|
||||
}}
|
||||
/>
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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}`;
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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> {
|
||||
|
|
|
@ -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',
|
||||
}),
|
||||
};
|
||||
}
|
|
@ -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'
|
||||
);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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",
|
||||
]
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue