diff --git a/src/plugins/unified_search/public/__stories__/search_bar.stories.tsx b/src/plugins/unified_search/public/__stories__/search_bar.stories.tsx index 534cd48ade81f..86ebf06c2b84e 100644 --- a/src/plugins/unified_search/public/__stories__/search_bar.stories.tsx +++ b/src/plugins/unified_search/public/__stories__/search_bar.stories.tsx @@ -14,7 +14,7 @@ import { I18nProvider } from '@kbn/i18n-react'; import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; import type { DataView, DataViewsContract } from '@kbn/data-views-plugin/public'; import { buildExistsFilter } from '@kbn/es-query'; -import { EuiComboBox } from '@elastic/eui'; +import { EuiButton, EuiComboBox } from '@elastic/eui'; import { SearchBar, SearchBarProps } from '../search_bar'; import { setIndexPatterns } from '../services'; @@ -695,4 +695,20 @@ storiesOf('SearchBar', module) }, submitButtonStyle: 'full', }) + ) + + .add('with renderQueryInputAppend prop', () => + wrapSearchBarInContext({ + dataViewPickerComponentProps: { + currentDataViewId: '1234', + trigger: { + 'data-test-subj': 'dataView-switch-link', + label: 'logstash-*', + title: 'logstash-*', + }, + onChangeDataView: action('onChangeDataView'), + }, + submitButtonStyle: 'full', + renderQueryInputAppend: () => {}}>Append, + }) ); diff --git a/src/plugins/unified_search/public/query_string_input/query_bar_menu.tsx b/src/plugins/unified_search/public/query_string_input/query_bar_menu.tsx index 9f84aa3096213..14b11737e8844 100644 --- a/src/plugins/unified_search/public/query_string_input/query_bar_menu.tsx +++ b/src/plugins/unified_search/public/query_string_input/query_bar_menu.tsx @@ -70,6 +70,7 @@ export interface QueryBarMenuProps extends WithCloseFilterEditorConfirmModalProp buttonProps?: Partial; isDisabled?: boolean; suggestionsAbstraction?: SuggestionsAbstraction; + renderQueryInputAppend?: () => React.ReactNode; } function QueryBarMenuComponent({ diff --git a/src/plugins/unified_search/public/query_string_input/query_bar_top_row.tsx b/src/plugins/unified_search/public/query_string_input/query_bar_top_row.tsx index bd0ffc12565a0..4b63ed3e1ad28 100644 --- a/src/plugins/unified_search/public/query_string_input/query_bar_top_row.tsx +++ b/src/plugins/unified_search/public/query_string_input/query_bar_top_row.tsx @@ -163,6 +163,7 @@ export interface QueryBarTopRowProps onTextLangQuerySubmit: (query?: Query | AggregateQuery) => void; onTextLangQueryChange: (query: AggregateQuery) => void; submitOnBlur?: boolean; + renderQueryInputAppend?: () => React.ReactNode; } export const SharingMetaFields = React.memo(function SharingMetaFields({ @@ -702,6 +703,7 @@ export const QueryBarTopRow = React.memo( ? renderTextLangEditor() : null} + {props.renderQueryInputAppend?.()} {shouldShowDatePickerAsBadge() && props.filterBar} {renderUpdateButton()} diff --git a/src/plugins/unified_search/public/search_bar/search_bar.tsx b/src/plugins/unified_search/public/search_bar/search_bar.tsx index 0372775922120..7d3c712181c0e 100644 --- a/src/plugins/unified_search/public/search_bar/search_bar.tsx +++ b/src/plugins/unified_search/public/search_bar/search_bar.tsx @@ -120,6 +120,8 @@ export interface SearchBarOwnProps { isDisabled?: boolean; submitOnBlur?: boolean; + + renderQueryInputAppend?: () => React.ReactNode; } export type SearchBarProps = SearchBarOwnProps & @@ -524,6 +526,7 @@ class SearchBarUI extends C : undefined } suggestionsAbstraction={this.props.suggestionsAbstraction} + renderQueryInputAppend={this.props.renderQueryInputAppend} /> ) : undefined; @@ -610,6 +613,7 @@ class SearchBarUI extends C onTextLangQueryChange={this.onTextLangQueryChange} submitOnBlur={this.props.submitOnBlur} suggestionsAbstraction={this.props.suggestionsAbstraction} + renderQueryInputAppend={this.props.renderQueryInputAppend} /> ); diff --git a/x-pack/packages/kbn-slo-schema/src/rest_specs/slo.ts b/x-pack/packages/kbn-slo-schema/src/rest_specs/slo.ts index 38233d2982c08..4f72827957ef7 100644 --- a/x-pack/packages/kbn-slo-schema/src/rest_specs/slo.ts +++ b/x-pack/packages/kbn-slo-schema/src/rest_specs/slo.ts @@ -87,6 +87,7 @@ const sortBySchema = t.union([ const findSLOParamsSchema = t.partial({ query: t.partial({ + filters: t.string, kqlQuery: t.string, page: t.string, perPage: t.string, diff --git a/x-pack/plugins/observability/public/components/slo/feedback_button/feedback_button.tsx b/x-pack/plugins/observability/public/components/slo/feedback_button/feedback_button.tsx deleted file mode 100644 index dbd45bf74dee4..0000000000000 --- a/x-pack/plugins/observability/public/components/slo/feedback_button/feedback_button.tsx +++ /dev/null @@ -1,33 +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 { EuiButton } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; - -const SLO_FEEDBACK_LINK = 'https://ela.st/slo-feedback'; - -interface Props { - disabled?: boolean; -} - -export function FeedbackButton({ disabled }: Props) { - return ( - - {i18n.translate('xpack.observability.slo.feedbackButtonLabel', { - defaultMessage: 'Tell us what you think!', - })} - - ); -} diff --git a/x-pack/plugins/observability/public/components/slo/slo_outdated_callout/index.tsx b/x-pack/plugins/observability/public/components/slo/slo_outdated_callout/index.tsx index dae8f76fb85a5..de09904d0637c 100644 --- a/x-pack/plugins/observability/public/components/slo/slo_outdated_callout/index.tsx +++ b/x-pack/plugins/observability/public/components/slo/slo_outdated_callout/index.tsx @@ -4,7 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { EuiButton, EuiCallOut } from '@elastic/eui'; +import { EuiButton, EuiCallOut, EuiSpacer } from '@elastic/eui'; import React from 'react'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; @@ -25,37 +25,40 @@ export function SloOutdatedCallout() { const { isLoading, data } = useFetchSloDefinitions({ includeOutdatedOnly: true }); if (!isLoading && data && data.total > 0) { return ( - -

- -

-

- + <> + +

- -

-
+

+

+ + + +

+ + + ); } return null; diff --git a/x-pack/plugins/observability/public/hooks/slo/query_key_factory.ts b/x-pack/plugins/observability/public/hooks/slo/query_key_factory.ts index 8f687abda8805..c0be8dfd5f6e2 100644 --- a/x-pack/plugins/observability/public/hooks/slo/query_key_factory.ts +++ b/x-pack/plugins/observability/public/hooks/slo/query_key_factory.ts @@ -13,6 +13,8 @@ interface SloListFilter { perPage: number; sortBy: string; sortDirection: string; + filters: string; + lastRefresh?: number; } export const sloKeys = { diff --git a/x-pack/plugins/observability/public/hooks/slo/use_fetch_slo_list.ts b/x-pack/plugins/observability/public/hooks/slo/use_fetch_slo_list.ts index 5b1296fed67c2..56169bb1dd7fc 100644 --- a/x-pack/plugins/observability/public/hooks/slo/use_fetch_slo_list.ts +++ b/x-pack/plugins/observability/public/hooks/slo/use_fetch_slo_list.ts @@ -8,9 +8,13 @@ import { i18n } from '@kbn/i18n'; import { FindSLOResponse } from '@kbn/slo-schema'; import { useQuery, useQueryClient } from '@tanstack/react-query'; -import { useState } from 'react'; -import { DEFAULT_SLO_PAGE_SIZE } from '../../../common/slo/constants'; -import { SLO_LONG_REFETCH_INTERVAL, SLO_SHORT_REFETCH_INTERVAL } from '../../constants'; +import { useMemo } from 'react'; +import { buildQueryFromFilters, Filter } from '@kbn/es-query'; +import { useCreateDataView } from '../use_create_data_view'; +import { + DEFAULT_SLO_PAGE_SIZE, + SLO_SUMMARY_DESTINATION_INDEX_NAME, +} from '../../../common/slo/constants'; import { useKibana } from '../../utils/kibana_react'; import { sloKeys } from './query_key_factory'; @@ -20,8 +24,9 @@ interface SLOListParams { page?: number; sortBy?: string; sortDirection?: 'asc' | 'desc'; - shouldRefetch?: boolean; perPage?: number; + filters?: Filter[]; + lastRefresh?: number; } export interface UseFetchSloListResponse { @@ -38,37 +43,57 @@ export function useFetchSloList({ page = 1, sortBy = 'status', sortDirection = 'desc', - shouldRefetch, perPage = DEFAULT_SLO_PAGE_SIZE, + filters: filterDSL = [], + lastRefresh, }: SLOListParams = {}): UseFetchSloListResponse { const { http, notifications: { toasts }, } = useKibana().services; const queryClient = useQueryClient(); - const [stateRefetchInterval, setStateRefetchInterval] = useState( - SLO_SHORT_REFETCH_INTERVAL - ); + + const { dataView } = useCreateDataView({ + indexPatternString: SLO_SUMMARY_DESTINATION_INDEX_NAME, + }); + + const filters = useMemo(() => { + try { + return JSON.stringify( + buildQueryFromFilters(filterDSL, dataView, { + ignoreFilterIfFieldNotInIndex: true, + }) + ); + } catch (e) { + return ''; + } + }, [filterDSL, dataView]); const { isInitialLoading, isLoading, isError, isSuccess, isRefetching, data } = useQuery({ - queryKey: sloKeys.list({ kqlQuery, page, perPage, sortBy, sortDirection }), + queryKey: sloKeys.list({ + kqlQuery, + page, + perPage, + sortBy, + sortDirection, + filters, + lastRefresh, + }), queryFn: async ({ signal }) => { - const response = await http.get(`/api/observability/slos`, { + return await http.get(`/api/observability/slos`, { query: { ...(kqlQuery && { kqlQuery }), ...(sortBy && { sortBy }), ...(sortDirection && { sortDirection }), ...(page && { page }), ...(perPage && { perPage }), + ...(filters && { filters }), }, signal, }); - - return response; }, cacheTime: 0, refetchOnWindowFocus: false, - refetchInterval: shouldRefetch ? stateRefetchInterval : undefined, retry: (failureCount, error) => { if (String(error) === 'Error: Forbidden') { return false; @@ -79,16 +104,6 @@ export function useFetchSloList({ queryClient.invalidateQueries({ queryKey: sloKeys.historicalSummaries(), exact: false }); queryClient.invalidateQueries({ queryKey: sloKeys.activeAlerts(), exact: false }); queryClient.invalidateQueries({ queryKey: sloKeys.rules(), exact: false }); - - if (!shouldRefetch) { - return; - } - - if (results.find((slo) => slo.summary.status === 'NO_DATA' || !slo.summary)) { - setStateRefetchInterval(SLO_SHORT_REFETCH_INTERVAL); - } else { - setStateRefetchInterval(SLO_LONG_REFETCH_INTERVAL); - } }, onError: (error: Error) => { toasts.addError(error, { diff --git a/x-pack/plugins/observability/public/locators/slo_list.test.ts b/x-pack/plugins/observability/public/locators/slo_list.test.ts index 677e831134496..5eb537b2182a4 100644 --- a/x-pack/plugins/observability/public/locators/slo_list.test.ts +++ b/x-pack/plugins/observability/public/locators/slo_list.test.ts @@ -14,7 +14,7 @@ describe('SloListLocator', () => { const location = await locator.getLocation({}); expect(location.app).toEqual('observability'); expect(location.path).toMatchInlineSnapshot( - `"/slos?search=(compact:!t,kqlQuery:'',page:0,perPage:25,sort:(by:status,direction:desc),view:cardView)"` + `"/slos?search=(compact:!t,filters:!(),kqlQuery:'',lastRefresh:0,page:0,perPage:25,sort:(by:status,direction:desc),view:cardView)"` ); }); @@ -24,7 +24,7 @@ describe('SloListLocator', () => { }); expect(location.app).toEqual('observability'); expect(location.path).toMatchInlineSnapshot( - `"/slos?search=(compact:!t,kqlQuery:'slo.name:%20%22Service%20Availability%22%20and%20slo.indicator.type%20:%20%22sli.kql.custom%22',page:0,perPage:25,sort:(by:status,direction:desc),view:cardView)"` + `"/slos?search=(compact:!t,filters:!(),kqlQuery:'slo.name:%20%22Service%20Availability%22%20and%20slo.indicator.type%20:%20%22sli.kql.custom%22',lastRefresh:0,page:0,perPage:25,sort:(by:status,direction:desc),view:cardView)"` ); }); }); diff --git a/x-pack/plugins/observability/public/pages/overview/components/header_menu/header_menu.tsx b/x-pack/plugins/observability/public/pages/overview/components/header_menu/header_menu.tsx index b57c512565fc1..be167785e2bad 100644 --- a/x-pack/plugins/observability/public/pages/overview/components/header_menu/header_menu.tsx +++ b/x-pack/plugins/observability/public/pages/overview/components/header_menu/header_menu.tsx @@ -12,6 +12,7 @@ import React from 'react'; import { useKibana } from '../../../../utils/kibana_react'; import { usePluginContext } from '../../../../hooks/use_plugin_context'; import HeaderMenuPortal from './header_menu_portal'; +const SLO_FEEDBACK_LINK = 'https://ela.st/slo-feedback'; export function HeaderMenu(): React.ReactElement | null { const { @@ -27,6 +28,16 @@ export function HeaderMenu(): React.ReactElement | null { return ( + + {i18n.translate('xpack.observability.slo.giveFeedback', { + defaultMessage: 'Give feedback', + })} + , - , ], bottomBorder: false, }} diff --git a/x-pack/plugins/observability/public/pages/slo_edit/slo_edit.tsx b/x-pack/plugins/observability/public/pages/slo_edit/slo_edit.tsx index 7a26fc117cc7a..052ef26cd758b 100644 --- a/x-pack/plugins/observability/public/pages/slo_edit/slo_edit.tsx +++ b/x-pack/plugins/observability/public/pages/slo_edit/slo_edit.tsx @@ -11,6 +11,7 @@ import { i18n } from '@kbn/i18n'; import { useBreadcrumbs } from '@kbn/observability-shared-plugin/public'; import { createHtmlPortalNode, OutPortal } from 'react-reverse-portal'; +import { FeedbackButton } from '../alert_details/components/feedback_button'; import { paths } from '../../../common/locators/paths'; import { useKibana } from '../../utils/kibana_react'; import { usePluginContext } from '../../hooks/use_plugin_context'; @@ -18,7 +19,6 @@ import { useFetchSloDetails } from '../../hooks/slo/use_fetch_slo_details'; import { useLicense } from '../../hooks/use_license'; import { useCapabilities } from '../../hooks/slo/use_capabilities'; import { useFetchSloGlobalDiagnosis } from '../../hooks/slo/use_fetch_global_diagnosis'; -import { FeedbackButton } from '../../components/slo/feedback_button/feedback_button'; import { SloEditForm } from './components/slo_edit_form'; import { HeaderMenu } from '../overview/components/header_menu/header_menu'; diff --git a/x-pack/plugins/observability/public/pages/slos/components/common/create_slo_btn.tsx b/x-pack/plugins/observability/public/pages/slos/components/common/create_slo_btn.tsx new file mode 100644 index 0000000000000..1a71c82e23d3f --- /dev/null +++ b/x-pack/plugins/observability/public/pages/slos/components/common/create_slo_btn.tsx @@ -0,0 +1,39 @@ +/* + * 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 } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import React from 'react'; +import { useKibana } from '../../../../utils/kibana_react'; +import { paths } from '../../../../../common/locators/paths'; +import { useCapabilities } from '../../../../hooks/slo/use_capabilities'; + +export function CreateSloBtn() { + const { + application: { navigateToUrl }, + http: { basePath }, + } = useKibana().services; + + const { hasWriteCapabilities } = useCapabilities(); + + const handleClickCreateSlo = () => { + navigateToUrl(basePath.prepend(paths.observability.sloCreate)); + }; + return ( + + {i18n.translate('xpack.observability.slo.sloList.pageHeader.create', { + defaultMessage: 'Create SLO', + })} + + ); +} diff --git a/x-pack/plugins/observability/public/pages/slos/components/common/sort_by_select.tsx b/x-pack/plugins/observability/public/pages/slos/components/common/sort_by_select.tsx new file mode 100644 index 0000000000000..ac14034864395 --- /dev/null +++ b/x-pack/plugins/observability/public/pages/slos/components/common/sort_by_select.tsx @@ -0,0 +1,113 @@ +/* + * 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 { + EuiFilterButton, + EuiFilterGroup, + EuiPopover, + EuiPopoverTitle, + EuiSelectable, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import React, { useState } from 'react'; +import { SearchState } from '../../hooks/use_url_search_state'; +import { Item, SortField } from '../slo_list_search_bar'; + +interface Props { + initialState: SearchState; + loading: boolean; + onStateChange: (newState: Partial) => void; +} + +export function SortBySelect({ initialState, onStateChange, loading }: Props) { + const [isSortPopoverOpen, setSortPopoverOpen] = useState(false); + const [sortOptions, setSortOptions] = useState>>( + SORT_OPTIONS.map((option) => ({ + ...option, + checked: option.type === initialState.sort.by ? 'on' : undefined, + })) + ); + + const selectedSort = sortOptions.find((option) => option.checked === 'on'); + + const handleToggleSortButton = () => setSortPopoverOpen(!isSortPopoverOpen); + const handleChangeSort = (newOptions: Array>) => { + setSortOptions(newOptions); + setSortPopoverOpen(false); + onStateChange({ + page: 0, + sort: { by: newOptions.find((o) => o.checked)!.type, direction: initialState.sort.direction }, + }); + }; + + return ( + + + {i18n.translate('xpack.observability.slo.list.sortByType', { + defaultMessage: 'Sort by {type}', + values: { type: selectedSort?.label.toLowerCase() ?? '' }, + })} + + } + isOpen={isSortPopoverOpen} + closePopover={handleToggleSortButton} + panelPaddingSize="none" + anchorPosition="downCenter" + > +
+ + {i18n.translate('xpack.observability.slo.list.sortBy', { + defaultMessage: 'Sort by', + })} + + > + singleSelection="always" + options={sortOptions} + onChange={handleChangeSort} + isLoading={loading} + > + {(list) => list} + +
+
+
+ ); +} + +const SORT_OPTIONS: Array> = [ + { + label: i18n.translate('xpack.observability.slo.list.sortBy.sliValue', { + defaultMessage: 'SLI value', + }), + type: 'sli_value', + }, + { + label: i18n.translate('xpack.observability.slo.list.sortBy.sloStatus', { + defaultMessage: 'SLO status', + }), + type: 'status', + }, + { + label: i18n.translate('xpack.observability.slo.list.sortBy.errorBudgetConsumed', { + defaultMessage: 'Error budget consumed', + }), + type: 'error_budget_consumed', + }, + { + label: i18n.translate('xpack.observability.slo.list.sortBy.errorBudgetRemaining', { + defaultMessage: 'Error budget remaining', + }), + type: 'error_budget_remaining', + }, +]; diff --git a/x-pack/plugins/observability/public/pages/slos/components/slo_list.stories.tsx b/x-pack/plugins/observability/public/pages/slos/components/slo_list.stories.tsx index 244bda63596b7..3e6dd1c87d798 100644 --- a/x-pack/plugins/observability/public/pages/slos/components/slo_list.stories.tsx +++ b/x-pack/plugins/observability/public/pages/slos/components/slo_list.stories.tsx @@ -9,7 +9,7 @@ import React from 'react'; import { ComponentStory } from '@storybook/react'; import { KibanaReactStorybookDecorator } from '../../../utils/kibana_react.storybook_decorator'; -import { SloList as Component, Props } from './slo_list'; +import { SloList as Component } from './slo_list'; export default { component: Component, @@ -18,11 +18,9 @@ export default { decorators: [KibanaReactStorybookDecorator], }; -const Template: ComponentStory = (props: Props) => ; +const Template: ComponentStory = () => ; -const defaultProps = { - autoRefresh: true, -}; +const defaultProps = {}; export const SloList = Template.bind({}); SloList.args = defaultProps; diff --git a/x-pack/plugins/observability/public/pages/slos/components/slo_list.tsx b/x-pack/plugins/observability/public/pages/slos/components/slo_list.tsx index a25aae4afd4f9..893f866b3990b 100644 --- a/x-pack/plugins/observability/public/pages/slos/components/slo_list.tsx +++ b/x-pack/plugins/observability/public/pages/slos/components/slo_list.tsx @@ -7,26 +7,17 @@ import { EuiFlexGroup, EuiFlexItem, EuiTablePagination } from '@elastic/eui'; import { useIsMutating } from '@tanstack/react-query'; -import React, { useState } from 'react'; +import React from 'react'; +import { CreateSloBtn } from './common/create_slo_btn'; import { useFetchSloList } from '../../../hooks/slo/use_fetch_slo_list'; -import { useUrlSearchState } from '../hooks/use_url_search_state'; +import { SearchState, useUrlSearchState } from '../hooks/use_url_search_state'; import { SlosView } from './slos_view'; -import { SloListSearchBar, SortDirection, SortField } from './slo_list_search_bar'; -import { SLOView, ToggleSLOView } from './toggle_slo_view'; +import { SloListSearchBar } from './slo_list_search_bar'; +import { ToggleSLOView } from './toggle_slo_view'; -export interface Props { - autoRefresh: boolean; -} - -export function SloList({ autoRefresh }: Props) { +export function SloList() { const { state, store: storeState } = useUrlSearchState(); - const [page, setPage] = useState(state.page); - const [perPage, setPerPage] = useState(state.perPage); - const [query, setQuery] = useState(state.kqlQuery); - const [sort, setSort] = useState(state.sort.by); - const [direction] = useState(state.sort.direction); - const [view, setView] = useState(state.view); - const [isCompact, setCompact] = useState(state.compact); + const { view, page, perPage, kqlQuery, filters, compact: isCompact } = state; const { isLoading, @@ -35,11 +26,12 @@ export function SloList({ autoRefresh }: Props) { data: sloList, } = useFetchSloList({ perPage, + filters, page: page + 1, - kqlQuery: query, - sortBy: sort, - sortDirection: direction, - shouldRefetch: autoRefresh, + kqlQuery, + sortBy: state.sort.by, + sortDirection: state.sort.direction, + lastRefresh: state.lastRefresh, }); const { results = [], total = 0 } = sloList ?? {}; @@ -49,49 +41,34 @@ export function SloList({ autoRefresh }: Props) { const isUpdatingSlo = Boolean(useIsMutating(['updatingSlo'])); const isDeletingSlo = Boolean(useIsMutating(['deleteSlo'])); - const handlePageClick = (pageNumber: number) => { - setPage(pageNumber); - storeState({ page: pageNumber }); - }; - - const handleChangeQuery = (newQuery: string) => { - setPage(0); - setQuery(newQuery); - storeState({ page: 0, kqlQuery: newQuery }); - }; - - const handleChangeSort = (newSort: SortField) => { - setPage(0); - setSort(newSort); - storeState({ page: 0, sort: { by: newSort, direction: state.sort.direction } }); - }; - - const handleChangeView = (newView: SLOView) => { - setView(newView); - storeState({ view: newView }); - }; - - const handleToggleCompactView = () => { - const newCompact = !isCompact; - setCompact(newCompact); - storeState({ compact: newCompact }); + const onStateChange = (newState: Partial) => { + storeState({ page: 0, ...newState }); }; return ( - + + + + + + + + onStateChange({ view: newView })} + onToggleCompactView={() => onStateChange({ compact: !isCompact })} isCompact={isCompact} /> @@ -108,12 +85,13 @@ export function SloList({ autoRefresh }: Props) { { + onStateChange({ page: newPage }); + }} itemsPerPage={perPage} itemsPerPageOptions={[10, 25, 50, 100]} onChangeItemsPerPage={(newPerPage) => { - setPerPage(newPerPage); - storeState({ perPage: newPerPage }); + storeState({ perPage: newPerPage, page: 0 }); }} /> diff --git a/x-pack/plugins/observability/public/pages/slos/components/slo_list_search_bar.stories.tsx b/x-pack/plugins/observability/public/pages/slos/components/slo_list_search_bar.stories.tsx index fb39f46e81e96..fbf2afa253c7c 100644 --- a/x-pack/plugins/observability/public/pages/slos/components/slo_list_search_bar.stories.tsx +++ b/x-pack/plugins/observability/public/pages/slos/components/slo_list_search_bar.stories.tsx @@ -22,8 +22,7 @@ const Template: ComponentStory = (props: Props) => {}, - onChangeSort: () => {}, + onStateChange: () => {}, initialState: DEFAULT_STATE, }; diff --git a/x-pack/plugins/observability/public/pages/slos/components/slo_list_search_bar.tsx b/x-pack/plugins/observability/public/pages/slos/components/slo_list_search_bar.tsx index c1f08ca69fd9d..df9593d99e2eb 100644 --- a/x-pack/plugins/observability/public/pages/slos/components/slo_list_search_bar.tsx +++ b/x-pack/plugins/observability/public/pages/slos/components/slo_list_search_bar.tsx @@ -5,29 +5,25 @@ * 2.0. */ -import { - EuiFilterButton, - EuiFilterGroup, - EuiFlexGroup, - EuiFlexItem, - EuiPopover, - EuiPopoverTitle, - EuiSelectable, - EuiSelectableOption, -} from '@elastic/eui'; +import { EuiSelectableOption } from '@elastic/eui'; import { EuiSelectableOptionCheckedType } from '@elastic/eui/src/components/selectable/selectable_option'; -import { Query } from '@kbn/es-query'; import { i18n } from '@kbn/i18n'; -import React, { useState } from 'react'; -import { useCreateDataView } from '../../../hooks/use_create_data_view'; +import React from 'react'; +import { Filter } from '@kbn/es-query'; +import styled from 'styled-components'; import { useKibana } from '../../../utils/kibana_react'; +import { ObservabilityPublicPluginsStart } from '../../..'; +import { SortBySelect } from './common/sort_by_select'; +import { SLO_SUMMARY_DESTINATION_INDEX_NAME } from '../../../../common/slo/constants'; +import { useCreateDataView } from '../../../hooks/use_create_data_view'; import { SearchState } from '../hooks/use_url_search_state'; export interface Props { + query?: string; + filters?: Filter[]; loading: boolean; initialState: SearchState; - onChangeQuery: (query: string) => void; - onChangeSort: (sort: SortField) => void; + onStateChange: (newState: Partial) => void; } export type SortField = 'sli_value' | 'error_budget_consumed' | 'error_budget_remaining' | 'status'; @@ -39,127 +35,54 @@ export type Item = EuiSelectableOption & { checked?: EuiSelectableOptionCheckedType; }; -const SORT_OPTIONS: Array> = [ - { - label: i18n.translate('xpack.observability.slo.list.sortBy.sliValue', { - defaultMessage: 'SLI value', - }), - type: 'sli_value', - }, - { - label: i18n.translate('xpack.observability.slo.list.sortBy.sloStatus', { - defaultMessage: 'SLO status', - }), - type: 'status', - }, - { - label: i18n.translate('xpack.observability.slo.list.sortBy.errorBudgetConsumed', { - defaultMessage: 'Error budget consumed', - }), - type: 'error_budget_consumed', - }, - { - label: i18n.translate('xpack.observability.slo.list.sortBy.errorBudgetRemaining', { - defaultMessage: 'Error budget remaining', - }), - type: 'error_budget_remaining', - }, -]; - export type ViewMode = 'default' | 'compact'; -export function SloListSearchBar({ loading, onChangeQuery, onChangeSort, initialState }: Props) { +export function SloListSearchBar({ query, filters, loading, initialState, onStateChange }: Props) { + const { dataView } = useCreateDataView({ + indexPatternString: SLO_SUMMARY_DESTINATION_INDEX_NAME, + }); + const { unifiedSearch: { - ui: { QueryStringInput }, + ui: { SearchBar }, }, - } = useKibana().services; - const { dataView } = useCreateDataView({ indexPatternString: '.slo-observability.summary-*' }); - - const [query, setQuery] = useState(initialState.kqlQuery); - const [isSortPopoverOpen, setSortPopoverOpen] = useState(false); - const [sortOptions, setSortOptions] = useState>>( - SORT_OPTIONS.map((option) => ({ - ...option, - checked: option.type === initialState.sort.by ? 'on' : undefined, - })) - ); - const selectedSort = sortOptions.find((option) => option.checked === 'on'); - - const handleToggleSortButton = () => setSortPopoverOpen(!isSortPopoverOpen); - const handleChangeSort = (newOptions: Array>) => { - setSortOptions(newOptions); - setSortPopoverOpen(false); - onChangeSort(newOptions.find((o) => o.checked)!.type); - }; + } = useKibana().services; return ( - - - { - setQuery(String(value.query)); - onChangeQuery(String(value.query)); - }} - disableLanguageSwitcher - isDisabled={loading} - autoSubmit - indexPatterns={dataView ? [dataView] : []} - placeholder={i18n.translate('xpack.observability.slo.list.search', { - defaultMessage: 'Search your SLOs...', - })} - query={{ query: String(query), language: 'kuery' }} - size="s" - onChange={(value) => setQuery(String(value.query))} - /> - - - - - - - - {i18n.translate('xpack.observability.slo.list.sortByType', { - defaultMessage: 'Sort by {type}', - values: { type: selectedSort?.label.toLowerCase() ?? '' }, - })} - - } - isOpen={isSortPopoverOpen} - closePopover={handleToggleSortButton} - panelPaddingSize="none" - anchorPosition="downCenter" - > -
- - {i18n.translate('xpack.observability.slo.list.sortBy', { - defaultMessage: 'Sort by', - })} - - > - singleSelection="always" - options={sortOptions} - onChange={handleChangeSort} - isLoading={loading} - > - {(list) => list} - -
-
-
-
-
-
-
+ + ( + + )} + filters={filters} + onFiltersUpdated={(newFilters) => { + onStateChange({ filters: newFilters }); + }} + onQuerySubmit={({ query: value }) => { + onStateChange({ kqlQuery: String(value?.query), lastRefresh: Date.now() }); + }} + query={{ query: String(query), language: 'kuery' }} + showSubmitButton={true} + showDatePicker={false} + showQueryInput={true} + disableQueryLanguageSwitcher={true} + /> + ); } + +const Container = styled.div` + .uniSearchBar { + padding: 0; + } +`; diff --git a/x-pack/plugins/observability/public/pages/slos/components/toggle_slo_view.tsx b/x-pack/plugins/observability/public/pages/slos/components/toggle_slo_view.tsx index e5881e8f1ff4c..15d376f1f0d10 100644 --- a/x-pack/plugins/observability/public/pages/slos/components/toggle_slo_view.tsx +++ b/x-pack/plugins/observability/public/pages/slos/components/toggle_slo_view.tsx @@ -7,7 +7,9 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; -import { EuiButtonGroup, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import { EuiButtonGroup, EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { FindSLOResponse } from '@kbn/slo-schema'; import { SLOViewSettings } from './slo_view_settings'; export type SLOView = 'cardView' | 'listView'; @@ -17,6 +19,7 @@ interface Props { onChangeView: (view: SLOView) => void; isCompact: boolean; sloView: SLOView; + sloList?: FindSLOResponse; } const toggleButtonsIcons = [ @@ -39,10 +42,38 @@ export function ToggleSLOView({ onChangeView, onToggleCompactView, isCompact = true, + sloList, }: Props) { + const total = sloList?.total ?? 0; + const pageSize = sloList?.perPage ?? 0; + const pageIndex = sloList?.page ?? 1; + + const rangeStart = (total === 0 ? 0 : pageSize * (pageIndex - 1)) + 1; + const rangeEnd = Math.min(total, pageSize * (pageIndex - 1) + pageSize); + return ( - + + + {`${rangeStart}-${rangeEnd}`}, + total, + slos: ( + + + + ), + }} + /> + + + ) => Promise; } { + const [state, setState] = useState(DEFAULT_STATE); const history = useHistory(); - const urlStateStorage = createKbnUrlStateStorage({ - history, - useHash: false, - useHashQuery: false, - }); + const urlStateStorage = useRef( + createKbnUrlStateStorage({ + history, + useHash: false, + useHashQuery: false, + }) + ); - const searchState = - urlStateStorage.get(SLO_LIST_SEARCH_URL_STORAGE_KEY) ?? DEFAULT_STATE; + useEffect(() => { + const sub = urlStateStorage.current + ?.change$(SLO_LIST_SEARCH_URL_STORAGE_KEY) + .subscribe((newSearchState) => { + if (newSearchState) { + setState(newSearchState); + } + }); + + setState( + urlStateStorage.current?.get(SLO_LIST_SEARCH_URL_STORAGE_KEY) ?? DEFAULT_STATE + ); + + return () => { + sub?.unsubscribe(); + }; + }, [urlStateStorage]); return { - state: deepmerge(DEFAULT_STATE, searchState), - store: (state: Partial) => - urlStateStorage.set(SLO_LIST_SEARCH_URL_STORAGE_KEY, deepmerge(searchState, state), { - replace: true, - }), + state: deepmerge(DEFAULT_STATE, state), + store: (newState: Partial) => + urlStateStorage.current?.set( + SLO_LIST_SEARCH_URL_STORAGE_KEY, + { ...state, ...newState }, + { + replace: true, + } + ), }; } diff --git a/x-pack/plugins/observability/public/pages/slos/slos.test.tsx b/x-pack/plugins/observability/public/pages/slos/slos.test.tsx index 430a160515039..ca09a51af1464 100644 --- a/x-pack/plugins/observability/public/pages/slos/slos.test.tsx +++ b/x-pack/plugins/observability/public/pages/slos/slos.test.tsx @@ -111,6 +111,7 @@ const mockKibana = () => { }, unifiedSearch: { ui: { + SearchBar: () =>
SearchBar
, QueryStringInput: () =>
Query String Input
, }, autocomplete: { @@ -181,22 +182,7 @@ describe('SLOs Page', () => { render(); }); - expect(screen.getByText('Create new SLO')).toBeTruthy(); - }); - - it('should have an Auto Refresh button', async () => { - useFetchSloListMock.mockReturnValue({ isLoading: false, data: sloList }); - - useFetchHistoricalSummaryMock.mockReturnValue({ - isLoading: false, - data: historicalSummaryData, - }); - - await act(async () => { - render(); - }); - - expect(screen.getByTestId('autoRefreshButton')).toBeTruthy(); + expect(screen.getByText('Create SLO')).toBeTruthy(); }); describe('when API has returned results', () => { @@ -218,7 +204,7 @@ describe('SLOs Page', () => { expect(screen.queryByTestId('slosPage')).toBeTruthy(); expect(screen.queryByTestId('sloList')).toBeTruthy(); expect(screen.queryAllByTestId('sloItem')).toBeTruthy(); - expect(screen.queryAllByTestId('sloItem').length).toBe(sloList.results.length); + expect((await screen.findAllByTestId('sloItem')).length).toBe(sloList.results.length); }); it('allows editing an SLO', async () => { diff --git a/x-pack/plugins/observability/public/pages/slos/slos.tsx b/x-pack/plugins/observability/public/pages/slos/slos.tsx index 4ea3f27fade3c..931ab7885b8ac 100644 --- a/x-pack/plugins/observability/public/pages/slos/slos.tsx +++ b/x-pack/plugins/observability/public/pages/slos/slos.tsx @@ -5,22 +5,16 @@ * 2.0. */ -import React, { useEffect, useState } from 'react'; -import { EuiButton, EuiSpacer } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; +import React, { useEffect } from 'react'; import { useBreadcrumbs } from '@kbn/observability-shared-plugin/public'; +import { i18n } from '@kbn/i18n'; import { useKibana } from '../../utils/kibana_react'; import { usePluginContext } from '../../hooks/use_plugin_context'; import { useLicense } from '../../hooks/use_license'; -import { useCapabilities } from '../../hooks/slo/use_capabilities'; import { useFetchSloList } from '../../hooks/slo/use_fetch_slo_list'; import { SloList } from './components/slo_list'; -import { AutoRefreshButton } from '../../components/slo/auto_refresh_button'; -import { HeaderTitle } from './components/header_title'; -import { FeedbackButton } from '../../components/slo/feedback_button/feedback_button'; 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'; @@ -30,15 +24,11 @@ export function SlosPage() { http: { basePath }, } = useKibana().services; const { ObservabilityPageTemplate } = usePluginContext(); - const { hasWriteCapabilities } = useCapabilities(); const { hasAtLeast } = useLicense(); const { isLoading, isError, data: sloList } = useFetchSloList(); const { total } = sloList ?? { total: 0 }; - const { storeAutoRefreshState, getAutoRefreshState } = useAutoRefreshStorage(); - const [isAutoRefreshing, setIsAutoRefreshing] = useState(getAutoRefreshState()); - useBreadcrumbs([ { href: basePath.prepend(paths.observability.slos), @@ -55,45 +45,11 @@ export function SlosPage() { } }, [basePath, hasAtLeast, isError, isLoading, navigateToUrl, total]); - const handleClickCreateSlo = () => { - navigateToUrl(basePath.prepend(paths.observability.sloCreate)); - }; - - const handleToggleAutoRefresh = () => { - setIsAutoRefreshing(!isAutoRefreshing); - storeAutoRefreshState(!isAutoRefreshing); - }; - return ( - , - rightSideItems: [ - - {i18n.translate('xpack.observability.slo.sloList.pageHeader.createNewButtonLabel', { - defaultMessage: 'Create new SLO', - })} - , - , - , - ], - bottomBorder: false, - }} - data-test-subj="slosPage" - > + - - + ); } diff --git a/x-pack/plugins/observability/server/services/slo/find_slo.test.ts b/x-pack/plugins/observability/server/services/slo/find_slo.test.ts index e8d80ef8e74f5..396f026b936b3 100644 --- a/x-pack/plugins/observability/server/services/slo/find_slo.test.ts +++ b/x-pack/plugins/observability/server/services/slo/find_slo.test.ts @@ -35,6 +35,7 @@ describe('FindSLO', () => { expect(mockSummarySearchClient.search.mock.calls[0]).toMatchInlineSnapshot(` Array [ + "", "", Object { "direction": "asc", @@ -128,6 +129,7 @@ describe('FindSLO', () => { expect(mockSummarySearchClient.search.mock.calls[0]).toMatchInlineSnapshot(` Array [ "slo.name:'Service*' and slo.indicator.type:'sli.kql.custom'", + "", Object { "direction": "asc", "field": "error_budget_consumed", diff --git a/x-pack/plugins/observability/server/services/slo/find_slo.ts b/x-pack/plugins/observability/server/services/slo/find_slo.ts index fb90ec86d04d5..690d61a30c260 100644 --- a/x-pack/plugins/observability/server/services/slo/find_slo.ts +++ b/x-pack/plugins/observability/server/services/slo/find_slo.ts @@ -24,6 +24,7 @@ export class FindSLO { public async execute(params: FindSLOParams): Promise { const sloSummaryList = await this.summarySearchClient.search( params.kqlQuery ?? '', + params.filters ?? '', toSort(params), toPagination(params) ); diff --git a/x-pack/plugins/observability/server/services/slo/summary_search_client.test.ts b/x-pack/plugins/observability/server/services/slo/summary_search_client.test.ts index 256aa9164ea3b..3743cbea46e06 100644 --- a/x-pack/plugins/observability/server/services/slo/summary_search_client.test.ts +++ b/x-pack/plugins/observability/server/services/slo/summary_search_client.test.ts @@ -36,7 +36,7 @@ describe('Summary Search Client', () => { it('returns an empty response on error', async () => { esClientMock.count.mockRejectedValue(new Error('Cannot reach es')); - await expect(service.search('', defaultSort, defaultPagination)).resolves + await expect(service.search('', '', defaultSort, defaultPagination)).resolves .toMatchInlineSnapshot(` Object { "page": 1, @@ -53,7 +53,7 @@ describe('Summary Search Client', () => { _shards: { failed: 0, successful: 1, total: 1 }, }); - await expect(service.search('', defaultSort, defaultPagination)).resolves + await expect(service.search('', '', defaultSort, defaultPagination)).resolves .toMatchInlineSnapshot(` Object { "page": 1, @@ -99,7 +99,7 @@ describe('Summary Search Client', () => { }, }); - const results = await service.search('', defaultSort, defaultPagination); + const results = await service.search('', '', defaultSort, defaultPagination); expect(esClientMock.deleteByQuery).toHaveBeenCalled(); expect(esClientMock.deleteByQuery.mock.calls[0]).toMatchSnapshot(); diff --git a/x-pack/plugins/observability/server/services/slo/summary_search_client.ts b/x-pack/plugins/observability/server/services/slo/summary_search_client.ts index 9715d727f6fc5..be52cb583802a 100644 --- a/x-pack/plugins/observability/server/services/slo/summary_search_client.ts +++ b/x-pack/plugins/observability/server/services/slo/summary_search_client.ts @@ -44,7 +44,12 @@ export interface Sort { } export interface SummarySearchClient { - search(kqlQuery: string, sort: Sort, pagination: Pagination): Promise>; + search( + kqlQuery: string, + filters: string, + sort: Sort, + pagination: Pagination + ): Promise>; } export class DefaultSummarySearchClient implements SummarySearchClient { @@ -56,16 +61,29 @@ export class DefaultSummarySearchClient implements SummarySearchClient { async search( kqlQuery: string, + filters: string, sort: Sort, pagination: Pagination ): Promise> { + let parsedFilters: any = {}; + + try { + parsedFilters = JSON.parse(filters); + } catch (e) { + this.logger.error(`Failed to parse filters: ${e.message}`); + } + try { const summarySearch = await this.esClient.search({ index: SLO_SUMMARY_DESTINATION_INDEX_PATTERN, track_total_hits: true, query: { bool: { - filter: [{ term: { spaceId: this.spaceId } }, getElastichsearchQueryOrThrow(kqlQuery)], + filter: [ + { term: { spaceId: this.spaceId } }, + getElastichsearchQueryOrThrow(kqlQuery), + ...(parsedFilters.filter ?? []), + ], }, }, sort: { diff --git a/x-pack/plugins/observability_shared/public/hooks/use_breadcrumbs.ts b/x-pack/plugins/observability_shared/public/hooks/use_breadcrumbs.ts index 53f1c1f4e02ab..18a08f59db249 100644 --- a/x-pack/plugins/observability_shared/public/hooks/use_breadcrumbs.ts +++ b/x-pack/plugins/observability_shared/public/hooks/use_breadcrumbs.ts @@ -9,6 +9,7 @@ import { i18n } from '@kbn/i18n'; import { ApplicationStart, ChromeBreadcrumb, ChromeStart } from '@kbn/core/public'; import { MouseEvent, useEffect } from 'react'; import { useKibana } from '@kbn/kibana-react-plugin/public'; +import { ChromeBreadcrumbsAppendExtension } from '@kbn/core-chrome-browser'; import { useQueryParams } from './use_query_params'; function addClickHandlers( @@ -36,13 +37,14 @@ function getTitleFromBreadCrumbs(breadcrumbs: ChromeBreadcrumb[]) { export const useBreadcrumbs = ( extraCrumbs: ChromeBreadcrumb[], - app?: { id: string; label: string } + app?: { id: string; label: string }, + breadcrumbsAppendExtension?: ChromeBreadcrumbsAppendExtension ) => { const params = useQueryParams(); const { services: { - chrome: { docTitle, setBreadcrumbs }, + chrome: { docTitle, setBreadcrumbs, setBreadcrumbsAppendExtension }, application: { getUrlForApp, navigateToUrl }, }, } = useKibana<{ @@ -52,6 +54,17 @@ export const useBreadcrumbs = ( const setTitle = docTitle.change; const appPath = getUrlForApp(app?.id ?? 'observability-overview') ?? ''; + useEffect(() => { + if (breadcrumbsAppendExtension) { + setBreadcrumbsAppendExtension(breadcrumbsAppendExtension); + } + return () => { + if (breadcrumbsAppendExtension) { + setBreadcrumbsAppendExtension(undefined); + } + }; + }, [breadcrumbsAppendExtension, setBreadcrumbsAppendExtension]); + useEffect(() => { const breadcrumbs = [ { diff --git a/x-pack/plugins/observability_shared/tsconfig.json b/x-pack/plugins/observability_shared/tsconfig.json index 7a875f30e8736..b63bcf88c2df5 100644 --- a/x-pack/plugins/observability_shared/tsconfig.json +++ b/x-pack/plugins/observability_shared/tsconfig.json @@ -39,7 +39,8 @@ "@kbn/shared-ux-error-boundary", "@kbn/management-settings-field-definition", "@kbn/management-settings-types", - "@kbn/management-settings-utilities" + "@kbn/management-settings-utilities", + "@kbn/core-chrome-browser" ], "exclude": ["target/**/*"] } diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index 278c8191415e4..be3e8b9b14bad 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -29019,7 +29019,6 @@ "xpack.observability.slo.duration.minutely": "Par minute", "xpack.observability.slo.duration.monthly": "Mensuel", "xpack.observability.slo.duration.weekly": "Hebdomadaire", - "xpack.observability.slo.feedbackButtonLabel": "Dites-nous ce que vous pensez !", "xpack.observability.slo.globalDiagnosis.errorNotification": "Vous ne disposez pas des autorisations nécessaires pour utiliser cette fonctionnalité.", "xpack.observability.slo.indicators.apmAvailability": "Disponibilité APM", "xpack.observability.slo.indicators.apmLatency": "Latence APM", @@ -29199,7 +29198,6 @@ "xpack.observability.slo.sloEdit.timeWindowDuration.tooltip": "La durée de la fenêtre temporelle utilisée pour calculer le SLO.", "xpack.observability.slo.sloEdit.timeWindowType.label": "Fenêtre temporelle", "xpack.observability.slo.sloEdit.timeWindowType.tooltip": "Choisissez entre une fenêtre glissante ou alignée sur le calendrier.", - "xpack.observability.slo.sloList.pageHeader.createNewButtonLabel": "Créer un nouveau SLO", "xpack.observability.slo.sloList.welcomePrompt.buttonLabel": "Créer un SLO", "xpack.observability.slo.sloList.welcomePrompt.getStartedMessage": "Pour commencer, créez votre premier SLO.", "xpack.observability.slo.sloList.welcomePrompt.learnMore": "Envie d'en savoir plus ?", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 23c6cf1ef7ad4..a2b9dbfe95c87 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -29020,7 +29020,6 @@ "xpack.observability.slo.duration.minutely": "毎分", "xpack.observability.slo.duration.monthly": "月ごと", "xpack.observability.slo.duration.weekly": "週ごと", - "xpack.observability.slo.feedbackButtonLabel": "ご意見をお聞かせください。", "xpack.observability.slo.globalDiagnosis.errorNotification": "この機能を使用する権限がありません。", "xpack.observability.slo.indicators.apmAvailability": "APM可用性", "xpack.observability.slo.indicators.apmLatency": "APMレイテンシ", @@ -29200,7 +29199,6 @@ "xpack.observability.slo.sloEdit.timeWindowDuration.tooltip": "SLOを計算するために使用される時間枠期間。", "xpack.observability.slo.sloEdit.timeWindowType.label": "時間枠", "xpack.observability.slo.sloEdit.timeWindowType.tooltip": "ローリング時間枠とカレンダー時間枠のどちらかを選択します。", - "xpack.observability.slo.sloList.pageHeader.createNewButtonLabel": "新規SLOを作成", "xpack.observability.slo.sloList.welcomePrompt.buttonLabel": "SLOの作成", "xpack.observability.slo.sloList.welcomePrompt.getStartedMessage": "開始するには、まずSLOを作成します。", "xpack.observability.slo.sloList.welcomePrompt.learnMore": "詳細について", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 3bb3a386b626f..54261e08bc1e8 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -29004,7 +29004,6 @@ "xpack.observability.slo.duration.minutely": "每分钟", "xpack.observability.slo.duration.monthly": "每月", "xpack.observability.slo.duration.weekly": "每周", - "xpack.observability.slo.feedbackButtonLabel": "告诉我们您的看法!", "xpack.observability.slo.globalDiagnosis.errorNotification": "您没有适当权限,无法使用此功能。", "xpack.observability.slo.indicators.apmAvailability": "APM 可用性", "xpack.observability.slo.indicators.apmLatency": "APM 延迟", @@ -29184,7 +29183,6 @@ "xpack.observability.slo.sloEdit.timeWindowDuration.tooltip": "用于在其间计算 SLO 的时间窗口持续时间。", "xpack.observability.slo.sloEdit.timeWindowType.label": "时间窗口", "xpack.observability.slo.sloEdit.timeWindowType.tooltip": "选择滚动或日历对齐窗口。", - "xpack.observability.slo.sloList.pageHeader.createNewButtonLabel": "创建新 SLO", "xpack.observability.slo.sloList.welcomePrompt.buttonLabel": "创建 SLO", "xpack.observability.slo.sloList.welcomePrompt.getStartedMessage": "要开始使用,请创建您的首个 SLO。", "xpack.observability.slo.sloList.welcomePrompt.learnMore": "希望了解详情?",