mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 09:19:04 -04:00
# Backport This will backport the following commits from `main` to `8.12`: - [[SLO] Reset UI for updating outdated SLOs (#172883)](https://github.com/elastic/kibana/pull/172883) <!--- Backport version: 8.9.7 --> ### Questions ? Please refer to the [Backport tool documentation](https://github.com/sqren/backport) <!--BACKPORT [{"author":{"name":"Chris Cowan","email":"chris@elastic.co"},"sourceCommit":{"committedDate":"2023-12-12T19:36:20Z","message":"[SLO] Reset UI for updating outdated SLOs (#172883)\n\n## Summary\r\n\r\nThis PR is a follow up to #172224, it adds a UI for resetting the SLO\r\ndefinitions from the previous model. Once #179473 is merged I will\r\nrebase this against `main` and convert it from a \"draft\" PR to \"ready to\r\nreview\".\r\n\r\n\r\n\r\n\r\n\r\n### Testing\r\n\r\n1. Start by loading `main`\r\n2. Ingest some data\r\n3. Create some SLOs\r\n4. Change Kibana from `main` to this PR\r\n5. Visit the SLO page, you should see a banner similar to the screen\r\nshot above.\r\n6. Do your best to break this\r\n\r\n---------\r\n\r\nCo-authored-by: shahzad31 <shahzad31comp@gmail.com>\r\nCo-authored-by: Dominique Clarke <doclarke71@gmail.com>","sha":"c2003d9f83f6d437ec9ce46943a402b38c07ece5","branchLabelMapping":{"^v8.13.0$":"main","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:enhancement","backport:prev-minor","Feature:SLO","v8.12.0","Team:obs-ux-management","v8.13.0"],"number":172883,"url":"https://github.com/elastic/kibana/pull/172883","mergeCommit":{"message":"[SLO] Reset UI for updating outdated SLOs (#172883)\n\n## Summary\r\n\r\nThis PR is a follow up to #172224, it adds a UI for resetting the SLO\r\ndefinitions from the previous model. Once #179473 is merged I will\r\nrebase this against `main` and convert it from a \"draft\" PR to \"ready to\r\nreview\".\r\n\r\n\r\n\r\n\r\n\r\n### Testing\r\n\r\n1. Start by loading `main`\r\n2. Ingest some data\r\n3. Create some SLOs\r\n4. Change Kibana from `main` to this PR\r\n5. Visit the SLO page, you should see a banner similar to the screen\r\nshot above.\r\n6. Do your best to break this\r\n\r\n---------\r\n\r\nCo-authored-by: shahzad31 <shahzad31comp@gmail.com>\r\nCo-authored-by: Dominique Clarke <doclarke71@gmail.com>","sha":"c2003d9f83f6d437ec9ce46943a402b38c07ece5"}},"sourceBranch":"main","suggestedTargetBranches":["8.12"],"targetPullRequestStates":[{"branch":"8.12","label":"v8.12.0","labelRegex":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"},{"branch":"main","label":"v8.13.0","labelRegex":"^v8.13.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/172883","number":172883,"mergeCommit":{"message":"[SLO] Reset UI for updating outdated SLOs (#172883)\n\n## Summary\r\n\r\nThis PR is a follow up to #172224, it adds a UI for resetting the SLO\r\ndefinitions from the previous model. Once #179473 is merged I will\r\nrebase this against `main` and convert it from a \"draft\" PR to \"ready to\r\nreview\".\r\n\r\n\r\n\r\n\r\n\r\n### Testing\r\n\r\n1. Start by loading `main`\r\n2. Ingest some data\r\n3. Create some SLOs\r\n4. Change Kibana from `main` to this PR\r\n5. Visit the SLO page, you should see a banner similar to the screen\r\nshot above.\r\n6. Do your best to break this\r\n\r\n---------\r\n\r\nCo-authored-by: shahzad31 <shahzad31comp@gmail.com>\r\nCo-authored-by: Dominique Clarke <doclarke71@gmail.com>","sha":"c2003d9f83f6d437ec9ce46943a402b38c07ece5"}}]}] BACKPORT--> Co-authored-by: Chris Cowan <chris@elastic.co> Co-authored-by: Dominique Clarke <dominique.clarke@elastic.co>
This commit is contained in:
parent
3b421f390e
commit
5716fb945f
15 changed files with 550 additions and 15 deletions
|
@ -20,6 +20,7 @@ export const SLOS_WELCOME_PATH = '/slos/welcome' as const;
|
|||
export const SLO_DETAIL_PATH = '/slos/:sloId' as const;
|
||||
export const SLO_CREATE_PATH = '/slos/create' as const;
|
||||
export const SLO_EDIT_PATH = '/slos/edit/:sloId' as const;
|
||||
export const SLOS_OUTDATED_DEFINITIONS_PATH = '/slos/outdated-definitions' as const;
|
||||
export const CASES_PATH = '/cases' as const;
|
||||
|
||||
export const paths = {
|
||||
|
@ -31,6 +32,7 @@ export const paths = {
|
|||
ruleDetails: (ruleId: string) => `${OBSERVABILITY_BASE_PATH}${RULES_PATH}/${encodeURI(ruleId)}`,
|
||||
slos: `${OBSERVABILITY_BASE_PATH}${SLOS_PATH}`,
|
||||
slosWelcome: `${OBSERVABILITY_BASE_PATH}${SLOS_WELCOME_PATH}`,
|
||||
slosOutdatedDefinitions: `${OBSERVABILITY_BASE_PATH}${SLOS_OUTDATED_DEFINITIONS_PATH}`,
|
||||
sloCreate: `${OBSERVABILITY_BASE_PATH}${SLO_CREATE_PATH}`,
|
||||
sloCreateWithEncodedForm: (encodedParams: string) =>
|
||||
`${OBSERVABILITY_BASE_PATH}${SLO_CREATE_PATH}?_a=${encodedParams}`,
|
||||
|
|
|
@ -7,11 +7,11 @@
|
|||
|
||||
import { EuiConfirmModal } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { ALL_VALUE, SLOWithSummaryResponse } from '@kbn/slo-schema';
|
||||
import { ALL_VALUE, SLOResponse, SLOWithSummaryResponse } from '@kbn/slo-schema';
|
||||
import React from 'react';
|
||||
|
||||
export interface SloDeleteConfirmationModalProps {
|
||||
slo: SLOWithSummaryResponse;
|
||||
slo: SLOWithSummaryResponse | SLOResponse;
|
||||
onCancel: () => void;
|
||||
onConfirm: () => void;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* 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 { EuiConfirmModal } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { SLOResponse, SLOWithSummaryResponse } from '@kbn/slo-schema';
|
||||
import React from 'react';
|
||||
|
||||
export interface SloResetConfirmationModalProps {
|
||||
slo: SLOWithSummaryResponse | SLOResponse;
|
||||
onCancel: () => void;
|
||||
onConfirm: () => void;
|
||||
}
|
||||
|
||||
export function SloResetConfirmationModal({
|
||||
slo,
|
||||
onCancel,
|
||||
onConfirm,
|
||||
}: SloResetConfirmationModalProps) {
|
||||
const { name } = slo;
|
||||
return (
|
||||
<EuiConfirmModal
|
||||
buttonColor="danger"
|
||||
data-test-subj="sloResetConfirmationModal"
|
||||
title={i18n.translate('xpack.observability.slo.resetConfirmationModal.title', {
|
||||
defaultMessage: 'Reset {name}?',
|
||||
values: { name },
|
||||
})}
|
||||
cancelButtonText={i18n.translate(
|
||||
'xpack.observability.slo.resetConfirmationModal.cancelButtonLabel',
|
||||
{ defaultMessage: 'Cancel' }
|
||||
)}
|
||||
confirmButtonText={i18n.translate(
|
||||
'xpack.observability.slo.resetConfirmationModal.resetButtonLabel',
|
||||
{ defaultMessage: 'Reset' }
|
||||
)}
|
||||
onCancel={onCancel}
|
||||
onConfirm={onConfirm}
|
||||
>
|
||||
{i18n.translate('xpack.observability.slo.resetConfirmationModal.descriptionText', {
|
||||
defaultMessage: 'Resetting this SLO will also regenerate the historical data.',
|
||||
})}
|
||||
</EuiConfirmModal>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
/*
|
||||
* 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 { EuiButton, EuiCallOut } from '@elastic/eui';
|
||||
import React from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { useFetchSloDefinitions } from '../../../hooks/slo/use_fetch_slo_definitions';
|
||||
import { useKibana } from '../../../utils/kibana_react';
|
||||
import { paths } from '../../../../common/locators/paths';
|
||||
|
||||
export function SloOutdatedCallout() {
|
||||
const {
|
||||
application: { navigateToUrl },
|
||||
http: { basePath },
|
||||
} = useKibana().services;
|
||||
|
||||
const handleClick = () => {
|
||||
navigateToUrl(basePath.prepend(paths.observability.slosOutdatedDefinitions));
|
||||
};
|
||||
|
||||
const { isLoading, data } = useFetchSloDefinitions({ includeOutdatedOnly: true });
|
||||
if (!isLoading && data && data.total > 0) {
|
||||
return (
|
||||
<EuiCallOut
|
||||
color="warning"
|
||||
iconType="warning"
|
||||
title={i18n.translate('xpack.observability.slo.outdatedSloCallout.title', {
|
||||
defaultMessage: '{total} Outdated SLOs Detected',
|
||||
values: {
|
||||
total: data.total,
|
||||
},
|
||||
})}
|
||||
>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.observability.slo.outdatedSloCallout.message"
|
||||
defaultMessage="We've noticed that you have {total} outdated SLO definitions, these SLOs will not be running or alerting until you've reset them. Please click the button below to review the SLO definitions; you can choose to either reset the SLO definition or remove it."
|
||||
values={{ total: data.total }}
|
||||
/>
|
||||
</p>
|
||||
<p>
|
||||
<EuiButton
|
||||
color="warning"
|
||||
data-test-subj="o11ySloOutdatedCalloutViewOutdatedSloDefinitionsButton"
|
||||
fill
|
||||
onClick={handleClick}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.observability.outdatedSloCallout.buttonLabel"
|
||||
defaultMessage="Review Outdated SLO Definitions"
|
||||
/>
|
||||
</EuiButton>
|
||||
</p>
|
||||
</EuiCallOut>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
|
@ -29,7 +29,8 @@ export const sloKeys = {
|
|||
historicalSummaries: () => [...sloKeys.all, 'historicalSummary'] as const,
|
||||
historicalSummary: (list: Array<{ sloId: string; instanceId: string }>) =>
|
||||
[...sloKeys.historicalSummaries(), list] as const,
|
||||
definitions: (search: string) => [...sloKeys.all, 'definitions', search] as const,
|
||||
definitions: (search: string, page: number, perPage: number, includeOutdatedOnly: boolean) =>
|
||||
[...sloKeys.all, 'definitions', search, page, perPage, includeOutdatedOnly] as const,
|
||||
globalDiagnosis: () => [...sloKeys.all, 'globalDiagnosis'] as const,
|
||||
burnRates: (sloId: string, instanceId: string | undefined) =>
|
||||
[...sloKeys.all, 'burnRates', sloId, instanceId] as const,
|
||||
|
|
|
@ -15,23 +15,32 @@ export interface UseFetchSloDefinitionsResponse {
|
|||
isLoading: boolean;
|
||||
isSuccess: boolean;
|
||||
isError: boolean;
|
||||
refetch: () => void;
|
||||
}
|
||||
|
||||
interface Params {
|
||||
name?: string;
|
||||
includeOutdatedOnly?: boolean;
|
||||
page?: number;
|
||||
perPage?: number;
|
||||
}
|
||||
|
||||
export function useFetchSloDefinitions({ name = '' }: Params): UseFetchSloDefinitionsResponse {
|
||||
export function useFetchSloDefinitions({
|
||||
name = '',
|
||||
includeOutdatedOnly = false,
|
||||
page = 1,
|
||||
perPage = 100,
|
||||
}: Params): UseFetchSloDefinitionsResponse {
|
||||
const { http } = useKibana().services;
|
||||
const search = name.endsWith('*') ? name : `${name}*`;
|
||||
|
||||
const { isLoading, isError, isSuccess, data } = useQuery({
|
||||
queryKey: sloKeys.definitions(search),
|
||||
const { isLoading, isError, isSuccess, data, refetch } = useQuery({
|
||||
queryKey: sloKeys.definitions(search, page, perPage, includeOutdatedOnly),
|
||||
queryFn: async ({ signal }) => {
|
||||
try {
|
||||
const response = await http.get<FindSLODefinitionsResponse>(
|
||||
'/api/observability/slos/_definitions',
|
||||
{ query: { search }, signal }
|
||||
{ query: { search, includeOutdatedOnly, page, perPage }, signal }
|
||||
);
|
||||
|
||||
return response;
|
||||
|
@ -43,5 +52,5 @@ export function useFetchSloDefinitions({ name = '' }: Params): UseFetchSloDefini
|
|||
refetchOnWindowFocus: false,
|
||||
});
|
||||
|
||||
return { isLoading, isError, isSuccess, data };
|
||||
return { isLoading, isError, isSuccess, data, refetch };
|
||||
}
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* 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 { useMutation } from '@tanstack/react-query';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { IHttpFetchError, ResponseErrorBody } from '@kbn/core/public';
|
||||
import { useKibana } from '../../utils/kibana_react';
|
||||
|
||||
type ServerError = IHttpFetchError<ResponseErrorBody>;
|
||||
|
||||
export function useResetSlo() {
|
||||
const {
|
||||
http,
|
||||
notifications: { toasts },
|
||||
} = useKibana().services;
|
||||
return useMutation<string, ServerError, { id: string; name: string }>(
|
||||
['resetSlo'],
|
||||
({ id, name }) => {
|
||||
try {
|
||||
return http.post(`/api/observability/slos/${id}/_reset`);
|
||||
} catch (error) {
|
||||
return Promise.reject(
|
||||
i18n.translate('xpack.observability.slo.slo.reset.errorMessage', {
|
||||
defaultMessage: 'Failed to reset {name} (id: {id}), something went wrong: {error}',
|
||||
values: { error: String(error), name, id },
|
||||
})
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
onError: (error, { name, id }) => {
|
||||
toasts.addError(new Error(error.body?.message ?? error.message), {
|
||||
title: i18n.translate('xpack.observability.slo.slo.reset.errorNotification', {
|
||||
defaultMessage: 'Failed to reset {name} (id: {id})',
|
||||
values: { name, id },
|
||||
}),
|
||||
});
|
||||
},
|
||||
onSuccess: (_data, { name }) => {
|
||||
toasts.addSuccess(
|
||||
i18n.translate('xpack.observability.slo.slo.reset.successNotification', {
|
||||
defaultMessage: '{name} reset successfully',
|
||||
values: { name },
|
||||
})
|
||||
);
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
|
@ -0,0 +1,141 @@
|
|||
/*
|
||||
* 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, { useState } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiTablePagination, EuiText } from '@elastic/eui';
|
||||
import { useBreadcrumbs } from '@kbn/observability-shared-plugin/public';
|
||||
import { useKibana } from '../../utils/kibana_react';
|
||||
import { useLicense } from '../../hooks/use_license';
|
||||
import { usePluginContext } from '../../hooks/use_plugin_context';
|
||||
import { useCapabilities } from '../../hooks/slo/use_capabilities';
|
||||
import { useFetchSloGlobalDiagnosis } from '../../hooks/slo/use_fetch_global_diagnosis';
|
||||
import { HeaderMenu } from '../overview/components/header_menu/header_menu';
|
||||
import { useFetchSloDefinitions } from '../../hooks/slo/use_fetch_slo_definitions';
|
||||
import { paths } from '../../../common/locators/paths';
|
||||
import { SloListEmpty } from '../slos/components/slo_list_empty';
|
||||
import { OutdatedSlo } from './outdated_slo';
|
||||
import { OutdatedSloSearchBar } from './outdated_slo_search_bar';
|
||||
|
||||
export function SlosOutdatedDefinitions() {
|
||||
const {
|
||||
http: { basePath },
|
||||
} = useKibana().services;
|
||||
const { hasWriteCapabilities } = useCapabilities();
|
||||
const { data: globalDiagnosis } = useFetchSloGlobalDiagnosis();
|
||||
const { ObservabilityPageTemplate } = usePluginContext();
|
||||
|
||||
useBreadcrumbs([
|
||||
{
|
||||
href: basePath.prepend(paths.observability.slos),
|
||||
text: i18n.translate('xpack.observability.breadcrumbs.slosLinkText', {
|
||||
defaultMessage: 'SLOs',
|
||||
}),
|
||||
deepLinkId: 'observability-overview:slos',
|
||||
},
|
||||
{
|
||||
text: i18n.translate('xpack.observability.breadcrumbs.slosOutdatedDefinitions', {
|
||||
defaultMessage: 'Outdated SLO Definitions',
|
||||
}),
|
||||
},
|
||||
]);
|
||||
|
||||
const [search, setSearch] = useState<string>('');
|
||||
const [activePage, setActivePage] = useState<number>(0);
|
||||
const [perPage, setPerPage] = useState<number>(10);
|
||||
|
||||
const handlePerPageChange = (perPageNumber: number) => {
|
||||
setPerPage(perPageNumber);
|
||||
setActivePage(0);
|
||||
};
|
||||
|
||||
const { hasAtLeast } = useLicense();
|
||||
|
||||
const { isLoading, data, refetch } = useFetchSloDefinitions({
|
||||
name: search,
|
||||
includeOutdatedOnly: true,
|
||||
page: activePage + 1,
|
||||
perPage,
|
||||
});
|
||||
const { total } = data ?? { total: 0 };
|
||||
|
||||
const hasRequiredWritePrivileges =
|
||||
!!globalDiagnosis?.userPrivileges.write.has_all_requested && hasWriteCapabilities;
|
||||
|
||||
const hasPlatinumLicense = hasAtLeast('platinum') === true;
|
||||
|
||||
const hasSlosAndHasPermissions = hasPlatinumLicense && hasRequiredWritePrivileges;
|
||||
|
||||
const errors = !hasRequiredWritePrivileges ? (
|
||||
<EuiText>
|
||||
{i18n.translate('xpack.observability.slo.slosOutdatedDefinitions.sloPermissionsError', {
|
||||
defaultMessage: 'You must have write permissions for SLOs to access this page',
|
||||
})}
|
||||
</EuiText>
|
||||
) : !hasPlatinumLicense ? (
|
||||
<EuiText>
|
||||
{i18n.translate('xpack.observability.slo.slosOutdatedDefinitions.licenseError', {
|
||||
defaultMessage: 'You must have atleast a platinum license to access this page',
|
||||
})}
|
||||
</EuiText>
|
||||
) : null;
|
||||
|
||||
return (
|
||||
<ObservabilityPageTemplate
|
||||
data-test-subj="slosOutdatedDefinitions"
|
||||
pageHeader={{
|
||||
pageTitle: i18n.translate('xpack.observability.slo.slosOutdatedDefinitions.pageTitle', {
|
||||
defaultMessage: 'Outdated SLO Definitions',
|
||||
}),
|
||||
}}
|
||||
>
|
||||
<HeaderMenu />
|
||||
|
||||
{!hasSlosAndHasPermissions ? (
|
||||
errors
|
||||
) : (
|
||||
<>
|
||||
<p>
|
||||
{i18n.translate('xpack.observability.slo.slosOutdatedDefinitions.description', {
|
||||
defaultMessage:
|
||||
'The following SLOs are from a previous version and need to either be reset to upgrade to the latest version OR deleted and removed from the system. When you reset the SLO, the transform will be updated to the latest version and the historical data will be regenerated from the source data.',
|
||||
})}
|
||||
</p>
|
||||
<EuiSpacer size="l" />
|
||||
<EuiFlexGroup direction="column" gutterSize="s">
|
||||
<EuiFlexItem>
|
||||
<OutdatedSloSearchBar
|
||||
initialSearch={search}
|
||||
onRefresh={refetch}
|
||||
onSearch={setSearch}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
{!isLoading && total === 0 && <SloListEmpty />}
|
||||
{!isLoading &&
|
||||
total > 0 &&
|
||||
data &&
|
||||
data.results.map((slo) => (
|
||||
<OutdatedSlo slo={slo} onDelete={refetch} onReset={refetch} />
|
||||
))}
|
||||
</EuiFlexGroup>
|
||||
<EuiSpacer size="m" />
|
||||
{!isLoading && data && (
|
||||
<EuiTablePagination
|
||||
activePage={activePage}
|
||||
pageCount={Math.ceil(total / perPage)}
|
||||
itemsPerPage={perPage}
|
||||
onChangePage={setActivePage}
|
||||
onChangeItemsPerPage={handlePerPageChange}
|
||||
itemsPerPageOptions={[10, 20, 50, 100]}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</ObservabilityPageTemplate>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,124 @@
|
|||
/*
|
||||
* 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, { useState } from 'react';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiPanel, EuiText, EuiButton } from '@elastic/eui';
|
||||
import { SLOResponse } from '@kbn/slo-schema';
|
||||
import { SloDeleteConfirmationModal } from '../../components/slo/delete_confirmation_modal/slo_delete_confirmation_modal';
|
||||
import { SloTimeWindowBadge } from '../slos/components/badges/slo_time_window_badge';
|
||||
import { SloIndicatorTypeBadge } from '../slos/components/badges/slo_indicator_type_badge';
|
||||
import { useDeleteSlo } from '../../hooks/slo/use_delete_slo';
|
||||
import { useResetSlo } from '../../hooks/slo/use_reset_slo';
|
||||
import { SloResetConfirmationModal } from '../../components/slo/reset_confirmation_modal/slo_reset_confirmation_modal';
|
||||
|
||||
interface OutdatedSloProps {
|
||||
slo: SLOResponse;
|
||||
onReset: () => void;
|
||||
onDelete: () => void;
|
||||
}
|
||||
|
||||
export function OutdatedSlo({ slo, onReset, onDelete }: OutdatedSloProps) {
|
||||
const { mutateAsync: resetSlo, isLoading: isResetLoading } = useResetSlo();
|
||||
const { mutateAsync: deleteSlo, isLoading: isDeleteLoading } = useDeleteSlo();
|
||||
const [isDeleteConfirmationModalOpen, setDeleteConfirmationModalOpen] = useState(false);
|
||||
const [isResetConfirmationModalOpen, setResetConfirmationModalOpen] = useState(false);
|
||||
|
||||
const handleDelete = () => {
|
||||
setDeleteConfirmationModalOpen(true);
|
||||
};
|
||||
|
||||
const handleDeleteConfirm = async () => {
|
||||
setDeleteConfirmationModalOpen(false);
|
||||
await deleteSlo({ id: slo.id, name: slo.name });
|
||||
onDelete();
|
||||
};
|
||||
|
||||
const handleDeleteCancel = () => {
|
||||
setDeleteConfirmationModalOpen(false);
|
||||
};
|
||||
|
||||
const handleReset = () => {
|
||||
setResetConfirmationModalOpen(true);
|
||||
};
|
||||
|
||||
const handleResetConfirm = async () => {
|
||||
setResetConfirmationModalOpen(false);
|
||||
await resetSlo({ id: slo.id, name: slo.name });
|
||||
onReset();
|
||||
};
|
||||
|
||||
const handleResetCancel = () => {
|
||||
setResetConfirmationModalOpen(false);
|
||||
};
|
||||
return (
|
||||
<EuiPanel hasBorder hasShadow={false}>
|
||||
<EuiFlexGroup alignItems="center" gutterSize="s">
|
||||
<EuiFlexItem>
|
||||
<EuiFlexGroup direction="column" gutterSize="s">
|
||||
<EuiFlexItem>
|
||||
<EuiText>
|
||||
<span>{slo.name}</span>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexGroup
|
||||
direction="row"
|
||||
responsive={false}
|
||||
gutterSize="s"
|
||||
alignItems="center"
|
||||
wrap
|
||||
>
|
||||
<SloIndicatorTypeBadge slo={slo} />
|
||||
<SloTimeWindowBadge slo={slo} />
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={0}>
|
||||
<EuiButton
|
||||
data-test-subj="o11ySlosOutdatedDefinitionsResetButton"
|
||||
color="primary"
|
||||
fill
|
||||
isLoading={isResetLoading}
|
||||
onClick={handleReset}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.observability.slosOutdatedDefinitions.resetButtonLabel"
|
||||
defaultMessage="Reset"
|
||||
/>
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={0}>
|
||||
<EuiButton
|
||||
data-test-subj="o11ySlosOutdatedDefinitionsDeleteButton"
|
||||
color="danger"
|
||||
fill
|
||||
isLoading={isDeleteLoading}
|
||||
onClick={handleDelete}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.observability.slosOutdatedDefinitions.deleteButtonLabel"
|
||||
defaultMessage="Delete"
|
||||
/>
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
{isDeleteConfirmationModalOpen ? (
|
||||
<SloDeleteConfirmationModal
|
||||
slo={slo}
|
||||
onCancel={handleDeleteCancel}
|
||||
onConfirm={handleDeleteConfirm}
|
||||
/>
|
||||
) : null}
|
||||
{isResetConfirmationModalOpen ? (
|
||||
<SloResetConfirmationModal
|
||||
slo={slo}
|
||||
onCancel={handleResetCancel}
|
||||
onConfirm={handleResetConfirm}
|
||||
/>
|
||||
) : null}
|
||||
</EuiPanel>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,82 @@
|
|||
/*
|
||||
* 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, { useState } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiButton, EuiFieldSearch } from '@elastic/eui';
|
||||
|
||||
interface OutdatedSloSearchBarProps {
|
||||
initialSearch?: string;
|
||||
onRefresh: () => void;
|
||||
onSearch: (search: string) => void;
|
||||
}
|
||||
|
||||
export function OutdatedSloSearchBar({
|
||||
onSearch,
|
||||
onRefresh,
|
||||
initialSearch = '',
|
||||
}: OutdatedSloSearchBarProps) {
|
||||
const [tempSearch, setTempSearch] = useState<string>(initialSearch);
|
||||
const [search, setSearch] = useState<string>(initialSearch);
|
||||
|
||||
const refreshOrUpdateSearch = () => {
|
||||
if (tempSearch !== search) {
|
||||
setSearch(tempSearch);
|
||||
onSearch(tempSearch);
|
||||
} else {
|
||||
onRefresh();
|
||||
}
|
||||
};
|
||||
|
||||
const handleClick = (event: React.ChangeEvent<HTMLInputElement>) =>
|
||||
setTempSearch(event.target.value);
|
||||
|
||||
const handleKeyPress = (event: React.KeyboardEvent<HTMLInputElement>) => {
|
||||
if (event.key === 'Enter') {
|
||||
refreshOrUpdateSearch();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<EuiFlexGroup gutterSize="s">
|
||||
<EuiFlexItem>
|
||||
<EuiFieldSearch
|
||||
data-test-subj="o11ySlosOutdatedDefinitionsFieldSearch"
|
||||
fullWidth
|
||||
value={tempSearch}
|
||||
onChange={handleClick}
|
||||
onKeyDown={handleKeyPress}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={0}>
|
||||
{search === tempSearch && (
|
||||
<EuiButton
|
||||
data-test-subj="o11ySlosOutdatedDefinitionsRefreshButton"
|
||||
iconType="refresh"
|
||||
onClick={refreshOrUpdateSearch}
|
||||
>
|
||||
{i18n.translate('xpack.observability.slosOutdatedDefinitions.refreshButtonLabel', {
|
||||
defaultMessage: 'Refresh',
|
||||
})}
|
||||
</EuiButton>
|
||||
)}
|
||||
{search !== tempSearch && (
|
||||
<EuiButton
|
||||
data-test-subj="o11ySlosOutdatedDefinitionsUpdateButton"
|
||||
iconType="kqlFunction"
|
||||
color="success"
|
||||
fill
|
||||
onClick={refreshOrUpdateSearch}
|
||||
>
|
||||
{i18n.translate('xpack.observability.slosOutdatedDefinitions.updateButtonLabel', {
|
||||
defaultMessage: 'Update',
|
||||
})}
|
||||
</EuiButton>
|
||||
)}
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
}
|
|
@ -7,10 +7,9 @@
|
|||
|
||||
import React from 'react';
|
||||
import { EuiBadge, EuiFlexItem, EuiToolTip, EuiBadgeProps } from '@elastic/eui';
|
||||
import { SLOWithSummaryResponse } from '@kbn/slo-schema';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { SLOResponse, SLOWithSummaryResponse } from '@kbn/slo-schema';
|
||||
import { euiLightVars } from '@kbn/ui-theme';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { useKibana } from '../../../../utils/kibana_react';
|
||||
import { convertSliApmParamsToApmAppDeeplinkUrl } from '../../../../utils/slo/convert_sli_apm_params_to_apm_app_deeplink_url';
|
||||
import { isApmIndicatorType } from '../../../../utils/slo/indicator';
|
||||
|
@ -18,7 +17,7 @@ import { toIndicatorTypeLabel } from '../../../../utils/slo/labels';
|
|||
|
||||
export interface Props {
|
||||
color?: EuiBadgeProps['color'];
|
||||
slo: SLOWithSummaryResponse;
|
||||
slo: SLOWithSummaryResponse | SLOResponse;
|
||||
}
|
||||
|
||||
export function SloIndicatorTypeBadge({ slo, color }: Props) {
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
import { EuiBadge, EuiBadgeProps, EuiFlexItem } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { rollingTimeWindowTypeSchema, SLOWithSummaryResponse } from '@kbn/slo-schema';
|
||||
import { rollingTimeWindowTypeSchema, SLOResponse, SLOWithSummaryResponse } from '@kbn/slo-schema';
|
||||
import { euiLightVars } from '@kbn/ui-theme';
|
||||
import moment from 'moment';
|
||||
import React from 'react';
|
||||
|
@ -16,7 +16,7 @@ import { toDurationLabel } from '../../../../utils/slo/labels';
|
|||
|
||||
export interface Props {
|
||||
color?: EuiBadgeProps['color'];
|
||||
slo: SLOWithSummaryResponse;
|
||||
slo: SLOWithSummaryResponse | SLOResponse;
|
||||
}
|
||||
|
||||
export function SloTimeWindowBadge({ slo, color }: Props) {
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
*/
|
||||
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { EuiButton } from '@elastic/eui';
|
||||
import { EuiButton, EuiSpacer } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { useBreadcrumbs } from '@kbn/observability-shared-plugin/public';
|
||||
|
||||
|
@ -22,6 +22,7 @@ import { FeedbackButton } from '../../components/slo/feedback_button/feedback_bu
|
|||
import { paths } from '../../../common/locators/paths';
|
||||
import { useAutoRefreshStorage } from '../../components/slo/auto_refresh_button/hooks/use_auto_refresh_storage';
|
||||
import { HeaderMenu } from '../overview/components/header_menu/header_menu';
|
||||
import { SloOutdatedCallout } from '../../components/slo/slo_outdated_callout';
|
||||
|
||||
export function SlosPage() {
|
||||
const {
|
||||
|
@ -90,6 +91,8 @@ export function SlosPage() {
|
|||
data-test-subj="slosPage"
|
||||
>
|
||||
<HeaderMenu />
|
||||
<SloOutdatedCallout />
|
||||
<EuiSpacer size="l" />
|
||||
<SloList autoRefresh={isAutoRefreshing} />
|
||||
</ObservabilityPageTemplate>
|
||||
);
|
||||
|
|
|
@ -27,6 +27,7 @@ import { paths } from '../../../common/locators/paths';
|
|||
import illustration from './assets/illustration.svg';
|
||||
import { useFetchSloGlobalDiagnosis } from '../../hooks/slo/use_fetch_global_diagnosis';
|
||||
import { HeaderMenu } from '../overview/components/header_menu/header_menu';
|
||||
import { SloOutdatedCallout } from '../../components/slo/slo_outdated_callout';
|
||||
|
||||
export function SlosWelcomePage() {
|
||||
const {
|
||||
|
@ -62,6 +63,7 @@ export function SlosWelcomePage() {
|
|||
return hasSlosAndHasPermissions || isLoading ? null : (
|
||||
<ObservabilityPageTemplate data-test-subj="slosPageWelcomePrompt">
|
||||
<HeaderMenu />
|
||||
<SloOutdatedCallout />
|
||||
<EuiPageTemplate.EmptyPrompt
|
||||
title={
|
||||
<EuiTitle size="l">
|
||||
|
|
|
@ -31,6 +31,7 @@ import {
|
|||
RULES_LOGS_PATH,
|
||||
RULES_PATH,
|
||||
RULE_DETAIL_PATH,
|
||||
SLOS_OUTDATED_DEFINITIONS_PATH,
|
||||
SLOS_PATH,
|
||||
SLOS_WELCOME_PATH,
|
||||
SLO_CREATE_PATH,
|
||||
|
@ -38,6 +39,7 @@ import {
|
|||
SLO_EDIT_PATH,
|
||||
} from '../../common/locators/paths';
|
||||
import { HasDataContextProvider } from '../context/has_data_context/has_data_context';
|
||||
import { SlosOutdatedDefinitions } from '../pages/slo_outdated_definitions';
|
||||
|
||||
// Note: React Router DOM <Redirect> component was not working here
|
||||
// so I've recreated this simple version for this purpose.
|
||||
|
@ -158,6 +160,13 @@ export const routes = {
|
|||
params: {},
|
||||
exact: true,
|
||||
},
|
||||
[SLOS_OUTDATED_DEFINITIONS_PATH]: {
|
||||
handler: () => {
|
||||
return <SlosOutdatedDefinitions />;
|
||||
},
|
||||
params: {},
|
||||
exact: true,
|
||||
},
|
||||
[SLO_EDIT_PATH]: {
|
||||
handler: () => {
|
||||
return <SloEditPage />;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue