From 7b488c3b24bc0c479f333771c61a411ef1f60d40 Mon Sep 17 00:00:00 2001 From: Marco Antonio Ghiani Date: Wed, 19 Apr 2023 20:09:50 +0200 Subject: [PATCH 01/39] feat(infra): wip useInventoryViews --- .../infra/public/hooks/use_inventory_views.ts | 122 ++++++++++++++++++ .../infra/public/pages/metrics/index.tsx | 11 +- 2 files changed, 131 insertions(+), 2 deletions(-) create mode 100644 x-pack/plugins/infra/public/hooks/use_inventory_views.ts diff --git a/x-pack/plugins/infra/public/hooks/use_inventory_views.ts b/x-pack/plugins/infra/public/hooks/use_inventory_views.ts new file mode 100644 index 0000000000000..71b021d81f087 --- /dev/null +++ b/x-pack/plugins/infra/public/hooks/use_inventory_views.ts @@ -0,0 +1,122 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; +import { useQuery } from '@tanstack/react-query'; + +import * as rt from 'io-ts'; +import { pipe } from 'fp-ts/lib/pipeable'; +import { fold } from 'fp-ts/lib/Either'; +import { constant, identity } from 'fp-ts/lib/function'; + +import { useUiTracker } from '@kbn/observability-plugin/public'; +import { useKibanaContextForPlugin } from './use_kibana'; +import { useUrlState } from '../utils/use_url_state'; + +const queryBuilder = { + find: ['inventory-views-find'] as const, + get: (id?: string) => ['inventory-views-get', id].filter(Boolean), +}; + +const placeholderView = { + id: null, + attributes: {}, +}; + +export const useInventoryViews = () => { + const { inventoryViews } = useKibanaContextForPlugin().services; + const trackMetric = useUiTracker({ app: 'infra_metrics' }); // TODO: move this tracking to backend + + const [currentViewId, switchViewById] = useUrlState({ + defaultState: '0', + decodeUrlState, + encodeUrlState, + urlStateKey: 'inventoryViewId', + writeDefaultState: true, + }); + + const notify = useInventoryViewsNotifier(); + + const { + data: views, + refetch: fetchViews, + isFetching: isFetchingViews, + } = useQuery({ + queryKey: queryBuilder.find, + queryFn: () => inventoryViews.findInventoryViews(), + enabled: false, // We will manually fetch the list when necessary + placeholderData: [], // Use a default empty array instead of undefined + onError: (error) => notify.getViewFailure(error.response.message), + onSuccess: (data) => { + const prefix = data.length >= 1000 ? 'over' : 'under'; + trackMetric({ metric: `${prefix}_1000_saved_objects_for_inventory_view` }); + }, + }); + + const { data: currentView, isFetching: isFetchingCurrentView } = useQuery({ + queryKey: queryBuilder.get(currentViewId), + queryFn: ({ queryKey: [, id] }) => inventoryViews.getInventoryView(id), + onError: (error) => notify.getViewFailure(error.response.message), + placeholderData: placeholderView, + }); + + return { + // Values about views list + views, + fetchViews, + isFetchingViews, + // Values about current view + currentView, + isFetchingCurrentView, + // Actions about updating view + switchViewById, + }; +}; + +/** + * Support hooks + */ +const useInventoryViewsNotifier = () => { + const { notifications } = useKibanaContextForPlugin(); + + const getViewFailure = (message?: string) => { + notifications.toasts.danger({ + toastLifeTimeMs: 3000, + title: + message || + i18n.translate('xpack.infra.savedView.findError.title', { + defaultMessage: `An error occurred while loading views.`, + }), + }); + }; + + const upsertViewFailure = (message?: string) => { + notifications.toasts.danger({ + toastLifeTimeMs: 3000, + title: + message || + i18n.translate('xpack.infra.savedView.errorOnCreate.title', { + defaultMessage: `An error occured saving view.`, + }), + }); + }; + + return { + getViewFailure, + upsertViewFailure, + }; +}; + +const inventoryViewIdRT = rt.string; + +type InventoryViewId = rt.TypeOf; + +const encodeUrlState = inventoryViewIdRT.encode; +const decodeUrlState = (value: unknown) => { + const state = pipe(inventoryViewIdRT.decode(value), fold(constant('0'), identity)); + return state; +}; diff --git a/x-pack/plugins/infra/public/pages/metrics/index.tsx b/x-pack/plugins/infra/public/pages/metrics/index.tsx index 37f2a8bbbf01e..f2e398f5cb536 100644 --- a/x-pack/plugins/infra/public/pages/metrics/index.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/index.tsx @@ -7,7 +7,7 @@ import { i18n } from '@kbn/i18n'; -import React, { useContext } from 'react'; +import React, { useContext, useState } from 'react'; import { RouteComponentProps, Switch } from 'react-router-dom'; import { Route } from '@kbn/shared-ux-router'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; @@ -51,7 +51,14 @@ const ADD_DATA_LABEL = i18n.translate('xpack.infra.metricsHeaderAddDataButtonLab export const InfrastructurePage = ({ match }: RouteComponentProps) => { const uiCapabilities = useKibana().services.application?.capabilities; const { setHeaderActionMenu, theme$ } = useContext(HeaderActionMenuContext); - const queryClient = new QueryClient(); + const [queryClient] = useState( + () => + new QueryClient({ + defaultOptions: { + queries: { keepPreviousData: true }, + }, + }) + ); const settingsTabTitle = i18n.translate('xpack.infra.metrics.settingsTabTitle', { defaultMessage: 'Settings', From 7228a314c94faaa62e5b8f0b827082c1398f2584 Mon Sep 17 00:00:00 2001 From: Marco Antonio Ghiani Date: Thu, 20 Apr 2023 09:46:51 +0200 Subject: [PATCH 02/39] feat(infra): extract react query config --- .../containers/react_query_provider.tsx | 50 +++++++++++++++++++ .../infra/public/pages/metrics/index.tsx | 18 ++----- 2 files changed, 54 insertions(+), 14 deletions(-) create mode 100644 x-pack/plugins/infra/public/containers/react_query_provider.tsx diff --git a/x-pack/plugins/infra/public/containers/react_query_provider.tsx b/x-pack/plugins/infra/public/containers/react_query_provider.tsx new file mode 100644 index 0000000000000..25c7d44041467 --- /dev/null +++ b/x-pack/plugins/infra/public/containers/react_query_provider.tsx @@ -0,0 +1,50 @@ +/* + * 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 { QueryClient, QueryClientConfig, QueryClientProvider } from '@tanstack/react-query'; +import merge from 'lodash/merge'; +import { EuiButtonIcon } from '@elastic/eui'; +import { ReactQueryDevtools } from '@tanstack/react-query-devtools'; + +const DEFAULT_CONFIG = { + defaultOptions: { + queries: { keepPreviousData: true }, + }, +}; + +interface ProviderProps { + children: React.ReactNode; + config?: QueryClientConfig; +} + +export function ReactQueryProvider({ children, config = {} }: ProviderProps) { + const [queryClient] = useState(() => new QueryClient(merge(DEFAULT_CONFIG, config))); + + return ( + + + {children} + + ); +} + +function HideableReactQueryDevTools() { + const [isHidden, setIsHidden] = useState(false); + + return !isHidden ? ( +
+ setIsHidden(!isHidden)} + /> + +
+ ) : null; +} diff --git a/x-pack/plugins/infra/public/pages/metrics/index.tsx b/x-pack/plugins/infra/public/pages/metrics/index.tsx index f2e398f5cb536..1c735bfcb3638 100644 --- a/x-pack/plugins/infra/public/pages/metrics/index.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/index.tsx @@ -7,11 +7,9 @@ import { i18n } from '@kbn/i18n'; -import React, { useContext, useState } from 'react'; +import React, { useContext } from 'react'; import { RouteComponentProps, Switch } from 'react-router-dom'; import { Route } from '@kbn/shared-ux-router'; -import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; -import { ReactQueryDevtools } from '@tanstack/react-query-devtools'; import { EuiErrorBoundary, EuiHeaderLinks, EuiHeaderLink } from '@elastic/eui'; import { useKibana } from '@kbn/kibana-react-plugin/public'; @@ -43,6 +41,7 @@ import { AnomalyDetectionFlyout } from './inventory_view/components/ml/anomaly_d import { HeaderActionMenuContext } from '../../utils/header_action_menu_provider'; import { CreateDerivedIndexPattern, useSourceContext } from '../../containers/metrics_source'; import { NotFoundPage } from '../404'; +import { ReactQueryProvider } from '../../containers/react_query_provider'; const ADD_DATA_LABEL = i18n.translate('xpack.infra.metricsHeaderAddDataButtonLabel', { defaultMessage: 'Add data', @@ -51,14 +50,6 @@ const ADD_DATA_LABEL = i18n.translate('xpack.infra.metricsHeaderAddDataButtonLab export const InfrastructurePage = ({ match }: RouteComponentProps) => { const uiCapabilities = useKibana().services.application?.capabilities; const { setHeaderActionMenu, theme$ } = useContext(HeaderActionMenuContext); - const [queryClient] = useState( - () => - new QueryClient({ - defaultOptions: { - queries: { keepPreviousData: true }, - }, - }) - ); const settingsTabTitle = i18n.translate('xpack.infra.metrics.settingsTabTitle', { defaultMessage: 'Settings', @@ -81,8 +72,7 @@ export const InfrastructurePage = ({ match }: RouteComponentProps) => { - - + { } /> - + From 34e85717833302dba729b70cd3c1a37d8d97c9a0 Mon Sep 17 00:00:00 2001 From: Marco Antonio Ghiani Date: Thu, 20 Apr 2023 13:20:25 +0200 Subject: [PATCH 03/39] feat(infra): wip useInventoryViews mutations --- .../infra/public/hooks/use_inventory_views.ts | 180 +++++++++++++----- .../public/hooks/use_saved_views_notifier.ts | 52 +++++ 2 files changed, 184 insertions(+), 48 deletions(-) create mode 100644 x-pack/plugins/infra/public/hooks/use_saved_views_notifier.ts diff --git a/x-pack/plugins/infra/public/hooks/use_inventory_views.ts b/x-pack/plugins/infra/public/hooks/use_inventory_views.ts index 71b021d81f087..ae11e61efd861 100644 --- a/x-pack/plugins/infra/public/hooks/use_inventory_views.ts +++ b/x-pack/plugins/infra/public/hooks/use_inventory_views.ts @@ -4,20 +4,20 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ - -import { i18n } from '@kbn/i18n'; -import { useQuery } from '@tanstack/react-query'; - import * as rt from 'io-ts'; import { pipe } from 'fp-ts/lib/pipeable'; import { fold } from 'fp-ts/lib/Either'; import { constant, identity } from 'fp-ts/lib/function'; +import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { useUiTracker } from '@kbn/observability-plugin/public'; + import { useKibanaContextForPlugin } from './use_kibana'; import { useUrlState } from '../utils/use_url_state'; +import { useSavedViewsNotifier } from './use_saved_views_notifier'; +import { useSourceContext } from '../containers/metrics_source'; -const queryBuilder = { +const queryKeys = { find: ['inventory-views-find'] as const, get: (id?: string) => ['inventory-views-get', id].filter(Boolean), }; @@ -29,24 +29,29 @@ const placeholderView = { export const useInventoryViews = () => { const { inventoryViews } = useKibanaContextForPlugin().services; - const trackMetric = useUiTracker({ app: 'infra_metrics' }); // TODO: move this tracking to backend + const trackMetric = useUiTracker({ app: 'infra_metrics' }); + + const queryClient = useQueryClient(); + const { source, updateSourceConfiguration } = useSourceContext(); + + const defaultViewId = source?.configuration.inventoryDefaultView ?? '0'; const [currentViewId, switchViewById] = useUrlState({ - defaultState: '0', + defaultState: defaultViewId, decodeUrlState, encodeUrlState, urlStateKey: 'inventoryViewId', writeDefaultState: true, }); - const notify = useInventoryViewsNotifier(); + const notify = useSavedViewsNotifier(); const { data: views, refetch: fetchViews, isFetching: isFetchingViews, } = useQuery({ - queryKey: queryBuilder.find, + queryKey: queryKeys.find, queryFn: () => inventoryViews.findInventoryViews(), enabled: false, // We will manually fetch the list when necessary placeholderData: [], // Use a default empty array instead of undefined @@ -58,56 +63,118 @@ export const useInventoryViews = () => { }); const { data: currentView, isFetching: isFetchingCurrentView } = useQuery({ - queryKey: queryBuilder.get(currentViewId), + queryKey: queryKeys.get(currentViewId), queryFn: ({ queryKey: [, id] }) => inventoryViews.getInventoryView(id), onError: (error) => notify.getViewFailure(error.response.message), placeholderData: placeholderView, }); + const { mutate: setDefaultViewById } = useMutation({ + mutationFn: (id: string) => updateSourceConfiguration({ inventoryDefaultView: id }), + /** + * To provide a quick feedback, we perform an optimistic update on the list + * when updating the default view. + * 1. Cancel any outgoing refetches (so they don't overwrite our optimistic update) + * 2. Snapshot the previous views list + * 3. Optimistically update the list with new default view and store in cache + * 4. Return a context object with the snapshotted views + */ + onMutate: async (id) => { + await queryClient.cancelQueries({ queryKey: queryKeys.find }); // 1 + const previousViews = queryClient.getQueryData(queryKeys.find); // 2 + const updatedViews = getListWithUpdatedDefault(id, previousViews); // 3 + queryClient.setQueryData(queryKeys.find, updatedViews); + return { previousViews }; // 4 + }, + // If the mutation fails, use the context returned from onMutate to roll back + onError: (error, _id, context) => { + notify.setDefaultViewFailure(error.response.message); + queryClient.setQueryData(queryKeys.find, context.previousViews); + }, + // Always refetch after error or success: + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: queryKeys.get() }); // Invalidate all single views cache entries + }, + onSettled: () => { + fetchViews(); // Always invalidate views list cache and refetch views on success/error + }, + }); + + const { mutate: createView, isLoading: isCreatingView } = useMutation({ + mutationFn: (attributes /* TODO: add type */) => inventoryViews.createInventoryView(attributes), + onError: (error) => { + notify.upsertViewFailure(error.response.message); + }, + onSuccess: (createdView) => { + queryClient.setQueryData(queryKeys.get(createdView.id), createdView); // Store in cache created view + switchViewById(createdView.id); // Update current view and url state + fetchViews(); // Invalidate views list cache and refetch views + }, + }); + + const { mutate: updateViewById, isLoading: isUpdatingView } = useMutation({ + mutationFn: ({ id, attributes } /* TODO: add type */) => + inventoryViews.updateInventoryView(id, attributes), + onError: (error) => { + notify.upsertViewFailure(error.response.message); + }, + onSuccess: (updatedView) => { + queryClient.setQueryData(queryKeys.get(updatedView.id), updatedView); // Store in cache updated view + fetchViews(); // Invalidate views list cache and refetch views + }, + }); + + const { mutate: deleteViewById } = useMutation({ + mutationFn: (id: string) => inventoryViews.deleteInventoryView(id), + /** + * To provide a quick feedback, we perform an optimistic update on the list + * when deleting a view. + * 1. Cancel any outgoing refetches (so they don't overwrite our optimistic update) + * 2. Snapshot the previous views list + * 3. Optimistically update the list removing the view and store in cache + * 4. Return a context object with the snapshotted views + */ + onMutate: async (id) => { + await queryClient.cancelQueries({ queryKey: queryKeys.find }); // 1 + + const previousViews = queryClient.getQueryData(queryKeys.find); // 2 + + const updatedViews = getListWithoutDeletedView(id, previousViews); // 3 + queryClient.setQueryData(queryKeys.find, updatedViews); + + return { previousViews }; // 4 + }, + // If the mutation fails, use the context returned from onMutate to roll back + onError: (error, _id, context) => { + queryClient.setQueryData(queryKeys.find, context.previousViews); + }, + onSuccess: (_data, id) => { + // If the deleted view was the current one, switch to the default view + if (currentView.id === id) { + switchViewById(defaultViewId); + } + }, + onSettled: (updatedView) => { + fetchViews(); // Invalidate views list cache and refetch views + }, + }); + return { // Values about views list views, - fetchViews, - isFetchingViews, - // Values about current view currentView, - isFetchingCurrentView, // Actions about updating view + createView, + deleteViewById, + fetchViews, + updateViewById, switchViewById, - }; -}; - -/** - * Support hooks - */ -const useInventoryViewsNotifier = () => { - const { notifications } = useKibanaContextForPlugin(); - - const getViewFailure = (message?: string) => { - notifications.toasts.danger({ - toastLifeTimeMs: 3000, - title: - message || - i18n.translate('xpack.infra.savedView.findError.title', { - defaultMessage: `An error occurred while loading views.`, - }), - }); - }; - - const upsertViewFailure = (message?: string) => { - notifications.toasts.danger({ - toastLifeTimeMs: 3000, - title: - message || - i18n.translate('xpack.infra.savedView.errorOnCreate.title', { - defaultMessage: `An error occured saving view.`, - }), - }); - }; - - return { - getViewFailure, - upsertViewFailure, + setDefaultViewById, + // Status flags + isCreatingView, + isFetchingCurrentView, + isFetchingViews, + isUpdatingView, }; }; @@ -120,3 +187,20 @@ const decodeUrlState = (value: unknown) => { const state = pipe(inventoryViewIdRT.decode(value), fold(constant('0'), identity)); return state; }; + +/** + * Helpers + */ +const getListWithUpdatedDefault = (id: string, views) => { + return views.map((view) => ({ + ...view, + attributes: { + ...view.attributes, + isDefault: view.id === id, + }, + })); +}; + +const getListWithoutDeletedView = (id: string, views) => { + return views.filter((view) => view.id !== id); +}; diff --git a/x-pack/plugins/infra/public/hooks/use_saved_views_notifier.ts b/x-pack/plugins/infra/public/hooks/use_saved_views_notifier.ts new file mode 100644 index 0000000000000..9c4cb785b83b7 --- /dev/null +++ b/x-pack/plugins/infra/public/hooks/use_saved_views_notifier.ts @@ -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 { i18n } from '@kbn/i18n'; +import { useKibanaContextForPlugin } from './use_kibana'; + +export const useSavedViewsNotifier = () => { + const { notifications } = useKibanaContextForPlugin(); + + const getViewFailure = (message?: string) => { + notifications.toasts.danger({ + toastLifeTimeMs: 3000, + title: + message || + i18n.translate('xpack.infra.savedView.findError.title', { + defaultMessage: `An error occurred while loading views.`, + }), + }); + }; + + const setDefaultViewFailure = (message?: string) => { + notifications.toasts.danger({ + toastLifeTimeMs: 3000, + title: + message || + i18n.translate('xpack.infra.savedView.errorOnMakeDefault.title', { + defaultMessage: `An error updating the default view.`, + }), + }); + }; + + const upsertViewFailure = (message?: string) => { + notifications.toasts.danger({ + toastLifeTimeMs: 3000, + title: + message || + i18n.translate('xpack.infra.savedView.errorOnCreate.title', { + defaultMessage: `An error occured saving view.`, + }), + }); + }; + + return { + getViewFailure, + setDefaultViewFailure, + upsertViewFailure, + }; +}; From 54a5172a5e7d796ffd9eb4f8e5695fa7b3f87794 Mon Sep 17 00:00:00 2001 From: Marco Antonio Ghiani Date: Thu, 20 Apr 2023 15:23:11 +0200 Subject: [PATCH 04/39] refactor(infra): remove currentView placeholder --- .../plugins/infra/public/hooks/use_inventory_views.ts | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/x-pack/plugins/infra/public/hooks/use_inventory_views.ts b/x-pack/plugins/infra/public/hooks/use_inventory_views.ts index ae11e61efd861..4dba51c2bd222 100644 --- a/x-pack/plugins/infra/public/hooks/use_inventory_views.ts +++ b/x-pack/plugins/infra/public/hooks/use_inventory_views.ts @@ -22,11 +22,6 @@ const queryKeys = { get: (id?: string) => ['inventory-views-get', id].filter(Boolean), }; -const placeholderView = { - id: null, - attributes: {}, -}; - export const useInventoryViews = () => { const { inventoryViews } = useKibanaContextForPlugin().services; const trackMetric = useUiTracker({ app: 'infra_metrics' }); @@ -66,7 +61,7 @@ export const useInventoryViews = () => { queryKey: queryKeys.get(currentViewId), queryFn: ({ queryKey: [, id] }) => inventoryViews.getInventoryView(id), onError: (error) => notify.getViewFailure(error.response.message), - placeholderData: placeholderView, + placeholderData: null, }); const { mutate: setDefaultViewById } = useMutation({ @@ -154,7 +149,7 @@ export const useInventoryViews = () => { switchViewById(defaultViewId); } }, - onSettled: (updatedView) => { + onSettled: () => { fetchViews(); // Invalidate views list cache and refetch views }, }); @@ -179,7 +174,6 @@ export const useInventoryViews = () => { }; const inventoryViewIdRT = rt.string; - type InventoryViewId = rt.TypeOf; const encodeUrlState = inventoryViewIdRT.encode; From a76f79a785b006f1527a000e584488262cf0b2bb Mon Sep 17 00:00:00 2001 From: Marco Antonio Ghiani Date: Wed, 26 Apr 2023 10:33:11 +0200 Subject: [PATCH 05/39] refactor(infra): update types --- .../infra/public/hooks/use_inventory_views.ts | 48 ++++++++++--------- 1 file changed, 26 insertions(+), 22 deletions(-) diff --git a/x-pack/plugins/infra/public/hooks/use_inventory_views.ts b/x-pack/plugins/infra/public/hooks/use_inventory_views.ts index 4dba51c2bd222..f5410ab81d66b 100644 --- a/x-pack/plugins/infra/public/hooks/use_inventory_views.ts +++ b/x-pack/plugins/infra/public/hooks/use_inventory_views.ts @@ -12,14 +12,19 @@ import { constant, identity } from 'fp-ts/lib/function'; import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { useUiTracker } from '@kbn/observability-plugin/public'; +import { IHttpFetchError, ResponseErrorBody } from '@kbn/core-http-browser'; +import { InventoryView } from '../../common/inventory_views'; import { useKibanaContextForPlugin } from './use_kibana'; import { useUrlState } from '../utils/use_url_state'; import { useSavedViewsNotifier } from './use_saved_views_notifier'; import { useSourceContext } from '../containers/metrics_source'; +type ServerError = IHttpFetchError; + const queryKeys = { find: ['inventory-views-find'] as const, - get: (id?: string) => ['inventory-views-get', id].filter(Boolean), + get: ['inventory-views-get'] as const, + getById: (id: string) => ['inventory-views-get', id] as const, }; export const useInventoryViews = () => { @@ -50,7 +55,7 @@ export const useInventoryViews = () => { queryFn: () => inventoryViews.findInventoryViews(), enabled: false, // We will manually fetch the list when necessary placeholderData: [], // Use a default empty array instead of undefined - onError: (error) => notify.getViewFailure(error.response.message), + onError: (error: ServerError) => notify.getViewFailure(error.body?.message ?? error.message), onSuccess: (data) => { const prefix = data.length >= 1000 ? 'over' : 'under'; trackMetric({ metric: `${prefix}_1000_saved_objects_for_inventory_view` }); @@ -58,9 +63,9 @@ export const useInventoryViews = () => { }); const { data: currentView, isFetching: isFetchingCurrentView } = useQuery({ - queryKey: queryKeys.get(currentViewId), + queryKey: queryKeys.getById(currentViewId), queryFn: ({ queryKey: [, id] }) => inventoryViews.getInventoryView(id), - onError: (error) => notify.getViewFailure(error.response.message), + onError: (error: ServerError) => notify.getViewFailure(error.body?.message ?? error.message), placeholderData: null, }); @@ -76,19 +81,19 @@ export const useInventoryViews = () => { */ onMutate: async (id) => { await queryClient.cancelQueries({ queryKey: queryKeys.find }); // 1 - const previousViews = queryClient.getQueryData(queryKeys.find); // 2 + const previousViews = queryClient.getQueryData(queryKeys.find); // 2 const updatedViews = getListWithUpdatedDefault(id, previousViews); // 3 queryClient.setQueryData(queryKeys.find, updatedViews); return { previousViews }; // 4 }, // If the mutation fails, use the context returned from onMutate to roll back - onError: (error, _id, context) => { - notify.setDefaultViewFailure(error.response.message); + onError: (error: ServerError, _id, context: { previousViews: InventoryView[] }) => { + notify.setDefaultViewFailure(error.body?.message ?? error.message); queryClient.setQueryData(queryKeys.find, context.previousViews); }, // Always refetch after error or success: onSuccess: () => { - queryClient.invalidateQueries({ queryKey: queryKeys.get() }); // Invalidate all single views cache entries + queryClient.invalidateQueries({ queryKey: queryKeys.get }); // Invalidate all single views cache entries }, onSettled: () => { fetchViews(); // Always invalidate views list cache and refetch views on success/error @@ -96,25 +101,24 @@ export const useInventoryViews = () => { }); const { mutate: createView, isLoading: isCreatingView } = useMutation({ - mutationFn: (attributes /* TODO: add type */) => inventoryViews.createInventoryView(attributes), - onError: (error) => { - notify.upsertViewFailure(error.response.message); + mutationFn: (attributes) => inventoryViews.createInventoryView(attributes), + onError: (error: ServerError) => { + notify.upsertViewFailure(error.body?.message ?? error.message); }, - onSuccess: (createdView) => { - queryClient.setQueryData(queryKeys.get(createdView.id), createdView); // Store in cache created view + onSuccess: (createdView: InventoryView) => { + queryClient.setQueryData(queryKeys.getById(createdView.id), createdView); // Store in cache created view switchViewById(createdView.id); // Update current view and url state fetchViews(); // Invalidate views list cache and refetch views }, }); const { mutate: updateViewById, isLoading: isUpdatingView } = useMutation({ - mutationFn: ({ id, attributes } /* TODO: add type */) => - inventoryViews.updateInventoryView(id, attributes), + mutationFn: ({ id, attributes }) => inventoryViews.updateInventoryView(id, attributes), onError: (error) => { - notify.upsertViewFailure(error.response.message); + notify.upsertViewFailure(error.body?.message); }, - onSuccess: (updatedView) => { - queryClient.setQueryData(queryKeys.get(updatedView.id), updatedView); // Store in cache updated view + onSuccess: (updatedView: InventoryView) => { + queryClient.setQueryData(queryKeys.getById(updatedView.id), updatedView); // Store in cache updated view fetchViews(); // Invalidate views list cache and refetch views }, }); @@ -132,7 +136,7 @@ export const useInventoryViews = () => { onMutate: async (id) => { await queryClient.cancelQueries({ queryKey: queryKeys.find }); // 1 - const previousViews = queryClient.getQueryData(queryKeys.find); // 2 + const previousViews = queryClient.getQueryData(queryKeys.find); // 2 const updatedViews = getListWithoutDeletedView(id, previousViews); // 3 queryClient.setQueryData(queryKeys.find, updatedViews); @@ -155,7 +159,7 @@ export const useInventoryViews = () => { }); return { - // Values about views list + // Values views, currentView, // Actions about updating view @@ -185,7 +189,7 @@ const decodeUrlState = (value: unknown) => { /** * Helpers */ -const getListWithUpdatedDefault = (id: string, views) => { +const getListWithUpdatedDefault = (id: string, views: InventoryView[] = []) => { return views.map((view) => ({ ...view, attributes: { @@ -195,6 +199,6 @@ const getListWithUpdatedDefault = (id: string, views) => { })); }; -const getListWithoutDeletedView = (id: string, views) => { +const getListWithoutDeletedView = (id: string, views: InventoryView[] = []) => { return views.filter((view) => view.id !== id); }; From 5f65776d50e4c59e533ec99e8c6ad6aec14e212b Mon Sep 17 00:00:00 2001 From: Marco Antonio Ghiani Date: Wed, 26 Apr 2023 11:57:49 +0200 Subject: [PATCH 06/39] refactor(infra): finalize types --- .../infra/public/hooks/use_inventory_views.ts | 54 +++++++++++++------ .../public/services/inventory_views/types.ts | 10 ++-- 2 files changed, 44 insertions(+), 20 deletions(-) diff --git a/x-pack/plugins/infra/public/hooks/use_inventory_views.ts b/x-pack/plugins/infra/public/hooks/use_inventory_views.ts index f5410ab81d66b..2237c5a6336bb 100644 --- a/x-pack/plugins/infra/public/hooks/use_inventory_views.ts +++ b/x-pack/plugins/infra/public/hooks/use_inventory_views.ts @@ -13,6 +13,10 @@ import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { useUiTracker } from '@kbn/observability-plugin/public'; import { IHttpFetchError, ResponseErrorBody } from '@kbn/core-http-browser'; +import { + CreateInventoryViewAttributesRequestPayload, + UpdateInventoryViewAttributesRequestPayload, +} from '../../common/http_api/latest'; import { InventoryView } from '../../common/inventory_views'; import { useKibanaContextForPlugin } from './use_kibana'; import { useUrlState } from '../utils/use_url_state'; @@ -20,6 +24,10 @@ import { useSavedViewsNotifier } from './use_saved_views_notifier'; import { useSourceContext } from '../containers/metrics_source'; type ServerError = IHttpFetchError; +interface MutationContext { + id?: string; + previousViews?: InventoryView[]; +} const queryKeys = { find: ['inventory-views-find'] as const, @@ -52,7 +60,7 @@ export const useInventoryViews = () => { isFetching: isFetchingViews, } = useQuery({ queryKey: queryKeys.find, - queryFn: () => inventoryViews.findInventoryViews(), + queryFn: () => inventoryViews.client.findInventoryViews(), enabled: false, // We will manually fetch the list when necessary placeholderData: [], // Use a default empty array instead of undefined onError: (error: ServerError) => notify.getViewFailure(error.body?.message ?? error.message), @@ -64,13 +72,13 @@ export const useInventoryViews = () => { const { data: currentView, isFetching: isFetchingCurrentView } = useQuery({ queryKey: queryKeys.getById(currentViewId), - queryFn: ({ queryKey: [, id] }) => inventoryViews.getInventoryView(id), + queryFn: ({ queryKey: [, id] }) => inventoryViews.client.getInventoryView(id), onError: (error: ServerError) => notify.getViewFailure(error.body?.message ?? error.message), placeholderData: null, }); - const { mutate: setDefaultViewById } = useMutation({ - mutationFn: (id: string) => updateSourceConfiguration({ inventoryDefaultView: id }), + const { mutate: setDefaultViewById } = useMutation({ + mutationFn: async (id) => void updateSourceConfiguration({ inventoryDefaultView: id }), /** * To provide a quick feedback, we perform an optimistic update on the list * when updating the default view. @@ -87,9 +95,11 @@ export const useInventoryViews = () => { return { previousViews }; // 4 }, // If the mutation fails, use the context returned from onMutate to roll back - onError: (error: ServerError, _id, context: { previousViews: InventoryView[] }) => { + onError: (error: ServerError, _id, context) => { notify.setDefaultViewFailure(error.body?.message ?? error.message); - queryClient.setQueryData(queryKeys.find, context.previousViews); + if (context?.previousViews) { + queryClient.setQueryData(queryKeys.find, context.previousViews); + } }, // Always refetch after error or success: onSuccess: () => { @@ -100,31 +110,39 @@ export const useInventoryViews = () => { }, }); - const { mutate: createView, isLoading: isCreatingView } = useMutation({ - mutationFn: (attributes) => inventoryViews.createInventoryView(attributes), - onError: (error: ServerError) => { + const { mutate: createView, isLoading: isCreatingView } = useMutation< + InventoryView, + ServerError, + CreateInventoryViewAttributesRequestPayload + >({ + mutationFn: (attributes) => inventoryViews.client.createInventoryView(attributes), + onError: (error) => { notify.upsertViewFailure(error.body?.message ?? error.message); }, - onSuccess: (createdView: InventoryView) => { + onSuccess: (createdView) => { queryClient.setQueryData(queryKeys.getById(createdView.id), createdView); // Store in cache created view switchViewById(createdView.id); // Update current view and url state fetchViews(); // Invalidate views list cache and refetch views }, }); - const { mutate: updateViewById, isLoading: isUpdatingView } = useMutation({ - mutationFn: ({ id, attributes }) => inventoryViews.updateInventoryView(id, attributes), + const { mutate: updateViewById, isLoading: isUpdatingView } = useMutation< + InventoryView, + ServerError, + { id: string; attributes: UpdateInventoryViewAttributesRequestPayload } + >({ + mutationFn: ({ id, attributes }) => inventoryViews.client.updateInventoryView(id, attributes), onError: (error) => { notify.upsertViewFailure(error.body?.message); }, - onSuccess: (updatedView: InventoryView) => { + onSuccess: (updatedView) => { queryClient.setQueryData(queryKeys.getById(updatedView.id), updatedView); // Store in cache updated view fetchViews(); // Invalidate views list cache and refetch views }, }); - const { mutate: deleteViewById } = useMutation({ - mutationFn: (id: string) => inventoryViews.deleteInventoryView(id), + const { mutate: deleteViewById } = useMutation({ + mutationFn: (id: string) => inventoryViews.client.deleteInventoryView(id), /** * To provide a quick feedback, we perform an optimistic update on the list * when deleting a view. @@ -145,11 +163,13 @@ export const useInventoryViews = () => { }, // If the mutation fails, use the context returned from onMutate to roll back onError: (error, _id, context) => { - queryClient.setQueryData(queryKeys.find, context.previousViews); + if (context?.previousViews) { + queryClient.setQueryData(queryKeys.find, context.previousViews); + } }, onSuccess: (_data, id) => { // If the deleted view was the current one, switch to the default view - if (currentView.id === id) { + if (currentView?.id === id) { switchViewById(defaultViewId); } }, diff --git a/x-pack/plugins/infra/public/services/inventory_views/types.ts b/x-pack/plugins/infra/public/services/inventory_views/types.ts index 2a690c4cc6c2e..d17d5516563b7 100644 --- a/x-pack/plugins/infra/public/services/inventory_views/types.ts +++ b/x-pack/plugins/infra/public/services/inventory_views/types.ts @@ -6,7 +6,11 @@ */ import { HttpStart } from '@kbn/core/public'; -import { InventoryView, InventoryViewAttributes } from '../../../common/inventory_views'; +import { + CreateInventoryViewAttributesRequestPayload, + UpdateInventoryViewAttributesRequestPayload, +} from '../../../common/http_api/latest'; +import { InventoryView } from '../../../common/inventory_views'; export type InventoryViewsServiceSetup = void; @@ -22,11 +26,11 @@ export interface IInventoryViewsClient { findInventoryViews(): Promise; getInventoryView(inventoryViewId: string): Promise; createInventoryView( - inventoryViewAttributes: Partial + inventoryViewAttributes: CreateInventoryViewAttributesRequestPayload ): Promise; updateInventoryView( inventoryViewId: string, - inventoryViewAttributes: Partial + inventoryViewAttributes: UpdateInventoryViewAttributesRequestPayload ): Promise; deleteInventoryView(inventoryViewId: string): Promise; } From d23b7735331447ae678055f87591d59a7f6ee5c6 Mon Sep 17 00:00:00 2001 From: Marco Antonio Ghiani Date: Wed, 26 Apr 2023 14:56:16 +0200 Subject: [PATCH 07/39] feat(infra): use new useInventoryView hook --- .../saved_views/manage_views_flyout.tsx | 35 ++-- .../saved_views/toolbar_control.tsx | 194 +++++++----------- .../components/saved_views/upsert_modal.tsx | 6 +- .../saved_views/view_list_modal.tsx | 8 +- .../infra/public/hooks/use_inventory_views.ts | 7 +- .../inventory_view/components/layout_view.tsx | 4 +- .../inventory_view/components/saved_views.tsx | 30 ++- .../hooks/use_waffle_view_state.ts | 36 ++-- .../pages/metrics/inventory_view/index.tsx | 52 ++--- .../inventory_views/inventory_views_client.ts | 8 +- 10 files changed, 179 insertions(+), 201 deletions(-) diff --git a/x-pack/plugins/infra/public/components/saved_views/manage_views_flyout.tsx b/x-pack/plugins/infra/public/components/saved_views/manage_views_flyout.tsx index 33e5b71c56a3f..f931bd154afe9 100644 --- a/x-pack/plugins/infra/public/components/saved_views/manage_views_flyout.tsx +++ b/x-pack/plugins/infra/public/components/saved_views/manage_views_flyout.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { useState } from 'react'; +import React from 'react'; import useToggle from 'react-use/lib/useToggle'; import { @@ -22,15 +22,15 @@ import { } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; +import { InventoryView } from '../../../common/inventory_views'; import { SavedView } from '../../containers/saved_view/saved_view'; -interface Props { - views: Array>; +interface Props { + views: InventoryView[]; loading: boolean; - sourceIsLoading: boolean; onClose(): void; onMakeDefaultView(id: string): void; - setView(viewState: ViewState): void; + onSwitchView(id: string): void; onDeleteView(id: string): void; } @@ -74,20 +74,17 @@ const DeleteConfimation = ({ isDisabled, onConfirm }: DeleteConfimationProps) => export function SavedViewManageViewsFlyout({ onClose, views, - setView, + onSwitchView, onMakeDefaultView, onDeleteView, loading, - sourceIsLoading, -}: Props) { - const [inProgressView, setInProgressView] = useState(null); - - const renderName = (name: string, item: SavedView) => ( +}: Props) { + const renderName = (name: string, item: InventoryView) => ( { - setView(item); + onSwitchView(item.id); onClose(); }} > @@ -95,11 +92,11 @@ export function SavedViewManageViewsFlyout({ ); - const renderDeleteAction = (item: SavedView) => { + const renderDeleteAction = (item: InventoryView) => { return ( { onDeleteView(item.id); }} @@ -107,15 +104,13 @@ export function SavedViewManageViewsFlyout({ ); }; - const renderMakeDefaultAction = (item: SavedView) => { + const renderMakeDefaultAction = (item: InventoryView) => { return ( { - setInProgressView(item.id); onMakeDefaultView(item.id); }} /> @@ -124,7 +119,7 @@ export function SavedViewManageViewsFlyout({ const columns = [ { - field: 'name', + field: 'attributes.name', name: i18n.translate('xpack.infra.openView.columnNames.name', { defaultMessage: 'Name' }), sortable: true, truncateText: true, @@ -139,7 +134,7 @@ export function SavedViewManageViewsFlyout({ render: renderMakeDefaultAction, }, { - available: (item: SavedView) => item.id !== '0', + available: (item: InventoryView) => !item.attributes.isStatic, render: renderDeleteAction, }, ], diff --git a/x-pack/plugins/infra/public/components/saved_views/toolbar_control.tsx b/x-pack/plugins/infra/public/components/saved_views/toolbar_control.tsx index 1610b1b63fd82..75fb125c888c0 100644 --- a/x-pack/plugins/infra/public/components/saved_views/toolbar_control.tsx +++ b/x-pack/plugins/infra/public/components/saved_views/toolbar_control.tsx @@ -5,61 +5,82 @@ * 2.0. */ -import React, { useCallback, useState, useEffect } from 'react'; +import React, { useState } from 'react'; import { i18n } from '@kbn/i18n'; import { EuiButton, EuiPopover, EuiListGroup, EuiListGroupItem } from '@elastic/eui'; -import { useKibana } from '@kbn/kibana-react-plugin/public'; import { FormattedMessage } from '@kbn/i18n-react'; +import { InventoryView } from '../../../common/inventory_views'; import { SavedViewManageViewsFlyout } from './manage_views_flyout'; -import { useSavedViewContext } from '../../containers/saved_view/saved_view'; import { SavedViewListModal } from './view_list_modal'; import { useBoolean } from '../../hooks/use_boolean'; import { UpsertViewModal } from './upsert_modal'; interface Props { - viewState: ViewState; + viewState: ViewState & { time?: number }; + currentView: InventoryView; + views: InventoryView[]; + isFetchingViews: boolean; + isFetchingCurrentView: boolean; + onCreateView: (attributes: any) => Promise; + onUpdateView: ({ id, attributes }: { id: string; attributes: any }) => Promise; + onDeleteView: () => void; + onLoadViews: () => void; + onSetDefaultView: (id: string) => void; + onSwitchView: (id: string) => void; } export function SavedViewsToolbarControls(props: Props) { - const kibana = useKibana(); const { - views, - saveView, - loading, - updateView, - deletedId, - deleteView, - makeDefault, - sourceIsLoading, - find, - errorOnFind, - errorOnCreate, - createdView, - updatedView, currentView, - setCurrentView, - } = useSavedViewContext(); + views, + isFetchingViews, + isFetchingCurrentView, + onCreateView, + onDeleteView, + onUpdateView, + onLoadViews, + onSetDefaultView, + onSwitchView, + viewState, + } = props; + // const { + // views, + // saveView, + // loading, + // updateView, + // deletedId, + // deleteView, + // makeDefault, + // sourceIsLoading, + // find, + // errorOnFind, + // errorOnCreate, + // createdView, + // updatedView, + // currentView, + // setCurrentView, + // } = useSavedViewContext(); const [isPopoverOpen, { off: closePopover, toggle: togglePopover }] = useBoolean(false); const [isManageFlyoutOpen, { on: openManageFlyout, off: closeManageFlyout }] = useBoolean(false); const [isUpdateModalOpen, { on: openUpdateModal, off: closeUpdateModal }] = useBoolean(false); - const [isLoadModalOpen, { on: openLoadModal, off: closeLoadModal }] = useBoolean(false); + // const [isLoadModalOpen, { on: openLoadModal, off: closeLoadModal }] = useBoolean(false); const [isCreateModalOpen, { on: openCreateModal, off: closeCreateModal }] = useBoolean(false); const [isInvalid, setIsInvalid] = useState(false); const goToManageViews = () => { closePopover(); - find(); + onLoadViews(); openManageFlyout(); }; - const goToLoadView = () => { - closePopover(); - find(); - openLoadModal(); - }; + // const goToLoadView = () => { + // closePopover(); + // onLoadViews(); + // openLoadModal(); + // }; const goToCreateView = () => { closePopover(); @@ -73,64 +94,25 @@ export function SavedViewsToolbarControls(props: Props) { openUpdateModal(); }; - const save = useCallback( - (name: string, hasTime: boolean = false) => { - const currentState = { - ...props.viewState, - ...(!hasTime ? { time: undefined } : {}), - }; - saveView({ ...currentState, name }); - }, - [props.viewState, saveView] - ); - - const update = useCallback( - (name: string, hasTime: boolean = false) => { - const currentState = { - ...props.viewState, - ...(!hasTime ? { time: undefined } : {}), - }; - updateView(currentView.id, { ...currentState, name }); - }, - [props.viewState, updateView, currentView] - ); + const handleCreateView = (name: string, shouldIncludeTime: boolean = false) => { + const attributes = { ...viewState, name }; - useEffect(() => { - if (errorOnCreate) { - setIsInvalid(true); + if (!shouldIncludeTime) { + delete attributes.time; } - }, [errorOnCreate]); - useEffect(() => { - if (updatedView !== undefined) { - setCurrentView(updatedView); - // INFO: Close the modal after the view is created. - closeUpdateModal(); - } - }, [updatedView, setCurrentView, closeUpdateModal]); + onCreateView(attributes).then(closeCreateModal); + }; - useEffect(() => { - if (createdView !== undefined) { - // INFO: Close the modal after the view is created. - setCurrentView(createdView); - closeCreateModal(); - } - }, [createdView, setCurrentView, closeCreateModal]); + const handleUpdateView = (name: string, shouldIncludeTime: boolean = false) => { + const attributes = { ...viewState, name }; - useEffect(() => { - if (deletedId !== undefined) { - // INFO: Refresh view list after an item is deleted - find(); + if (!shouldIncludeTime) { + delete attributes.time; } - }, [deletedId, find]); - useEffect(() => { - if (errorOnCreate) { - kibana.notifications.toasts.warning(getErrorToast('create', errorOnCreate)!); - } else if (errorOnFind) { - kibana.notifications.toasts.warning(getErrorToast('find', errorOnFind)!); - } - }, [errorOnCreate, errorOnFind, kibana]); + onUpdateView({ id: currentView.id, attributes }).then(closeUpdateModal); + }; return ( <> @@ -143,9 +125,10 @@ export function SavedViewsToolbarControls(props: Props) { iconType="arrowDown" iconSide="right" color="text" + isLoading={isFetchingCurrentView} > {currentView - ? currentView.name + ? currentView.attributes.name : i18n.translate('xpack.infra.savedView.unknownView', { defaultMessage: 'No view selected', })} @@ -168,19 +151,19 @@ export function SavedViewsToolbarControls(props: Props) { data-test-subj="savedViews-updateView" iconType="refresh" onClick={goToUpdateView} - isDisabled={!currentView || currentView.id === '0'} + isDisabled={!currentView || currentView.attributes.isStatic} label={i18n.translate('xpack.infra.savedView.updateView', { defaultMessage: 'Update view', })} /> - + /> */} (props: Props) { (props: Props) { (props: Props) { } /> )} - {isLoadModalOpen && ( + {/* {isLoadModalOpen && ( currentView={currentView} views={views} onClose={closeLoadModal} - setView={setCurrentView} + onSwitchView={onSwitchView} /> - )} + )} */} {isManageFlyoutOpen && ( - - sourceIsLoading={sourceIsLoading} - loading={loading} + )} ); } - -const getErrorToast = (type: 'create' | 'find', msg?: string) => { - if (type === 'create') { - return { - toastLifeTimeMs: 3000, - title: - msg || - i18n.translate('xpack.infra.savedView.errorOnCreate.title', { - defaultMessage: `An error occured saving view.`, - }), - }; - } else if (type === 'find') { - return { - toastLifeTimeMs: 3000, - title: - msg || - i18n.translate('xpack.infra.savedView.findError.title', { - defaultMessage: `An error occurred while loading views.`, - }), - }; - } -}; diff --git a/x-pack/plugins/infra/public/components/saved_views/upsert_modal.tsx b/x-pack/plugins/infra/public/components/saved_views/upsert_modal.tsx index fa2fc3777ca90..6f451503c9c60 100644 --- a/x-pack/plugins/infra/public/components/saved_views/upsert_modal.tsx +++ b/x-pack/plugins/infra/public/components/saved_views/upsert_modal.tsx @@ -41,7 +41,7 @@ export const UpsertViewModal = ({ title, }: Props) => { const [viewName, setViewName] = useState(initialName); - const [includeTime, setIncludeTime] = useState(initialIncludeTime); + const [shouldIncludeTime, setIncludeTime] = useState(initialIncludeTime); const handleNameChange: React.ChangeEventHandler = (e) => { setViewName(e.target.value); @@ -52,7 +52,7 @@ export const UpsertViewModal = ({ }; const saveView = () => { - onSave(viewName, includeTime); + onSave(viewName, shouldIncludeTime); }; return ( @@ -82,7 +82,7 @@ export const UpsertViewModal = ({ id="xpack.infra.waffle.savedViews.includeTimeFilterLabel" /> } - checked={includeTime} + checked={shouldIncludeTime} onChange={handleTimeCheckChange} /> diff --git a/x-pack/plugins/infra/public/components/saved_views/view_list_modal.tsx b/x-pack/plugins/infra/public/components/saved_views/view_list_modal.tsx index 43ca2776b284d..93a97c60ca6f7 100644 --- a/x-pack/plugins/infra/public/components/saved_views/view_list_modal.tsx +++ b/x-pack/plugins/infra/public/components/saved_views/view_list_modal.tsx @@ -18,14 +18,14 @@ import { SavedView } from '../../containers/saved_view/saved_view'; interface Props { views: Array>; onClose(): void; - setView(viewState: ViewState): void; + onSwitchView(viewState: ViewState): void; currentView?: ViewState; } export function SavedViewListModal({ onClose, views, - setView, + onSwitchView, currentView, }: Props) { const [options, setOptions] = useState(null); @@ -45,9 +45,9 @@ export function SavedViewListModal v.id === selected.key)!); + onSwitchView(views.find((v) => v.id === selected.key)!); onClose(); - }, [options, views, setView, onClose]); + }, [options, views, onSwitchView, onClose]); const defaultOptions = useMemo(() => { return views.map((v) => ({ diff --git a/x-pack/plugins/infra/public/hooks/use_inventory_views.ts b/x-pack/plugins/infra/public/hooks/use_inventory_views.ts index 2237c5a6336bb..27b4c9758e219 100644 --- a/x-pack/plugins/infra/public/hooks/use_inventory_views.ts +++ b/x-pack/plugins/infra/public/hooks/use_inventory_views.ts @@ -110,7 +110,7 @@ export const useInventoryViews = () => { }, }); - const { mutate: createView, isLoading: isCreatingView } = useMutation< + const { mutateAsync: createView, isLoading: isCreatingView } = useMutation< InventoryView, ServerError, CreateInventoryViewAttributesRequestPayload @@ -126,14 +126,14 @@ export const useInventoryViews = () => { }, }); - const { mutate: updateViewById, isLoading: isUpdatingView } = useMutation< + const { mutateAsync: updateViewById, isLoading: isUpdatingView } = useMutation< InventoryView, ServerError, { id: string; attributes: UpdateInventoryViewAttributesRequestPayload } >({ mutationFn: ({ id, attributes }) => inventoryViews.client.updateInventoryView(id, attributes), onError: (error) => { - notify.upsertViewFailure(error.body?.message); + notify.upsertViewFailure(error.body?.message ?? error.message); }, onSuccess: (updatedView) => { queryClient.setQueryData(queryKeys.getById(updatedView.id), updatedView); // Store in cache updated view @@ -194,6 +194,7 @@ export const useInventoryViews = () => { isFetchingCurrentView, isFetchingViews, isUpdatingView, + shouldLoadDefault: true, }; }; diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/layout_view.tsx b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/layout_view.tsx index af9c9ab5e2b30..04f26d730cf88 100644 --- a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/layout_view.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/layout_view.tsx @@ -6,8 +6,8 @@ */ import React from 'react'; +import { useInventoryViews } from '../../../../hooks/use_inventory_views'; import { SnapshotNode } from '../../../../../common/http_api'; -import { useSavedViewContext } from '../../../../containers/saved_view/saved_view'; import { Layout } from './layout'; interface Props { @@ -18,6 +18,6 @@ interface Props { } export const LayoutView = (props: Props) => { - const { shouldLoadDefault, currentView } = useSavedViewContext(); + const { shouldLoadDefault, currentView } = useInventoryViews(); return ; }; diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/saved_views.tsx b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/saved_views.tsx index 484c0a38f4d06..097c039b22383 100644 --- a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/saved_views.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/saved_views.tsx @@ -6,10 +6,38 @@ */ import React from 'react'; +import { useInventoryViews } from '../../../../hooks/use_inventory_views'; import { SavedViewsToolbarControls } from '../../../../components/saved_views/toolbar_control'; import { useWaffleViewState } from '../hooks/use_waffle_view_state'; export const SavedViews = () => { const { viewState } = useWaffleViewState(); - return ; + const { + currentView, + views, + isFetchingViews, + isFetchingCurrentView, + createView, + deleteViewById, + fetchViews, + updateViewById, + switchViewById, + setDefaultViewById, + } = useInventoryViews(); + + return ( + + ); }; diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/hooks/use_waffle_view_state.ts b/x-pack/plugins/infra/public/pages/metrics/inventory_view/hooks/use_waffle_view_state.ts index 02a2144f1282e..c7bbe71c3310c 100644 --- a/x-pack/plugins/infra/public/pages/metrics/inventory_view/hooks/use_waffle_view_state.ts +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/hooks/use_waffle_view_state.ts @@ -65,30 +65,30 @@ export const useWaffleViewState = () => { }; const onViewChange = useCallback( - (newState: WaffleViewState) => { + (newState: { attributes: WaffleViewState }) => { setWaffleOptionsState({ - sort: newState.sort, - metric: newState.metric, - groupBy: newState.groupBy, - nodeType: newState.nodeType, - view: newState.view, - customOptions: newState.customOptions, - customMetrics: newState.customMetrics, - boundsOverride: newState.boundsOverride, - autoBounds: newState.autoBounds, - accountId: newState.accountId, - region: newState.region, - legend: newState.legend, - timelineOpen: newState.timelineOpen, + sort: newState.attributes.sort, + metric: newState.attributes.metric, + groupBy: newState.attributes.groupBy, + nodeType: newState.attributes.nodeType, + view: newState.attributes.view, + customOptions: newState.attributes.customOptions, + customMetrics: newState.attributes.customMetrics, + boundsOverride: newState.attributes.boundsOverride, + autoBounds: newState.attributes.autoBounds, + accountId: newState.attributes.accountId, + region: newState.attributes.region, + legend: newState.attributes.legend, + timelineOpen: newState.attributes.timelineOpen, }); - if (newState.time) { + if (newState.attributes.time) { setWaffleTimeState({ - currentTime: newState.time, - isAutoReloading: newState.autoReload, + currentTime: newState.attributes.time, + isAutoReloading: newState.attributes.autoReload, }); } - setWaffleFiltersState(newState.filterQuery); + setWaffleFiltersState(newState.attributes.filterQuery); }, [setWaffleOptionsState, setWaffleTimeState, setWaffleFiltersState] ); diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/index.tsx b/x-pack/plugins/infra/public/pages/metrics/inventory_view/index.tsx index 922f0cdfaaaa8..58988d0ea25e2 100644 --- a/x-pack/plugins/infra/public/pages/metrics/inventory_view/index.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/index.tsx @@ -16,9 +16,6 @@ import { SourceLoadingPage } from '../../../components/source_loading_page'; import { useSourceContext } from '../../../containers/metrics_source'; import { useMetricsBreadcrumbs } from '../../../hooks/use_metrics_breadcrumbs'; import { LayoutView } from './components/layout_view'; -import { SavedViewProvider } from '../../../containers/saved_view/saved_view'; -import { DEFAULT_WAFFLE_VIEW_STATE } from './hooks/use_waffle_view_state'; -import { useWaffleOptionsContext } from './hooks/use_waffle_options'; import { MetricsPageTemplate } from '../page_template'; import { inventoryTitle } from '../../../translations'; import { SavedViews } from './components/saved_views'; @@ -32,7 +29,6 @@ export const SnapshotPage = () => { useTrackPageview({ app: 'infra_metrics', path: 'inventory' }); useTrackPageview({ app: 'infra_metrics', path: 'inventory', delay: 15000 }); - const { source: optionsSource } = useWaffleOptionsContext(); useMetricsBreadcrumbs([ { @@ -60,36 +56,30 @@ export const SnapshotPage = () => { return (
- - , ], - }} - pageSectionProps={{ - contentProps: { - css: css` + , ], + }} + pageSectionProps={{ + contentProps: { + css: css` ${fullHeightContentStyles}; padding-bottom: 0; `, - }, - }} - > - ( - <> - - - - )} - /> - - + }, + }} + > + ( + <> + + + + )} + /> +
); diff --git a/x-pack/plugins/infra/public/services/inventory_views/inventory_views_client.ts b/x-pack/plugins/infra/public/services/inventory_views/inventory_views_client.ts index a72dd8c7fefbd..eeabd15e60a4b 100644 --- a/x-pack/plugins/infra/public/services/inventory_views/inventory_views_client.ts +++ b/x-pack/plugins/infra/public/services/inventory_views/inventory_views_client.ts @@ -68,7 +68,9 @@ export class InventoryViewsClient implements IInventoryViewsClient { ), }) .catch((error) => { - throw new UpsertInventoryViewError(`Failed to create new inventory view : ${error}`); + throw new UpsertInventoryViewError( + `Failed to create new inventory view: ${error.body?.message ?? error.message}` + ); }); const { data } = decodeOrThrow( @@ -92,7 +94,9 @@ export class InventoryViewsClient implements IInventoryViewsClient { }) .catch((error) => { throw new UpsertInventoryViewError( - `Failed to update inventory view "${inventoryViewId}": ${error}` + `Failed to update inventory view "${inventoryViewId}": ${ + error.body?.message ?? error.message + }` ); }); From c95eed3829819c928d32abb13ba7696239465e92 Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Wed, 26 Apr 2023 13:05:07 +0000 Subject: [PATCH 08/39] [CI] Auto-commit changed files from 'node scripts/precommit_hook.js --ref HEAD~1..HEAD --fix' --- .../public/components/saved_views/manage_views_flyout.tsx | 1 - .../infra/public/components/saved_views/toolbar_control.tsx | 1 - .../infra/public/pages/metrics/inventory_view/index.tsx | 6 +++--- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/x-pack/plugins/infra/public/components/saved_views/manage_views_flyout.tsx b/x-pack/plugins/infra/public/components/saved_views/manage_views_flyout.tsx index f931bd154afe9..24bb1856689cc 100644 --- a/x-pack/plugins/infra/public/components/saved_views/manage_views_flyout.tsx +++ b/x-pack/plugins/infra/public/components/saved_views/manage_views_flyout.tsx @@ -23,7 +23,6 @@ import { import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; import { InventoryView } from '../../../common/inventory_views'; -import { SavedView } from '../../containers/saved_view/saved_view'; interface Props { views: InventoryView[]; diff --git a/x-pack/plugins/infra/public/components/saved_views/toolbar_control.tsx b/x-pack/plugins/infra/public/components/saved_views/toolbar_control.tsx index 75fb125c888c0..e9ef1e0f26c3a 100644 --- a/x-pack/plugins/infra/public/components/saved_views/toolbar_control.tsx +++ b/x-pack/plugins/infra/public/components/saved_views/toolbar_control.tsx @@ -11,7 +11,6 @@ import { EuiButton, EuiPopover, EuiListGroup, EuiListGroupItem } from '@elastic/ import { FormattedMessage } from '@kbn/i18n-react'; import { InventoryView } from '../../../common/inventory_views'; import { SavedViewManageViewsFlyout } from './manage_views_flyout'; -import { SavedViewListModal } from './view_list_modal'; import { useBoolean } from '../../hooks/use_boolean'; import { UpsertViewModal } from './upsert_modal'; diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/index.tsx b/x-pack/plugins/infra/public/pages/metrics/inventory_view/index.tsx index 58988d0ea25e2..6c97172a30f82 100644 --- a/x-pack/plugins/infra/public/pages/metrics/inventory_view/index.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/index.tsx @@ -65,9 +65,9 @@ export const SnapshotPage = () => { pageSectionProps={{ contentProps: { css: css` - ${fullHeightContentStyles}; - padding-bottom: 0; - `, + ${fullHeightContentStyles}; + padding-bottom: 0; + `, }, }} > From f1ff109be8a9b91b7266d27e112812f33e568b60 Mon Sep 17 00:00:00 2001 From: Marco Antonio Ghiani Date: Wed, 26 Apr 2023 18:06:42 +0200 Subject: [PATCH 09/39] fix(infra): update insertion logic for view --- .../inventory_views/create_inventory_view.ts | 6 ++- .../create_metrics_explorer_view.ts | 10 ++++- .../inventory_views_client.mock.ts | 1 - .../inventory_views_client.test.ts | 44 +------------------ .../inventory_views/inventory_views_client.ts | 31 +++++-------- .../server/services/inventory_views/types.ts | 6 +-- .../metrics_explorer_views_client.mock.ts | 1 - .../metrics_explorer_views_client.test.ts | 44 +------------------ .../metrics_explorer_views_client.ts | 34 +++++--------- .../services/metrics_explorer_views/types.ts | 6 +-- 10 files changed, 37 insertions(+), 146 deletions(-) diff --git a/x-pack/plugins/infra/server/routes/inventory_views/create_inventory_view.ts b/x-pack/plugins/infra/server/routes/inventory_views/create_inventory_view.ts index 90bb47d8a2d76..d2df0a0f96f65 100644 --- a/x-pack/plugins/infra/server/routes/inventory_views/create_inventory_view.ts +++ b/x-pack/plugins/infra/server/routes/inventory_views/create_inventory_view.ts @@ -9,6 +9,7 @@ import { isBoom } from '@hapi/boom'; import { createValidationFunction } from '../../../common/runtime_types'; import { createInventoryViewRequestPayloadRT, + inventoryViewRequestQueryRT, inventoryViewResponsePayloadRT, INVENTORY_VIEW_URL, } from '../../../common/http_api/latest'; @@ -24,15 +25,16 @@ export const initCreateInventoryViewRoute = ({ path: INVENTORY_VIEW_URL, validate: { body: createValidationFunction(createInventoryViewRequestPayloadRT), + query: createValidationFunction(inventoryViewRequestQueryRT), }, }, async (_requestContext, request, response) => { - const { body } = request; + const { body, query } = request; const [, , { inventoryViews }] = await getStartServices(); const inventoryViewsClient = inventoryViews.getScopedClient(request); try { - const inventoryView = await inventoryViewsClient.create(body.attributes); + const inventoryView = await inventoryViewsClient.update(null, body.attributes, query); return response.custom({ statusCode: 201, diff --git a/x-pack/plugins/infra/server/routes/metrics_explorer_views/create_metrics_explorer_view.ts b/x-pack/plugins/infra/server/routes/metrics_explorer_views/create_metrics_explorer_view.ts index 948dd757e7e01..d02ed1208eb11 100644 --- a/x-pack/plugins/infra/server/routes/metrics_explorer_views/create_metrics_explorer_view.ts +++ b/x-pack/plugins/infra/server/routes/metrics_explorer_views/create_metrics_explorer_view.ts @@ -9,6 +9,7 @@ import { isBoom } from '@hapi/boom'; import { createValidationFunction } from '../../../common/runtime_types'; import { createMetricsExplorerViewRequestPayloadRT, + metricsExplorerViewRequestQueryRT, metricsExplorerViewResponsePayloadRT, METRICS_EXPLORER_VIEW_URL, } from '../../../common/http_api/latest'; @@ -24,15 +25,20 @@ export const initCreateMetricsExplorerViewRoute = ({ path: METRICS_EXPLORER_VIEW_URL, validate: { body: createValidationFunction(createMetricsExplorerViewRequestPayloadRT), + query: createValidationFunction(metricsExplorerViewRequestQueryRT), }, }, async (_requestContext, request, response) => { - const { body } = request; + const { body, query } = request; const [, , { metricsExplorerViews }] = await getStartServices(); const metricsExplorerViewsClient = metricsExplorerViews.getScopedClient(request); try { - const metricsExplorerView = await metricsExplorerViewsClient.create(body.attributes); + const metricsExplorerView = await metricsExplorerViewsClient.update( + null, + body.attributes, + query + ); return response.custom({ statusCode: 201, diff --git a/x-pack/plugins/infra/server/services/inventory_views/inventory_views_client.mock.ts b/x-pack/plugins/infra/server/services/inventory_views/inventory_views_client.mock.ts index 9d832f8502104..5b21a4e43d267 100644 --- a/x-pack/plugins/infra/server/services/inventory_views/inventory_views_client.mock.ts +++ b/x-pack/plugins/infra/server/services/inventory_views/inventory_views_client.mock.ts @@ -11,6 +11,5 @@ export const createInventoryViewsClientMock = (): jest.Mocked { const mockFindInventoryList = (savedObjectsClient: jest.Mocked) => { @@ -115,45 +112,6 @@ describe('InventoryViewsClient class', () => { expect(inventoryView).toEqual(inventoryViewMock); }); - describe('.create', () => { - it('generate a new inventory view', async () => { - const { inventoryViewsClient, savedObjectsClient } = createInventoryViewsClient(); - - const inventoryViewMock = createInventoryViewMock('new_id', { - name: 'New view', - isStatic: false, - } as InventoryViewAttributes); - - mockFindInventoryList(savedObjectsClient); - - savedObjectsClient.create.mockResolvedValue({ - ...inventoryViewMock, - type: inventoryViewSavedObjectName, - references: [], - }); - - const inventoryView = await inventoryViewsClient.create({ - name: 'New view', - } as CreateInventoryViewAttributesRequestPayload); - - expect(savedObjectsClient.create).toHaveBeenCalled(); - expect(inventoryView).toEqual(inventoryViewMock); - }); - - it('throws an error when a conflicting name is given', async () => { - const { inventoryViewsClient, savedObjectsClient } = createInventoryViewsClient(); - - mockFindInventoryList(savedObjectsClient); - - await expect( - async () => - await inventoryViewsClient.create({ - name: 'Custom', - } as CreateInventoryViewAttributesRequestPayload) - ).rejects.toThrow('A view with that name already exists.'); - }); - }); - describe('.update', () => { it('update an existing inventory view by id', async () => { const { inventoryViewsClient, infraSources, savedObjectsClient } = diff --git a/x-pack/plugins/infra/server/services/inventory_views/inventory_views_client.ts b/x-pack/plugins/infra/server/services/inventory_views/inventory_views_client.ts index c32da344354b6..5efce009da410 100644 --- a/x-pack/plugins/infra/server/services/inventory_views/inventory_views_client.ts +++ b/x-pack/plugins/infra/server/services/inventory_views/inventory_views_client.ts @@ -5,11 +5,12 @@ * 2.0. */ -import type { +import { Logger, SavedObject, SavedObjectsClientContract, SavedObjectsUpdateResponse, + SavedObjectsUtils, } from '@kbn/core/server'; import Boom from '@hapi/boom'; import { @@ -50,6 +51,7 @@ export class InventoryViewsClient implements IInventoryViewsClient { const defaultView = InventoryViewsClient.createStaticView( sourceConfiguration.configuration.inventoryDefaultView ); + const views = inventoryViewSavedObject.saved_objects.map((savedObject) => this.mapSavedObjectToInventoryView( savedObject, @@ -95,37 +97,26 @@ export class InventoryViewsClient implements IInventoryViewsClient { ); } - public async create( - attributes: CreateInventoryViewAttributesRequestPayload - ): Promise { - this.logger.debug(`Trying to create inventory view ...`); - - // Validate there is not a view with the same name - await this.assertNameConflict(attributes.name); - - const inventoryViewSavedObject = await this.savedObjectsClient.create( - inventoryViewSavedObjectName, - attributes - ); - - return this.mapSavedObjectToInventoryView(inventoryViewSavedObject); - } - public async update( - inventoryViewId: string, + inventoryViewId: string | null, attributes: CreateInventoryViewAttributesRequestPayload, query: InventoryViewRequestQuery ): Promise { this.logger.debug(`Trying to update inventory view with id "${inventoryViewId}"...`); + const viewId = inventoryViewId ?? SavedObjectsUtils.generateId(); + // Validate there is not a view with the same name - await this.assertNameConflict(attributes.name, [inventoryViewId]); + await this.assertNameConflict(attributes.name, [viewId]); const sourceId = query.sourceId ?? InventoryViewsClient.DEFAULT_SOURCE_ID; const [sourceConfiguration, inventoryViewSavedObject] = await Promise.all([ this.infraSources.getSourceConfiguration(this.savedObjectsClient, sourceId), - this.savedObjectsClient.update(inventoryViewSavedObjectName, inventoryViewId, attributes), + this.savedObjectsClient.create(inventoryViewSavedObjectName, attributes, { + id: viewId, + overwrite: true, + }), ]); return this.mapSavedObjectToInventoryView( diff --git a/x-pack/plugins/infra/server/services/inventory_views/types.ts b/x-pack/plugins/infra/server/services/inventory_views/types.ts index 3e023b77af6c2..2537203d1754c 100644 --- a/x-pack/plugins/infra/server/services/inventory_views/types.ts +++ b/x-pack/plugins/infra/server/services/inventory_views/types.ts @@ -11,7 +11,6 @@ import type { SavedObjectsServiceStart, } from '@kbn/core/server'; import type { - CreateInventoryViewAttributesRequestPayload, InventoryViewRequestQuery, UpdateInventoryViewAttributesRequestPayload, } from '../../../common/http_api/latest'; @@ -34,11 +33,8 @@ export interface IInventoryViewsClient { delete(inventoryViewId: string): Promise<{}>; find(query: InventoryViewRequestQuery): Promise; get(inventoryViewId: string, query: InventoryViewRequestQuery): Promise; - create( - inventoryViewAttributes: CreateInventoryViewAttributesRequestPayload - ): Promise; update( - inventoryViewId: string, + inventoryViewId: string | null, inventoryViewAttributes: UpdateInventoryViewAttributesRequestPayload, query: InventoryViewRequestQuery ): Promise; diff --git a/x-pack/plugins/infra/server/services/metrics_explorer_views/metrics_explorer_views_client.mock.ts b/x-pack/plugins/infra/server/services/metrics_explorer_views/metrics_explorer_views_client.mock.ts index 82a8cba3f6427..a19b8847adee1 100644 --- a/x-pack/plugins/infra/server/services/metrics_explorer_views/metrics_explorer_views_client.mock.ts +++ b/x-pack/plugins/infra/server/services/metrics_explorer_views/metrics_explorer_views_client.mock.ts @@ -12,6 +12,5 @@ export const createMetricsExplorerViewsClientMock = delete: jest.fn(), find: jest.fn(), get: jest.fn(), - create: jest.fn(), update: jest.fn(), }); diff --git a/x-pack/plugins/infra/server/services/metrics_explorer_views/metrics_explorer_views_client.test.ts b/x-pack/plugins/infra/server/services/metrics_explorer_views/metrics_explorer_views_client.test.ts index c903e9af360f8..c05977bf9dfd5 100644 --- a/x-pack/plugins/infra/server/services/metrics_explorer_views/metrics_explorer_views_client.test.ts +++ b/x-pack/plugins/infra/server/services/metrics_explorer_views/metrics_explorer_views_client.test.ts @@ -15,10 +15,7 @@ import { createInfraSourcesMock } from '../../lib/sources/mocks'; import { metricsExplorerViewSavedObjectName } from '../../saved_objects/metrics_explorer_view'; import { MetricsExplorerViewsClient } from './metrics_explorer_views_client'; import { createMetricsExplorerViewMock } from '../../../common/metrics_explorer_views/metrics_explorer_view.mock'; -import { - CreateMetricsExplorerViewAttributesRequestPayload, - UpdateMetricsExplorerViewAttributesRequestPayload, -} from '../../../common/http_api/latest'; +import { UpdateMetricsExplorerViewAttributesRequestPayload } from '../../../common/http_api/latest'; describe('MetricsExplorerViewsClient class', () => { const mockFindMetricsExplorerList = ( @@ -118,45 +115,6 @@ describe('MetricsExplorerViewsClient class', () => { expect(metricsExplorerView).toEqual(metricsExplorerViewMock); }); - describe('.create', () => { - it('generate a new metrics explorer view', async () => { - const { metricsExplorerViewsClient, savedObjectsClient } = createMetricsExplorerViewsClient(); - - const metricsExplorerViewMock = createMetricsExplorerViewMock('new_id', { - name: 'New view', - isStatic: false, - } as MetricsExplorerViewAttributes); - - mockFindMetricsExplorerList(savedObjectsClient); - - savedObjectsClient.create.mockResolvedValue({ - ...metricsExplorerViewMock, - type: metricsExplorerViewSavedObjectName, - references: [], - }); - - const metricsExplorerView = await metricsExplorerViewsClient.create({ - name: 'New view', - } as CreateMetricsExplorerViewAttributesRequestPayload); - - expect(savedObjectsClient.create).toHaveBeenCalled(); - expect(metricsExplorerView).toEqual(metricsExplorerViewMock); - }); - - it('throws an error when a conflicting name is given', async () => { - const { metricsExplorerViewsClient, savedObjectsClient } = createMetricsExplorerViewsClient(); - - mockFindMetricsExplorerList(savedObjectsClient); - - await expect( - async () => - await metricsExplorerViewsClient.create({ - name: 'Custom', - } as CreateMetricsExplorerViewAttributesRequestPayload) - ).rejects.toThrow('A view with that name already exists.'); - }); - }); - describe('.update', () => { it('update an existing metrics explorer view by id', async () => { const { metricsExplorerViewsClient, infraSources, savedObjectsClient } = diff --git a/x-pack/plugins/infra/server/services/metrics_explorer_views/metrics_explorer_views_client.ts b/x-pack/plugins/infra/server/services/metrics_explorer_views/metrics_explorer_views_client.ts index 1ba34456d88a8..e2dd15940bb19 100644 --- a/x-pack/plugins/infra/server/services/metrics_explorer_views/metrics_explorer_views_client.ts +++ b/x-pack/plugins/infra/server/services/metrics_explorer_views/metrics_explorer_views_client.ts @@ -5,11 +5,12 @@ * 2.0. */ -import type { +import { Logger, SavedObject, SavedObjectsClientContract, SavedObjectsUpdateResponse, + SavedObjectsUtils, } from '@kbn/core/server'; import Boom from '@hapi/boom'; import { @@ -98,24 +99,8 @@ export class MetricsExplorerViewsClient implements IMetricsExplorerViewsClient { ); } - public async create( - attributes: CreateMetricsExplorerViewAttributesRequestPayload - ): Promise { - this.logger.debug(`Trying to create metrics explorer view ...`); - - // Validate there is not a view with the same name - await this.assertNameConflict(attributes.name); - - const metricsExplorerViewSavedObject = await this.savedObjectsClient.create( - metricsExplorerViewSavedObjectName, - attributes - ); - - return this.mapSavedObjectToMetricsExplorerView(metricsExplorerViewSavedObject); - } - public async update( - metricsExplorerViewId: string, + metricsExplorerViewId: string | null, attributes: CreateMetricsExplorerViewAttributesRequestPayload, query: MetricsExplorerViewRequestQuery ): Promise { @@ -123,18 +108,19 @@ export class MetricsExplorerViewsClient implements IMetricsExplorerViewsClient { `Trying to update metrics explorer view with id "${metricsExplorerViewId}"...` ); + const viewId = metricsExplorerViewId ?? SavedObjectsUtils.generateId(); + // Validate there is not a view with the same name - await this.assertNameConflict(attributes.name, [metricsExplorerViewId]); + await this.assertNameConflict(attributes.name, [viewId]); const sourceId = query.sourceId ?? MetricsExplorerViewsClient.DEFAULT_SOURCE_ID; const [sourceConfiguration, metricsExplorerViewSavedObject] = await Promise.all([ this.infraSources.getSourceConfiguration(this.savedObjectsClient, sourceId), - this.savedObjectsClient.update( - metricsExplorerViewSavedObjectName, - metricsExplorerViewId, - attributes - ), + this.savedObjectsClient.create(metricsExplorerViewSavedObjectName, attributes, { + id: viewId, + overwrite: true, + }), ]); return this.mapSavedObjectToMetricsExplorerView( diff --git a/x-pack/plugins/infra/server/services/metrics_explorer_views/types.ts b/x-pack/plugins/infra/server/services/metrics_explorer_views/types.ts index 0e64aaa83d27e..851cdf3ad77f0 100644 --- a/x-pack/plugins/infra/server/services/metrics_explorer_views/types.ts +++ b/x-pack/plugins/infra/server/services/metrics_explorer_views/types.ts @@ -11,7 +11,6 @@ import type { SavedObjectsServiceStart, } from '@kbn/core/server'; import type { - CreateMetricsExplorerViewAttributesRequestPayload, MetricsExplorerViewRequestQuery, UpdateMetricsExplorerViewAttributesRequestPayload, } from '../../../common/http_api/latest'; @@ -37,11 +36,8 @@ export interface IMetricsExplorerViewsClient { metricsExplorerViewId: string, query: MetricsExplorerViewRequestQuery ): Promise; - create( - metricsExplorerViewAttributes: CreateMetricsExplorerViewAttributesRequestPayload - ): Promise; update( - metricsExplorerViewId: string, + metricsExplorerViewId: string | null, metricsExplorerViewAttributes: UpdateMetricsExplorerViewAttributesRequestPayload, query: MetricsExplorerViewRequestQuery ): Promise; From 8d150fa43613f59d5d065d101e79020cbb503822 Mon Sep 17 00:00:00 2001 From: Marco Antonio Ghiani Date: Wed, 26 Apr 2023 18:09:01 +0200 Subject: [PATCH 10/39] fix(infra): wait to update source --- x-pack/plugins/infra/public/hooks/use_inventory_views.ts | 5 +++-- .../pages/metrics/inventory_view/components/layout.tsx | 3 +-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/x-pack/plugins/infra/public/hooks/use_inventory_views.ts b/x-pack/plugins/infra/public/hooks/use_inventory_views.ts index 27b4c9758e219..53a215362cc8d 100644 --- a/x-pack/plugins/infra/public/hooks/use_inventory_views.ts +++ b/x-pack/plugins/infra/public/hooks/use_inventory_views.ts @@ -22,6 +22,7 @@ import { useKibanaContextForPlugin } from './use_kibana'; import { useUrlState } from '../utils/use_url_state'; import { useSavedViewsNotifier } from './use_saved_views_notifier'; import { useSourceContext } from '../containers/metrics_source'; +import { MetricsSourceConfigurationResponse } from '@kbn/infra-plugin/common/metrics_sources'; type ServerError = IHttpFetchError; interface MutationContext { @@ -77,8 +78,8 @@ export const useInventoryViews = () => { placeholderData: null, }); - const { mutate: setDefaultViewById } = useMutation({ - mutationFn: async (id) => void updateSourceConfiguration({ inventoryDefaultView: id }), + const { mutate: setDefaultViewById } = useMutation({ + mutationFn: (id) => updateSourceConfiguration({ inventoryDefaultView: id }), /** * To provide a quick feedback, we perform an optimistic update on the list * when updating the default view. diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/layout.tsx b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/layout.tsx index 373a6a563fee9..e4fc1ae973808 100644 --- a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/layout.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/layout.tsx @@ -126,8 +126,7 @@ export const Layout = React.memo( * TODO: Should refactor this in the future to make it more clear where all the view state is coming * from and it's precedence [query params, localStorage, defaultView, out of the box view] */ - /* eslint-disable-next-line react-hooks/exhaustive-deps */ - }, [reload, shouldLoadDefault]); + }, [currentView, reload, shouldLoadDefault]); useEffect(() => { setShowLoading(true); From b3265ca1f5eb0b73c54dadaa22cd04510025d673 Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Wed, 26 Apr 2023 16:14:06 +0000 Subject: [PATCH 11/39] [CI] Auto-commit changed files from 'node scripts/lint_ts_projects --fix' --- x-pack/plugins/infra/tsconfig.json | 1 + 1 file changed, 1 insertion(+) diff --git a/x-pack/plugins/infra/tsconfig.json b/x-pack/plugins/infra/tsconfig.json index 76cbce409ce2f..d2fb4c24d2e19 100644 --- a/x-pack/plugins/infra/tsconfig.json +++ b/x-pack/plugins/infra/tsconfig.json @@ -63,6 +63,7 @@ "@kbn/observability-alert-details", "@kbn/observability-shared-plugin", "@kbn/ui-theme", + "@kbn/infra-plugin", ], "exclude": ["target/**/*"] } From d597acf7f898fe263159e17d9de08b2fd42e3ab7 Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Wed, 26 Apr 2023 16:51:43 +0000 Subject: [PATCH 12/39] [CI] Auto-commit changed files from 'node scripts/eslint --no-cache --fix' --- x-pack/plugins/infra/public/hooks/use_inventory_views.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/infra/public/hooks/use_inventory_views.ts b/x-pack/plugins/infra/public/hooks/use_inventory_views.ts index 53a215362cc8d..7324e0ce3e933 100644 --- a/x-pack/plugins/infra/public/hooks/use_inventory_views.ts +++ b/x-pack/plugins/infra/public/hooks/use_inventory_views.ts @@ -13,6 +13,7 @@ import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { useUiTracker } from '@kbn/observability-plugin/public'; import { IHttpFetchError, ResponseErrorBody } from '@kbn/core-http-browser'; +import { MetricsSourceConfigurationResponse } from '../../common/metrics_sources'; import { CreateInventoryViewAttributesRequestPayload, UpdateInventoryViewAttributesRequestPayload, @@ -22,7 +23,6 @@ import { useKibanaContextForPlugin } from './use_kibana'; import { useUrlState } from '../utils/use_url_state'; import { useSavedViewsNotifier } from './use_saved_views_notifier'; import { useSourceContext } from '../containers/metrics_source'; -import { MetricsSourceConfigurationResponse } from '@kbn/infra-plugin/common/metrics_sources'; type ServerError = IHttpFetchError; interface MutationContext { @@ -78,7 +78,12 @@ export const useInventoryViews = () => { placeholderData: null, }); - const { mutate: setDefaultViewById } = useMutation({ + const { mutate: setDefaultViewById } = useMutation< + MetricsSourceConfigurationResponse, + ServerError, + string, + MutationContext + >({ mutationFn: (id) => updateSourceConfiguration({ inventoryDefaultView: id }), /** * To provide a quick feedback, we perform an optimistic update on the list From 5de74e8dab5cd93bb8e611f1ddaad7ac95a0a0ec Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Wed, 26 Apr 2023 16:57:59 +0000 Subject: [PATCH 13/39] [CI] Auto-commit changed files from 'node scripts/lint_ts_projects --fix' --- x-pack/plugins/infra/tsconfig.json | 1 - 1 file changed, 1 deletion(-) diff --git a/x-pack/plugins/infra/tsconfig.json b/x-pack/plugins/infra/tsconfig.json index d2fb4c24d2e19..76cbce409ce2f 100644 --- a/x-pack/plugins/infra/tsconfig.json +++ b/x-pack/plugins/infra/tsconfig.json @@ -63,7 +63,6 @@ "@kbn/observability-alert-details", "@kbn/observability-shared-plugin", "@kbn/ui-theme", - "@kbn/infra-plugin", ], "exclude": ["target/**/*"] } From ac2095909e7ed9d2937bfe790f4b5e29b06541d2 Mon Sep 17 00:00:00 2001 From: Marco Antonio Ghiani Date: Thu, 27 Apr 2023 11:30:33 +0200 Subject: [PATCH 14/39] feat(infra): update typing Inventory Views --- .../saved_views/manage_views_flyout.tsx | 15 +- .../saved_views/toolbar_control.tsx | 90 ++--- .../components/saved_views/upsert_modal.tsx | 27 +- .../saved_views/view_list_modal.tsx | 103 ------ .../containers/react_query_provider.tsx | 3 +- .../infra/public/hooks/use_inventory_views.ts | 64 +++- .../public/hooks/use_saved_views_notifier.ts | 14 +- .../inventory_view/components/layout.tsx | 335 +++++++++--------- .../inventory_view/components/layout_view.tsx | 4 +- .../inventory_view/components/saved_views.tsx | 8 +- 10 files changed, 275 insertions(+), 388 deletions(-) delete mode 100644 x-pack/plugins/infra/public/components/saved_views/view_list_modal.tsx diff --git a/x-pack/plugins/infra/public/components/saved_views/manage_views_flyout.tsx b/x-pack/plugins/infra/public/components/saved_views/manage_views_flyout.tsx index 24bb1856689cc..ee55669dfff8c 100644 --- a/x-pack/plugins/infra/public/components/saved_views/manage_views_flyout.tsx +++ b/x-pack/plugins/infra/public/components/saved_views/manage_views_flyout.tsx @@ -23,14 +23,15 @@ import { import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; import { InventoryView } from '../../../common/inventory_views'; +import { UseInventoryViewsResult } from '../../hooks/use_inventory_views'; -interface Props { - views: InventoryView[]; +interface Props { + views: UseViewResult['views']; loading: boolean; onClose(): void; - onMakeDefaultView(id: string): void; - onSwitchView(id: string): void; - onDeleteView(id: string): void; + onMakeDefaultView: UseViewResult['setDefaultViewById']; + onSwitchView: UseViewResult['switchViewById']; + onDeleteView: UseViewResult['deleteViewById']; } interface DeleteConfimationProps { @@ -70,14 +71,14 @@ const DeleteConfimation = ({ isDisabled, onConfirm }: DeleteConfimationProps) => ); }; -export function SavedViewManageViewsFlyout({ +export function SavedViewManageViewsFlyout({ onClose, views, onSwitchView, onMakeDefaultView, onDeleteView, loading, -}: Props) { +}: Props) { const renderName = (name: string, item: InventoryView) => ( { +interface Props { viewState: ViewState & { time?: number }; - currentView: InventoryView; - views: InventoryView[]; - isFetchingViews: boolean; - isFetchingCurrentView: boolean; - onCreateView: (attributes: any) => Promise; - onUpdateView: ({ id, attributes }: { id: string; attributes: any }) => Promise; - onDeleteView: () => void; - onLoadViews: () => void; - onSetDefaultView: (id: string) => void; - onSwitchView: (id: string) => void; + currentView: UseViewResult['currentView']; + views: UseViewResult['views']; + isFetchingViews: UseViewResult['isFetchingViews']; + isFetchingCurrentView: UseViewResult['isFetchingCurrentView']; + isCreatingView: UseViewResult['isCreatingView']; + isUpdatingView: UseViewResult['isUpdatingView']; + onCreateView: UseViewResult['createView']; + onDeleteView: UseViewResult['deleteViewById']; + onUpdateView: UseViewResult['updateViewById']; + onLoadViews: UseViewResult['fetchViews']; + onSetDefaultView: UseViewResult['setDefaultViewById']; + onSwitchView: UseViewResult['switchViewById']; } -export function SavedViewsToolbarControls(props: Props) { +export function SavedViewsToolbarControls( + props: Props +) { const { currentView, views, isFetchingViews, isFetchingCurrentView, + isCreatingView, + isUpdatingView, onCreateView, onDeleteView, onUpdateView, @@ -42,32 +50,12 @@ export function SavedViewsToolbarControls(props: Props) { onSwitchView, viewState, } = props; - // const { - // views, - // saveView, - // loading, - // updateView, - // deletedId, - // deleteView, - // makeDefault, - // sourceIsLoading, - // find, - // errorOnFind, - // errorOnCreate, - // createdView, - // updatedView, - // currentView, - // setCurrentView, - // } = useSavedViewContext(); const [isPopoverOpen, { off: closePopover, toggle: togglePopover }] = useBoolean(false); const [isManageFlyoutOpen, { on: openManageFlyout, off: closeManageFlyout }] = useBoolean(false); - const [isUpdateModalOpen, { on: openUpdateModal, off: closeUpdateModal }] = useBoolean(false); - // const [isLoadModalOpen, { on: openLoadModal, off: closeLoadModal }] = useBoolean(false); const [isCreateModalOpen, { on: openCreateModal, off: closeCreateModal }] = useBoolean(false); - - const [isInvalid, setIsInvalid] = useState(false); + const [isUpdateModalOpen, { on: openUpdateModal, off: closeUpdateModal }] = useBoolean(false); const goToManageViews = () => { closePopover(); @@ -75,25 +63,17 @@ export function SavedViewsToolbarControls(props: Props) { openManageFlyout(); }; - // const goToLoadView = () => { - // closePopover(); - // onLoadViews(); - // openLoadModal(); - // }; - const goToCreateView = () => { closePopover(); - setIsInvalid(false); openCreateModal(); }; const goToUpdateView = () => { closePopover(); - setIsInvalid(false); openUpdateModal(); }; - const handleCreateView = (name: string, shouldIncludeTime: boolean = false) => { + const handleCreateView = (name: NonEmptyString, shouldIncludeTime: boolean = false) => { const attributes = { ...viewState, name }; if (!shouldIncludeTime) { @@ -103,7 +83,9 @@ export function SavedViewsToolbarControls(props: Props) { onCreateView(attributes).then(closeCreateModal); }; - const handleUpdateView = (name: string, shouldIncludeTime: boolean = false) => { + const handleUpdateView = (name: NonEmptyString, shouldIncludeTime: boolean = false) => { + if (!currentView) return; + const attributes = { ...viewState, name }; if (!shouldIncludeTime) { @@ -155,14 +137,6 @@ export function SavedViewsToolbarControls(props: Props) { defaultMessage: 'Update view', })} /> - {/* */} (props: Props) { {isCreateModalOpen && ( (props: Props) { )} {isUpdateModalOpen && ( (props: Props) { } /> )} - {/* {isLoadModalOpen && ( - - currentView={currentView} - views={views} - onClose={closeLoadModal} - onSwitchView={onSwitchView} - /> - )} */} {isManageFlyoutOpen && ( = (e) => { setViewName(e.target.value); }; @@ -52,7 +59,7 @@ export const UpsertViewModal = ({ }; const saveView = () => { - onSave(viewName, shouldIncludeTime); + onSave(trimmedName, shouldIncludeTime); }; return ( @@ -62,16 +69,11 @@ export const UpsertViewModal = ({ diff --git a/x-pack/plugins/infra/public/components/saved_views/view_list_modal.tsx b/x-pack/plugins/infra/public/components/saved_views/view_list_modal.tsx deleted file mode 100644 index 93a97c60ca6f7..0000000000000 --- a/x-pack/plugins/infra/public/components/saved_views/view_list_modal.tsx +++ /dev/null @@ -1,103 +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, { useCallback, useState, useMemo } from 'react'; - -import { EuiButtonEmpty, EuiModalFooter, EuiButton, EuiSpacer } from '@elastic/eui'; -import { FormattedMessage } from '@kbn/i18n-react'; -import { EuiModal, EuiModalHeader, EuiModalHeaderTitle, EuiModalBody } from '@elastic/eui'; -import { EuiSelectable } from '@elastic/eui'; -import { EuiSelectableOption } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; -import { SavedView } from '../../containers/saved_view/saved_view'; - -interface Props { - views: Array>; - onClose(): void; - onSwitchView(viewState: ViewState): void; - currentView?: ViewState; -} - -export function SavedViewListModal({ - onClose, - views, - onSwitchView, - currentView, -}: Props) { - const [options, setOptions] = useState(null); - - const onChange = useCallback((opts: EuiSelectableOption[]) => { - setOptions(opts); - }, []); - - const loadView = useCallback(() => { - if (!options) { - onClose(); - return; - } - - const selected = options.find((o) => o.checked); - if (!selected) { - onClose(); - return; - } - onSwitchView(views.find((v) => v.id === selected.key)!); - onClose(); - }, [options, views, onSwitchView, onClose]); - - const defaultOptions = useMemo(() => { - return views.map((v) => ({ - label: v.name, - key: v.id, - checked: currentView?.id === v.id ? 'on' : undefined, - })); - }, [views, currentView]); - - return ( - - - - - - - - - {(list, search) => ( - <> - {search} - - {list} - - )} - - - - - - - - - - - - ); -} diff --git a/x-pack/plugins/infra/public/containers/react_query_provider.tsx b/x-pack/plugins/infra/public/containers/react_query_provider.tsx index 25c7d44041467..050575dcc054a 100644 --- a/x-pack/plugins/infra/public/containers/react_query_provider.tsx +++ b/x-pack/plugins/infra/public/containers/react_query_provider.tsx @@ -13,7 +13,7 @@ import { ReactQueryDevtools } from '@tanstack/react-query-devtools'; const DEFAULT_CONFIG = { defaultOptions: { - queries: { keepPreviousData: true }, + queries: { keepPreviousData: true, refetchOnWindowFocus: false }, }, }; @@ -43,6 +43,7 @@ function HideableReactQueryDevTools() { color="primary" style={{ zIndex: 99999, position: 'fixed', bottom: '40px', left: '40px' }} onClick={() => setIsHidden(!isHidden)} + aria-label="Disable React Query Dev Tools" /> diff --git a/x-pack/plugins/infra/public/hooks/use_inventory_views.ts b/x-pack/plugins/infra/public/hooks/use_inventory_views.ts index 7324e0ce3e933..75149c41d4bc6 100644 --- a/x-pack/plugins/infra/public/hooks/use_inventory_views.ts +++ b/x-pack/plugins/infra/public/hooks/use_inventory_views.ts @@ -9,7 +9,14 @@ import { pipe } from 'fp-ts/lib/pipeable'; import { fold } from 'fp-ts/lib/Either'; import { constant, identity } from 'fp-ts/lib/function'; -import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; +import { + QueryObserverBaseResult, + UseMutateAsyncFunction, + UseMutateFunction, + useMutation, + useQuery, + useQueryClient, +} from '@tanstack/react-query'; import { useUiTracker } from '@kbn/observability-plugin/public'; import { IHttpFetchError, ResponseErrorBody } from '@kbn/core-http-browser'; @@ -24,7 +31,37 @@ import { useUrlState } from '../utils/use_url_state'; import { useSavedViewsNotifier } from './use_saved_views_notifier'; import { useSourceContext } from '../containers/metrics_source'; +interface UpdateViewParams { + id: string; + attributes: UpdateInventoryViewAttributesRequestPayload; +} + +export interface UseInventoryViewsResult { + views?: InventoryView[]; + currentView?: InventoryView | null; + createView: UseMutateAsyncFunction< + InventoryView, + ServerError, + CreateInventoryViewAttributesRequestPayload + >; + deleteViewById: UseMutateFunction; + fetchViews: QueryObserverBaseResult['refetch']; + updateViewById: UseMutateAsyncFunction; + switchViewById: (id: InventoryViewId) => void; + setDefaultViewById: UseMutateFunction< + MetricsSourceConfigurationResponse, + ServerError, + string, + MutationContext + >; + isCreatingView: boolean; + isFetchingCurrentView: boolean; + isFetchingViews: boolean; + isUpdatingView: boolean; +} + type ServerError = IHttpFetchError; + interface MutationContext { id?: string; previousViews?: InventoryView[]; @@ -36,7 +73,7 @@ const queryKeys = { getById: (id: string) => ['inventory-views-get', id] as const, }; -export const useInventoryViews = () => { +export const useInventoryViews = (): UseInventoryViewsResult => { const { inventoryViews } = useKibanaContextForPlugin().services; const trackMetric = useUiTracker({ app: 'infra_metrics' }); @@ -100,19 +137,12 @@ export const useInventoryViews = () => { queryClient.setQueryData(queryKeys.find, updatedViews); return { previousViews }; // 4 }, - // If the mutation fails, use the context returned from onMutate to roll back - onError: (error: ServerError, _id, context) => { - notify.setDefaultViewFailure(error.body?.message ?? error.message); - if (context?.previousViews) { - queryClient.setQueryData(queryKeys.find, context.previousViews); + // If the mutation fails but doesn't retrigger the error, use the context returned from onMutate to roll back + onSuccess: (data, _id, context) => { + if (!data && context?.previousViews) { + return queryClient.setQueryData(queryKeys.find, context.previousViews); } - }, - // Always refetch after error or success: - onSuccess: () => { - queryClient.invalidateQueries({ queryKey: queryKeys.get }); // Invalidate all single views cache entries - }, - onSettled: () => { - fetchViews(); // Always invalidate views list cache and refetch views on success/error + return queryClient.invalidateQueries({ queryKey: queryKeys.get }); }, }); @@ -128,14 +158,13 @@ export const useInventoryViews = () => { onSuccess: (createdView) => { queryClient.setQueryData(queryKeys.getById(createdView.id), createdView); // Store in cache created view switchViewById(createdView.id); // Update current view and url state - fetchViews(); // Invalidate views list cache and refetch views }, }); const { mutateAsync: updateViewById, isLoading: isUpdatingView } = useMutation< InventoryView, ServerError, - { id: string; attributes: UpdateInventoryViewAttributesRequestPayload } + UpdateViewParams >({ mutationFn: ({ id, attributes }) => inventoryViews.client.updateInventoryView(id, attributes), onError: (error) => { @@ -143,7 +172,6 @@ export const useInventoryViews = () => { }, onSuccess: (updatedView) => { queryClient.setQueryData(queryKeys.getById(updatedView.id), updatedView); // Store in cache updated view - fetchViews(); // Invalidate views list cache and refetch views }, }); @@ -169,6 +197,7 @@ export const useInventoryViews = () => { }, // If the mutation fails, use the context returned from onMutate to roll back onError: (error, _id, context) => { + notify.deleteViewFailure(error.body?.message ?? error.message); if (context?.previousViews) { queryClient.setQueryData(queryKeys.find, context.previousViews); } @@ -200,7 +229,6 @@ export const useInventoryViews = () => { isFetchingCurrentView, isFetchingViews, isUpdatingView, - shouldLoadDefault: true, }; }; diff --git a/x-pack/plugins/infra/public/hooks/use_saved_views_notifier.ts b/x-pack/plugins/infra/public/hooks/use_saved_views_notifier.ts index 9c4cb785b83b7..f91560d8e7db4 100644 --- a/x-pack/plugins/infra/public/hooks/use_saved_views_notifier.ts +++ b/x-pack/plugins/infra/public/hooks/use_saved_views_notifier.ts @@ -11,24 +11,24 @@ import { useKibanaContextForPlugin } from './use_kibana'; export const useSavedViewsNotifier = () => { const { notifications } = useKibanaContextForPlugin(); - const getViewFailure = (message?: string) => { + const deleteViewFailure = (message?: string) => { notifications.toasts.danger({ toastLifeTimeMs: 3000, title: message || - i18n.translate('xpack.infra.savedView.findError.title', { - defaultMessage: `An error occurred while loading views.`, + i18n.translate('xpack.infra.savedView.errorOnCreate.title', { + defaultMessage: `An error occured deleting the view.`, }), }); }; - const setDefaultViewFailure = (message?: string) => { + const getViewFailure = (message?: string) => { notifications.toasts.danger({ toastLifeTimeMs: 3000, title: message || - i18n.translate('xpack.infra.savedView.errorOnMakeDefault.title', { - defaultMessage: `An error updating the default view.`, + i18n.translate('xpack.infra.savedView.findError.title', { + defaultMessage: `An error occurred while loading views.`, }), }); }; @@ -45,8 +45,8 @@ export const useSavedViewsNotifier = () => { }; return { + deleteViewFailure, getViewFailure, - setDefaultViewFailure, upsertViewFailure, }; }; diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/layout.tsx b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/layout.tsx index e4fc1ae973808..9827c866b1424 100644 --- a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/layout.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/layout.tsx @@ -36,7 +36,6 @@ import { LegendControls } from './waffle/legend_controls'; import { TryItButton } from '../../../../components/try_it_button'; interface Props { - shouldLoadDefault: boolean; currentView: SavedView | null; reload: () => Promise; interval: string; @@ -52,185 +51,173 @@ interface LegendControlOptions { const HOSTS_LINK_LOCAL_STORAGE_KEY = 'inventoryUI:hostsLinkClicked'; -export const Layout = React.memo( - ({ shouldLoadDefault, currentView, reload, interval, nodes, loading }: Props) => { - const [showLoading, setShowLoading] = useState(true); - const { - metric, - groupBy, - sort, - nodeType, - changeView, - view, - autoBounds, - boundsOverride, - legend, - changeBoundsOverride, - changeAutoBounds, - changeLegend, - } = useWaffleOptionsContext(); - const { currentTime, jumpToTime, isAutoReloading } = useWaffleTimeContext(); - const { applyFilterQuery } = useWaffleFiltersContext(); - const legendPalette = legend?.palette ?? DEFAULT_LEGEND.palette; - const legendSteps = legend?.steps ?? DEFAULT_LEGEND.steps; - const legendReverseColors = legend?.reverseColors ?? DEFAULT_LEGEND.reverseColors; - - const [hostsLinkClicked, setHostsLinkClicked] = useLocalStorage( - HOSTS_LINK_LOCAL_STORAGE_KEY, - false - ); - const hostsLinkClickedRef = useRef(hostsLinkClicked); - - const options = { - formatter: InfraFormatterType.percent, - formatTemplate: '{{value}}', - legend: createLegend(legendPalette, legendSteps, legendReverseColors), - metric, - sort, - groupBy, - }; - - useInterval( - () => { - if (!loading) { - jumpToTime(Date.now()); - } - }, - isAutoReloading ? 5000 : null - ); - - const dataBounds = calculateBoundsFromNodes(nodes); - const bounds = autoBounds ? dataBounds : boundsOverride; - /* eslint-disable-next-line react-hooks/exhaustive-deps */ - const formatter = useCallback(createInventoryMetricFormatter(options.metric), [options.metric]); - const { onViewChange } = useWaffleViewState(); - - useEffect(() => { - if (currentView) { - onViewChange(currentView); +export const Layout = React.memo(({ currentView, reload, interval, nodes, loading }: Props) => { + const [showLoading, setShowLoading] = useState(true); + const { + metric, + groupBy, + sort, + nodeType, + changeView, + view, + autoBounds, + boundsOverride, + legend, + changeBoundsOverride, + changeAutoBounds, + changeLegend, + } = useWaffleOptionsContext(); + const { currentTime, jumpToTime, isAutoReloading } = useWaffleTimeContext(); + const { applyFilterQuery } = useWaffleFiltersContext(); + const legendPalette = legend?.palette ?? DEFAULT_LEGEND.palette; + const legendSteps = legend?.steps ?? DEFAULT_LEGEND.steps; + const legendReverseColors = legend?.reverseColors ?? DEFAULT_LEGEND.reverseColors; + + const [hostsLinkClicked, setHostsLinkClicked] = useLocalStorage( + HOSTS_LINK_LOCAL_STORAGE_KEY, + false + ); + const hostsLinkClickedRef = useRef(hostsLinkClicked); + + const options = { + formatter: InfraFormatterType.percent, + formatTemplate: '{{value}}', + legend: createLegend(legendPalette, legendSteps, legendReverseColors), + metric, + sort, + groupBy, + }; + + useInterval( + () => { + if (!loading) { + jumpToTime(Date.now()); } - }, [currentView, onViewChange]); - - useEffect(() => { - // load snapshot data after default view loaded, unless we're not loading a view - if (currentView != null || !shouldLoadDefault) { - reload(); - } - - /** - * INFO: why disable exhaustive-deps - * We need to wait on the currentView not to be null because it is loaded async and could change the view state. - * We don't actually need to watch the value of currentView though, since the view state will be synched up by the - * changing params in the reload method so we should only "watch" the reload method. - * - * TODO: Should refactor this in the future to make it more clear where all the view state is coming - * from and it's precedence [query params, localStorage, defaultView, out of the box view] - */ - }, [currentView, reload, shouldLoadDefault]); - - useEffect(() => { - setShowLoading(true); - }, [options.metric, nodeType]); - - useEffect(() => { - const hasNodes = nodes && nodes.length; - // Don't show loading screen when we're auto-reloading - setShowLoading(!hasNodes); - }, [nodes]); - - const handleLegendControlChange = useCallback( - (opts: LegendControlOptions) => { - changeBoundsOverride(opts.bounds); - changeAutoBounds(opts.auto); - changeLegend(opts.legend); - }, - [changeBoundsOverride, changeAutoBounds, changeLegend] - ); - - return ( - <> - - - - - - - {view === 'map' && ( - - - - )} + }, + isAutoReloading ? 5000 : null + ); + + const dataBounds = calculateBoundsFromNodes(nodes); + const bounds = autoBounds ? dataBounds : boundsOverride; + /* eslint-disable-next-line react-hooks/exhaustive-deps */ + const formatter = useCallback(createInventoryMetricFormatter(options.metric), [options.metric]); + const { onViewChange } = useWaffleViewState(); + + useEffect(() => { + if (currentView) { + onViewChange(currentView); + } + }, [currentView, onViewChange]); + + useEffect(() => { + // load snapshot data after default view loaded, unless we're not loading a view + if (currentView != null) { + reload(); + } + }, [currentView, reload]); + + useEffect(() => { + setShowLoading(true); + }, [options.metric, nodeType]); + + useEffect(() => { + const hasNodes = nodes && nodes.length; + // Don't show loading screen when we're auto-reloading + setShowLoading(!hasNodes); + }, [nodes]); + + const handleLegendControlChange = useCallback( + (opts: LegendControlOptions) => { + changeBoundsOverride(opts.bounds); + changeAutoBounds(opts.auto); + changeLegend(opts.legend); + }, + [changeBoundsOverride, changeAutoBounds, changeLegend] + ); + + return ( + <> + + + + + + + {view === 'map' && ( - + - + )} + + + - - - {!hostsLinkClickedRef.current && nodeType === 'host' && ( - { - setHostsLinkClicked(true); - }} + + + + {!hostsLinkClickedRef.current && nodeType === 'host' && ( + { + setHostsLinkClicked(true); + }} + /> + )} + + + + {({ bounds: { height = 0 } }) => ( + )} - - - - {({ bounds: { height = 0 } }) => ( - - )} - - - - - - - ); - } -); + + + + + + + ); +}); const TopActionContainer = euiStyled(EuiFlexItem)` padding: ${(props) => `${props.theme.eui.euiSizeM} 0`}; diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/layout_view.tsx b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/layout_view.tsx index 04f26d730cf88..f2ea500a98fd1 100644 --- a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/layout_view.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/layout_view.tsx @@ -18,6 +18,6 @@ interface Props { } export const LayoutView = (props: Props) => { - const { shouldLoadDefault, currentView } = useInventoryViews(); - return ; + const { currentView } = useInventoryViews(); + return ; }; diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/saved_views.tsx b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/saved_views.tsx index 097c039b22383..4547b0dbb0147 100644 --- a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/saved_views.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/saved_views.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { useInventoryViews } from '../../../../hooks/use_inventory_views'; import { SavedViewsToolbarControls } from '../../../../components/saved_views/toolbar_control'; -import { useWaffleViewState } from '../hooks/use_waffle_view_state'; +import { useWaffleViewState, WaffleViewState } from '../hooks/use_waffle_view_state'; export const SavedViews = () => { const { viewState } = useWaffleViewState(); @@ -17,6 +17,8 @@ export const SavedViews = () => { views, isFetchingViews, isFetchingCurrentView, + isCreatingView, + isUpdatingView, createView, deleteViewById, fetchViews, @@ -26,11 +28,13 @@ export const SavedViews = () => { } = useInventoryViews(); return ( - currentView={currentView} views={views} isFetchingViews={isFetchingViews} isFetchingCurrentView={isFetchingCurrentView} + isCreatingView={isCreatingView} + isUpdatingView={isUpdatingView} onCreateView={createView} onDeleteView={deleteViewById} onUpdateView={updateViewById} From 9cc2345ac3744e620d28f764aa04f3a6fc67d7ea Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Thu, 27 Apr 2023 09:35:18 +0000 Subject: [PATCH 15/39] [CI] Auto-commit changed files from 'node scripts/precommit_hook.js --ref HEAD~1..HEAD --fix' --- .../infra/public/components/saved_views/toolbar_control.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/x-pack/plugins/infra/public/components/saved_views/toolbar_control.tsx b/x-pack/plugins/infra/public/components/saved_views/toolbar_control.tsx index 9473974ff40c0..79fe67947b3e4 100644 --- a/x-pack/plugins/infra/public/components/saved_views/toolbar_control.tsx +++ b/x-pack/plugins/infra/public/components/saved_views/toolbar_control.tsx @@ -10,7 +10,6 @@ import { i18n } from '@kbn/i18n'; import { EuiButton, EuiPopover, EuiListGroup, EuiListGroupItem } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; import { NonEmptyString } from '@kbn/io-ts-utils'; -import { InventoryView } from '../../../common/inventory_views'; import { SavedViewManageViewsFlyout } from './manage_views_flyout'; import { useBoolean } from '../../hooks/use_boolean'; import { UpsertViewModal } from './upsert_modal'; From a164def8bc4257f990550f771405b0e0ecf9654b Mon Sep 17 00:00:00 2001 From: Marco Antonio Ghiani Date: Thu, 27 Apr 2023 14:39:00 +0200 Subject: [PATCH 16/39] feat(infra): add useMetricsExplorerViews hook --- .../saved_views/manage_views_flyout.tsx | 41 ++- .../saved_views/toolbar_control.tsx | 18 +- .../components/saved_views/upsert_modal.tsx | 2 +- .../infra/public/hooks/use_inventory_views.ts | 2 +- .../hooks/use_metrics_explorer_views.ts | 260 ++++++++++++++++++ .../infra/public/pages/metrics/index.tsx | 24 +- .../components/saved_views.tsx | 45 +++ .../hooks/use_metric_explorer_state.ts | 17 +- .../pages/metrics/metrics_explorer/index.tsx | 26 +- 9 files changed, 377 insertions(+), 58 deletions(-) create mode 100644 x-pack/plugins/infra/public/hooks/use_metrics_explorer_views.ts create mode 100644 x-pack/plugins/infra/public/pages/metrics/metrics_explorer/components/saved_views.tsx diff --git a/x-pack/plugins/infra/public/components/saved_views/manage_views_flyout.tsx b/x-pack/plugins/infra/public/components/saved_views/manage_views_flyout.tsx index ee55669dfff8c..16bf2339e74e7 100644 --- a/x-pack/plugins/infra/public/components/saved_views/manage_views_flyout.tsx +++ b/x-pack/plugins/infra/public/components/saved_views/manage_views_flyout.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React from 'react'; +import React, { useMemo } from 'react'; import useToggle from 'react-use/lib/useToggle'; import { @@ -22,10 +22,15 @@ import { } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; +import { EuiBasicTableColumn } from '@elastic/eui'; +import { MetricsExplorerView } from '../../../common/metrics_explorer_views'; import { InventoryView } from '../../../common/inventory_views'; import { UseInventoryViewsResult } from '../../hooks/use_inventory_views'; +import { UseMetricsExplorerViewsResult } from '../../hooks/use_metrics_explorer_views'; -interface Props { +type View = InventoryView | MetricsExplorerView; + +interface Props { views: UseViewResult['views']; loading: boolean; onClose(): void; @@ -71,15 +76,22 @@ const DeleteConfimation = ({ isDisabled, onConfirm }: DeleteConfimationProps) => ); }; +const searchConfig = { + box: { incremental: true }, +}; + export function SavedViewManageViewsFlyout({ onClose, - views, + views = [], onSwitchView, onMakeDefaultView, onDeleteView, loading, -}: Props) { - const renderName = (name: string, item: InventoryView) => ( +}: Props) { + // Add name as top level property to allow in memory search + const namedViews = useMemo(() => views.map(addOwnName), [views]); + + const renderName = (name: string, item: View) => ( ); - const renderDeleteAction = (item: InventoryView) => { + const renderDeleteAction = (item: View) => { return ( { + const renderMakeDefaultAction = (item: View) => { return ( > = [ { - field: 'attributes.name', + field: 'name', name: i18n.translate('xpack.infra.openView.columnNames.name', { defaultMessage: 'Name' }), sortable: true, truncateText: true, @@ -134,7 +146,7 @@ export function SavedViewManageViewsFlyout({ render: renderMakeDefaultAction, }, { - available: (item: InventoryView) => !item.attributes.isStatic, + available: (item) => !item.attributes.isStatic, render: renderDeleteAction, }, ], @@ -156,10 +168,10 @@ export function SavedViewManageViewsFlyout({ @@ -173,3 +185,8 @@ export function SavedViewManageViewsFlyout({ ); } + +/** + * Helpers + */ +const addOwnName = (view: View) => ({ ...view, name: view.attributes.name }); diff --git a/x-pack/plugins/infra/public/components/saved_views/toolbar_control.tsx b/x-pack/plugins/infra/public/components/saved_views/toolbar_control.tsx index 79fe67947b3e4..6e733392b4b32 100644 --- a/x-pack/plugins/infra/public/components/saved_views/toolbar_control.tsx +++ b/x-pack/plugins/infra/public/components/saved_views/toolbar_control.tsx @@ -14,8 +14,12 @@ import { SavedViewManageViewsFlyout } from './manage_views_flyout'; import { useBoolean } from '../../hooks/use_boolean'; import { UpsertViewModal } from './upsert_modal'; import { UseInventoryViewsResult } from '../../hooks/use_inventory_views'; +import { UseMetricsExplorerViewsResult } from '../../hooks/use_metrics_explorer_views'; -interface Props { +interface Props< + UseViewResult extends UseInventoryViewsResult | UseMetricsExplorerViewsResult, + ViewState +> { viewState: ViewState & { time?: number }; currentView: UseViewResult['currentView']; views: UseViewResult['views']; @@ -32,7 +36,7 @@ interface Props { } export function SavedViewsToolbarControls( - props: Props + props: Props ) { const { currentView, @@ -56,9 +60,15 @@ export function SavedViewsToolbarControls( const [isCreateModalOpen, { on: openCreateModal, off: closeCreateModal }] = useBoolean(false); const [isUpdateModalOpen, { on: openUpdateModal, off: closeUpdateModal }] = useBoolean(false); + const togglePopoverAndLoad = () => { + if (!isPopoverOpen) { + onLoadViews(); + } + togglePopover(); + }; + const goToManageViews = () => { closePopover(); - onLoadViews(); openManageFlyout(); }; @@ -100,7 +110,7 @@ export function SavedViewsToolbarControls( data-test-subj="savedViews-popover" button={ ; const encodeUrlState = inventoryViewIdRT.encode; const decodeUrlState = (value: unknown) => { - const state = pipe(inventoryViewIdRT.decode(value), fold(constant('0'), identity)); + const state = pipe(inventoryViewIdRT.decode(value), fold(constant(undefined), identity)); return state; }; diff --git a/x-pack/plugins/infra/public/hooks/use_metrics_explorer_views.ts b/x-pack/plugins/infra/public/hooks/use_metrics_explorer_views.ts new file mode 100644 index 0000000000000..181eb22a6eec8 --- /dev/null +++ b/x-pack/plugins/infra/public/hooks/use_metrics_explorer_views.ts @@ -0,0 +1,260 @@ +/* + * 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 * as rt from 'io-ts'; +import { pipe } from 'fp-ts/lib/pipeable'; +import { fold } from 'fp-ts/lib/Either'; +import { constant, identity } from 'fp-ts/lib/function'; + +import { + QueryObserverBaseResult, + UseMutateAsyncFunction, + UseMutateFunction, + useMutation, + useQuery, + useQueryClient, +} from '@tanstack/react-query'; +import { useUiTracker } from '@kbn/observability-plugin/public'; + +import { IHttpFetchError, ResponseErrorBody } from '@kbn/core-http-browser'; +import { MetricsSourceConfigurationResponse } from '../../common/metrics_sources'; +import { + CreateMetricsExplorerViewAttributesRequestPayload, + UpdateMetricsExplorerViewAttributesRequestPayload, +} from '../../common/http_api/latest'; +import { MetricsExplorerView } from '../../common/metrics_explorer_views'; +import { useKibanaContextForPlugin } from './use_kibana'; +import { useUrlState } from '../utils/use_url_state'; +import { useSavedViewsNotifier } from './use_saved_views_notifier'; +import { useSourceContext } from '../containers/metrics_source'; + +interface UpdateViewParams { + id: string; + attributes: UpdateMetricsExplorerViewAttributesRequestPayload; +} + +export interface UseMetricsExplorerViewsResult { + views?: MetricsExplorerView[]; + currentView?: MetricsExplorerView | null; + createView: UseMutateAsyncFunction< + MetricsExplorerView, + ServerError, + CreateMetricsExplorerViewAttributesRequestPayload + >; + deleteViewById: UseMutateFunction; + fetchViews: QueryObserverBaseResult['refetch']; + updateViewById: UseMutateAsyncFunction; + switchViewById: (id: MetricsExplorerViewId) => void; + setDefaultViewById: UseMutateFunction< + MetricsSourceConfigurationResponse, + ServerError, + string, + MutationContext + >; + isCreatingView: boolean; + isFetchingCurrentView: boolean; + isFetchingViews: boolean; + isUpdatingView: boolean; +} + +type ServerError = IHttpFetchError; + +interface MutationContext { + id?: string; + previousViews?: MetricsExplorerView[]; +} + +const queryKeys = { + find: ['metrics-explorer-views-find'] as const, + get: ['metrics-explorer-views-get'] as const, + getById: (id: string) => ['metrics-explorer-views-get', id] as const, +}; + +export const useMetricsExplorerViews = (): UseMetricsExplorerViewsResult => { + const { metricsExplorerViews } = useKibanaContextForPlugin().services; + const trackMetric = useUiTracker({ app: 'infra_metrics' }); + + const queryClient = useQueryClient(); + const { source, updateSourceConfiguration } = useSourceContext(); + + const defaultViewId = source?.configuration.metricsExplorerDefaultView ?? '0'; + + const [currentViewId, switchViewById] = useUrlState({ + defaultState: defaultViewId, + decodeUrlState, + encodeUrlState, + urlStateKey: 'metricsExplorerViewId', + writeDefaultState: true, + }); + + const notify = useSavedViewsNotifier(); + + const { + data: views, + refetch: fetchViews, + isFetching: isFetchingViews, + } = useQuery({ + queryKey: queryKeys.find, + queryFn: () => metricsExplorerViews.client.findMetricsExplorerViews(), + enabled: false, // We will manually fetch the list when necessary + placeholderData: [], // Use a default empty array instead of undefined + onError: (error: ServerError) => notify.getViewFailure(error.body?.message ?? error.message), + onSuccess: (data) => { + const prefix = data.length >= 1000 ? 'over' : 'under'; + trackMetric({ metric: `${prefix}_1000_saved_objects_for_metrics_explorer_view` }); + }, + }); + + const { data: currentView, isFetching: isFetchingCurrentView } = useQuery({ + queryKey: queryKeys.getById(currentViewId), + queryFn: ({ queryKey: [, id] }) => metricsExplorerViews.client.getMetricsExplorerView(id), + onError: (error: ServerError) => notify.getViewFailure(error.body?.message ?? error.message), + placeholderData: null, + }); + + const { mutate: setDefaultViewById } = useMutation< + MetricsSourceConfigurationResponse, + ServerError, + string, + MutationContext + >({ + mutationFn: (id) => updateSourceConfiguration({ metricsExplorerDefaultView: id }), + /** + * To provide a quick feedback, we perform an optimistic update on the list + * when updating the default view. + * 1. Cancel any outgoing refetches (so they don't overwrite our optimistic update) + * 2. Snapshot the previous views list + * 3. Optimistically update the list with new default view and store in cache + * 4. Return a context object with the snapshotted views + */ + onMutate: async (id) => { + await queryClient.cancelQueries({ queryKey: queryKeys.find }); // 1 + const previousViews = queryClient.getQueryData(queryKeys.find); // 2 + const updatedViews = getListWithUpdatedDefault(id, previousViews); // 3 + queryClient.setQueryData(queryKeys.find, updatedViews); + return { previousViews }; // 4 + }, + // If the mutation fails but doesn't retrigger the error, use the context returned from onMutate to roll back + onSuccess: (data, _id, context) => { + if (!data && context?.previousViews) { + return queryClient.setQueryData(queryKeys.find, context.previousViews); + } + return queryClient.invalidateQueries({ queryKey: queryKeys.get }); + }, + }); + + const { mutateAsync: createView, isLoading: isCreatingView } = useMutation< + MetricsExplorerView, + ServerError, + CreateMetricsExplorerViewAttributesRequestPayload + >({ + mutationFn: (attributes) => metricsExplorerViews.client.createMetricsExplorerView(attributes), + onError: (error) => { + notify.upsertViewFailure(error.body?.message ?? error.message); + }, + onSuccess: (createdView) => { + queryClient.setQueryData(queryKeys.getById(createdView.id), createdView); // Store in cache created view + switchViewById(createdView.id); // Update current view and url state + }, + }); + + const { mutateAsync: updateViewById, isLoading: isUpdatingView } = useMutation< + MetricsExplorerView, + ServerError, + UpdateViewParams + >({ + mutationFn: ({ id, attributes }) => + metricsExplorerViews.client.updateMetricsExplorerView(id, attributes), + onError: (error) => { + notify.upsertViewFailure(error.body?.message ?? error.message); + }, + onSuccess: (updatedView) => { + queryClient.setQueryData(queryKeys.getById(updatedView.id), updatedView); // Store in cache updated view + }, + }); + + const { mutate: deleteViewById } = useMutation({ + mutationFn: (id: string) => metricsExplorerViews.client.deleteMetricsExplorerView(id), + /** + * To provide a quick feedback, we perform an optimistic update on the list + * when deleting a view. + * 1. Cancel any outgoing refetches (so they don't overwrite our optimistic update) + * 2. Snapshot the previous views list + * 3. Optimistically update the list removing the view and store in cache + * 4. Return a context object with the snapshotted views + */ + onMutate: async (id) => { + await queryClient.cancelQueries({ queryKey: queryKeys.find }); // 1 + + const previousViews = queryClient.getQueryData(queryKeys.find); // 2 + + const updatedViews = getListWithoutDeletedView(id, previousViews); // 3 + queryClient.setQueryData(queryKeys.find, updatedViews); + + return { previousViews }; // 4 + }, + // If the mutation fails, use the context returned from onMutate to roll back + onError: (error, _id, context) => { + notify.deleteViewFailure(error.body?.message ?? error.message); + if (context?.previousViews) { + queryClient.setQueryData(queryKeys.find, context.previousViews); + } + }, + onSuccess: (_data, id) => { + // If the deleted view was the current one, switch to the default view + if (currentView?.id === id) { + switchViewById(defaultViewId); + } + }, + onSettled: () => { + fetchViews(); // Invalidate views list cache and refetch views + }, + }); + + return { + // Values + views, + currentView, + // Actions about updating view + createView, + deleteViewById, + fetchViews, + updateViewById, + switchViewById, + setDefaultViewById, + // Status flags + isCreatingView, + isFetchingCurrentView, + isFetchingViews, + isUpdatingView, + }; +}; + +const metricsExplorerViewIdRT = rt.string; +type MetricsExplorerViewId = rt.TypeOf; + +const encodeUrlState = metricsExplorerViewIdRT.encode; +const decodeUrlState = (value: unknown) => { + const state = pipe(metricsExplorerViewIdRT.decode(value), fold(constant(undefined), identity)); + return state; +}; + +/** + * Helpers + */ +const getListWithUpdatedDefault = (id: string, views: MetricsExplorerView[] = []) => { + return views.map((view) => ({ + ...view, + attributes: { + ...view.attributes, + isDefault: view.id === id, + }, + })); +}; + +const getListWithoutDeletedView = (id: string, views: MetricsExplorerView[] = []) => { + return views.filter((view) => view.id !== id); +}; diff --git a/x-pack/plugins/infra/public/pages/metrics/index.tsx b/x-pack/plugins/infra/public/pages/metrics/index.tsx index 1c735bfcb3638..df66dc5eb88ba 100644 --- a/x-pack/plugins/infra/public/pages/metrics/index.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/index.tsx @@ -18,11 +18,7 @@ import { useLinkProps } from '@kbn/observability-plugin/public'; import { MetricsSourceConfigurationProperties } from '../../../common/metrics_sources'; import { HelpCenterContent } from '../../components/help_center_content'; import { useReadOnlyBadge } from '../../hooks/use_readonly_badge'; -import { - MetricsExplorerOptionsContainer, - useMetricsExplorerOptionsContainerContext, - DEFAULT_METRICS_EXPLORER_VIEW_STATE, -} from './metrics_explorer/hooks/use_metrics_explorer_options'; +import { MetricsExplorerOptionsContainer } from './metrics_explorer/hooks/use_metrics_explorer_options'; import { WithMetricsExplorerOptionsUrlState } from '../../containers/metrics_explorer/with_metrics_explorer_options_url_state'; import { MetricsExplorerPage } from './metrics_explorer'; import { SnapshotPage } from './inventory_view'; @@ -34,7 +30,6 @@ import { WaffleOptionsProvider } from './inventory_view/hooks/use_waffle_options import { WaffleTimeProvider } from './inventory_view/hooks/use_waffle_time'; import { WaffleFiltersProvider } from './inventory_view/hooks/use_waffle_filters'; import { MetricsAlertDropdown } from '../../alerting/common/components/metrics_alert_dropdown'; -import { SavedViewProvider } from '../../containers/saved_view/saved_view'; import { AlertPrefillProvider } from '../../alerting/use_alert_prefill'; import { InfraMLCapabilitiesProvider } from '../../containers/ml/infra_ml_capabilities'; import { AnomalyDetectionFlyout } from './inventory_view/components/ml/anomaly_detection/anomaly_detection_flyout'; @@ -133,19 +128,12 @@ const PageContent = (props: { createDerivedIndexPattern: CreateDerivedIndexPattern; }) => { const { createDerivedIndexPattern, configuration } = props; - const { options } = useMetricsExplorerOptionsContainerContext(); return ( - - - + ); }; diff --git a/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/components/saved_views.tsx b/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/components/saved_views.tsx new file mode 100644 index 0000000000000..138f99170f25a --- /dev/null +++ b/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/components/saved_views.tsx @@ -0,0 +1,45 @@ +/* + * 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 { useMetricsExplorerViews } from '../../../../hooks/use_metrics_explorer_views'; +import { SavedViewsToolbarControls } from '../../../../components/saved_views/toolbar_control'; + +export const SavedViews = ({ viewState }) => { + const { + currentView, + views, + isFetchingViews, + isFetchingCurrentView, + isCreatingView, + isUpdatingView, + createView, + deleteViewById, + fetchViews, + updateViewById, + switchViewById, + setDefaultViewById, + } = useMetricsExplorerViews(); + + return ( + + currentView={currentView} + views={views} + isFetchingViews={isFetchingViews} + isFetchingCurrentView={isFetchingCurrentView} + isCreatingView={isCreatingView} + isUpdatingView={isUpdatingView} + onCreateView={createView} + onDeleteView={deleteViewById} + onUpdateView={updateViewById} + onLoadViews={fetchViews} + onSetDefaultView={setDefaultViewById} + onSwitchView={switchViewById} + viewState={viewState} + /> + ); +}; diff --git a/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/hooks/use_metric_explorer_state.ts b/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/hooks/use_metric_explorer_state.ts index a9a4611094174..bc098e441eb29 100644 --- a/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/hooks/use_metric_explorer_state.ts +++ b/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/hooks/use_metric_explorer_state.ts @@ -8,6 +8,7 @@ import DateMath from '@kbn/datemath'; import { useCallback, useEffect } from 'react'; import { DataViewBase } from '@kbn/es-query'; +import { MetricsExplorerView } from '../../../../../common/metrics_explorer_views'; import { MetricsSourceConfigurationProperties } from '../../../../../common/metrics_sources'; import { MetricsExplorerMetric, @@ -124,19 +125,19 @@ export const useMetricsExplorerState = ( ); const onViewStateChange = useCallback( - (vs: MetricExplorerViewState) => { - if (vs.chartOptions) { - setChartOptions(vs.chartOptions); + (view: MetricsExplorerView) => { + if (view.attributes.chartOptions) { + setChartOptions(view.attributes.chartOptions as MetricsExplorerChartOptions); } - if (vs.currentTimerange) { + if (view.attributes.currentTimerange) { // if this is the "Default View" view, don't update the time range to the view's time range, // this way it will use the global Kibana time or the default time already set - if (vs.id !== '0') { - setTimeRange(vs.currentTimerange); + if (!view.attributes.isStatic) { + setTimeRange(view.attributes.currentTimerange as MetricsExplorerTimeOptions); } } - if (vs.options) { - setOptions(vs.options); + if (view.attributes.options) { + setOptions(view.attributes.options as MetricsExplorerOptions); } }, [setChartOptions, setOptions, setTimeRange] diff --git a/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/index.tsx b/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/index.tsx index d62d6ea1e82b1..07d5e210c46a1 100644 --- a/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/index.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/index.tsx @@ -9,6 +9,7 @@ import { EuiErrorBoundary } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React, { useEffect, useState } from 'react'; import { useTrackPageview } from '@kbn/observability-plugin/public'; +import { useMetricsExplorerViews } from '../../../hooks/use_metrics_explorer_views'; import { MetricsSourceConfigurationProperties } from '../../../../common/metrics_sources'; import { useMetricsBreadcrumbs } from '../../../hooks/use_metrics_breadcrumbs'; import { NoData } from '../../../components/empty_states'; @@ -16,11 +17,10 @@ import { MetricsExplorerCharts } from './components/charts'; import { MetricsExplorerToolbar } from './components/toolbar'; import { useMetricsExplorerState } from './hooks/use_metric_explorer_state'; import { useSourceContext } from '../../../containers/metrics_source'; -import { useSavedViewContext } from '../../../containers/saved_view/saved_view'; import { MetricsPageTemplate } from '../page_template'; import { metricsExplorerTitle } from '../../../translations'; -import { SavedViewsToolbarControls } from '../../../components/saved_views/toolbar_control'; import { DerivedIndexPattern } from '../../../containers/metrics_source'; +import { SavedViews } from './components/saved_views'; interface MetricsExplorerPageProps { source: MetricsSourceConfigurationProperties; derivedIndexPattern: DerivedIndexPattern; @@ -45,7 +45,7 @@ export const MetricsExplorerPage = ({ source, derivedIndexPattern }: MetricsExpl onViewStateChange, refresh, } = useMetricsExplorerState(source, derivedIndexPattern, enabled); - const { currentView, shouldLoadDefault } = useSavedViewContext(); + const { currentView } = useMetricsExplorerViews(); useTrackPageview({ app: 'infra_metrics', path: 'metrics_explorer' }); useTrackPageview({ app: 'infra_metrics', path: 'metrics_explorer', delay: 15000 }); @@ -58,11 +58,11 @@ export const MetricsExplorerPage = ({ source, derivedIndexPattern }: MetricsExpl }, [currentView, onViewStateChange]); useEffect(() => { - if (currentView != null || !shouldLoadDefault) { + if (currentView != null) { // load metrics explorer data after default view loaded, unless we're not isLoading a view setEnabled(true); } - }, [currentView, shouldLoadDefault]); + }, [currentView]); useMetricsBreadcrumbs([ { @@ -70,21 +70,19 @@ export const MetricsExplorerPage = ({ source, derivedIndexPattern }: MetricsExpl }, ]); + const viewState = { + options, + chartOptions, + currentTimerange: timeRange, + }; + return ( , - ], + rightSideItems: [], }} > Date: Thu, 27 Apr 2023 14:47:28 +0200 Subject: [PATCH 17/39] feat(infra): refine types for metrics views --- .../metrics/metrics_explorer/components/saved_views.tsx | 9 +++++++-- .../metrics_explorer_views_client.ts | 6 ++++-- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/components/saved_views.tsx b/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/components/saved_views.tsx index 138f99170f25a..2d329f121f008 100644 --- a/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/components/saved_views.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/components/saved_views.tsx @@ -8,8 +8,13 @@ import React from 'react'; import { useMetricsExplorerViews } from '../../../../hooks/use_metrics_explorer_views'; import { SavedViewsToolbarControls } from '../../../../components/saved_views/toolbar_control'; +import { MetricExplorerViewState } from '../hooks/use_metric_explorer_state'; -export const SavedViews = ({ viewState }) => { +interface Props { + viewState: MetricExplorerViewState; +} + +export const SavedViews = ({ viewState }: Props) => { const { currentView, views, @@ -26,7 +31,7 @@ export const SavedViews = ({ viewState }) => { } = useMetricsExplorerViews(); return ( - + currentView={currentView} views={views} isFetchingViews={isFetchingViews} diff --git a/x-pack/plugins/infra/public/services/metrics_explorer_views/metrics_explorer_views_client.ts b/x-pack/plugins/infra/public/services/metrics_explorer_views/metrics_explorer_views_client.ts index d37d2fc81b31d..788a8789abe73 100644 --- a/x-pack/plugins/infra/public/services/metrics_explorer_views/metrics_explorer_views_client.ts +++ b/x-pack/plugins/infra/public/services/metrics_explorer_views/metrics_explorer_views_client.ts @@ -73,7 +73,7 @@ export class MetricsExplorerViewsClient implements IMetricsExplorerViewsClient { }) .catch((error) => { throw new UpsertMetricsExplorerViewError( - `Failed to create new metrics explorer view: ${error}` + `Failed to create new metrics explorer view: ${error.body?.message ?? error.message}` ); }); @@ -102,7 +102,9 @@ export class MetricsExplorerViewsClient implements IMetricsExplorerViewsClient { }) .catch((error) => { throw new UpsertMetricsExplorerViewError( - `Failed to update metrics explorer view "${metricsExplorerViewId}": ${error}` + `Failed to update metrics explorer view "${metricsExplorerViewId}": ${ + error.body?.message ?? error.message + }` ); }); From b0b9f0349b0571fafce4440ea2a441613bf0a8a2 Mon Sep 17 00:00:00 2001 From: Marco Antonio Ghiani Date: Thu, 27 Apr 2023 15:14:35 +0200 Subject: [PATCH 18/39] fix(infra): types --- .../public/hooks/use_saved_views_notifier.ts | 2 +- .../hooks/use_waffle_view_state.ts | 38 ++++++++++--------- 2 files changed, 21 insertions(+), 19 deletions(-) diff --git a/x-pack/plugins/infra/public/hooks/use_saved_views_notifier.ts b/x-pack/plugins/infra/public/hooks/use_saved_views_notifier.ts index f91560d8e7db4..b7b8822e3ec14 100644 --- a/x-pack/plugins/infra/public/hooks/use_saved_views_notifier.ts +++ b/x-pack/plugins/infra/public/hooks/use_saved_views_notifier.ts @@ -16,7 +16,7 @@ export const useSavedViewsNotifier = () => { toastLifeTimeMs: 3000, title: message || - i18n.translate('xpack.infra.savedView.errorOnCreate.title', { + i18n.translate('xpack.infra.savedView.errorOnDelete.title', { defaultMessage: `An error occured deleting the view.`, }), }); diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/hooks/use_waffle_view_state.ts b/x-pack/plugins/infra/public/pages/metrics/inventory_view/hooks/use_waffle_view_state.ts index c7bbe71c3310c..6e685a6cc105f 100644 --- a/x-pack/plugins/infra/public/pages/metrics/inventory_view/hooks/use_waffle_view_state.ts +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/hooks/use_waffle_view_state.ts @@ -65,30 +65,32 @@ export const useWaffleViewState = () => { }; const onViewChange = useCallback( - (newState: { attributes: WaffleViewState }) => { + (newState) => { + const attributes = newState.attributes as WaffleViewState; + setWaffleOptionsState({ - sort: newState.attributes.sort, - metric: newState.attributes.metric, - groupBy: newState.attributes.groupBy, - nodeType: newState.attributes.nodeType, - view: newState.attributes.view, - customOptions: newState.attributes.customOptions, - customMetrics: newState.attributes.customMetrics, - boundsOverride: newState.attributes.boundsOverride, - autoBounds: newState.attributes.autoBounds, - accountId: newState.attributes.accountId, - region: newState.attributes.region, - legend: newState.attributes.legend, - timelineOpen: newState.attributes.timelineOpen, + sort: attributes.sort, + metric: attributes.metric, + groupBy: attributes.groupBy, + nodeType: attributes.nodeType, + view: attributes.view, + customOptions: attributes.customOptions, + customMetrics: attributes.customMetrics, + boundsOverride: attributes.boundsOverride, + autoBounds: attributes.autoBounds, + accountId: attributes.accountId, + region: attributes.region, + legend: attributes.legend, + timelineOpen: attributes.timelineOpen, }); - if (newState.attributes.time) { + if (attributes.time) { setWaffleTimeState({ - currentTime: newState.attributes.time, - isAutoReloading: newState.attributes.autoReload, + currentTime: attributes.time, + isAutoReloading: attributes.autoReload, }); } - setWaffleFiltersState(newState.attributes.filterQuery); + setWaffleFiltersState(attributes.filterQuery); }, [setWaffleOptionsState, setWaffleTimeState, setWaffleFiltersState] ); From 4092e287ec925d971916a6743ab8ee1790bf8ec4 Mon Sep 17 00:00:00 2001 From: Marco Antonio Ghiani Date: Thu, 27 Apr 2023 15:27:05 +0200 Subject: [PATCH 19/39] tests(infra): remove legacy tests --- x-pack/test/functional/apps/infra/home_page.ts | 9 +-------- x-pack/test/functional/apps/infra/metrics_explorer.ts | 7 ------- x-pack/test/functional/page_objects/infra_saved_views.ts | 4 ---- 3 files changed, 1 insertion(+), 19 deletions(-) diff --git a/x-pack/test/functional/apps/infra/home_page.ts b/x-pack/test/functional/apps/infra/home_page.ts index 54435f11a33e1..f3a17099d81d9 100644 --- a/x-pack/test/functional/apps/infra/home_page.ts +++ b/x-pack/test/functional/apps/infra/home_page.ts @@ -221,7 +221,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { }); }); // Failing: See https://github.com/elastic/kibana/issues/106650 - describe.skip('Saved Views', () => { + describe.only('Saved Views', () => { before(() => esArchiver.load('x-pack/test/functional/es_archives/infra/metrics_and_logs')); after(() => esArchiver.unload('x-pack/test/functional/es_archives/infra/metrics_and_logs')); it('should have save and load controls', async () => { @@ -244,13 +244,6 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { await pageObjects.infraSavedViews.createNewSavedView('view1'); await pageObjects.infraSavedViews.ensureViewIsLoaded('view1'); }); - - it('should new views should be listed in the load views list', async () => { - await pageObjects.infraSavedViews.clickSavedViewsButton(); - await pageObjects.infraSavedViews.clickLoadViewButton(); - await pageObjects.infraSavedViews.ensureViewIsLoadable('view1'); - await pageObjects.infraSavedViews.closeSavedViewsLoadModal(); - }); }); }); }; diff --git a/x-pack/test/functional/apps/infra/metrics_explorer.ts b/x-pack/test/functional/apps/infra/metrics_explorer.ts index 4d6859a4e99e7..a2b94e6126957 100644 --- a/x-pack/test/functional/apps/infra/metrics_explorer.ts +++ b/x-pack/test/functional/apps/infra/metrics_explorer.ts @@ -119,13 +119,6 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { await pageObjects.infraSavedViews.createNewSavedView('view1'); await pageObjects.infraSavedViews.ensureViewIsLoaded('view1'); }); - - it('should new views should be listed in the load views list', async () => { - await pageObjects.infraSavedViews.clickSavedViewsButton(); - await pageObjects.infraSavedViews.clickLoadViewButton(); - await pageObjects.infraSavedViews.ensureViewIsLoadable('view1'); - await pageObjects.infraSavedViews.closeSavedViewsLoadModal(); - }); }); }); }); diff --git a/x-pack/test/functional/page_objects/infra_saved_views.ts b/x-pack/test/functional/page_objects/infra_saved_views.ts index 839b10fef1c68..7125c8783c2b4 100644 --- a/x-pack/test/functional/page_objects/infra_saved_views.ts +++ b/x-pack/test/functional/page_objects/infra_saved_views.ts @@ -36,10 +36,6 @@ export function InfraSavedViewsProvider({ getService }: FtrProviderContext) { return await testSubjects.find('savedViews-loadView'); }, - async clickLoadViewButton() { - return await testSubjects.click('savedViews-loadView'); - }, - async getManageViewsButton() { return await testSubjects.find('savedViews-manageViews'); }, From 4cfe534430462e4485c4ee461ba16d1843fc1453 Mon Sep 17 00:00:00 2001 From: Marco Antonio Ghiani Date: Thu, 27 Apr 2023 15:40:31 +0200 Subject: [PATCH 20/39] tests(infra): remove exclusive tests --- x-pack/test/functional/apps/infra/home_page.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/test/functional/apps/infra/home_page.ts b/x-pack/test/functional/apps/infra/home_page.ts index f3a17099d81d9..c217418f9bed3 100644 --- a/x-pack/test/functional/apps/infra/home_page.ts +++ b/x-pack/test/functional/apps/infra/home_page.ts @@ -221,7 +221,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { }); }); // Failing: See https://github.com/elastic/kibana/issues/106650 - describe.only('Saved Views', () => { + describe('Saved Views', () => { before(() => esArchiver.load('x-pack/test/functional/es_archives/infra/metrics_and_logs')); after(() => esArchiver.unload('x-pack/test/functional/es_archives/infra/metrics_and_logs')); it('should have save and load controls', async () => { From 5a88bef10ad646b807732cf08c345494e2f5d1d6 Mon Sep 17 00:00:00 2001 From: Marco Antonio Ghiani Date: Thu, 27 Apr 2023 15:58:48 +0200 Subject: [PATCH 21/39] refactor(translation): remove unused translations --- x-pack/plugins/translations/translations/fr-FR.json | 4 ---- x-pack/plugins/translations/translations/ja-JP.json | 4 ---- x-pack/plugins/translations/translations/zh-CN.json | 4 ---- 3 files changed, 12 deletions(-) diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index 285e5b8a1284f..625b0770d2393 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -17888,7 +17888,6 @@ "xpack.infra.openView.columnNames.actions": "Actions", "xpack.infra.openView.columnNames.name": "Nom", "xpack.infra.openView.flyoutHeader": "Gérer les vues enregistrées", - "xpack.infra.openView.loadButton": "Charger la vue", "xpack.infra.registerFeatures.infraOpsDescription": "Explorez les indicateurs et logs d'infrastructure pour les serveurs, conteneurs et services courants.", "xpack.infra.registerFeatures.infraOpsTitle": "Indicateurs", "xpack.infra.registerFeatures.logsDescription": "Diffusez les logs en temps réel ou faites défiler les vues d'historique comme sur une console.", @@ -17898,10 +17897,8 @@ "xpack.infra.savedView.errorOnCreate.duplicateViewName": "Une vue portant ce nom existe déjà.", "xpack.infra.savedView.errorOnCreate.title": "Une erreur s'est produite lors de l'enregistrement de la vue.", "xpack.infra.savedView.findError.title": "Une erreur s'est produite lors du chargement des vues.", - "xpack.infra.savedView.loadView": "Charger la vue", "xpack.infra.savedView.manageViews": "Gérer les vues", "xpack.infra.savedView.saveNewView": "Enregistrer la nouvelle vue", - "xpack.infra.savedView.searchPlaceholder": "Rechercher les vues enregistrées", "xpack.infra.savedView.unknownView": "Aucune vue sélectionnée", "xpack.infra.savedView.updateView": "Mettre à jour la vue", "xpack.infra.showHistory": "Afficher l'historique", @@ -18014,7 +18011,6 @@ "xpack.infra.waffle.region": "Tous", "xpack.infra.waffle.regionLabel": "Région", "xpack.infra.waffle.savedView.createHeader": "Enregistrer la vue", - "xpack.infra.waffle.savedView.selectViewHeader": "Sélectionner une vue à charger", "xpack.infra.waffle.savedView.updateHeader": "Mettre à jour la vue", "xpack.infra.waffle.savedViews.cancel": "annuler", "xpack.infra.waffle.savedViews.cancelButton": "Annuler", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 8c87e175bea63..a403070fefa31 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -17887,7 +17887,6 @@ "xpack.infra.openView.columnNames.actions": "アクション", "xpack.infra.openView.columnNames.name": "名前", "xpack.infra.openView.flyoutHeader": "保存されたビューの管理", - "xpack.infra.openView.loadButton": "ビューの読み込み", "xpack.infra.registerFeatures.infraOpsDescription": "共通のサーバー、コンテナー、サービスのインフラストラクチャメトリックとログを閲覧します。", "xpack.infra.registerFeatures.infraOpsTitle": "メトリック", "xpack.infra.registerFeatures.logsDescription": "ログをリアルタイムでストリーするか、コンソール式の UI で履歴ビューをスクロールします。", @@ -17897,10 +17896,8 @@ "xpack.infra.savedView.errorOnCreate.duplicateViewName": "その名前のビューはすでに存在します。", "xpack.infra.savedView.errorOnCreate.title": "ビューの保存中にエラーが発生しました。", "xpack.infra.savedView.findError.title": "ビューの読み込み中にエラーが発生しました。", - "xpack.infra.savedView.loadView": "ビューの読み込み", "xpack.infra.savedView.manageViews": "ビューの管理", "xpack.infra.savedView.saveNewView": "新しいビューの保存", - "xpack.infra.savedView.searchPlaceholder": "保存されたビューの検索", "xpack.infra.savedView.unknownView": "ビューが選択されていません", "xpack.infra.savedView.updateView": "ビューの更新", "xpack.infra.showHistory": "履歴を表示", @@ -18013,7 +18010,6 @@ "xpack.infra.waffle.region": "すべて", "xpack.infra.waffle.regionLabel": "地域", "xpack.infra.waffle.savedView.createHeader": "ビューを保存", - "xpack.infra.waffle.savedView.selectViewHeader": "読み込むビューを選択", "xpack.infra.waffle.savedView.updateHeader": "ビューの更新", "xpack.infra.waffle.savedViews.cancel": "キャンセル", "xpack.infra.waffle.savedViews.cancelButton": "キャンセル", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 23ecf9913249e..bd1a9698e54ec 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -17889,7 +17889,6 @@ "xpack.infra.openView.columnNames.actions": "操作", "xpack.infra.openView.columnNames.name": "名称", "xpack.infra.openView.flyoutHeader": "管理已保存视图", - "xpack.infra.openView.loadButton": "加载视图", "xpack.infra.registerFeatures.infraOpsDescription": "浏览常用服务器、容器和服务的基础设施指标和日志。", "xpack.infra.registerFeatures.infraOpsTitle": "指标", "xpack.infra.registerFeatures.logsDescription": "实时流式传输日志或在类似控制台的工具中滚动浏览历史视图。", @@ -17899,10 +17898,8 @@ "xpack.infra.savedView.errorOnCreate.duplicateViewName": "具有该名称的视图已存在。", "xpack.infra.savedView.errorOnCreate.title": "保存视图时出错。", "xpack.infra.savedView.findError.title": "加载视图时出错。", - "xpack.infra.savedView.loadView": "加载视图", "xpack.infra.savedView.manageViews": "管理视图", "xpack.infra.savedView.saveNewView": "保存新视图", - "xpack.infra.savedView.searchPlaceholder": "搜索已保存视图", "xpack.infra.savedView.unknownView": "未选择视图", "xpack.infra.savedView.updateView": "更新视图", "xpack.infra.showHistory": "显示历史记录", @@ -18015,7 +18012,6 @@ "xpack.infra.waffle.region": "全部", "xpack.infra.waffle.regionLabel": "地区", "xpack.infra.waffle.savedView.createHeader": "保存视图", - "xpack.infra.waffle.savedView.selectViewHeader": "选择要加载的视图", "xpack.infra.waffle.savedView.updateHeader": "更新视图", "xpack.infra.waffle.savedViews.cancel": "取消", "xpack.infra.waffle.savedViews.cancelButton": "取消", From 78df44dda1f8356d27aa097bb054d25988201fc4 Mon Sep 17 00:00:00 2001 From: Marco Antonio Ghiani Date: Thu, 27 Apr 2023 16:43:53 +0200 Subject: [PATCH 22/39] refactor(translation): update toolbar types --- .../components/saved_views/toolbar_control.tsx | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/x-pack/plugins/infra/public/components/saved_views/toolbar_control.tsx b/x-pack/plugins/infra/public/components/saved_views/toolbar_control.tsx index 6e733392b4b32..d608d76702fca 100644 --- a/x-pack/plugins/infra/public/components/saved_views/toolbar_control.tsx +++ b/x-pack/plugins/infra/public/components/saved_views/toolbar_control.tsx @@ -16,17 +16,9 @@ import { UpsertViewModal } from './upsert_modal'; import { UseInventoryViewsResult } from '../../hooks/use_inventory_views'; import { UseMetricsExplorerViewsResult } from '../../hooks/use_metrics_explorer_views'; -interface Props< - UseViewResult extends UseInventoryViewsResult | UseMetricsExplorerViewsResult, - ViewState -> { +type UseViewResult = UseInventoryViewsResult | UseMetricsExplorerViewsResult; +interface Props extends UseInventoryViewsResult, UseMetricsExplorerViewsResult { viewState: ViewState & { time?: number }; - currentView: UseViewResult['currentView']; - views: UseViewResult['views']; - isFetchingViews: UseViewResult['isFetchingViews']; - isFetchingCurrentView: UseViewResult['isFetchingCurrentView']; - isCreatingView: UseViewResult['isCreatingView']; - isUpdatingView: UseViewResult['isUpdatingView']; onCreateView: UseViewResult['createView']; onDeleteView: UseViewResult['deleteViewById']; onUpdateView: UseViewResult['updateViewById']; @@ -35,9 +27,7 @@ interface Props< onSwitchView: UseViewResult['switchViewById']; } -export function SavedViewsToolbarControls( - props: Props -) { +export function SavedViewsToolbarControls(props: Props) { const { currentView, views, From d55002f65610c1690bbf3f3392a82b43a580eac9 Mon Sep 17 00:00:00 2001 From: Marco Antonio Ghiani Date: Thu, 27 Apr 2023 16:57:19 +0200 Subject: [PATCH 23/39] refactor(infra): switch to icon button for better alignment --- .../public/components/saved_views/manage_views_flyout.tsx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/infra/public/components/saved_views/manage_views_flyout.tsx b/x-pack/plugins/infra/public/components/saved_views/manage_views_flyout.tsx index 16bf2339e74e7..8fa9422e2de15 100644 --- a/x-pack/plugins/infra/public/components/saved_views/manage_views_flyout.tsx +++ b/x-pack/plugins/infra/public/components/saved_views/manage_views_flyout.tsx @@ -23,6 +23,7 @@ import { import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; import { EuiBasicTableColumn } from '@elastic/eui'; +import { EuiButtonIcon } from '@elastic/eui'; import { MetricsExplorerView } from '../../../common/metrics_explorer_views'; import { InventoryView } from '../../../common/inventory_views'; import { UseInventoryViewsResult } from '../../hooks/use_inventory_views'; @@ -67,10 +68,11 @@ const DeleteConfimation = ({ isDisabled, onConfirm }: DeleteConfimationProps) => ) : ( - ); @@ -118,10 +120,11 @@ export function SavedViewManageViewsFlyout({ const renderMakeDefaultAction = (item: View) => { return ( - { onMakeDefaultView(item.id); }} From ee872cd397b34b8d6b4b6adc1b384cc2eda6e1c6 Mon Sep 17 00:00:00 2001 From: Marco Antonio Ghiani Date: Thu, 27 Apr 2023 17:02:39 +0200 Subject: [PATCH 24/39] refactor(infra): update test --- .../services/inventory_views/inventory_views_client.test.ts | 4 ++-- .../metrics_explorer_views_client.test.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/x-pack/plugins/infra/server/services/inventory_views/inventory_views_client.test.ts b/x-pack/plugins/infra/server/services/inventory_views/inventory_views_client.test.ts index 29ff1a44f9b7b..7ec614dbbfe22 100644 --- a/x-pack/plugins/infra/server/services/inventory_views/inventory_views_client.test.ts +++ b/x-pack/plugins/infra/server/services/inventory_views/inventory_views_client.test.ts @@ -129,7 +129,7 @@ describe('InventoryViewsClient class', () => { infraSources.getSourceConfiguration.mockResolvedValue(basicTestSourceConfiguration); - savedObjectsClient.update.mockResolvedValue({ + savedObjectsClient.create.mockResolvedValue({ ...inventoryViewMock, type: inventoryViewSavedObjectName, references: [], @@ -143,7 +143,7 @@ describe('InventoryViewsClient class', () => { {} ); - expect(savedObjectsClient.update).toHaveBeenCalled(); + expect(savedObjectsClient.create).toHaveBeenCalled(); expect(inventoryView).toEqual(inventoryViewMock); }); diff --git a/x-pack/plugins/infra/server/services/metrics_explorer_views/metrics_explorer_views_client.test.ts b/x-pack/plugins/infra/server/services/metrics_explorer_views/metrics_explorer_views_client.test.ts index c05977bf9dfd5..791b7366f086c 100644 --- a/x-pack/plugins/infra/server/services/metrics_explorer_views/metrics_explorer_views_client.test.ts +++ b/x-pack/plugins/infra/server/services/metrics_explorer_views/metrics_explorer_views_client.test.ts @@ -132,7 +132,7 @@ describe('MetricsExplorerViewsClient class', () => { infraSources.getSourceConfiguration.mockResolvedValue(basicTestSourceConfiguration); - savedObjectsClient.update.mockResolvedValue({ + savedObjectsClient.create.mockResolvedValue({ ...metricsExplorerViewMock, type: metricsExplorerViewSavedObjectName, references: [], @@ -146,7 +146,7 @@ describe('MetricsExplorerViewsClient class', () => { {} ); - expect(savedObjectsClient.update).toHaveBeenCalled(); + expect(savedObjectsClient.create).toHaveBeenCalled(); expect(metricsExplorerView).toEqual(metricsExplorerViewMock); }); From 7c8dae6a7d4307104a394efc96019be6d53ad2a9 Mon Sep 17 00:00:00 2001 From: Marco Antonio Ghiani Date: Thu, 27 Apr 2023 20:16:53 +0200 Subject: [PATCH 25/39] refactor(infra): update types --- .../components/saved_views/toolbar_control.tsx | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/infra/public/components/saved_views/toolbar_control.tsx b/x-pack/plugins/infra/public/components/saved_views/toolbar_control.tsx index d608d76702fca..fb200100d05c5 100644 --- a/x-pack/plugins/infra/public/components/saved_views/toolbar_control.tsx +++ b/x-pack/plugins/infra/public/components/saved_views/toolbar_control.tsx @@ -16,8 +16,19 @@ import { UpsertViewModal } from './upsert_modal'; import { UseInventoryViewsResult } from '../../hooks/use_inventory_views'; import { UseMetricsExplorerViewsResult } from '../../hooks/use_metrics_explorer_views'; +type UseViewProps = + | 'currentView' + | 'views' + | 'isFetchingViews' + | 'isFetchingCurrentView' + | 'isCreatingView' + | 'isUpdatingView'; + type UseViewResult = UseInventoryViewsResult | UseMetricsExplorerViewsResult; -interface Props extends UseInventoryViewsResult, UseMetricsExplorerViewsResult { +type InventoryViewsResult = Pick; +type MetricsExplorerViewsResult = Pick; + +interface Props extends InventoryViewsResult, MetricsExplorerViewsResult { viewState: ViewState & { time?: number }; onCreateView: UseViewResult['createView']; onDeleteView: UseViewResult['deleteViewById']; From b526b145e211cc65e9a813ac3d453f2d737a1dee Mon Sep 17 00:00:00 2001 From: Marco Antonio Ghiani Date: Fri, 28 Apr 2023 08:48:39 +0200 Subject: [PATCH 26/39] refactor(infra): minor components changes --- .../saved_views/manage_views_flyout.tsx | 73 ++++++++++--------- .../saved_views/toolbar_control.tsx | 4 +- .../components/saved_views/upsert_modal.tsx | 1 - 3 files changed, 39 insertions(+), 39 deletions(-) diff --git a/x-pack/plugins/infra/public/components/saved_views/manage_views_flyout.tsx b/x-pack/plugins/infra/public/components/saved_views/manage_views_flyout.tsx index 8fa9422e2de15..8723641427c08 100644 --- a/x-pack/plugins/infra/public/components/saved_views/manage_views_flyout.tsx +++ b/x-pack/plugins/infra/public/components/saved_views/manage_views_flyout.tsx @@ -30,8 +30,9 @@ import { UseInventoryViewsResult } from '../../hooks/use_inventory_views'; import { UseMetricsExplorerViewsResult } from '../../hooks/use_metrics_explorer_views'; type View = InventoryView | MetricsExplorerView; +type UseViewResult = UseInventoryViewsResult | UseMetricsExplorerViewsResult; -interface Props { +export interface ManageViewsFlyoutProps { views: UseViewResult['views']; loading: boolean; onClose(): void; @@ -45,51 +46,18 @@ interface DeleteConfimationProps { onConfirm(): void; } -const DeleteConfimation = ({ isDisabled, onConfirm }: DeleteConfimationProps) => { - const [isConfirmVisible, toggleVisibility] = useToggle(false); - - return isConfirmVisible ? ( - - - - - - - - - ) : ( - - ); -}; - const searchConfig = { box: { incremental: true }, }; -export function SavedViewManageViewsFlyout({ +export function ManageViewsFlyout({ onClose, views = [], onSwitchView, onMakeDefaultView, onDeleteView, loading, -}: Props) { +}: ManageViewsFlyoutProps) { // Add name as top level property to allow in memory search const namedViews = useMemo(() => views.map(addOwnName), [views]); @@ -189,6 +157,39 @@ export function SavedViewManageViewsFlyout({ ); } +const DeleteConfimation = ({ isDisabled, onConfirm }: DeleteConfimationProps) => { + const [isConfirmVisible, toggleVisibility] = useToggle(false); + + return isConfirmVisible ? ( + + + + + + + + + ) : ( + + ); +}; + /** * Helpers */ diff --git a/x-pack/plugins/infra/public/components/saved_views/toolbar_control.tsx b/x-pack/plugins/infra/public/components/saved_views/toolbar_control.tsx index fb200100d05c5..b52d83cac60c6 100644 --- a/x-pack/plugins/infra/public/components/saved_views/toolbar_control.tsx +++ b/x-pack/plugins/infra/public/components/saved_views/toolbar_control.tsx @@ -10,7 +10,7 @@ import { i18n } from '@kbn/i18n'; import { EuiButton, EuiPopover, EuiListGroup, EuiListGroupItem } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; import { NonEmptyString } from '@kbn/io-ts-utils'; -import { SavedViewManageViewsFlyout } from './manage_views_flyout'; +import { ManageViewsFlyout } from './manage_views_flyout'; import { useBoolean } from '../../hooks/use_boolean'; import { UpsertViewModal } from './upsert_modal'; import { UseInventoryViewsResult } from '../../hooks/use_inventory_views'; @@ -186,7 +186,7 @@ export function SavedViewsToolbarControls(props: Props) { /> )} {isManageFlyoutOpen && ( - Date: Fri, 28 Apr 2023 09:53:49 +0200 Subject: [PATCH 27/39] tests(infra): update views tets --- .../test/functional/apps/infra/home_page.ts | 50 +++++++++--- .../functional/apps/infra/metrics_explorer.ts | 66 +++++++++++----- .../page_objects/infra_saved_views.ts | 76 +++++++++++++------ 3 files changed, 134 insertions(+), 58 deletions(-) diff --git a/x-pack/test/functional/apps/infra/home_page.ts b/x-pack/test/functional/apps/infra/home_page.ts index c217418f9bed3..13acd911cfbea 100644 --- a/x-pack/test/functional/apps/infra/home_page.ts +++ b/x-pack/test/functional/apps/infra/home_page.ts @@ -19,6 +19,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { const retry = getService('retry'); const pageObjects = getPageObjects(['common', 'header', 'infraHome', 'infraSavedViews']); const kibanaServer = getService('kibanaServer'); + const testSubjects = getService('testSubjects'); describe('Home page', function () { this.tags('includeFirefox'); @@ -220,30 +221,55 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { await pageObjects.infraHome.ensurePopoverClosed(); }); }); - // Failing: See https://github.com/elastic/kibana/issues/106650 + describe('Saved Views', () => { - before(() => esArchiver.load('x-pack/test/functional/es_archives/infra/metrics_and_logs')); - after(() => esArchiver.unload('x-pack/test/functional/es_archives/infra/metrics_and_logs')); - it('should have save and load controls', async () => { + before(async () => { + esArchiver.load('x-pack/test/functional/es_archives/infra/metrics_and_logs'); await pageObjects.common.navigateToApp('infraOps'); await pageObjects.infraHome.waitForLoading(); - await pageObjects.infraHome.goToTime(DATE_WITH_DATA); - await pageObjects.infraSavedViews.getSavedViewsButton(); + }); + + after(() => esArchiver.unload('x-pack/test/functional/es_archives/infra/metrics_and_logs')); + + it('should render a button with the view name', async () => { await pageObjects.infraSavedViews.ensureViewIsLoaded('Default view'); }); - it('should open popover', async () => { + it('should open/close the views popover menu on button click', async () => { await pageObjects.infraSavedViews.clickSavedViewsButton(); + testSubjects.existOrFail('savedViews-popover'); await pageObjects.infraSavedViews.closeSavedViewsPopover(); }); - it('should create new saved view and load it', async () => { - await pageObjects.infraSavedViews.clickSavedViewsButton(); - await pageObjects.infraSavedViews.clickSaveNewViewButton(); - await pageObjects.infraSavedViews.getCreateSavedViewModal(); - await pageObjects.infraSavedViews.createNewSavedView('view1'); + it('should create a new saved view and load it', async () => { + await pageObjects.infraSavedViews.createView('view1'); await pageObjects.infraSavedViews.ensureViewIsLoaded('view1'); }); + + it('should laod a clicked view from the manage views section', async () => { + await pageObjects.infraSavedViews.ensureViewIsLoaded('view1'); + const views = await pageObjects.infraSavedViews.getManageViewsEntries(); + await views[0].click(); + await pageObjects.infraSavedViews.ensureViewIsLoaded('Default view'); + }); + + it('should update the current saved view and load it', async () => { + let views = await pageObjects.infraSavedViews.getManageViewsEntries(); + expect(views.length).to.equal(2); + await pageObjects.infraSavedViews.pressEsc(); + + await pageObjects.infraSavedViews.createView('view2'); + await pageObjects.infraSavedViews.ensureViewIsLoaded('view2'); + views = await pageObjects.infraSavedViews.getManageViewsEntries(); + expect(views.length).to.equal(3); + await pageObjects.infraSavedViews.pressEsc(); + + await pageObjects.infraSavedViews.updateView('view3'); + await pageObjects.infraSavedViews.ensureViewIsLoaded('view3'); + views = await pageObjects.infraSavedViews.getManageViewsEntries(); + expect(views.length).to.equal(3); + await pageObjects.infraSavedViews.pressEsc(); + }); }); }); }; diff --git a/x-pack/test/functional/apps/infra/metrics_explorer.ts b/x-pack/test/functional/apps/infra/metrics_explorer.ts index a2b94e6126957..fb6cb97b26742 100644 --- a/x-pack/test/functional/apps/infra/metrics_explorer.ts +++ b/x-pack/test/functional/apps/infra/metrics_explorer.ts @@ -24,6 +24,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { 'timePicker', 'infraSavedViews', ]); + const testSubjects = getService('testSubjects'); describe('Metrics Explorer', function () { this.tags('includeFirefox'); @@ -97,28 +98,51 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { }); describe('Saved Views', () => { - before(() => esArchiver.load('x-pack/test/functional/es_archives/infra/metrics_and_logs')); + before(async () => { + esArchiver.load('x-pack/test/functional/es_archives/infra/metrics_and_logs'); + await pageObjects.infraHome.goToMetricExplorer(); + }); + after(() => esArchiver.unload('x-pack/test/functional/es_archives/infra/metrics_and_logs')); - describe('save functionality', () => { - it('should have saved views component', async () => { - await pageObjects.common.navigateToApp('infraOps'); - await pageObjects.infraHome.goToMetricExplorer(); - await pageObjects.infraSavedViews.getSavedViewsButton(); - await pageObjects.infraSavedViews.ensureViewIsLoaded('Default view'); - }); - - it('should open popover', async () => { - await pageObjects.infraSavedViews.clickSavedViewsButton(); - await pageObjects.infraSavedViews.closeSavedViewsPopover(); - }); - - it('should create new saved view and load it', async () => { - await pageObjects.infraSavedViews.clickSavedViewsButton(); - await pageObjects.infraSavedViews.clickSaveNewViewButton(); - await pageObjects.infraSavedViews.getCreateSavedViewModal(); - await pageObjects.infraSavedViews.createNewSavedView('view1'); - await pageObjects.infraSavedViews.ensureViewIsLoaded('view1'); - }); + + it('should render a button with the view name', async () => { + await pageObjects.infraSavedViews.ensureViewIsLoaded('Default view'); + }); + + it('should open/close the views popover menu on button click', async () => { + await pageObjects.infraSavedViews.clickSavedViewsButton(); + testSubjects.existOrFail('savedViews-popover'); + await pageObjects.infraSavedViews.closeSavedViewsPopover(); + }); + + it('should create a new saved view and load it', async () => { + await pageObjects.infraSavedViews.createView('view1'); + await pageObjects.infraSavedViews.ensureViewIsLoaded('view1'); + }); + + it('should laod a clicked view from the manage views section', async () => { + await pageObjects.infraSavedViews.ensureViewIsLoaded('view1'); + const views = await pageObjects.infraSavedViews.getManageViewsEntries(); + await views[0].click(); + await pageObjects.infraSavedViews.ensureViewIsLoaded('Default view'); + }); + + it('should update the current saved view and load it', async () => { + let views = await pageObjects.infraSavedViews.getManageViewsEntries(); + expect(views.length).to.equal(2); + await pageObjects.infraSavedViews.pressEsc(); + + await pageObjects.infraSavedViews.createView('view2'); + await pageObjects.infraSavedViews.ensureViewIsLoaded('view2'); + views = await pageObjects.infraSavedViews.getManageViewsEntries(); + expect(views.length).to.equal(3); + await pageObjects.infraSavedViews.pressEsc(); + + await pageObjects.infraSavedViews.updateView('view3'); + await pageObjects.infraSavedViews.ensureViewIsLoaded('view3'); + views = await pageObjects.infraSavedViews.getManageViewsEntries(); + expect(views.length).to.equal(3); + await pageObjects.infraSavedViews.pressEsc(); }); }); }); diff --git a/x-pack/test/functional/page_objects/infra_saved_views.ts b/x-pack/test/functional/page_objects/infra_saved_views.ts index 7125c8783c2b4..2097be3d083a8 100644 --- a/x-pack/test/functional/page_objects/infra_saved_views.ts +++ b/x-pack/test/functional/page_objects/infra_saved_views.ts @@ -15,53 +15,67 @@ export function InfraSavedViewsProvider({ getService }: FtrProviderContext) { const browser = getService('browser'); return { - async getSavedViewsButton() { - return await testSubjects.find('savedViews-openPopover'); + getSavedViewsButton() { + return testSubjects.find('savedViews-openPopover'); }, - async clickSavedViewsButton() { - return await testSubjects.click('savedViews-openPopover'); + clickSavedViewsButton() { + return testSubjects.click('savedViews-openPopover'); }, - async getSavedViewsPopoer() { - return await testSubjects.find('savedViews-popover'); + getSavedViewsPopover() { + return testSubjects.find('savedViews-popover'); + }, + + pressEsc() { + return browser.pressKeys([Key.ESCAPE]); }, async closeSavedViewsPopover() { await testSubjects.find('savedViews-popover'); - return await browser.pressKeys([Key.ESCAPE]); + return this.pressEsc(); + }, + + getLoadViewButton() { + return testSubjects.find('savedViews-loadView'); + }, + + getManageViewsButton() { + return testSubjects.find('savedViews-manageViews'); }, - async getLoadViewButton() { - return await testSubjects.find('savedViews-loadView'); + clickManageViewsButton() { + return testSubjects.click('savedViews-manageViews'); }, - async getManageViewsButton() { - return await testSubjects.find('savedViews-manageViews'); + getManageViewsFlyout() { + return testSubjects.find('loadViewsFlyout'); }, - async clickManageViewsButton() { - return await testSubjects.click('savedViews-manageViews'); + async getManageViewsEntries() { + await this.clickSavedViewsButton(); + await this.clickManageViewsButton(); + return testSubjects.findAll('infraRenderNameButton'); }, - async getUpdateViewButton() { - return await testSubjects.find('savedViews-updateView'); + getUpdateViewButton() { + return testSubjects.find('savedViews-updateView'); }, - async clickUpdateViewButton() { - return await testSubjects.click('savedViews-updateView'); + clickUpdateViewButton() { + return testSubjects.click('savedViews-updateView'); }, - async getSaveNewViewButton() { - return await testSubjects.find('savedViews-saveNewView'); + getSaveNewViewButton() { + return testSubjects.find('savedViews-saveNewView'); }, - async clickSaveNewViewButton() { - return await testSubjects.click('savedViews-saveNewView'); + clickSaveNewViewButton() { + return testSubjects.click('savedViews-saveNewView'); }, - async getCreateSavedViewModal() { - return await testSubjects.find('savedViews-upsertModal'); + getCreateSavedViewModal() { + return testSubjects.find('savedViews-upsertModal'); }, async createNewSavedView(name: string) { @@ -70,6 +84,18 @@ export function InfraSavedViewsProvider({ getService }: FtrProviderContext) { await testSubjects.missingOrFail('savedViews-upsertModal'); }, + async createView(name: string) { + await this.clickSavedViewsButton(); + await this.clickSaveNewViewButton(); + await this.createNewSavedView(name); + }, + + async updateView(name: string) { + await this.clickSavedViewsButton(); + await this.clickUpdateViewButton(); + await this.createNewSavedView(name); + }, + async ensureViewIsLoaded(name: string) { await retry.try(async () => { const subject = await testSubjects.find('savedViews-openPopover'); @@ -82,8 +108,8 @@ export function InfraSavedViewsProvider({ getService }: FtrProviderContext) { await subject.findByCssSelector(`li[title="${name}"]`); }, - async closeSavedViewsLoadModal() { - return await testSubjects.click('cancelSavedViewModal'); + closeSavedViewsLoadModal() { + return testSubjects.click('cancelSavedViewModal'); }, }; } From cca34264bfa10c09123736b338c54a7328f0e2bb Mon Sep 17 00:00:00 2001 From: Marco Antonio Ghiani Date: Fri, 28 Apr 2023 10:16:56 +0200 Subject: [PATCH 28/39] fix(infra): handle not found view with fallback to default --- .../infra/public/hooks/use_inventory_views.ts | 5 ++++- .../public/hooks/use_metrics_explorer_views.ts | 5 ++++- .../infra/public/hooks/use_saved_views_notifier.ts | 14 +++++++++----- 3 files changed, 17 insertions(+), 7 deletions(-) diff --git a/x-pack/plugins/infra/public/hooks/use_inventory_views.ts b/x-pack/plugins/infra/public/hooks/use_inventory_views.ts index 65c48bc689740..31c5cf73fd1d6 100644 --- a/x-pack/plugins/infra/public/hooks/use_inventory_views.ts +++ b/x-pack/plugins/infra/public/hooks/use_inventory_views.ts @@ -111,7 +111,10 @@ export const useInventoryViews = (): UseInventoryViewsResult => { const { data: currentView, isFetching: isFetchingCurrentView } = useQuery({ queryKey: queryKeys.getById(currentViewId), queryFn: ({ queryKey: [, id] }) => inventoryViews.client.getInventoryView(id), - onError: (error: ServerError) => notify.getViewFailure(error.body?.message ?? error.message), + onError: (error: ServerError) => { + notify.getViewFailure(error.body?.message ?? error.message); + switchViewById(defaultViewId); + }, placeholderData: null, }); diff --git a/x-pack/plugins/infra/public/hooks/use_metrics_explorer_views.ts b/x-pack/plugins/infra/public/hooks/use_metrics_explorer_views.ts index 181eb22a6eec8..34a3666b5c0e0 100644 --- a/x-pack/plugins/infra/public/hooks/use_metrics_explorer_views.ts +++ b/x-pack/plugins/infra/public/hooks/use_metrics_explorer_views.ts @@ -111,7 +111,10 @@ export const useMetricsExplorerViews = (): UseMetricsExplorerViewsResult => { const { data: currentView, isFetching: isFetchingCurrentView } = useQuery({ queryKey: queryKeys.getById(currentViewId), queryFn: ({ queryKey: [, id] }) => metricsExplorerViews.client.getMetricsExplorerView(id), - onError: (error: ServerError) => notify.getViewFailure(error.body?.message ?? error.message), + onError: (error: ServerError) => { + notify.getViewFailure(error.body?.message ?? error.message); + switchViewById(defaultViewId); + }, placeholderData: null, }); diff --git a/x-pack/plugins/infra/public/hooks/use_saved_views_notifier.ts b/x-pack/plugins/infra/public/hooks/use_saved_views_notifier.ts index b7b8822e3ec14..5fb33c92cf8fe 100644 --- a/x-pack/plugins/infra/public/hooks/use_saved_views_notifier.ts +++ b/x-pack/plugins/infra/public/hooks/use_saved_views_notifier.ts @@ -6,6 +6,7 @@ */ import { i18n } from '@kbn/i18n'; +import { useMemo } from 'react'; import { useKibanaContextForPlugin } from './use_kibana'; export const useSavedViewsNotifier = () => { @@ -44,9 +45,12 @@ export const useSavedViewsNotifier = () => { }); }; - return { - deleteViewFailure, - getViewFailure, - upsertViewFailure, - }; + return useMemo( + () => ({ + deleteViewFailure, + getViewFailure, + upsertViewFailure, + }), + [] + ); }; From 183b88caf17c376ae6ba54c7f787bc7dba729325 Mon Sep 17 00:00:00 2001 From: Marco Antonio Ghiani Date: Fri, 28 Apr 2023 10:26:16 +0200 Subject: [PATCH 29/39] fix(infra): remove memo --- .../infra/public/hooks/use_saved_views_notifier.ts | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/x-pack/plugins/infra/public/hooks/use_saved_views_notifier.ts b/x-pack/plugins/infra/public/hooks/use_saved_views_notifier.ts index 5fb33c92cf8fe..b7b8822e3ec14 100644 --- a/x-pack/plugins/infra/public/hooks/use_saved_views_notifier.ts +++ b/x-pack/plugins/infra/public/hooks/use_saved_views_notifier.ts @@ -6,7 +6,6 @@ */ import { i18n } from '@kbn/i18n'; -import { useMemo } from 'react'; import { useKibanaContextForPlugin } from './use_kibana'; export const useSavedViewsNotifier = () => { @@ -45,12 +44,9 @@ export const useSavedViewsNotifier = () => { }); }; - return useMemo( - () => ({ - deleteViewFailure, - getViewFailure, - upsertViewFailure, - }), - [] - ); + return { + deleteViewFailure, + getViewFailure, + upsertViewFailure, + }; }; From 04ac8c7146b3afb3af4d327987c1ae38b988a099 Mon Sep 17 00:00:00 2001 From: Marco Antonio Ghiani Date: Fri, 28 Apr 2023 11:37:04 +0200 Subject: [PATCH 30/39] fix(infra): fix flaky test --- x-pack/test/functional/apps/infra/metrics_explorer.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/x-pack/test/functional/apps/infra/metrics_explorer.ts b/x-pack/test/functional/apps/infra/metrics_explorer.ts index fb6cb97b26742..650040b95f420 100644 --- a/x-pack/test/functional/apps/infra/metrics_explorer.ts +++ b/x-pack/test/functional/apps/infra/metrics_explorer.ts @@ -25,6 +25,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { 'infraSavedViews', ]); const testSubjects = getService('testSubjects'); + const retry = getService('retry'); describe('Metrics Explorer', function () { this.tags('includeFirefox'); @@ -79,8 +80,10 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { }); it('should display multple charts', async () => { - const charts = await pageObjects.infraMetricsExplorer.getCharts(); - expect(charts.length).to.equal(6); + await retry.try(async () => { + const charts = await pageObjects.infraMetricsExplorer.getCharts(); + expect(charts.length).to.equal(6); + }); }); it('should render as area chart by default', async () => { From fa009c97dfe192639a3b89e471e73fffe368f927 Mon Sep 17 00:00:00 2001 From: Marco Antonio Ghiani Date: Tue, 2 May 2023 09:13:53 +0200 Subject: [PATCH 31/39] Update x-pack/plugins/infra/public/components/saved_views/manage_views_flyout.tsx Co-authored-by: jennypavlova --- .../infra/public/components/saved_views/manage_views_flyout.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/infra/public/components/saved_views/manage_views_flyout.tsx b/x-pack/plugins/infra/public/components/saved_views/manage_views_flyout.tsx index 8723641427c08..e503bdebafa03 100644 --- a/x-pack/plugins/infra/public/components/saved_views/manage_views_flyout.tsx +++ b/x-pack/plugins/infra/public/components/saved_views/manage_views_flyout.tsx @@ -25,7 +25,7 @@ import { FormattedMessage } from '@kbn/i18n-react'; import { EuiBasicTableColumn } from '@elastic/eui'; import { EuiButtonIcon } from '@elastic/eui'; import { MetricsExplorerView } from '../../../common/metrics_explorer_views'; -import { InventoryView } from '../../../common/inventory_views'; +import type { InventoryView } from '../../../common/inventory_views'; import { UseInventoryViewsResult } from '../../hooks/use_inventory_views'; import { UseMetricsExplorerViewsResult } from '../../hooks/use_metrics_explorer_views'; From 6915fb5b5f93f51833d98d5bb1b42ee4e338e1e1 Mon Sep 17 00:00:00 2001 From: Marco Antonio Ghiani Date: Tue, 2 May 2023 11:37:02 +0200 Subject: [PATCH 32/39] Update x-pack/plugins/infra/public/hooks/use_inventory_views.ts Co-authored-by: jennypavlova --- x-pack/plugins/infra/public/hooks/use_inventory_views.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/infra/public/hooks/use_inventory_views.ts b/x-pack/plugins/infra/public/hooks/use_inventory_views.ts index 31c5cf73fd1d6..a75e8beaf3f65 100644 --- a/x-pack/plugins/infra/public/hooks/use_inventory_views.ts +++ b/x-pack/plugins/infra/public/hooks/use_inventory_views.ts @@ -25,7 +25,7 @@ import { CreateInventoryViewAttributesRequestPayload, UpdateInventoryViewAttributesRequestPayload, } from '../../common/http_api/latest'; -import { InventoryView } from '../../common/inventory_views'; +import type { InventoryView } from '../../common/inventory_views'; import { useKibanaContextForPlugin } from './use_kibana'; import { useUrlState } from '../utils/use_url_state'; import { useSavedViewsNotifier } from './use_saved_views_notifier'; From bcd0eb334b016e7b3268d754a52c1fcca50fb573 Mon Sep 17 00:00:00 2001 From: Marco Antonio Ghiani Date: Tue, 2 May 2023 11:37:09 +0200 Subject: [PATCH 33/39] Update x-pack/plugins/infra/public/services/inventory_views/types.ts Co-authored-by: jennypavlova --- x-pack/plugins/infra/public/services/inventory_views/types.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/infra/public/services/inventory_views/types.ts b/x-pack/plugins/infra/public/services/inventory_views/types.ts index d17d5516563b7..573c144e9c441 100644 --- a/x-pack/plugins/infra/public/services/inventory_views/types.ts +++ b/x-pack/plugins/infra/public/services/inventory_views/types.ts @@ -10,7 +10,7 @@ import { CreateInventoryViewAttributesRequestPayload, UpdateInventoryViewAttributesRequestPayload, } from '../../../common/http_api/latest'; -import { InventoryView } from '../../../common/inventory_views'; +import type { InventoryView } from '../../../common/inventory_views'; export type InventoryViewsServiceSetup = void; From 6137f8c1eda7332b59101366e165ca5846025f6b Mon Sep 17 00:00:00 2001 From: Marco Antonio Ghiani Date: Fri, 28 Apr 2023 10:50:26 +0200 Subject: [PATCH 34/39] refactor(infra): remove dead code --- .../http_api/inventory_views/v1/common.ts | 4 - .../v1/create_inventory_view.ts | 4 - .../inventory_views/v1/find_inventory_view.ts | 2 - .../inventory_views/v1/get_inventory_view.ts | 2 - .../v1/update_inventory_view.ts | 4 - .../metrics_explorer_views/v1/common.ts | 6 - .../v1/create_metrics_explorer_view.ts | 4 - .../v1/find_metrics_explorer_view.ts | 4 - .../v1/get_metrics_explorer_view.ts | 4 - .../v1/update_metrics_explorer_view.ts | 4 - .../common/saved_objects/inventory_view.ts | 23 -- .../saved_objects/metrics_explorer_view.ts | 23 -- .../containers/saved_view/saved_view.tsx | 319 ------------------ .../hooks/use_bulk_get_saved_object.tsx | 48 --- .../public/hooks/use_create_saved_object.tsx | 54 --- .../public/hooks/use_delete_saved_object.tsx | 42 --- .../public/hooks/use_find_saved_object.tsx | 71 ---- .../public/hooks/use_get_saved_object.tsx | 46 --- .../public/hooks/use_update_saved_object.tsx | 54 --- 19 files changed, 718 deletions(-) delete mode 100644 x-pack/plugins/infra/common/saved_objects/inventory_view.ts delete mode 100644 x-pack/plugins/infra/common/saved_objects/metrics_explorer_view.ts delete mode 100644 x-pack/plugins/infra/public/containers/saved_view/saved_view.tsx delete mode 100644 x-pack/plugins/infra/public/hooks/use_bulk_get_saved_object.tsx delete mode 100644 x-pack/plugins/infra/public/hooks/use_create_saved_object.tsx delete mode 100644 x-pack/plugins/infra/public/hooks/use_delete_saved_object.tsx delete mode 100644 x-pack/plugins/infra/public/hooks/use_find_saved_object.tsx delete mode 100644 x-pack/plugins/infra/public/hooks/use_get_saved_object.tsx delete mode 100644 x-pack/plugins/infra/public/hooks/use_update_saved_object.tsx diff --git a/x-pack/plugins/infra/common/http_api/inventory_views/v1/common.ts b/x-pack/plugins/infra/common/http_api/inventory_views/v1/common.ts index 3db684628334e..65e056f30e0d9 100644 --- a/x-pack/plugins/infra/common/http_api/inventory_views/v1/common.ts +++ b/x-pack/plugins/infra/common/http_api/inventory_views/v1/common.ts @@ -29,8 +29,6 @@ export const inventoryViewRequestParamsRT = rt.type({ inventoryViewId: inventoryViewIdRT, }); -export type InventoryViewRequestParams = rt.TypeOf; - export const inventoryViewRequestQueryRT = rt.partial({ sourceId: rt.string, }); @@ -62,5 +60,3 @@ const inventoryViewResponseRT = rt.exact( export const inventoryViewResponsePayloadRT = rt.type({ data: inventoryViewResponseRT, }); - -export type InventoryViewResponsePayload = rt.TypeOf; diff --git a/x-pack/plugins/infra/common/http_api/inventory_views/v1/create_inventory_view.ts b/x-pack/plugins/infra/common/http_api/inventory_views/v1/create_inventory_view.ts index 99350daa358b0..8bad088b00542 100644 --- a/x-pack/plugins/infra/common/http_api/inventory_views/v1/create_inventory_view.ts +++ b/x-pack/plugins/infra/common/http_api/inventory_views/v1/create_inventory_view.ts @@ -23,7 +23,3 @@ export type CreateInventoryViewAttributesRequestPayload = rt.TypeOf< export const createInventoryViewRequestPayloadRT = rt.type({ attributes: createInventoryViewAttributesRequestPayloadRT, }); - -export type CreateInventoryViewRequestPayload = rt.TypeOf< - typeof createInventoryViewRequestPayloadRT ->; diff --git a/x-pack/plugins/infra/common/http_api/inventory_views/v1/find_inventory_view.ts b/x-pack/plugins/infra/common/http_api/inventory_views/v1/find_inventory_view.ts index 24812ccb43585..17a9820a93a4d 100644 --- a/x-pack/plugins/infra/common/http_api/inventory_views/v1/find_inventory_view.ts +++ b/x-pack/plugins/infra/common/http_api/inventory_views/v1/find_inventory_view.ts @@ -30,5 +30,3 @@ const findInventoryViewResponseRT = rt.exact( export const findInventoryViewResponsePayloadRT = rt.type({ data: rt.array(findInventoryViewResponseRT), }); - -export type FindInventoryViewResponsePayload = rt.TypeOf; diff --git a/x-pack/plugins/infra/common/http_api/inventory_views/v1/get_inventory_view.ts b/x-pack/plugins/infra/common/http_api/inventory_views/v1/get_inventory_view.ts index 3e862bdaa3388..8e5cef06bb916 100644 --- a/x-pack/plugins/infra/common/http_api/inventory_views/v1/get_inventory_view.ts +++ b/x-pack/plugins/infra/common/http_api/inventory_views/v1/get_inventory_view.ts @@ -10,5 +10,3 @@ import * as rt from 'io-ts'; export const getInventoryViewRequestParamsRT = rt.type({ inventoryViewId: rt.string, }); - -export type GetInventoryViewRequestParams = rt.TypeOf; diff --git a/x-pack/plugins/infra/common/http_api/inventory_views/v1/update_inventory_view.ts b/x-pack/plugins/infra/common/http_api/inventory_views/v1/update_inventory_view.ts index 7a2d33ebd6138..b21bafbecec18 100644 --- a/x-pack/plugins/infra/common/http_api/inventory_views/v1/update_inventory_view.ts +++ b/x-pack/plugins/infra/common/http_api/inventory_views/v1/update_inventory_view.ts @@ -23,7 +23,3 @@ export type UpdateInventoryViewAttributesRequestPayload = rt.TypeOf< export const updateInventoryViewRequestPayloadRT = rt.type({ attributes: updateInventoryViewAttributesRequestPayloadRT, }); - -export type UpdateInventoryViewRequestPayload = rt.TypeOf< - typeof updateInventoryViewRequestPayloadRT ->; diff --git a/x-pack/plugins/infra/common/http_api/metrics_explorer_views/v1/common.ts b/x-pack/plugins/infra/common/http_api/metrics_explorer_views/v1/common.ts index 76b6daf60a324..22b3f834cd8c0 100644 --- a/x-pack/plugins/infra/common/http_api/metrics_explorer_views/v1/common.ts +++ b/x-pack/plugins/infra/common/http_api/metrics_explorer_views/v1/common.ts @@ -29,8 +29,6 @@ export const metricsExplorerViewRequestParamsRT = rt.type({ metricsExplorerViewId: metricsExplorerViewIdRT, }); -export type MetricsExplorerViewRequestParams = rt.TypeOf; - export const metricsExplorerViewRequestQueryRT = rt.partial({ sourceId: rt.string, }); @@ -62,7 +60,3 @@ const metricsExplorerViewResponseRT = rt.exact( export const metricsExplorerViewResponsePayloadRT = rt.type({ data: metricsExplorerViewResponseRT, }); - -export type GetMetricsExplorerViewResponsePayload = rt.TypeOf< - typeof metricsExplorerViewResponsePayloadRT ->; diff --git a/x-pack/plugins/infra/common/http_api/metrics_explorer_views/v1/create_metrics_explorer_view.ts b/x-pack/plugins/infra/common/http_api/metrics_explorer_views/v1/create_metrics_explorer_view.ts index 5550404529cf1..64582a8d682dc 100644 --- a/x-pack/plugins/infra/common/http_api/metrics_explorer_views/v1/create_metrics_explorer_view.ts +++ b/x-pack/plugins/infra/common/http_api/metrics_explorer_views/v1/create_metrics_explorer_view.ts @@ -23,7 +23,3 @@ export type CreateMetricsExplorerViewAttributesRequestPayload = rt.TypeOf< export const createMetricsExplorerViewRequestPayloadRT = rt.type({ attributes: createMetricsExplorerViewAttributesRequestPayloadRT, }); - -export type CreateMetricsExplorerViewRequestPayload = rt.TypeOf< - typeof createMetricsExplorerViewRequestPayloadRT ->; diff --git a/x-pack/plugins/infra/common/http_api/metrics_explorer_views/v1/find_metrics_explorer_view.ts b/x-pack/plugins/infra/common/http_api/metrics_explorer_views/v1/find_metrics_explorer_view.ts index c504b54a4f914..4419b3d34e4bc 100644 --- a/x-pack/plugins/infra/common/http_api/metrics_explorer_views/v1/find_metrics_explorer_view.ts +++ b/x-pack/plugins/infra/common/http_api/metrics_explorer_views/v1/find_metrics_explorer_view.ts @@ -30,7 +30,3 @@ const findMetricsExplorerViewResponseRT = rt.exact( export const findMetricsExplorerViewResponsePayloadRT = rt.type({ data: rt.array(findMetricsExplorerViewResponseRT), }); - -export type FindMetricsExplorerViewResponsePayload = rt.TypeOf< - typeof findMetricsExplorerViewResponsePayloadRT ->; diff --git a/x-pack/plugins/infra/common/http_api/metrics_explorer_views/v1/get_metrics_explorer_view.ts b/x-pack/plugins/infra/common/http_api/metrics_explorer_views/v1/get_metrics_explorer_view.ts index 8a828e00c917f..aa4f37b864fea 100644 --- a/x-pack/plugins/infra/common/http_api/metrics_explorer_views/v1/get_metrics_explorer_view.ts +++ b/x-pack/plugins/infra/common/http_api/metrics_explorer_views/v1/get_metrics_explorer_view.ts @@ -10,7 +10,3 @@ import * as rt from 'io-ts'; export const getMetricsExplorerViewRequestParamsRT = rt.type({ metricsExplorerViewId: rt.string, }); - -export type GetMetricsExplorerViewRequestParams = rt.TypeOf< - typeof getMetricsExplorerViewRequestParamsRT ->; diff --git a/x-pack/plugins/infra/common/http_api/metrics_explorer_views/v1/update_metrics_explorer_view.ts b/x-pack/plugins/infra/common/http_api/metrics_explorer_views/v1/update_metrics_explorer_view.ts index 5bf327789a65c..e0df717662342 100644 --- a/x-pack/plugins/infra/common/http_api/metrics_explorer_views/v1/update_metrics_explorer_view.ts +++ b/x-pack/plugins/infra/common/http_api/metrics_explorer_views/v1/update_metrics_explorer_view.ts @@ -23,7 +23,3 @@ export type UpdateMetricsExplorerViewAttributesRequestPayload = rt.TypeOf< export const updateMetricsExplorerViewRequestPayloadRT = rt.type({ attributes: updateMetricsExplorerViewAttributesRequestPayloadRT, }); - -export type UpdateMetricsExplorerViewRequestPayload = rt.TypeOf< - typeof updateMetricsExplorerViewRequestPayloadRT ->; diff --git a/x-pack/plugins/infra/common/saved_objects/inventory_view.ts b/x-pack/plugins/infra/common/saved_objects/inventory_view.ts deleted file mode 100644 index 7e935086cb08a..0000000000000 --- a/x-pack/plugins/infra/common/saved_objects/inventory_view.ts +++ /dev/null @@ -1,23 +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 type { SavedObjectsType } from '@kbn/core/server'; - -export const inventoryViewSavedObjectName = 'inventory-view'; - -export const inventoryViewSavedObjectType: SavedObjectsType = { - name: inventoryViewSavedObjectName, - hidden: false, - namespaceType: 'single', - management: { - importableAndExportable: true, - }, - mappings: { - dynamic: false, - properties: {}, - }, -}; diff --git a/x-pack/plugins/infra/common/saved_objects/metrics_explorer_view.ts b/x-pack/plugins/infra/common/saved_objects/metrics_explorer_view.ts deleted file mode 100644 index 7f7ca7a932203..0000000000000 --- a/x-pack/plugins/infra/common/saved_objects/metrics_explorer_view.ts +++ /dev/null @@ -1,23 +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 type { SavedObjectsType } from '@kbn/core/server'; - -export const metricsExplorerViewSavedObjectName = 'metrics-explorer-view'; - -export const metricsExplorerViewSavedObjectType: SavedObjectsType = { - name: metricsExplorerViewSavedObjectName, - hidden: false, - namespaceType: 'single', - management: { - importableAndExportable: true, - }, - mappings: { - dynamic: false, - properties: {}, - }, -}; diff --git a/x-pack/plugins/infra/public/containers/saved_view/saved_view.tsx b/x-pack/plugins/infra/public/containers/saved_view/saved_view.tsx deleted file mode 100644 index 8eb3d4c50de1a..0000000000000 --- a/x-pack/plugins/infra/public/containers/saved_view/saved_view.tsx +++ /dev/null @@ -1,319 +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 createContainer from 'constate'; -import * as rt from 'io-ts'; -import { pipe } from 'fp-ts/lib/pipeable'; -import { fold } from 'fp-ts/lib/Either'; -import { constant, identity } from 'fp-ts/lib/function'; -import { useCallback, useMemo, useState, useEffect } from 'react'; -import { i18n } from '@kbn/i18n'; -import { SimpleSavedObject, SavedObjectAttributes } from '@kbn/core/public'; -import { useUrlState } from '../../utils/use_url_state'; -import { useFindSavedObject } from '../../hooks/use_find_saved_object'; -import { useCreateSavedObject } from '../../hooks/use_create_saved_object'; -import { useDeleteSavedObject } from '../../hooks/use_delete_saved_object'; -import { useSourceContext } from '../metrics_source'; -import { metricsExplorerViewSavedObjectName } from '../../../common/saved_objects/metrics_explorer_view'; -import { inventoryViewSavedObjectName } from '../../../common/saved_objects/inventory_view'; -import { useSourceConfigurationFormState } from '../../pages/metrics/settings/source_configuration_form_state'; -import { useGetSavedObject } from '../../hooks/use_get_saved_object'; -import { useUpdateSavedObject } from '../../hooks/use_update_saved_object'; - -export type SavedView = ViewState & { - name: string; - id: string; - isDefault?: boolean; -}; - -export type SavedViewSavedObject = ViewState & { - name: string; -}; - -export type ViewType = - | typeof metricsExplorerViewSavedObjectName - | typeof inventoryViewSavedObjectName; - -interface Props { - defaultViewState: SavedView; - viewType: ViewType; - shouldLoadDefault: boolean; -} - -const savedViewUrlStateRT = rt.type({ - viewId: rt.string, -}); -type SavedViewUrlState = rt.TypeOf; -const DEFAULT_SAVED_VIEW_STATE: SavedViewUrlState = { - viewId: '0', -}; - -export const useSavedView = (props: Props) => { - const { - source, - isLoading: sourceIsLoading, - sourceExists, - createSourceConfiguration, - updateSourceConfiguration, - } = useSourceContext(); - const { viewType, defaultViewState } = props; - type ViewState = typeof defaultViewState; - const { - data, - loading, - find, - error: errorOnFind, - hasView, - } = useFindSavedObject>(viewType); - const [urlState, setUrlState] = useUrlState({ - defaultState: DEFAULT_SAVED_VIEW_STATE, - decodeUrlState, - encodeUrlState, - urlStateKey: 'savedView', - }); - - const [shouldLoadDefault] = useState(props.shouldLoadDefault); - const [currentView, setCurrentView] = useState | null>(null); - const [loadingDefaultView, setLoadingDefaultView] = useState(null); - const { - create, - error: errorOnCreate, - data: createdViewData, - createdId, - } = useCreateSavedObject(viewType); - const { - update, - error: errorOnUpdate, - data: updatedViewData, - updatedId, - } = useUpdateSavedObject(viewType); - const { deleteObject, deletedId } = useDeleteSavedObject(viewType); - const { getObject, data: currentViewSavedObject } = useGetSavedObject(viewType); - const [createError, setCreateError] = useState(null); - - useEffect(() => setCreateError(errorOnCreate), [errorOnCreate]); - - const deleteView = useCallback((id: string) => deleteObject(id), [deleteObject]); - const formState = useSourceConfigurationFormState(source && source.configuration); - const defaultViewFieldName = useMemo( - () => (viewType === 'inventory-view' ? 'inventoryDefaultView' : 'metricsExplorerDefaultView'), - [viewType] - ); - - const makeDefault = useCallback( - async (id: string) => { - if (sourceExists) { - await updateSourceConfiguration({ - ...formState.formStateChanges, - [defaultViewFieldName]: id, - }); - } else { - await createSourceConfiguration({ - ...formState.formState, - [defaultViewFieldName]: id, - }); - } - }, - [ - formState.formState, - formState.formStateChanges, - sourceExists, - defaultViewFieldName, - createSourceConfiguration, - updateSourceConfiguration, - ] - ); - - const saveView = useCallback( - (d: { [p: string]: any }) => { - const doSave = async () => { - const exists = await hasView(d.name); - if (exists) { - setCreateError( - i18n.translate('xpack.infra.savedView.errorOnCreate.duplicateViewName', { - defaultMessage: `A view with that name already exists.`, - }) - ); - return; - } - create(d); - }; - setCreateError(null); - doSave(); - }, - [create, hasView] - ); - - const updateView = useCallback( - (id, d: { [p: string]: any }) => { - const doSave = async () => { - const view = await hasView(d.name); - if (view && view.id !== id) { - setCreateError( - i18n.translate('xpack.infra.savedView.errorOnCreate.duplicateViewName', { - defaultMessage: `A view with that name already exists.`, - }) - ); - return; - } - update(id, d); - }; - setCreateError(null); - doSave(); - }, - [update, hasView] - ); - - const defaultViewId = useMemo(() => { - if (!source || !source.configuration) { - return ''; - } - if (defaultViewFieldName === 'inventoryDefaultView') { - return source.configuration.inventoryDefaultView; - } else if (defaultViewFieldName === 'metricsExplorerDefaultView') { - return source.configuration.metricsExplorerDefaultView; - } else { - return ''; - } - }, [source, defaultViewFieldName]); - - const mapToView = useCallback( - (o: SimpleSavedObject) => { - return { - ...o.attributes, - id: o.id, - isDefault: defaultViewId === o.id, - }; - }, - [defaultViewId] - ); - - const savedObjects = useMemo(() => (data ? data.savedObjects : []), [data]); - - const views = useMemo(() => { - const items: Array> = [ - { - name: i18n.translate('xpack.infra.savedView.defaultViewNameHosts', { - defaultMessage: 'Default view', - }), - id: '0', - isDefault: !defaultViewId || defaultViewId === '0', // If there is no default view then hosts is the default - ...defaultViewState, - }, - ]; - - savedObjects.forEach((o) => o.type === viewType && items.push(mapToView(o))); - - return items; - }, [defaultViewState, savedObjects, viewType, defaultViewId, mapToView]); - - const createdView = useMemo(() => { - return createdViewData ? mapToView(createdViewData) : null; - }, [createdViewData, mapToView]); - - const updatedView = useMemo(() => { - return updatedViewData ? mapToView(updatedViewData) : null; - }, [updatedViewData, mapToView]); - - const loadDefaultView = useCallback(() => { - setLoadingDefaultView(true); - getObject(defaultViewId); - }, [setLoadingDefaultView, getObject, defaultViewId]); - - useEffect(() => { - if (currentViewSavedObject) { - setCurrentView(mapToView(currentViewSavedObject)); - setLoadingDefaultView(false); - } - }, [currentViewSavedObject, defaultViewId, mapToView]); - - const setDefault = useCallback(() => { - setCurrentView({ - name: i18n.translate('xpack.infra.savedView.defaultViewNameHosts', { - defaultMessage: 'Default view', - }), - id: '0', - isDefault: !defaultViewId || defaultViewId === '0', // If there is no default view then hosts is the default - ...defaultViewState, - }); - }, [setCurrentView, defaultViewId, defaultViewState]); - - const loadDefaultViewIfSet = useCallback(() => { - if (defaultViewId !== '0') { - loadDefaultView(); - } else { - setDefault(); - setLoadingDefaultView(false); - } - }, [defaultViewId, loadDefaultView, setDefault, setLoadingDefaultView]); - - useEffect(() => { - if (loadingDefaultView || currentView || !shouldLoadDefault) { - return; - } - - loadDefaultViewIfSet(); - }, [loadDefaultViewIfSet, loadingDefaultView, currentView, shouldLoadDefault]); - - useEffect(() => { - if (currentView && urlState.viewId !== currentView.id && data) - setUrlState({ viewId: currentView.id }); - }, [urlState, setUrlState, currentView, defaultViewId, data]); - - useEffect(() => { - if (!currentView && !loading && data && shouldLoadDefault) { - const viewToSet = views.find((v) => v.id === urlState.viewId); - if (viewToSet) setCurrentView(viewToSet); - else loadDefaultViewIfSet(); - } - }, [ - loading, - currentView, - data, - views, - setCurrentView, - loadDefaultViewIfSet, - urlState.viewId, - shouldLoadDefault, - ]); - - return { - views, - saveView, - defaultViewId, - loading, - updateView, - updatedView, - updatedId, - deletedId, - createdId, - createdView, - errorOnUpdate, - errorOnFind, - errorOnCreate: createError, - shouldLoadDefault, - makeDefault, - sourceIsLoading, - deleteView, - loadingDefaultView, - setCurrentView, - currentView, - loadDefaultView, - find, - }; -}; - -export const SavedView = createContainer(useSavedView); -export const [SavedViewProvider, useSavedViewContext] = SavedView; - -const encodeUrlState = (state: SavedViewUrlState) => { - return savedViewUrlStateRT.encode(state); -}; -const decodeUrlState = (value: unknown) => { - const state = pipe(savedViewUrlStateRT.decode(value), fold(constant(undefined), identity)); - return state; -}; diff --git a/x-pack/plugins/infra/public/hooks/use_bulk_get_saved_object.tsx b/x-pack/plugins/infra/public/hooks/use_bulk_get_saved_object.tsx deleted file mode 100644 index e7c77096b7978..0000000000000 --- a/x-pack/plugins/infra/public/hooks/use_bulk_get_saved_object.tsx +++ /dev/null @@ -1,48 +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 { useState, useCallback } from 'react'; -import { SavedObjectsBatchResponse } from '@kbn/core/public'; -import { useKibana } from '@kbn/kibana-react-plugin/public'; - -export const useBulkGetSavedObject = (type: string) => { - const kibana = useKibana(); - // TODO: define saved object type - const [data, setData] = useState | null>(null); - const [error, setError] = useState(null); - const [loading, setLoading] = useState(false); - - const bulkGet = useCallback( - (ids: string[]) => { - setLoading(true); - const fetchData = async () => { - try { - const savedObjectsClient = kibana.services.savedObjects?.client; - if (!savedObjectsClient) { - throw new Error('Saved objects client is unavailable'); - } - const d = await savedObjectsClient.bulkGet(ids.map((i) => ({ type, id: i }))); - setError(null); - setLoading(false); - setData(d); - } catch (e) { - setLoading(false); - setError(e); - } - }; - fetchData(); - }, - [type, kibana.services.savedObjects] - ); - - return { - data, - loading, - error, - bulkGet, - }; -}; diff --git a/x-pack/plugins/infra/public/hooks/use_create_saved_object.tsx b/x-pack/plugins/infra/public/hooks/use_create_saved_object.tsx deleted file mode 100644 index e034b2bc66f18..0000000000000 --- a/x-pack/plugins/infra/public/hooks/use_create_saved_object.tsx +++ /dev/null @@ -1,54 +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 { useState, useCallback } from 'react'; -import { - SavedObjectAttributes, - SavedObjectsCreateOptions, - SimpleSavedObject, -} from '@kbn/core/public'; -import { useKibana } from '@kbn/kibana-react-plugin/public'; - -export const useCreateSavedObject = (type: string) => { - const kibana = useKibana(); - const [data, setData] = useState | null>(null); - const [createdId, setCreatedId] = useState(null); - const [error, setError] = useState(null); - const [loading, setLoading] = useState(false); - - const create = useCallback( - (attributes: SavedObjectAttributes, options?: SavedObjectsCreateOptions) => { - setLoading(true); - const save = async () => { - try { - const savedObjectsClient = kibana.services.savedObjects?.client; - if (!savedObjectsClient) { - throw new Error('Saved objects client is unavailable'); - } - const d = await savedObjectsClient.create(type, attributes, options); - setCreatedId(d.id); - setError(null); - setData(d); - setLoading(false); - } catch (e) { - setLoading(false); - setError(e); - } - }; - save(); - }, - [type, kibana.services.savedObjects] - ); - - return { - data, - loading, - error, - create, - createdId, - }; -}; diff --git a/x-pack/plugins/infra/public/hooks/use_delete_saved_object.tsx b/x-pack/plugins/infra/public/hooks/use_delete_saved_object.tsx deleted file mode 100644 index 1bacf1efa487a..0000000000000 --- a/x-pack/plugins/infra/public/hooks/use_delete_saved_object.tsx +++ /dev/null @@ -1,42 +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 { useState, useCallback } from 'react'; -import { useKibana } from '@kbn/kibana-react-plugin/public'; - -export const useDeleteSavedObject = (type: string) => { - const kibana = useKibana(); - const [error, setError] = useState(null); - const [loading, setLoading] = useState(false); - const [deletedId, setDeletedId] = useState(null); - - const deleteObject = useCallback( - (id: string) => { - setLoading(true); - const dobj = async () => { - try { - await kibana.services.savedObjects?.client.delete(type, id); - setError(null); - setDeletedId(id); - setLoading(false); - } catch (e) { - setLoading(false); - setError(e); - } - }; - dobj(); - }, - [type, kibana.services.savedObjects] - ); - - return { - loading, - error, - deleteObject, - deletedId, - }; -}; diff --git a/x-pack/plugins/infra/public/hooks/use_find_saved_object.tsx b/x-pack/plugins/infra/public/hooks/use_find_saved_object.tsx deleted file mode 100644 index 60754961e05ab..0000000000000 --- a/x-pack/plugins/infra/public/hooks/use_find_saved_object.tsx +++ /dev/null @@ -1,71 +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 { useState, useCallback } from 'react'; -import { SavedObjectAttributes, SavedObjectsBatchResponse } from '@kbn/core/public'; -import { useUiTracker } from '@kbn/observability-plugin/public'; -import { useKibana } from '@kbn/kibana-react-plugin/public'; - -export const useFindSavedObject = (type: string) => { - const trackMetric = useUiTracker({ app: 'infra_metrics' }); - const kibana = useKibana(); - const [data, setData] = useState | null>(null); - const [error, setError] = useState(null); - const [loading, setLoading] = useState(false); - const find = useCallback( - (query?: string, searchFields: string[] = []) => { - setLoading(true); - const fetchData = async () => { - try { - const savedObjectsClient = kibana.services.savedObjects?.client; - if (!savedObjectsClient) { - throw new Error('Saved objects client is unavailable'); - } - const d = await savedObjectsClient.find({ - type, - search: query, - searchFields, - page: 1, - perPage: 1000, - }); - setError(null); - setLoading(false); - setData(d); - if (d.total > 1000) { - trackMetric({ metric: `over_1000_saved_objects_for_${type}` }); - } else { - trackMetric({ metric: `under_1000_saved_objects_for_${type}` }); - } - } catch (e) { - setLoading(false); - setError(e); - } - }; - fetchData(); - }, - [type, kibana.services.savedObjects, trackMetric] - ); - - const hasView = async (name: string) => { - const savedObjectsClient = kibana.services.savedObjects?.client; - if (!savedObjectsClient) { - throw new Error('Saved objects client is unavailable'); - } - const objects = await savedObjectsClient.find({ - type, - }); - return objects.savedObjects.find((o) => o.attributes.name === name); - }; - - return { - hasView, - data, - loading, - error, - find, - }; -}; diff --git a/x-pack/plugins/infra/public/hooks/use_get_saved_object.tsx b/x-pack/plugins/infra/public/hooks/use_get_saved_object.tsx deleted file mode 100644 index 0dad4a8d2cb56..0000000000000 --- a/x-pack/plugins/infra/public/hooks/use_get_saved_object.tsx +++ /dev/null @@ -1,46 +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 { useState, useCallback } from 'react'; -import { SavedObjectAttributes, SimpleSavedObject } from '@kbn/core/public'; -import { useKibana } from '@kbn/kibana-react-plugin/public'; - -export const useGetSavedObject = (type: string) => { - const kibana = useKibana(); - const [data, setData] = useState | null>(null); - const [error, setError] = useState(null); - const [loading, setLoading] = useState(false); - const getObject = useCallback( - (id: string) => { - setLoading(true); - const fetchData = async () => { - try { - const savedObjectsClient = kibana.services.savedObjects?.client; - if (!savedObjectsClient) { - throw new Error('Saved objects client is unavailable'); - } - const d = await savedObjectsClient.get(type, id); - setError(null); - setLoading(false); - setData(d); - } catch (e) { - setLoading(false); - setError(e); - } - }; - fetchData(); - }, - [type, kibana.services.savedObjects] - ); - - return { - data, - loading, - error, - getObject, - }; -}; diff --git a/x-pack/plugins/infra/public/hooks/use_update_saved_object.tsx b/x-pack/plugins/infra/public/hooks/use_update_saved_object.tsx deleted file mode 100644 index 0f0d38648169e..0000000000000 --- a/x-pack/plugins/infra/public/hooks/use_update_saved_object.tsx +++ /dev/null @@ -1,54 +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 { useState, useCallback } from 'react'; -import { - SavedObjectAttributes, - SavedObjectsCreateOptions, - SimpleSavedObject, -} from '@kbn/core/public'; -import { useKibana } from '@kbn/kibana-react-plugin/public'; - -export const useUpdateSavedObject = (type: string) => { - const kibana = useKibana(); - const [data, setData] = useState | null>(null); - const [updatedId, setUpdatedId] = useState(null); - const [error, setError] = useState(null); - const [loading, setLoading] = useState(false); - - const update = useCallback( - (id: string, attributes: SavedObjectAttributes, options?: SavedObjectsCreateOptions) => { - setLoading(true); - const save = async () => { - try { - const savedObjectsClient = kibana.services.savedObjects?.client; - if (!savedObjectsClient) { - throw new Error('Saved objects client is unavailable'); - } - const d = await savedObjectsClient.update(type, id, attributes, options); - setUpdatedId(d.id); - setError(null); - setData(d); - setLoading(false); - } catch (e) { - setLoading(false); - setError(e); - } - }; - save(); - }, - [type, kibana.services.savedObjects] - ); - - return { - data, - loading, - error, - update, - updatedId, - }; -}; From 3fad0e1624c9e87f896dc4406bf5434232922b68 Mon Sep 17 00:00:00 2001 From: Marco Antonio Ghiani Date: Fri, 28 Apr 2023 10:54:02 +0200 Subject: [PATCH 35/39] refactor(infra): remove dead testSubject code --- .../page_objects/infra_saved_views.ts | 41 ------------------- 1 file changed, 41 deletions(-) diff --git a/x-pack/test/functional/page_objects/infra_saved_views.ts b/x-pack/test/functional/page_objects/infra_saved_views.ts index 2097be3d083a8..55dfa7a060bf2 100644 --- a/x-pack/test/functional/page_objects/infra_saved_views.ts +++ b/x-pack/test/functional/page_objects/infra_saved_views.ts @@ -15,18 +15,10 @@ export function InfraSavedViewsProvider({ getService }: FtrProviderContext) { const browser = getService('browser'); return { - getSavedViewsButton() { - return testSubjects.find('savedViews-openPopover'); - }, - clickSavedViewsButton() { return testSubjects.click('savedViews-openPopover'); }, - getSavedViewsPopover() { - return testSubjects.find('savedViews-popover'); - }, - pressEsc() { return browser.pressKeys([Key.ESCAPE]); }, @@ -36,48 +28,24 @@ export function InfraSavedViewsProvider({ getService }: FtrProviderContext) { return this.pressEsc(); }, - getLoadViewButton() { - return testSubjects.find('savedViews-loadView'); - }, - - getManageViewsButton() { - return testSubjects.find('savedViews-manageViews'); - }, - clickManageViewsButton() { return testSubjects.click('savedViews-manageViews'); }, - getManageViewsFlyout() { - return testSubjects.find('loadViewsFlyout'); - }, - async getManageViewsEntries() { await this.clickSavedViewsButton(); await this.clickManageViewsButton(); return testSubjects.findAll('infraRenderNameButton'); }, - getUpdateViewButton() { - return testSubjects.find('savedViews-updateView'); - }, - clickUpdateViewButton() { return testSubjects.click('savedViews-updateView'); }, - getSaveNewViewButton() { - return testSubjects.find('savedViews-saveNewView'); - }, - clickSaveNewViewButton() { return testSubjects.click('savedViews-saveNewView'); }, - getCreateSavedViewModal() { - return testSubjects.find('savedViews-upsertModal'); - }, - async createNewSavedView(name: string) { await testSubjects.setValue('savedViewName', name); await testSubjects.click('createSavedViewButton'); @@ -102,14 +70,5 @@ export function InfraSavedViewsProvider({ getService }: FtrProviderContext) { expect(await subject.getVisibleText()).to.be(name); }); }, - - async ensureViewIsLoadable(name: string) { - const subject = await testSubjects.find('savedViews-loadList'); - await subject.findByCssSelector(`li[title="${name}"]`); - }, - - closeSavedViewsLoadModal() { - return testSubjects.click('cancelSavedViewModal'); - }, }; } From 63a59ff0422af36987301baf917e71780a48bf50 Mon Sep 17 00:00:00 2001 From: Marco Antonio Ghiani Date: Tue, 2 May 2023 10:15:28 +0200 Subject: [PATCH 36/39] fix(infra): remove translations --- x-pack/plugins/translations/translations/fr-FR.json | 1 - x-pack/plugins/translations/translations/ja-JP.json | 1 - x-pack/plugins/translations/translations/zh-CN.json | 1 - 3 files changed, 3 deletions(-) diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index a82ea33392517..1224793423a9b 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -17883,7 +17883,6 @@ "xpack.infra.registerFeatures.logsTitle": "Logs", "xpack.infra.sampleDataLinkLabel": "Logs", "xpack.infra.savedView.defaultViewNameHosts": "Vue par défaut", - "xpack.infra.savedView.errorOnCreate.duplicateViewName": "Une vue portant ce nom existe déjà.", "xpack.infra.savedView.errorOnCreate.title": "Une erreur s'est produite lors de l'enregistrement de la vue.", "xpack.infra.savedView.findError.title": "Une erreur s'est produite lors du chargement des vues.", "xpack.infra.savedView.manageViews": "Gérer les vues", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 3c793b60772bc..a689aff121cb6 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -17882,7 +17882,6 @@ "xpack.infra.registerFeatures.logsTitle": "ログ", "xpack.infra.sampleDataLinkLabel": "ログ", "xpack.infra.savedView.defaultViewNameHosts": "デフォルトビュー", - "xpack.infra.savedView.errorOnCreate.duplicateViewName": "その名前のビューはすでに存在します。", "xpack.infra.savedView.errorOnCreate.title": "ビューの保存中にエラーが発生しました。", "xpack.infra.savedView.findError.title": "ビューの読み込み中にエラーが発生しました。", "xpack.infra.savedView.manageViews": "ビューの管理", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index d20ad056909de..b052e2f5fdb3b 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -17884,7 +17884,6 @@ "xpack.infra.registerFeatures.logsTitle": "日志", "xpack.infra.sampleDataLinkLabel": "日志", "xpack.infra.savedView.defaultViewNameHosts": "默认视图", - "xpack.infra.savedView.errorOnCreate.duplicateViewName": "具有该名称的视图已存在。", "xpack.infra.savedView.errorOnCreate.title": "保存视图时出错。", "xpack.infra.savedView.findError.title": "加载视图时出错。", "xpack.infra.savedView.manageViews": "管理视图", From 624825b5ffd1f30c75f93b925e28dc8de71ac12a Mon Sep 17 00:00:00 2001 From: Marco Antonio Ghiani Date: Tue, 2 May 2023 10:17:51 +0200 Subject: [PATCH 37/39] fix(infra): fix type --- .../public/pages/metrics/inventory_view/components/layout.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/layout.tsx b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/layout.tsx index 9827c866b1424..8fa9e3f83e706 100644 --- a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/layout.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/layout.tsx @@ -12,8 +12,8 @@ import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { euiStyled } from '@kbn/kibana-react-plugin/common'; import { i18n } from '@kbn/i18n'; import useLocalStorage from 'react-use/lib/useLocalStorage'; +import { InventoryView } from '../../../../../common/inventory_views'; import { SnapshotNode } from '../../../../../common/http_api'; -import { SavedView } from '../../../../containers/saved_view/saved_view'; import { AutoSizer } from '../../../../components/auto_sizer'; import { NodesOverview } from './nodes_overview'; import { calculateBoundsFromNodes } from '../lib/calculate_bounds_from_nodes'; @@ -36,7 +36,7 @@ import { LegendControls } from './waffle/legend_controls'; import { TryItButton } from '../../../../components/try_it_button'; interface Props { - currentView: SavedView | null; + currentView: InventoryView; reload: () => Promise; interval: string; nodes: SnapshotNode[]; From 6ab5560286a20fa5524291225925041303996fb2 Mon Sep 17 00:00:00 2001 From: Marco Antonio Ghiani Date: Tue, 2 May 2023 10:47:30 +0200 Subject: [PATCH 38/39] fix(infra): fix type --- .../public/pages/metrics/inventory_view/components/layout.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/layout.tsx b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/layout.tsx index 8fa9e3f83e706..14d0ab3c55939 100644 --- a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/layout.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/layout.tsx @@ -36,7 +36,7 @@ import { LegendControls } from './waffle/legend_controls'; import { TryItButton } from '../../../../components/try_it_button'; interface Props { - currentView: InventoryView; + currentView?: InventoryView | null; reload: () => Promise; interval: string; nodes: SnapshotNode[]; From 28a1d5dd7b1712efb67d1122639bafce71ef2657 Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Tue, 2 May 2023 13:37:46 +0000 Subject: [PATCH 39/39] [CI] Auto-commit changed files from 'node scripts/precommit_hook.js --ref HEAD~1..HEAD --fix' --- x-pack/test/functional/page_objects/infra_saved_views.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/test/functional/page_objects/infra_saved_views.ts b/x-pack/test/functional/page_objects/infra_saved_views.ts index 46db9d52973e4..8d7bf2a71a869 100644 --- a/x-pack/test/functional/page_objects/infra_saved_views.ts +++ b/x-pack/test/functional/page_objects/infra_saved_views.ts @@ -68,6 +68,6 @@ export function InfraSavedViewsProvider({ getService }: FtrProviderContext) { const subject = await testSubjects.find('savedViews-openPopover'); expect(await subject.getVisibleText()).to.be(name); }); - } + }, }; }