[8.12] [SLO] Reset UI for updating outdated SLOs (#172883) (#173200)

# 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![image](729df2a0-61e6-41b3-aaa5-8501e7aa7797)\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![image](729df2a0-61e6-41b3-aaa5-8501e7aa7797)\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![image](729df2a0-61e6-41b3-aaa5-8501e7aa7797)\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:
Kibana Machine 2023-12-13 13:18:40 -05:00 committed by GitHub
parent 3b421f390e
commit 5716fb945f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 550 additions and 15 deletions

View file

@ -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}`,

View file

@ -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;
}

View file

@ -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>
);
}

View file

@ -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;
}

View file

@ -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,

View file

@ -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 };
}

View file

@ -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 },
})
);
},
}
);
}

View file

@ -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>
);
}

View file

@ -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>
);
}

View file

@ -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>
);
}

View file

@ -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) {

View file

@ -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) {

View file

@ -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>
);

View file

@ -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">

View file

@ -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 />;