From 2f678744ab3b512cf5e212671a35b81edd1aeec9 Mon Sep 17 00:00:00 2001 From: Shahzad Date: Wed, 16 Oct 2024 21:08:34 +0200 Subject: [PATCH] [Synthetic] Show monitors from all permitted spaces !! (#196109) ## Summary Fixes https://github.com/elastic/kibana/issues/194760 !! Fixes https://github.com/elastic/kibana/issues/169753 !! Added an options to list monitors from all spaces which user has permission for , user can either select default option which is to get monitors from current space or all permitted spaces !! ### Testing Create monitors in 3 spaces, assign 2 spaces to a role, and create a user. Make sure monitors only appears to which user have space permission. image --------- Co-authored-by: Justin Kambic --- .../runtime_types/monitor_management/state.ts | 2 + .../synthetics_overview_status.ts | 1 + .../synthetics/e2e/helpers/record_video.ts | 13 +- .../custom_status_alert.journey.ts | 1 + .../public/apps/locators/edit_monitor.ts | 4 +- .../public/apps/locators/monitor_detail.ts | 19 ++- .../alerting_callout.test.tsx | 16 +- .../alerting_callout/alerting_callout.tsx | 3 +- .../components/monitor_details_panel.tsx | 11 +- .../monitor_add_edit/form/run_test_btn.tsx | 7 +- .../hooks/use_monitor_save.tsx | 3 + .../monitor_add_edit/monitor_edit_page.tsx | 6 +- .../hooks/use_selected_monitor.tsx | 30 +++- .../monitor_details_location.tsx | 10 +- .../monitor_summary/edit_monitor_link.tsx | 9 +- .../monitor_details/run_test_manually.tsx | 10 +- .../monitor_filters/use_filters.test.tsx | 4 +- .../common/monitor_filters/use_filters.ts | 12 +- .../monitors_page/common/show_all_spaces.tsx | 150 ++++++++++++++++++ .../management/monitor_list_table/columns.tsx | 7 + .../monitor_list_table/delete_monitor.tsx | 6 +- .../monitor_list_header.tsx | 6 +- .../overview/overview/actions_popover.tsx | 3 +- .../overview/overview/metric_item.tsx | 4 +- .../overview/monitor_detail_flyout.test.tsx | 10 +- .../overview/monitor_detail_flyout.tsx | 27 ++-- .../overview/overview/overview_grid.tsx | 6 + .../monitors_page/overview/overview/types.ts | 1 + .../components/settings/hooks/api.ts | 5 +- .../hooks/use_edit_monitor_locator.ts | 8 +- .../hooks/use_monitor_detail_locator.ts | 7 +- .../state/manual_test_runs/actions.ts | 1 + .../synthetics/state/manual_test_runs/api.ts | 22 ++- .../state/monitor_details/actions.ts | 7 +- .../synthetics/state/monitor_details/api.ts | 3 + .../synthetics/state/monitor_list/actions.ts | 9 +- .../apps/synthetics/state/monitor_list/api.ts | 23 ++- .../synthetics/state/monitor_list/models.ts | 1 + .../state/monitor_management/api.ts | 3 + .../apps/synthetics/state/overview/models.ts | 8 +- .../state/overview_status/actions.ts | 1 + .../synthetics/state/overview_status/api.ts | 1 + .../synthetics/state/overview_status/index.ts | 7 + .../synthetics/utils/filters/filter_fields.ts | 2 +- .../url_params/get_supported_url_params.ts | 3 + .../public/utils/api_service/api_service.ts | 54 +++++-- .../server/queries/query_monitor_status.ts | 1 + .../synthetics/server/routes/common.ts | 10 +- .../server/routes/filters/filters.ts | 11 +- .../routes/monitor_cruds/get_monitor.ts | 19 ++- .../routes/monitor_cruds/get_monitors_list.ts | 12 +- .../overview_status/overview_status.test.ts | 9 ++ .../routes/overview_status/overview_status.ts | 3 +- .../synthetics_monitor/get_all_monitors.ts | 3 + .../apis/synthetics/add_monitor.ts | 9 +- .../apis/synthetics/edit_monitor.ts | 2 +- .../synthetics/enable_default_alerting.ts | 2 +- .../apis/synthetics/get_monitor.ts | 80 +++++++--- .../synthetics_monitor_test_service.ts | 5 +- 59 files changed, 584 insertions(+), 128 deletions(-) create mode 100644 x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/common/show_all_spaces.tsx diff --git a/x-pack/plugins/observability_solution/synthetics/common/runtime_types/monitor_management/state.ts b/x-pack/plugins/observability_solution/synthetics/common/runtime_types/monitor_management/state.ts index 6bde68b526723f..1eafbd2cd55de1 100644 --- a/x-pack/plugins/observability_solution/synthetics/common/runtime_types/monitor_management/state.ts +++ b/x-pack/plugins/observability_solution/synthetics/common/runtime_types/monitor_management/state.ts @@ -21,6 +21,7 @@ export const FetchMonitorManagementListQueryArgsCodec = t.partial({ schedules: t.array(t.string), monitorQueryIds: t.array(t.string), internal: t.boolean, + showFromAllSpaces: t.boolean, }); export type FetchMonitorManagementListQueryArgs = t.TypeOf< @@ -38,6 +39,7 @@ export const FetchMonitorOverviewQueryArgsCodec = t.partial({ monitorQueryIds: t.array(t.string), sortField: t.string, sortOrder: t.string, + showFromAllSpaces: t.boolean, }); export type FetchMonitorOverviewQueryArgs = t.TypeOf; diff --git a/x-pack/plugins/observability_solution/synthetics/common/runtime_types/monitor_management/synthetics_overview_status.ts b/x-pack/plugins/observability_solution/synthetics/common/runtime_types/monitor_management/synthetics_overview_status.ts index 06c877da7c5623..aec3d3ac3390fc 100644 --- a/x-pack/plugins/observability_solution/synthetics/common/runtime_types/monitor_management/synthetics_overview_status.ts +++ b/x-pack/plugins/observability_solution/synthetics/common/runtime_types/monitor_management/synthetics_overview_status.ts @@ -53,6 +53,7 @@ export const OverviewStatusMetaDataCodec = t.intersection([ updated_at: t.string, ping: OverviewPingCodec, timestamp: t.string, + spaceId: t.string, }), ]); diff --git a/x-pack/plugins/observability_solution/synthetics/e2e/helpers/record_video.ts b/x-pack/plugins/observability_solution/synthetics/e2e/helpers/record_video.ts index 23bcdfb643e729..76b869504d02c4 100644 --- a/x-pack/plugins/observability_solution/synthetics/e2e/helpers/record_video.ts +++ b/x-pack/plugins/observability_solution/synthetics/e2e/helpers/record_video.ts @@ -18,11 +18,14 @@ export const recordVideo = (page: Page, postfix = '') => { after(async () => { try { const videoFilePath = await page.video()?.path(); - const pathToVideo = videoFilePath?.replace('.journeys/videos/', '').replace('.webm', ''); - const newVideoPath = videoFilePath?.replace( - pathToVideo!, - postfix ? runner.currentJourney!.name + `-${postfix}` : runner.currentJourney!.name - ); + const fileName = videoFilePath?.split('/').pop(); + const fName = fileName?.split('.').shift(); + + const name = postfix + ? runner.currentJourney!.name + `-${postfix}` + : runner.currentJourney!.name; + + const newVideoPath = videoFilePath?.replace(fName!, name); fs.renameSync(videoFilePath!, newVideoPath!); } catch (e) { // eslint-disable-next-line no-console diff --git a/x-pack/plugins/observability_solution/synthetics/e2e/synthetics/journeys/alert_rules/custom_status_alert.journey.ts b/x-pack/plugins/observability_solution/synthetics/e2e/synthetics/journeys/alert_rules/custom_status_alert.journey.ts index 161a58d650e6c9..58f59995faabc9 100644 --- a/x-pack/plugins/observability_solution/synthetics/e2e/synthetics/journeys/alert_rules/custom_status_alert.journey.ts +++ b/x-pack/plugins/observability_solution/synthetics/e2e/synthetics/journeys/alert_rules/custom_status_alert.journey.ts @@ -48,6 +48,7 @@ journey(`CustomStatusAlert`, async ({ page, params }) => { step('should create status rule', async () => { await page.getByTestId('syntheticsRefreshButtonButton').click(); + await page.waitForTimeout(5000); await page.getByTestId('syntheticsAlertsRulesButton').click(); await page.getByTestId('manageStatusRuleName').click(); await page.getByTestId('createNewStatusRule').click(); diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/locators/edit_monitor.ts b/x-pack/plugins/observability_solution/synthetics/public/apps/locators/edit_monitor.ts index edc05addd6633b..276e9eda3140f3 100644 --- a/x-pack/plugins/observability_solution/synthetics/public/apps/locators/edit_monitor.ts +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/locators/edit_monitor.ts @@ -7,10 +7,10 @@ import { syntheticsEditMonitorLocatorID } from '@kbn/observability-plugin/common'; -async function navigate({ configId }: { configId: string }) { +async function navigate({ configId, spaceId }: { configId: string; spaceId?: string }) { return { app: 'synthetics', - path: `/edit-monitor/${configId}`, + path: `/edit-monitor/${configId}` + (spaceId ? `?spaceId=${spaceId}` : ''), state: {}, }; } diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/locators/monitor_detail.ts b/x-pack/plugins/observability_solution/synthetics/public/apps/locators/monitor_detail.ts index de26b30e220224..d79cce62fedf9a 100644 --- a/x-pack/plugins/observability_solution/synthetics/public/apps/locators/monitor_detail.ts +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/locators/monitor_detail.ts @@ -7,11 +7,24 @@ import { syntheticsMonitorDetailLocatorID } from '@kbn/observability-plugin/common'; -async function navigate({ configId, locationId }: { configId: string; locationId?: string }) { - const locationUrlQueryParam = locationId ? `?locationId=${locationId}` : ''; +async function navigate({ + configId, + locationId, + spaceId, +}: { + configId: string; + locationId?: string; + spaceId?: string; +}) { + let queryParam = locationId ? `?locationId=${locationId}` : ''; + + if (spaceId) { + queryParam += queryParam ? `&spaceId=${spaceId}` : `?spaceId=${spaceId}`; + } + return { app: 'synthetics', - path: `/monitor/${configId}${locationUrlQueryParam}`, + path: `/monitor/${configId}${queryParam}`, state: {}, }; } diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/common/alerting_callout/alerting_callout.test.tsx b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/common/alerting_callout/alerting_callout.test.tsx index 8401dd24c24316..f6cbde251a9250 100644 --- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/common/alerting_callout/alerting_callout.test.tsx +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/common/alerting_callout/alerting_callout.test.tsx @@ -35,7 +35,13 @@ describe('AlertingCallout', () => { const { getByText, queryByText } = render(, { state: { dynamicSettings: { - ...(shouldShowCallout ? { settings: {} } : {}), + ...(shouldShowCallout + ? { + settings: { + defaultTLSRuleEnabled: true, + }, + } + : {}), }, defaultAlerting: { data: { @@ -85,7 +91,13 @@ describe('AlertingCallout', () => { { state: { dynamicSettings: { - ...(shouldShowCallout ? { settings: {} } : {}), + ...(shouldShowCallout + ? { + settings: { + defaultTLSRuleEnabled: true, + }, + } + : {}), }, defaultAlerting: { data: { diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/common/alerting_callout/alerting_callout.tsx b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/common/alerting_callout/alerting_callout.tsx index a6353c674d7c0f..aef2906d2206da 100644 --- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/common/alerting_callout/alerting_callout.tsx +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/common/alerting_callout/alerting_callout.tsx @@ -34,6 +34,7 @@ export const AlertingCallout = ({ isAlertingEnabled }: { isAlertingEnabled?: boo const { settings } = useSelector(selectDynamicSettings); const hasDefaultConnector = !settings || !isEmpty(settings?.defaultConnectors); + const defaultRuleEnabled = settings?.defaultTLSRuleEnabled || settings?.defaultStatusRuleEnabled; const { canSave } = useSyntheticsSettingsContext(); @@ -55,7 +56,7 @@ export const AlertingCallout = ({ isAlertingEnabled }: { isAlertingEnabled?: boo (monitorsLoaded && monitors.some((monitor) => monitor[ConfigKey.ALERT_CONFIG]?.status?.enabled)); - const showCallout = !hasDefaultConnector && hasAlertingConfigured; + const showCallout = !hasDefaultConnector && hasAlertingConfigured && defaultRuleEnabled; const hasDefaultRules = !rulesLoaded || Boolean(defaultRules?.statusRule && defaultRules?.tlsRule); const missingRules = !hasDefaultRules && !canSave; diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/common/components/monitor_details_panel.tsx b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/common/components/monitor_details_panel.tsx index 1222455443bbf7..212fbbb8ec71cb 100644 --- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/common/components/monitor_details_panel.tsx +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/common/components/monitor_details_panel.tsx @@ -19,6 +19,7 @@ import { i18n } from '@kbn/i18n'; import { useDispatch } from 'react-redux'; import { TagsList } from '@kbn/observability-shared-plugin/public'; import { isEmpty } from 'lodash'; +import { useKibanaSpace } from '../../../../../hooks/use_kibana_space'; import { PanelWithTitle } from './panel_with_title'; import { MonitorEnabled } from '../../monitors_page/management/monitor_list_table/monitor_enabled'; import { getMonitorAction } from '../../../state'; @@ -32,6 +33,7 @@ import { } from '../../../../../../common/runtime_types'; import { MonitorTypeBadge } from './monitor_type_badge'; import { useDateFormat } from '../../../../../hooks/use_date_format'; +import { useGetUrlParams } from '../../../hooks'; export interface MonitorDetailsPanelProps { latestPing?: Ping; @@ -53,6 +55,8 @@ export const MonitorDetailsPanel = ({ hasBorder = true, }: MonitorDetailsPanelProps) => { const dispatch = useDispatch(); + const { space } = useKibanaSpace(); + const { spaceId } = useGetUrlParams(); if (!monitor) { return ; @@ -81,7 +85,12 @@ export const MonitorDetailsPanel = ({ configId={configId} monitor={monitor} reloadPage={() => { - dispatch(getMonitorAction.get({ monitorId: configId })); + dispatch( + getMonitorAction.get({ + monitorId: configId, + ...(spaceId && spaceId !== space?.id ? { spaceId } : {}), + }) + ); }} /> )} diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitor_add_edit/form/run_test_btn.tsx b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitor_add_edit/form/run_test_btn.tsx index 00ddc5b4a7ef3d..79b6fc76334f07 100644 --- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitor_add_edit/form/run_test_btn.tsx +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitor_add_edit/form/run_test_btn.tsx @@ -11,10 +11,12 @@ import { useFormContext } from 'react-hook-form'; import { i18n } from '@kbn/i18n'; import { v4 as uuidv4 } from 'uuid'; import { useFetcher } from '@kbn/observability-shared-plugin/public'; +import { useKibanaSpace } from '../../../../../hooks/use_kibana_space'; import { TestNowModeFlyout, TestRun } from '../../test_now_mode/test_now_mode_flyout'; import { format } from './formatter'; import { MonitorFields as MonitorFieldsType } from '../../../../../../common/runtime_types'; import { runOnceMonitor } from '../../../state/manual_test_runs/api'; +import { useGetUrlParams } from '../../../hooks'; export const RunTestButton = ({ canUsePublicLocations = true, @@ -27,6 +29,8 @@ export const RunTestButton = ({ const [inProgress, setInProgress] = useState(false); const [testRun, setTestRun] = useState(); + const { space } = useKibanaSpace(); + const { spaceId } = useGetUrlParams(); const handleTestNow = () => { const config = getValues() as MonitorFieldsType; @@ -50,9 +54,10 @@ export const RunTestButton = ({ return runOnceMonitor({ monitor: testRun.monitor, id: testRun.id, + ...(spaceId && spaceId !== space?.id ? { spaceId } : {}), }); } - }, [testRun?.id, testRun?.monitor]); + }, [space?.id, spaceId, testRun?.id, testRun?.monitor]); const { tooltipContent, isDisabled } = useTooltipContent(formState.isValid, inProgress); diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitor_add_edit/hooks/use_monitor_save.tsx b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitor_add_edit/hooks/use_monitor_save.tsx index 44def6edee978f..4bd8b503a247ce 100644 --- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitor_add_edit/hooks/use_monitor_save.tsx +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitor_add_edit/hooks/use_monitor_save.tsx @@ -11,6 +11,7 @@ import { useParams, useRouteMatch } from 'react-router-dom'; import React, { useEffect } from 'react'; import { useDispatch } from 'react-redux'; import { i18n } from '@kbn/i18n'; +import { useGetUrlParams } from '../../../hooks'; import { MONITOR_EDIT_ROUTE } from '../../../../../../common/constants'; import { SyntheticsMonitor } from '../../../../../../common/runtime_types'; import { createMonitorAPI, updateMonitorAPI } from '../../../state/monitor_management/api'; @@ -22,6 +23,7 @@ export const useMonitorSave = ({ monitorData }: { monitorData?: SyntheticsMonito const dispatch = useDispatch(); const { refreshApp } = useSyntheticsRefreshContext(); const { monitorId } = useParams<{ monitorId: string }>(); + const { spaceId } = useGetUrlParams(); const editRouteMatch = useRouteMatch({ path: MONITOR_EDIT_ROUTE }); const isEdit = editRouteMatch?.isExact; @@ -31,6 +33,7 @@ export const useMonitorSave = ({ monitorData }: { monitorData?: SyntheticsMonito if (isEdit) { return updateMonitorAPI({ id: monitorId, + spaceId, monitor: monitorData, }); } else { diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitor_add_edit/monitor_edit_page.tsx b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitor_add_edit/monitor_edit_page.tsx index 9bfd306c4a6d94..976ff942164b13 100644 --- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitor_add_edit/monitor_edit_page.tsx +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitor_add_edit/monitor_edit_page.tsx @@ -33,11 +33,13 @@ import { MonitorDetailsLinkPortal } from './monitor_details_portal'; import { useMonitorAddEditBreadcrumbs } from './use_breadcrumbs'; import { EDIT_MONITOR_STEPS } from './steps/step_config'; import { useMonitorNotFound } from './hooks/use_monitor_not_found'; +import { useGetUrlParams } from '../../hooks'; export const MonitorEditPage: React.FC = () => { useTrackPageview({ app: 'synthetics', path: 'edit-monitor' }); useTrackPageview({ app: 'synthetics', path: 'edit-monitor', delay: 15000 }); const { monitorId } = useParams<{ monitorId: string }>(); + const { spaceId } = useGetUrlParams(); useMonitorAddEditBreadcrumbs(true); const dispatch = useDispatch(); const { locationsLoaded, error: locationsError } = useSelector(selectServiceLocationsState); @@ -53,8 +55,8 @@ export const MonitorEditPage: React.FC = () => { const error = useSelector(selectSyntheticsMonitorError); useEffect(() => { - dispatch(getMonitorAction.get({ monitorId })); - }, [dispatch, monitorId]); + dispatch(getMonitorAction.get({ monitorId, spaceId })); + }, [dispatch, monitorId, spaceId]); const monitorNotFoundError = useMonitorNotFound(error, data?.id); diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitor_details/hooks/use_selected_monitor.tsx b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitor_details/hooks/use_selected_monitor.tsx index 1f4fab7e0d9778..57c5952771f7a8 100644 --- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitor_details/hooks/use_selected_monitor.tsx +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitor_details/hooks/use_selected_monitor.tsx @@ -7,6 +7,7 @@ import { useEffect, useMemo } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import { useParams } from 'react-router-dom'; +import { useKibanaSpace } from '../../../../../hooks/use_kibana_space'; import { ConfigKey } from '../../../../../../common/runtime_types'; import { useSyntheticsRefreshContext } from '../../../contexts'; import { @@ -16,10 +17,13 @@ import { selectorMonitorDetailsState, selectorError, } from '../../../state'; +import { useGetUrlParams } from '../../../hooks'; export const useSelectedMonitor = (monId?: string) => { let monitorId = monId; const { monitorId: urlMonitorId } = useParams<{ monitorId: string }>(); + const { space } = useKibanaSpace(); + const { spaceId } = useGetUrlParams(); if (!monitorId) { monitorId = urlMonitorId; } @@ -53,9 +57,22 @@ export const useSelectedMonitor = (monId?: string) => { useEffect(() => { if (monitorId && !availableMonitor && !syntheticsMonitorLoading && !isMonitorMissing) { - dispatch(getMonitorAction.get({ monitorId })); + dispatch( + getMonitorAction.get({ + monitorId, + ...(spaceId && spaceId !== space?.id ? { spaceId } : {}), + }) + ); } - }, [dispatch, monitorId, availableMonitor, syntheticsMonitorLoading, isMonitorMissing]); + }, [ + dispatch, + monitorId, + availableMonitor, + syntheticsMonitorLoading, + isMonitorMissing, + spaceId, + space?.id, + ]); useEffect(() => { // Only perform periodic refresh if the last dispatch was earlier enough @@ -66,7 +83,12 @@ export const useSelectedMonitor = (monId?: string) => { syntheticsMonitorDispatchedAt > 0 && Date.now() - syntheticsMonitorDispatchedAt > refreshInterval * 1000 ) { - dispatch(getMonitorAction.get({ monitorId })); + dispatch( + getMonitorAction.get({ + monitorId, + ...(spaceId && spaceId !== space?.id ? { spaceId } : {}), + }) + ); } }, [ dispatch, @@ -76,6 +98,8 @@ export const useSelectedMonitor = (monId?: string) => { monitorListLoading, syntheticsMonitorLoading, syntheticsMonitorDispatchedAt, + spaceId, + space?.id, ]); return { diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitor_details/monitor_details_location.tsx b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitor_details/monitor_details_location.tsx index 9c1f015f67d4f1..b7fecea112ff77 100644 --- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitor_details/monitor_details_location.tsx +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitor_details/monitor_details_location.tsx @@ -8,6 +8,7 @@ import React, { useCallback } from 'react'; import { useParams, useRouteMatch } from 'react-router-dom'; import { useKibana } from '@kbn/kibana-react-plugin/public'; +import { useKibanaSpace } from '../../../../hooks/use_kibana_space'; import { useGetUrlParams } from '../../hooks'; import { MONITOR_ALERTS_ROUTE, @@ -34,7 +35,14 @@ export const MonitorDetailsLocation = ({ isDisabled }: { isDisabled?: boolean }) const isHistoryTab = useRouteMatch(MONITOR_HISTORY_ROUTE); const isAlertsTab = useRouteMatch(MONITOR_ALERTS_ROUTE); - const params = `&dateRangeStart=${dateRangeStart}&dateRangeEnd=${dateRangeEnd}`; + const { space } = useKibanaSpace(); + const { spaceId } = useGetUrlParams(); + + let params = `&dateRangeStart=${dateRangeStart}&dateRangeEnd=${dateRangeEnd}`; + + if (spaceId && spaceId !== space?.id) { + params += `&spaceId=${spaceId}`; + } return ( { const { basePath } = useSyntheticsSettingsContext(); const { monitorId } = useParams<{ monitorId: string }>(); - + const { spaceId } = useGetUrlParams(); const canEditSynthetics = useCanEditSynthetics(); const isLinkDisabled = !canEditSynthetics; const linkProps = isLinkDisabled ? { disabled: true } - : { href: `${basePath}/app/synthetics/edit-monitor/${monitorId}` }; + : { + href: + `${basePath}/app/synthetics/edit-monitor/${monitorId}` + + (spaceId ? `?spaceId=${spaceId}` : ''), + }; return ( diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitor_details/run_test_manually.tsx b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitor_details/run_test_manually.tsx index a05bea3f7925ea..e32918705634b7 100644 --- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitor_details/run_test_manually.tsx +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitor_details/run_test_manually.tsx @@ -9,6 +9,7 @@ import { EuiButton, EuiToolTip } from '@elastic/eui'; import React from 'react'; import { i18n } from '@kbn/i18n'; import { useDispatch, useSelector } from 'react-redux'; +import { useKibanaSpace } from '../../../../hooks/use_kibana_space'; import { CANNOT_PERFORM_ACTION_PUBLIC_LOCATIONS } from '../common/components/permissions'; import { useCanUsePublicLocations } from '../../../../hooks/use_capabilities'; import { ConfigKey } from '../../../../../common/constants/monitor_management'; @@ -27,6 +28,8 @@ export const RunTestManually = () => { const canUsePublicLocations = useCanUsePublicLocations(monitor?.[ConfigKey.LOCATIONS]); + const { space } = useKibanaSpace(); + const content = !canUsePublicLocations ? CANNOT_PERFORM_ACTION_PUBLIC_LOCATIONS : testInProgress @@ -43,8 +46,13 @@ export const RunTestManually = () => { isDisabled={!canUsePublicLocations} onClick={() => { if (monitor) { + const spaceId = 'spaceId' in monitor ? (monitor.spaceId as string) : undefined; dispatch( - manualTestMonitorAction.get({ configId: monitor.config_id, name: monitor.name }) + manualTestMonitorAction.get({ + configId: monitor.config_id, + name: monitor.name, + ...(spaceId && spaceId !== space?.id ? { spaceId } : {}), + }) ); } }} diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/common/monitor_filters/use_filters.test.tsx b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/common/monitor_filters/use_filters.test.tsx index 4786e85993109b..3b949220342fbb 100644 --- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/common/monitor_filters/use_filters.test.tsx +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/common/monitor_filters/use_filters.test.tsx @@ -51,7 +51,7 @@ describe('useMonitorListFilters', () => { const { result } = renderHook(() => useFilters(), { wrapper: Wrapper }); expect(result.current).toStrictEqual(null); - expect(spy).toBeCalledWith(fetchMonitorFiltersAction.get()); + expect(spy).toBeCalledWith(fetchMonitorFiltersAction.get({})); }); it('picks up results from filters selector', () => { @@ -86,6 +86,6 @@ describe('useMonitorListFilters', () => { const { result } = renderHook(() => useFilters(), { wrapper: Wrapper }); expect(result.current).toStrictEqual(filters); - expect(spy).toBeCalledWith(fetchMonitorFiltersAction.get()); + expect(spy).toBeCalledWith(fetchMonitorFiltersAction.get({})); }); }); diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/common/monitor_filters/use_filters.ts b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/common/monitor_filters/use_filters.ts index 317b8c8a4694a0..34429b4f2096ca 100644 --- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/common/monitor_filters/use_filters.ts +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/common/monitor_filters/use_filters.ts @@ -16,6 +16,7 @@ import { updateManagementPageStateAction, fetchMonitorFiltersAction, selectMonitorFilterOptions, + selectOverviewState, } from '../../../../state'; import { useSyntheticsRefreshContext } from '../../../../contexts'; import { SyntheticsUrlParams } from '../../../../utils/url_params'; @@ -31,10 +32,17 @@ export const useFilters = (): MonitorFiltersResult | null => { const dispatch = useDispatch(); const filtersData = useSelector(selectMonitorFilterOptions); const { lastRefresh } = useSyntheticsRefreshContext(); + const { + pageState: { showFromAllSpaces }, + } = useSelector(selectOverviewState); useEffect(() => { - dispatch(fetchMonitorFiltersAction.get()); - }, [lastRefresh, dispatch]); + dispatch( + fetchMonitorFiltersAction.get({ + showFromAllSpaces, + }) + ); + }, [lastRefresh, dispatch, showFromAllSpaces]); return filtersData; }; diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/common/show_all_spaces.tsx b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/common/show_all_spaces.tsx new file mode 100644 index 00000000000000..19e5109144a9b7 --- /dev/null +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/common/show_all_spaces.tsx @@ -0,0 +1,150 @@ +/* + * 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 { + EuiFlexGroup, + EuiFlexItem, + EuiPopover, + EuiTitle, + EuiButtonEmpty, + EuiContextMenu, +} from '@elastic/eui'; +import React, { useEffect, useState } from 'react'; +import { i18n } from '@kbn/i18n'; +import { useDispatch, useSelector } from 'react-redux'; +import useSessionStorage from 'react-use/lib/useSessionStorage'; +import { clearOverviewStatusState } from '../../../state/overview_status'; +import { + selectOverviewState, + setOverviewPageStateAction, + updateManagementPageStateAction, +} from '../../../state'; +import { useKibanaSpace } from '../../../../../hooks/use_kibana_space'; + +export const ShowAllSpaces: React.FC = () => { + return ( + + + + + {i18n.translate('xpack.synthetics.showAllSpaces.spacesTextLabel', { + defaultMessage: 'Spaces', + })} + + + + + + + + ); +}; + +const SelectablePopover = () => { + const [isPopoverOpen, setIsPopoverOpen] = useState(false); + const { space } = useKibanaSpace(); + const [showFromAllSpacesVal, setShowFromAllSpacesVal] = useSessionStorage( + 'SyntheticsShowFromAllSpaces', + false + ); + const dispatch = useDispatch(); + + useEffect(() => { + if (showFromAllSpacesVal !== undefined) { + dispatch( + setOverviewPageStateAction({ + showFromAllSpaces: showFromAllSpacesVal, + }) + ); + dispatch( + updateManagementPageStateAction({ + showFromAllSpaces: showFromAllSpacesVal, + }) + ); + } + }, [dispatch, showFromAllSpacesVal]); + + const { + pageState: { showFromAllSpaces }, + } = useSelector(selectOverviewState); + + const updateState = (val: boolean) => { + setShowFromAllSpacesVal(val); + dispatch(clearOverviewStatusState()); + dispatch( + setOverviewPageStateAction({ + showFromAllSpaces: val, + }) + ); + dispatch( + updateManagementPageStateAction({ + showFromAllSpaces: val, + }) + ); + setIsPopoverOpen(false); + }; + + const button = ( + setIsPopoverOpen(!isPopoverOpen)} + size="xs" + > + {showFromAllSpaces ? ALL_SPACES_LABEL : space?.name || space?.id} + + ); + return ( + setIsPopoverOpen(false)} + panelPaddingSize="none" + anchorPosition="downLeft" + > + { + updateState(false); + }, + icon: showFromAllSpaces ? 'empty' : 'check', + }, + { + name: ALL_SPACES_LABEL, + onClick: () => { + updateState(true); + }, + icon: showFromAllSpaces ? 'check' : 'empty', + }, + ], + }, + ]} + /> + + ); +}; + +const ALL_SPACES_LABEL = i18n.translate('xpack.synthetics.showAllSpaces.allSpacesLabel', { + defaultMessage: 'All permitted spaces', +}); + +const CURRENT_SPACE_LABEL = i18n.translate('xpack.synthetics.showAllSpaces.currentSpaceLabel', { + defaultMessage: 'Current space', +}); + +const SHOW_MONITORS_FROM = i18n.translate('xpack.synthetics.showAllSpaces.showMonitorsFrom', { + defaultMessage: 'Show monitors from', +}); diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/management/monitor_list_table/columns.tsx b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/management/monitor_list_table/columns.tsx index 24aa4d5675a9ad..c5b0bbf146dc81 100644 --- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/management/monitor_list_table/columns.tsx +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/management/monitor_list_table/columns.tsx @@ -11,6 +11,7 @@ import React from 'react'; import { useHistory } from 'react-router-dom'; import { FETCH_STATUS, TagsList } from '@kbn/observability-shared-plugin/public'; import { useKibana } from '@kbn/kibana-react-plugin/public'; +import { useKibanaSpace } from '../../../../../../hooks/use_kibana_space'; import { useEnablement } from '../../../../hooks'; import { useCanEditSynthetics } from '../../../../../../hooks/use_capabilities'; import { @@ -52,6 +53,7 @@ export function useMonitorListColumns({ const canEditSynthetics = useCanEditSynthetics(); const { isServiceAllowed } = useEnablement(); + const { space } = useKibanaSpace(); const { alertStatus, updateAlertEnabledState } = useMonitorAlertEnable(); @@ -204,6 +206,11 @@ export function useMonitorListColumns({ isPublicLocationsAllowed(fields) && isServiceAllowed, href: (fields) => { + if ('spaceId' in fields && space?.id !== fields.spaceId) { + return http?.basePath.prepend( + `edit-monitor/${fields[ConfigKey.CONFIG_ID]}?spaceId=${fields.spaceId}` + )!; + } return http?.basePath.prepend(`edit-monitor/${fields[ConfigKey.CONFIG_ID]}`)!; }, }, diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/management/monitor_list_table/delete_monitor.tsx b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/management/monitor_list_table/delete_monitor.tsx index 1c01da7a21af52..d532cbf3ebc845 100644 --- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/management/monitor_list_table/delete_monitor.tsx +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/management/monitor_list_table/delete_monitor.tsx @@ -12,6 +12,7 @@ import { toMountPoint } from '@kbn/react-kibana-mount'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; +import { useGetUrlParams } from '../../../../hooks'; import { fetchDeleteMonitor } from '../../../../state'; import { kibanaService } from '../../../../../../utils/kibana_service'; import * as labels from './labels'; @@ -30,6 +31,7 @@ export const DeleteMonitor = ({ setMonitorPendingDeletion: (val: string[]) => void; }) => { const [isDeleting, setIsDeleting] = useState(false); + const { spaceId } = useGetUrlParams(); const handleConfirmDelete = () => { setIsDeleting(true); @@ -37,9 +39,9 @@ export const DeleteMonitor = ({ const { status: monitorDeleteStatus } = useFetcher(() => { if (isDeleting) { - return fetchDeleteMonitor({ configIds }); + return fetchDeleteMonitor({ configIds, spaceId }); } - }, [configIds, isDeleting]); + }, [configIds, isDeleting, spaceId]); useEffect(() => { const { coreStart, toasts } = kibanaService; diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/management/monitor_list_table/monitor_list_header.tsx b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/management/monitor_list_table/monitor_list_header.tsx index f8380088983857..0a5797c536d5be 100644 --- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/management/monitor_list_table/monitor_list_header.tsx +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/management/monitor_list_table/monitor_list_header.tsx @@ -8,6 +8,7 @@ import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import React from 'react'; +import { ShowAllSpaces } from '../../common/show_all_spaces'; import { BulkOperations } from './bulk_operations'; import { EncryptedSyntheticsSavedMonitor } from '../../../../../../../common/runtime_types'; @@ -22,7 +23,7 @@ export const MonitorListHeader = ({ }) => { return ( - + {recordRangeLabel} @@ -31,6 +32,9 @@ export const MonitorListHeader = ({ setMonitorPendingDeletion={setMonitorPendingDeletion} /> + + + ); }; diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/actions_popover.tsx b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/actions_popover.tsx index 32c0bbb4fd0f45..5155074291425e 100644 --- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/actions_popover.tsx +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/actions_popover.tsx @@ -113,8 +113,9 @@ export function ActionsPopover({ const detailUrl = useMonitorDetailLocator({ configId: monitor.configId, locationId: locationId ?? monitor.locationId, + spaceId: monitor.spaceId, }); - const editUrl = useEditMonitorLocator({ configId: monitor.configId }); + const editUrl = useEditMonitorLocator({ configId: monitor.configId, spaceId: monitor.spaceId }); const canEditSynthetics = useCanEditSynthetics(); diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/metric_item.tsx b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/metric_item.tsx index 5136494159a3b8..37836f6ef0711a 100644 --- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/metric_item.tsx +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/metric_item.tsx @@ -15,6 +15,7 @@ import { useTheme } from '@kbn/observability-shared-plugin/public'; import moment from 'moment'; import { useSelector, useDispatch } from 'react-redux'; +import { FlyoutParamProps } from './types'; import { MetricItemBody } from './metric_item/metric_item_body'; import { selectErrorPopoverState, @@ -62,7 +63,7 @@ export const MetricItem = ({ }: { monitor: OverviewStatusMetaData; style?: React.CSSProperties; - onClick: (params: { id: string; configId: string; location: string; locationId: string }) => void; + onClick: (params: FlyoutParamProps) => void; }) => { const trendData = useSelector(selectOverviewTrends)[monitor.configId + monitor.locationId]; const [isPopoverOpen, setIsPopoverOpen] = useState(false); @@ -127,6 +128,7 @@ export const MetricItem = ({ id: monitor.configId, location: locationName, locationId: monitor.locationId, + spaceId: monitor.spaceId, }); } }} diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/monitor_detail_flyout.test.tsx b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/monitor_detail_flyout.test.tsx index 49098f8de02255..5ff2efebc9a8cd 100644 --- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/monitor_detail_flyout.test.tsx +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/monitor_detail_flyout.test.tsx @@ -14,13 +14,21 @@ import * as monitorDetail from '../../../../hooks/use_monitor_detail'; import * as statusByLocation from '../../../../hooks/use_status_by_location'; import * as monitorDetailLocator from '../../../../hooks/use_monitor_detail_locator'; import { TagsList } from '@kbn/observability-shared-plugin/public'; +import { useFetcher } from '@kbn/observability-shared-plugin/public'; jest.mock('@kbn/observability-shared-plugin/public'); const TagsListMock = TagsList as jest.Mock; - TagsListMock.mockReturnValue(
Tags list
); +const useFetcherMock = useFetcher as jest.Mock; + +useFetcherMock.mockReturnValue({ + data: { monitor: { tags: ['tag1', 'tag2'] } }, + status: 200, + refetch: jest.fn(), +}); + describe('Monitor Detail Flyout', () => { beforeEach(() => { jest diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/monitor_detail_flyout.tsx b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/monitor_detail_flyout.tsx index 22dff2d8ddef23..876105ffe7c739 100644 --- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/monitor_detail_flyout.tsx +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/monitor_detail_flyout.tsx @@ -30,6 +30,8 @@ import { useKibana } from '@kbn/kibana-react-plugin/public'; import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import { useTheme } from '@kbn/observability-shared-plugin/public'; +import { FlyoutParamProps } from './types'; +import { useKibanaSpace } from '../../../../../../hooks/use_kibana_space'; import { useOverviewStatus } from '../../hooks/use_overview_status'; import { MonitorDetailsPanel } from '../../../common/components/monitor_details_panel'; import { ClientPluginsStart } from '../../../../../../plugin'; @@ -56,14 +58,10 @@ interface Props { id: string; location: string; locationId: string; + spaceId?: string; onClose: () => void; onEnabledChange: () => void; - onLocationChange: (params: { - configId: string; - id: string; - location: string; - locationId: string; - }) => void; + onLocationChange: (params: FlyoutParamProps) => void; currentDurationChartFrom?: string; currentDurationChartTo?: string; previousDurationChartFrom?: string; @@ -220,7 +218,7 @@ export function LoadingState() { } export function MonitorDetailFlyout(props: Props) { - const { id, configId, onLocationChange, locationId } = props; + const { id, configId, onLocationChange, locationId, spaceId } = props; const { status: overviewStatus } = useOverviewStatus({ scopeStatusByLocation: true }); @@ -235,8 +233,8 @@ export function MonitorDetailFlyout(props: Props) { const setLocation = useCallback( (location: string, locationIdT: string) => - onLocationChange({ id, configId, location, locationId: locationIdT }), - [id, configId, onLocationChange] + onLocationChange({ id, configId, location, locationId: locationIdT, spaceId }), + [onLocationChange, id, configId, spaceId] ); const detailLink = useMonitorDetailLocator({ @@ -259,9 +257,16 @@ export function MonitorDetailFlyout(props: Props) { const upsertSuccess = upsertStatus?.status === 'success'; + const { space } = useKibanaSpace(); + useEffect(() => { - dispatch(getMonitorAction.get({ monitorId: configId })); - }, [configId, dispatch, upsertSuccess]); + dispatch( + getMonitorAction.get({ + monitorId: configId, + ...(spaceId && spaceId !== space?.id ? { spaceId } : {}), + }) + ); + }, [configId, dispatch, space?.id, spaceId, upsertSuccess]); const [isActionsPopoverOpen, setIsActionsPopoverOpen] = useState(false); diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/overview_grid.tsx b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/overview_grid.tsx index e4918d1d4c07df..f0612498f86641 100644 --- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/overview_grid.tsx +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/overview_grid.tsx @@ -18,6 +18,7 @@ import { EuiAutoSizer, EuiAutoSize, } from '@elastic/eui'; +import { ShowAllSpaces } from '../../common/show_all_spaces'; import { OverviewStatusMetaData } from '../../../../../../../common/runtime_types'; import { quietFetchOverviewStatusAction } from '../../../../state/overview_status'; import type { TrendRequest } from '../../../../../../../common/types'; @@ -134,6 +135,10 @@ export const OverviewGrid = memo(() => { + + + + @@ -253,6 +258,7 @@ export const OverviewGrid = memo(() => { id={flyoutConfig.id} location={flyoutConfig.location} locationId={flyoutConfig.locationId} + spaceId={flyoutConfig.spaceId} onClose={hideFlyout} onEnabledChange={forceRefreshCallback} onLocationChange={setFlyoutConfigCallback} diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/types.ts b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/types.ts index a2e7d8581e6579..bfa1e36cb58e20 100644 --- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/types.ts +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/types.ts @@ -10,4 +10,5 @@ export interface FlyoutParamProps { configId: string; location: string; locationId: string; + spaceId?: string; } diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/settings/hooks/api.ts b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/settings/hooks/api.ts index 8d8839c508a0b9..ad3d45d0ba8063 100644 --- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/settings/hooks/api.ts +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/settings/hooks/api.ts @@ -22,9 +22,10 @@ export const getDslPolicies = async (): Promise<{ data: DataStream[] }> => { includeStats: true, }, undefined, - undefined, { - 'X-Elastic-Internal-Origin': 'Kibana', + headers: { + 'X-Elastic-Internal-Origin': 'Kibana', + }, } ); }; diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/hooks/use_edit_monitor_locator.ts b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/hooks/use_edit_monitor_locator.ts index a0ecb681e38c27..43492ec72243f3 100644 --- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/hooks/use_edit_monitor_locator.ts +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/hooks/use_edit_monitor_locator.ts @@ -9,15 +9,20 @@ import { useEffect, useState } from 'react'; import { LocatorClient } from '@kbn/share-plugin/common/url_service/locators'; import { syntheticsEditMonitorLocatorID } from '@kbn/observability-plugin/common'; import { useKibana } from '@kbn/kibana-react-plugin/public'; +import { useKibanaSpace } from '../../../hooks/use_kibana_space'; import { ClientPluginsStart } from '../../../plugin'; export function useEditMonitorLocator({ configId, locators, + spaceId, }: { configId: string; + spaceId?: string; locators?: LocatorClient; }) { + const { space } = useKibanaSpace(); + const [editUrl, setEditUrl] = useState(undefined); const syntheticsLocators = useKibana().services.share?.url.locators; const locator = (locators || syntheticsLocators)?.get(syntheticsEditMonitorLocatorID); @@ -26,11 +31,12 @@ export function useEditMonitorLocator({ async function generateUrl() { const url = await locator?.getUrl({ configId, + ...(spaceId && spaceId !== space?.id ? { spaceId } : {}), }); setEditUrl(url); } generateUrl(); - }, [locator, configId]); + }, [locator, configId, space, spaceId]); return editUrl; } diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/hooks/use_monitor_detail_locator.ts b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/hooks/use_monitor_detail_locator.ts index fc346bccfa6c66..3b98a6e60279fd 100644 --- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/hooks/use_monitor_detail_locator.ts +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/hooks/use_monitor_detail_locator.ts @@ -8,15 +8,19 @@ import { useEffect, useState } from 'react'; import { syntheticsMonitorDetailLocatorID } from '@kbn/observability-plugin/common'; import { useKibana } from '@kbn/kibana-react-plugin/public'; +import { useKibanaSpace } from '../../../hooks/use_kibana_space'; import { ClientPluginsStart } from '../../../plugin'; export function useMonitorDetailLocator({ configId, locationId, + spaceId, }: { configId: string; locationId?: string; + spaceId?: string; }) { + const { space } = useKibanaSpace(); const [monitorUrl, setMonitorUrl] = useState(undefined); const locator = useKibana().services?.share?.url.locators.get( syntheticsMonitorDetailLocatorID @@ -27,11 +31,12 @@ export function useMonitorDetailLocator({ const url = await locator?.getUrl({ configId, locationId, + ...(spaceId && spaceId !== space?.id ? { spaceId } : {}), }); setMonitorUrl(url); } generateUrl(); - }, [locator, configId, locationId]); + }, [locator, configId, locationId, spaceId, space?.id]); return monitorUrl; } diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/manual_test_runs/actions.ts b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/manual_test_runs/actions.ts index 65cf3ab97d0705..f12ebd596f411a 100644 --- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/manual_test_runs/actions.ts +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/manual_test_runs/actions.ts @@ -16,6 +16,7 @@ export const hideTestNowFlyoutAction = createAction('HIDE ALL TEST NOW FLYOUT AC export interface TestNowPayload { configId: string; name: string; + spaceId?: string; } export const manualTestMonitorAction = createAsyncAction< TestNowPayload, diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/manual_test_runs/api.ts b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/manual_test_runs/api.ts index ebac31a5b8a4db..169fa721c1838f 100644 --- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/manual_test_runs/api.ts +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/manual_test_runs/api.ts @@ -12,19 +12,37 @@ import { SYNTHETICS_API_URLS } from '../../../../../common/constants'; export const triggerTestNowMonitor = async ({ configId, + spaceId, }: { configId: string; name: string; + spaceId?: string; }): Promise => { - return await apiService.post(SYNTHETICS_API_URLS.TRIGGER_MONITOR + `/${configId}`); + return await apiService.post( + SYNTHETICS_API_URLS.TRIGGER_MONITOR + `/${configId}`, + undefined, + undefined, + { + spaceId, + } + ); }; export const runOnceMonitor = async ({ monitor, id, + spaceId, }: { monitor: SyntheticsMonitor; id: string; + spaceId?: string; }): Promise<{ errors: ServiceLocationErrors }> => { - return await apiService.post(SYNTHETICS_API_URLS.RUN_ONCE_MONITOR + `/${id}`, monitor); + return await apiService.post( + SYNTHETICS_API_URLS.RUN_ONCE_MONITOR + `/${id}`, + monitor, + undefined, + { + spaceId, + } + ); }; diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/monitor_details/actions.ts b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/monitor_details/actions.ts index dd0db45112113c..d3c7c5240fa6b6 100644 --- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/monitor_details/actions.ts +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/monitor_details/actions.ts @@ -14,9 +14,10 @@ export const setMonitorDetailsLocationAction = createAction( '[MONITOR SUMMARY] SET LOCATION' ); -export const getMonitorAction = createAsyncAction<{ monitorId: string }, SyntheticsMonitorWithId>( - '[MONITOR DETAILS] GET MONITOR' -); +export const getMonitorAction = createAsyncAction< + { monitorId: string; spaceId?: string }, + SyntheticsMonitorWithId +>('[MONITOR DETAILS] GET MONITOR'); export const getMonitorLastRunAction = createAsyncAction< { monitorId: string; locationId: string }, diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/monitor_details/api.ts b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/monitor_details/api.ts index 6fa59e4c175d16..aa3a4533295e6d 100644 --- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/monitor_details/api.ts +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/monitor_details/api.ts @@ -65,13 +65,16 @@ export const fetchMonitorRecentPings = async ({ export const fetchSyntheticsMonitor = async ({ monitorId, + spaceId, }: { monitorId: string; + spaceId?: string; }): Promise => { return apiService.get( SYNTHETICS_API_URLS.GET_SYNTHETICS_MONITOR.replace('{monitorId}', monitorId), { internal: true, + spaceId, version: INITIAL_REST_VERSION, }, EncryptedSyntheticsMonitorCodec diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/monitor_list/actions.ts b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/monitor_list/actions.ts index e914b27a26b67c..ad7a98ef6d9db3 100644 --- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/monitor_list/actions.ts +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/monitor_list/actions.ts @@ -47,6 +47,9 @@ export const updateManagementPageStateAction = createAction( - 'fetchMonitorFiltersAction' -); +export const fetchMonitorFiltersAction = createAsyncAction< + { + showFromAllSpaces?: boolean; + }, + MonitorFiltersResult +>('fetchMonitorFiltersAction'); diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/monitor_list/api.ts b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/monitor_list/api.ts index 245c2be23f9c13..344897dd0eb1da 100644 --- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/monitor_list/api.ts +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/monitor_list/api.ts @@ -36,6 +36,7 @@ function toMonitorManagementListQueryArgs( monitorQueryIds: pageState.monitorQueryIds, searchFields: [], internal: true, + showFromAllSpaces: pageState.showFromAllSpaces, }; } @@ -50,10 +51,18 @@ export const fetchMonitorManagementList = async ( }); }; -export const fetchDeleteMonitor = async ({ configIds }: { configIds: string[] }): Promise => { +export const fetchDeleteMonitor = async ({ + configIds, + spaceId, +}: { + configIds: string[]; + spaceId?: string; +}): Promise => { + const baseUrl = SYNTHETICS_API_URLS.SYNTHETICS_MONITORS; + return await apiService.delete( - SYNTHETICS_API_URLS.SYNTHETICS_MONITORS, - { version: INITIAL_REST_VERSION }, + baseUrl, + { version: INITIAL_REST_VERSION, spaceId }, { ids: configIds, } @@ -92,6 +101,10 @@ export const createGettingStartedMonitor = async ({ }); }; -export const fetchMonitorFilters = async (): Promise => { - return await apiService.get(SYNTHETICS_API_URLS.FILTERS); +export const fetchMonitorFilters = async ({ + showFromAllSpaces = false, +}: { + showFromAllSpaces?: boolean; +}): Promise => { + return await apiService.get(SYNTHETICS_API_URLS.FILTERS, { showFromAllSpaces }); }; diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/monitor_list/models.ts b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/monitor_list/models.ts index 4c9a105a4c1fdf..c782d89fe1a65f 100644 --- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/monitor_list/models.ts +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/monitor_list/models.ts @@ -24,6 +24,7 @@ export interface MonitorFilterState { schedules?: string[]; locations?: string[]; monitorQueryIds?: string[]; // Monitor Query IDs + showFromAllSpaces?: boolean; } export interface MonitorListPageState extends MonitorFilterState { diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/monitor_management/api.ts b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/monitor_management/api.ts index f4118bed3b14c5..6ee33a03b9df79 100644 --- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/monitor_management/api.ts +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/monitor_management/api.ts @@ -49,11 +49,14 @@ export const inspectMonitorAPI = async ({ export const updateMonitorAPI = async ({ monitor, id, + spaceId, }: { monitor: SyntheticsMonitor | EncryptedSyntheticsMonitor; + spaceId?: string; id: string; }): Promise => { return await apiService.put(`${SYNTHETICS_API_URLS.SYNTHETICS_MONITORS}/${id}`, monitor, null, { + spaceId, internal: true, version: INITIAL_REST_VERSION, }); diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/overview/models.ts b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/overview/models.ts index 132167a3631d73..ba52b094084825 100644 --- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/overview/models.ts +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/overview/models.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { FlyoutParamProps } from '../../components/monitors_page/overview/overview/types'; import type { TrendTable } from '../../../../../common/types'; import type { MonitorListSortField } from '../../../../../common/runtime_types/monitor_management/sort_field'; import { ConfigKey } from '../../../../../common/runtime_types'; @@ -17,12 +18,7 @@ export interface MonitorOverviewPageState extends MonitorFilterState { sortField: MonitorListSortField; } -export type MonitorOverviewFlyoutConfig = { - configId: string; - id: string; - location: string; - locationId: string; -} | null; +export type MonitorOverviewFlyoutConfig = FlyoutParamProps | null; export interface MonitorOverviewState { flyoutConfig: MonitorOverviewFlyoutConfig; diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/overview_status/actions.ts b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/overview_status/actions.ts index 1e8f9e019098cd..cfd40d43037982 100644 --- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/overview_status/actions.ts +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/overview_status/actions.ts @@ -21,3 +21,4 @@ export const quietFetchOverviewStatusAction = createAsyncAction< >('quietFetchOverviewStatusAction'); export const clearOverviewStatusErrorAction = createAction('clearOverviewStatusErrorAction'); +export const clearOverviewStatusState = createAction('clearOverviewStatusState'); diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/overview_status/api.ts b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/overview_status/api.ts index 9ebbd8d67fc013..6e0c644695231a 100644 --- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/overview_status/api.ts +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/overview_status/api.ts @@ -25,6 +25,7 @@ export function toStatusOverviewQueryArgs( schedules: pageState.schedules, monitorTypes: pageState.monitorTypes, monitorQueryIds: pageState.monitorQueryIds, + showFromAllSpaces: pageState.showFromAllSpaces, searchFields: [], }; } diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/overview_status/index.ts b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/overview_status/index.ts index 2670aa913d61a9..28f8e43dad27ae 100644 --- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/overview_status/index.ts +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/overview_status/index.ts @@ -11,6 +11,7 @@ import { OverviewStatusMetaData, OverviewStatusState } from '../../../../../comm import { IHttpSerializedFetchError } from '..'; import { clearOverviewStatusErrorAction, + clearOverviewStatusState, fetchOverviewStatusAction, quietFetchOverviewStatusAction, } from './actions'; @@ -56,6 +57,12 @@ export const overviewStatusReducer = createReducer(initialState, (builder) => { state.error = action.payload; state.loading = false; }) + .addCase(clearOverviewStatusState, (state, action) => { + state.status = null; + state.loading = false; + state.loaded = false; + state.error = null; + }) .addCase(clearOverviewStatusErrorAction, (state) => { state.error = null; }); diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/utils/filters/filter_fields.ts b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/utils/filters/filter_fields.ts index 40ef046a6579be..2ee2b1525dd08d 100644 --- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/utils/filters/filter_fields.ts +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/utils/filters/filter_fields.ts @@ -12,7 +12,7 @@ import { MonitorFilterState } from '../../state'; export type SyntheticsMonitorFilterField = keyof Omit< MonitorFilterState, - 'query' | 'monitorQueryIds' + 'query' | 'monitorQueryIds' | 'showFromAllSpaces' >; export interface LabelWithCountValue { diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/utils/url_params/get_supported_url_params.ts b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/utils/url_params/get_supported_url_params.ts index 8b4612b1e0f39b..e2f96090322b64 100644 --- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/utils/url_params/get_supported_url_params.ts +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/utils/url_params/get_supported_url_params.ts @@ -34,6 +34,7 @@ export interface SyntheticsUrlParams { groupOrderBy?: MonitorOverviewState['groupBy']['order']; packagePolicyId?: string; cloneId?: string; + spaceId?: string; } const { ABSOLUTE_DATE_RANGE_START, ABSOLUTE_DATE_RANGE_END, SEARCH, FILTERS, STATUS_FILTER } = @@ -89,6 +90,7 @@ export const getSupportedUrlParams = (params: { groupBy, groupOrderBy, packagePolicyId, + spaceId, } = filteredParams; return { @@ -120,6 +122,7 @@ export const getSupportedUrlParams = (params: { schedules: parseFilters(schedules), locationId: locationId || undefined, cloneId: filteredParams.cloneId, + spaceId: spaceId || undefined, }; }; diff --git a/x-pack/plugins/observability_solution/synthetics/public/utils/api_service/api_service.ts b/x-pack/plugins/observability_solution/synthetics/public/utils/api_service/api_service.ts index 9a0b887a7d6ebe..58c1d88226e5eb 100644 --- a/x-pack/plugins/observability_solution/synthetics/public/utils/api_service/api_service.ts +++ b/x-pack/plugins/observability_solution/synthetics/public/utils/api_service/api_service.ts @@ -7,11 +7,15 @@ import { isRight } from 'fp-ts/lib/Either'; import { formatErrors } from '@kbn/securitysolution-io-ts-utils'; -import { HttpFetchQuery, HttpHeadersInit, HttpSetup } from '@kbn/core/public'; +import { HttpFetchOptions, HttpFetchQuery, HttpSetup } from '@kbn/core/public'; import { FETCH_STATUS, AddInspectorRequest } from '@kbn/observability-shared-plugin/public'; import type { InspectorRequestProps } from '@kbn/observability-shared-plugin/public/contexts/inspector/inspector_context'; +import { addSpaceIdToPath } from '@kbn/spaces-plugin/common'; +import { kibanaService } from '../kibana_service'; -type Params = HttpFetchQuery & { version?: string }; +type Params = HttpFetchQuery & { version?: string; spaceId?: string }; + +type FetchOptions = HttpFetchOptions & { asResponse?: true }; class ApiService { private static instance: ApiService; @@ -63,20 +67,27 @@ class ApiService { return response; } + private parseApiUrl(apiUrl: string, spaceId?: string) { + if (spaceId) { + const basePath = kibanaService.coreSetup.http.basePath; + return addSpaceIdToPath(basePath.serverBasePath, spaceId, apiUrl); + } + return apiUrl; + } + public async get( apiUrl: string, params: Params = {}, decodeType?: any, - asResponse = false, - headers?: HttpHeadersInit + options?: FetchOptions ) { - const { version, ...queryParams } = params; + const { version, spaceId, ...queryParams } = params; const response = await this._http!.fetch({ - path: apiUrl, + path: this.parseApiUrl(apiUrl, spaceId), query: queryParams, - asResponse, version, - headers, + ...(options ?? {}), + ...(spaceId ? { prependBasePath: false } : {}), }); this.addInspectorRequest?.({ @@ -89,13 +100,14 @@ class ApiService { } public async post(apiUrl: string, data?: any, decodeType?: any, params: Params = {}) { - const { version, ...queryParams } = params; + const { version, spaceId, ...queryParams } = params; - const response = await this._http!.post(apiUrl, { + const response = await this._http!.post(this.parseApiUrl(apiUrl, spaceId), { method: 'POST', body: JSON.stringify(data), query: queryParams, version, + ...(spaceId ? { prependBasePath: false } : {}), }); this.addInspectorRequest?.({ @@ -107,27 +119,37 @@ class ApiService { return this.parseResponse(response, apiUrl, decodeType); } - public async put(apiUrl: string, data?: any, decodeType?: any, params: Params = {}) { - const { version, ...queryParams } = params; + public async put( + apiUrl: string, + data?: any, + decodeType?: any, + params: Params = {}, + options?: FetchOptions + ) { + const { version, spaceId, ...queryParams } = params; - const response = await this._http!.put(apiUrl, { + const response = await this._http!.put(this.parseApiUrl(apiUrl, spaceId), { method: 'PUT', body: JSON.stringify(data), query: queryParams, version, + ...(options ?? {}), + ...(spaceId ? { prependBasePath: false } : {}), }); return this.parseResponse(response, apiUrl, decodeType); } - public async delete(apiUrl: string, params: Params = {}, data?: any) { - const { version, ...queryParams } = params; + public async delete(apiUrl: string, params: Params = {}, data?: any, options?: FetchOptions) { + const { version, spaceId, ...queryParams } = params; const response = await this._http!.delete({ - path: apiUrl, + path: this.parseApiUrl(apiUrl, spaceId), query: queryParams, body: JSON.stringify(data), version, + ...(options ?? {}), + ...(spaceId ? { prependBasePath: false } : {}), }); if (response instanceof Error) { diff --git a/x-pack/plugins/observability_solution/synthetics/server/queries/query_monitor_status.ts b/x-pack/plugins/observability_solution/synthetics/server/queries/query_monitor_status.ts index f7fa0fc0a50af4..5ccb33fb491fd9 100644 --- a/x-pack/plugins/observability_solution/synthetics/server/queries/query_monitor_status.ts +++ b/x-pack/plugins/observability_solution/synthetics/server/queries/query_monitor_status.ts @@ -295,5 +295,6 @@ const getMonitorMeta = (monitor: SavedObjectsFindResult; @@ -55,6 +56,7 @@ export const OverviewStatusSchema = schema.object({ schedules: StringOrArraySchema, status: StringOrArraySchema, scopeStatusByLocation: schema.maybe(schema.boolean()), + showFromAllSpaces: schema.maybe(schema.boolean()), }); export type OverviewStatusQuery = TypeOf; @@ -87,6 +89,7 @@ export const getMonitors = async ( projects, schedules, monitorQueryIds, + showFromAllSpaces, } = context.request.query; const { filtersStr } = await getMonitorFilters({ @@ -100,7 +103,7 @@ export const getMonitors = async ( context, }); - const findParams = { + return context.savedObjectsClient.find({ type: syntheticsMonitorType, perPage, page, @@ -111,9 +114,8 @@ export const getMonitors = async ( filter: filtersStr, searchAfter, fields, - }; - - return context.savedObjectsClient.find(findParams); + ...(showFromAllSpaces && { namespaces: ['*'] }), + }); }; interface Filters { diff --git a/x-pack/plugins/observability_solution/synthetics/server/routes/filters/filters.ts b/x-pack/plugins/observability_solution/synthetics/server/routes/filters/filters.ts index a140974f2d737a..03c255ef5ab368 100644 --- a/x-pack/plugins/observability_solution/synthetics/server/routes/filters/filters.ts +++ b/x-pack/plugins/observability_solution/synthetics/server/routes/filters/filters.ts @@ -4,6 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ +import { schema } from '@kbn/config-schema'; import { SyntheticsRestApiRouteFactory } from '../types'; import { syntheticsMonitorType } from '../../../common/types/saved_objects'; import { ConfigKey, MonitorFiltersResult } from '../../../common/runtime_types'; @@ -35,12 +36,18 @@ interface AggsResponse { export const getSyntheticsFilters: SyntheticsRestApiRouteFactory = () => ({ method: 'GET', path: SYNTHETICS_API_URLS.FILTERS, - validate: {}, - handler: async ({ savedObjectsClient }): Promise => { + validate: { + query: schema.object({ + showFromAllSpaces: schema.maybe(schema.boolean()), + }), + }, + handler: async ({ savedObjectsClient, request }): Promise => { + const showFromAllSpaces = request.query?.showFromAllSpaces; const data = await savedObjectsClient.find({ type: syntheticsMonitorType, perPage: 0, aggs, + ...(showFromAllSpaces ? { namespaces: ['*'] } : {}), }); const { monitorTypes, tags, locations, projects, schedules } = diff --git a/x-pack/plugins/observability_solution/synthetics/server/routes/monitor_cruds/get_monitor.ts b/x-pack/plugins/observability_solution/synthetics/server/routes/monitor_cruds/get_monitor.ts index 1a07ddc8325610..b9ca9f7b2b7ebf 100644 --- a/x-pack/plugins/observability_solution/synthetics/server/routes/monitor_cruds/get_monitor.ts +++ b/x-pack/plugins/observability_solution/synthetics/server/routes/monitor_cruds/get_monitor.ts @@ -60,15 +60,18 @@ export const getSyntheticsMonitorRoute: SyntheticsRestApiRouteFactory = () => ({ encryptedSavedObjectsClient, spaceId, }); - return mapSavedObjectToMonitor({ monitor, internal }); + return { ...mapSavedObjectToMonitor({ monitor, internal }), spaceId }; } else { - return mapSavedObjectToMonitor({ - monitor: await savedObjectsClient.get( - syntheticsMonitorType, - monitorId - ), - internal, - }); + return { + ...mapSavedObjectToMonitor({ + monitor: await savedObjectsClient.get( + syntheticsMonitorType, + monitorId + ), + internal, + }), + spaceId, + }; } } catch (getErr) { if (SavedObjectsErrorHelpers.isNotFoundError(getErr)) { diff --git a/x-pack/plugins/observability_solution/synthetics/server/routes/monitor_cruds/get_monitors_list.ts b/x-pack/plugins/observability_solution/synthetics/server/routes/monitor_cruds/get_monitors_list.ts index ed99e2b1d64d00..3e555daed54b3f 100644 --- a/x-pack/plugins/observability_solution/synthetics/server/routes/monitor_cruds/get_monitors_list.ts +++ b/x-pack/plugins/observability_solution/synthetics/server/routes/monitor_cruds/get_monitors_list.ts @@ -42,12 +42,16 @@ export const getAllSyntheticsMonitorRoute: SyntheticsRestApiRouteFactory = () => return { ...rest, - monitors: savedObjects.map((monitor) => - mapSavedObjectToMonitor({ + monitors: savedObjects.map((monitor) => { + const mon = mapSavedObjectToMonitor({ monitor, internal: request.query?.internal, - }) - ), + }); + return { + spaceId: monitor.namespaces?.[0], + ...mon, + }; + }), absoluteTotal, perPage: perPageT, syncErrors: syntheticsMonitorClient.syntheticsService.syncErrors, diff --git a/x-pack/plugins/observability_solution/synthetics/server/routes/overview_status/overview_status.test.ts b/x-pack/plugins/observability_solution/synthetics/server/routes/overview_status/overview_status.test.ts index b6909ba33a576c..a3ddc0079d4600 100644 --- a/x-pack/plugins/observability_solution/synthetics/server/routes/overview_status/overview_status.test.ts +++ b/x-pack/plugins/observability_solution/synthetics/server/routes/overview_status/overview_status.test.ts @@ -292,6 +292,7 @@ describe('current status route', () => { }, "projectId": "project-id", "schedule": "1", + "spaceId": undefined, "status": "down", "tags": Array [ "tag-1", @@ -337,6 +338,7 @@ describe('current status route', () => { }, "projectId": "project-id", "schedule": "1", + "spaceId": undefined, "status": "up", "tags": Array [ "tag-1", @@ -373,6 +375,7 @@ describe('current status route', () => { }, "projectId": "project-id", "schedule": "1", + "spaceId": undefined, "status": "up", "tags": Array [ "tag-1", @@ -558,6 +561,7 @@ describe('current status route', () => { }, "projectId": "project-id", "schedule": "1", + "spaceId": undefined, "status": "down", "tags": Array [ "tag-1", @@ -603,6 +607,7 @@ describe('current status route', () => { }, "projectId": "project-id", "schedule": "1", + "spaceId": undefined, "status": "up", "tags": Array [ "tag-1", @@ -639,6 +644,7 @@ describe('current status route', () => { }, "projectId": "project-id", "schedule": "1", + "spaceId": undefined, "status": "up", "tags": Array [ "tag-1", @@ -852,6 +858,7 @@ describe('current status route', () => { }, "projectId": "project-id", "schedule": "1", + "spaceId": undefined, "status": "down", "tags": Array [ "tag-1", @@ -972,6 +979,7 @@ describe('current status route', () => { }, "projectId": "project-id", "schedule": "1", + "spaceId": undefined, "status": "up", "tags": Array [ "tag-1", @@ -1008,6 +1016,7 @@ describe('current status route', () => { }, "projectId": "project-id", "schedule": "1", + "spaceId": undefined, "status": "up", "tags": Array [ "tag-1", diff --git a/x-pack/plugins/observability_solution/synthetics/server/routes/overview_status/overview_status.ts b/x-pack/plugins/observability_solution/synthetics/server/routes/overview_status/overview_status.ts index a68dcdea56657e..8fa52f98592cbd 100644 --- a/x-pack/plugins/observability_solution/synthetics/server/routes/overview_status/overview_status.ts +++ b/x-pack/plugins/observability_solution/synthetics/server/routes/overview_status/overview_status.ts @@ -38,7 +38,7 @@ export function periodToMs(schedule: { number: string; unit: Unit }) { export async function getStatus(context: RouteContext, params: OverviewStatusQuery) { const { syntheticsEsClient, savedObjectsClient } = context; - const { query, scopeStatusByLocation = true } = params; + const { query, scopeStatusByLocation = true, showFromAllSpaces } = params; /** * Walk through all monitor saved objects, bucket IDs by disabled/enabled status. @@ -54,6 +54,7 @@ export async function getStatus(context: RouteContext, params: OverviewStatusQue const allMonitors = await getAllMonitors({ soClient: savedObjectsClient, + showFromAllSpaces, search: query ? `${query}*` : undefined, filter: filtersStr, fields: [ diff --git a/x-pack/plugins/observability_solution/synthetics/server/saved_objects/synthetics_monitor/get_all_monitors.ts b/x-pack/plugins/observability_solution/synthetics/server/saved_objects/synthetics_monitor/get_all_monitors.ts index e5cafe323a5b65..8cae1ef32a8c3b 100644 --- a/x-pack/plugins/observability_solution/synthetics/server/saved_objects/synthetics_monitor/get_all_monitors.ts +++ b/x-pack/plugins/observability_solution/synthetics/server/saved_objects/synthetics_monitor/get_all_monitors.ts @@ -27,10 +27,12 @@ export const getAllMonitors = async ({ sortField = 'name.keyword', sortOrder = 'asc', searchFields, + showFromAllSpaces, }: { soClient: SavedObjectsClientContract; search?: string; filter?: string; + showFromAllSpaces?: boolean; } & Pick) => { const finder = soClient.createPointInTimeFinder({ type: syntheticsMonitorType, @@ -41,6 +43,7 @@ export const getAllMonitors = async ({ fields, filter, searchFields, + ...(showFromAllSpaces && { namespaces: ['*'] }), }); const hits: Array> = []; diff --git a/x-pack/test/api_integration/apis/synthetics/add_monitor.ts b/x-pack/test/api_integration/apis/synthetics/add_monitor.ts index 89fc77c034072d..01e5c175ee7d65 100644 --- a/x-pack/test/api_integration/apis/synthetics/add_monitor.ts +++ b/x-pack/test/api_integration/apis/synthetics/add_monitor.ts @@ -51,7 +51,14 @@ export const addMonitorAPIHelper = async (supertestAPI: any, monitor: any, statu return result.body; }; -export const keyToOmitList = ['created_at', 'updated_at', 'id', 'config_id', 'form_monitor_type']; +export const keyToOmitList = [ + 'created_at', + 'updated_at', + 'id', + 'config_id', + 'form_monitor_type', + 'spaceId', +]; export const omitMonitorKeys = (monitor: any) => { return omit(transformPublicKeys(monitor), keyToOmitList); diff --git a/x-pack/test/api_integration/apis/synthetics/edit_monitor.ts b/x-pack/test/api_integration/apis/synthetics/edit_monitor.ts index 522a359c6d51b2..7c23c4981c9cfc 100644 --- a/x-pack/test/api_integration/apis/synthetics/edit_monitor.ts +++ b/x-pack/test/api_integration/apis/synthetics/edit_monitor.ts @@ -23,7 +23,7 @@ import { SyntheticsMonitorTestService } from './services/synthetics_monitor_test import { LOCAL_LOCATION } from './get_filters'; export default function ({ getService }: FtrProviderContext) { - describe('EditMonitor', function () { + describe('EditMonitorAPI', function () { this.tags('skipCloud'); const supertest = getService('supertest'); diff --git a/x-pack/test/api_integration/apis/synthetics/enable_default_alerting.ts b/x-pack/test/api_integration/apis/synthetics/enable_default_alerting.ts index 0064ef490bb755..48630bb3802d2b 100644 --- a/x-pack/test/api_integration/apis/synthetics/enable_default_alerting.ts +++ b/x-pack/test/api_integration/apis/synthetics/enable_default_alerting.ts @@ -81,7 +81,7 @@ export default function ({ getService }: FtrProviderContext) { const { body: apiResponse } = await addMonitorAPI(newMonitor); - expect(apiResponse).eql(omitMonitorKeys(newMonitor)); + expect(apiResponse).eql(omitMonitorKeys({ ...newMonitor, spaceId: 'default' })); await retry.tryForTime(30 * 1000, async () => { const res = await supertest diff --git a/x-pack/test/api_integration/apis/synthetics/get_monitor.ts b/x-pack/test/api_integration/apis/synthetics/get_monitor.ts index 9f266fa42fc316..16b42c4dfd0ce2 100644 --- a/x-pack/test/api_integration/apis/synthetics/get_monitor.ts +++ b/x-pack/test/api_integration/apis/synthetics/get_monitor.ts @@ -15,6 +15,7 @@ import { import { SYNTHETICS_API_URLS } from '@kbn/synthetics-plugin/common/constants'; import expect from '@kbn/expect'; import { secretKeys } from '@kbn/synthetics-plugin/common/constants/monitor_management'; +import { v4 as uuidv4 } from 'uuid'; import { SyntheticsMonitorTestService } from './services/synthetics_monitor_test_service'; import { omitMonitorKeys } from './add_monitor'; import { FtrProviderContext } from '../../ftr_provider_context'; @@ -33,11 +34,12 @@ export default function ({ getService }: FtrProviderContext) { let _monitors: MonitorFields[]; let monitors: MonitorFields[]; - const saveMonitor = async (monitor: MonitorFields) => { - const res = await supertest - .post(SYNTHETICS_API_URLS.SYNTHETICS_MONITORS) - .set('kbn-xsrf', 'true') - .send(monitor); + const saveMonitor = async (monitor: MonitorFields, spaceId?: string) => { + let url = SYNTHETICS_API_URLS.SYNTHETICS_MONITORS + '?internal=true'; + if (spaceId) { + url = '/s/' + spaceId + url; + } + const res = await supertest.post(url).set('kbn-xsrf', 'true').send(monitor); expect(res.status).eql(200, JSON.stringify(res.body)); @@ -63,13 +65,12 @@ export default function ({ getService }: FtrProviderContext) { monitors = _monitors; }); - // FLAKY: https://github.com/elastic/kibana/issues/169753 - describe.skip('get many monitors', () => { + describe('get many monitors', () => { it('without params', async () => { - const [mon1, mon2] = await Promise.all(monitors.map(saveMonitor)); + const [mon1, mon2] = await Promise.all(monitors.map((mon) => saveMonitor(mon))); const apiResponse = await supertest - .get(SYNTHETICS_API_URLS.SYNTHETICS_MONITORS + '?perPage=1000') // 1000 to sort of load all saved monitors + .get(SYNTHETICS_API_URLS.SYNTHETICS_MONITORS + '?perPage=1000&internal=true') // 1000 to sort of load all saved monitors .expect(200); const found: MonitorFields[] = apiResponse.body.monitors.filter(({ id }: MonitorFields) => @@ -91,7 +92,7 @@ export default function ({ getService }: FtrProviderContext) { expect(moment(updatedAt).isValid()).to.be(true); }); - expect(foundMonitors.map((fm) => omit(fm, 'updated_at', 'created_at'))).eql( + expect(foundMonitors.map((fm) => omit(fm, 'updated_at', 'created_at', 'spaceId'))).eql( expected.map((expectedMon) => omit(expectedMon, ['updated_at', 'created_at', ...secretKeys]) ) @@ -99,11 +100,10 @@ export default function ({ getService }: FtrProviderContext) { }); it('with page params', async () => { - await Promise.all( - [...monitors, ...monitors] - .map((mon) => ({ ...mon, name: mon.name + '1' })) - .map(saveMonitor) - ); + const allMonitors = [...monitors, ...monitors]; + for (const mon of allMonitors) { + await saveMonitor({ ...mon, name: mon.name + Date.now() }); + } await retry.try(async () => { const firstPageResp = await supertest @@ -123,7 +123,7 @@ export default function ({ getService }: FtrProviderContext) { it('with single monitorQueryId filter', async () => { const [_, { id: id2 }] = await Promise.all( - monitors.map((mon) => ({ ...mon, name: mon.name + '2' })).map(saveMonitor) + monitors.map((mon) => ({ ...mon, name: mon.name + '2' })).map((mon) => saveMonitor(mon)) ); const resp = await supertest @@ -139,7 +139,7 @@ export default function ({ getService }: FtrProviderContext) { it('with multiple monitorQueryId filter', async () => { const [_, { id: id2 }, { id: id3 }] = await Promise.all( - monitors.map((mon) => ({ ...mon, name: mon.name + '3' })).map(saveMonitor) + monitors.map((mon) => ({ ...mon, name: mon.name + '3' })).map((monT) => saveMonitor(monT)) ); const resp = await supertest @@ -169,7 +169,7 @@ export default function ({ getService }: FtrProviderContext) { [ConfigKey.CUSTOM_HEARTBEAT_ID]: customHeartbeatId1, [ConfigKey.NAME]: `NAME-${customHeartbeatId1}`, }, - ].map(saveMonitor) + ].map((monT) => saveMonitor(monT)) ); const resp = await supertest @@ -184,12 +184,52 @@ export default function ({ getService }: FtrProviderContext) { expect(resultMonitorIds.length).eql(2); expect(resultMonitorIds).eql([customHeartbeatId0, customHeartbeatId1]); }); + + it('gets monitors from all spaces', async () => { + const SPACE_ID = `test-space-${uuidv4()}`; + const SPACE_NAME = `test-space-name ${uuidv4()}`; + await kibanaServer.spaces.create({ id: SPACE_ID, name: SPACE_NAME }); + + const allMonitors = [...monitors, ...monitors]; + for (const mon of allMonitors) { + await saveMonitor({ ...mon, name: mon.name + Date.now() }, SPACE_ID); + } + + const firstPageResp = await supertest + .get(`${SYNTHETICS_API_URLS.SYNTHETICS_MONITORS}?page=1&perPage=1000`) + .expect(200); + const defaultSpaceMons = firstPageResp.body.monitors.filter( + ({ spaceId }: { spaceId: string }) => spaceId === 'default' + ); + const testSpaceMons = firstPageResp.body.monitors.filter( + ({ spaceId }: { spaceId: string }) => spaceId === SPACE_ID + ); + + expect(defaultSpaceMons.length).to.eql(22); + expect(testSpaceMons.length).to.eql(0); + + const res = await supertest + .get( + `${SYNTHETICS_API_URLS.SYNTHETICS_MONITORS}?page=1&perPage=1000&showFromAllSpaces=true` + ) + .expect(200); + + const defaultSpaceMons1 = res.body.monitors.filter( + ({ spaceId }: { spaceId: string }) => spaceId === 'default' + ); + const testSpaceMons1 = res.body.monitors.filter( + ({ spaceId }: { spaceId: string }) => spaceId === SPACE_ID + ); + + expect(defaultSpaceMons1.length).to.eql(22); + expect(testSpaceMons1.length).to.eql(8); + }); }); describe('get one monitor', () => { it('should get by id', async () => { const [{ id: id1 }] = await Promise.all( - monitors.map((mon) => ({ ...mon, name: mon.name + '4' })).map(saveMonitor) + monitors.map((mon) => ({ ...mon, name: mon.name + '4' })).map((monT) => saveMonitor(monT)) ); const apiResponse = await monitorTestService.getMonitor(id1); @@ -209,7 +249,7 @@ export default function ({ getService }: FtrProviderContext) { it('should get by id with ui query param', async () => { const [{ id: id1 }] = await Promise.all( - monitors.map((mon) => ({ ...mon, name: mon.name + '5' })).map(saveMonitor) + monitors.map((mon) => ({ ...mon, name: mon.name + '5' })).map((monT) => saveMonitor(monT)) ); const apiResponse = await monitorTestService.getMonitor(id1, { internal: true }); diff --git a/x-pack/test/api_integration/apis/synthetics/services/synthetics_monitor_test_service.ts b/x-pack/test/api_integration/apis/synthetics/services/synthetics_monitor_test_service.ts index e11a5523ed7b4b..1c7376e41c4d7c 100644 --- a/x-pack/test/api_integration/apis/synthetics/services/synthetics_monitor_test_service.ts +++ b/x-pack/test/api_integration/apis/synthetics/services/synthetics_monitor_test_service.ts @@ -76,12 +76,14 @@ export class SyntheticsMonitorTestService { updated_at: updatedAt, id, config_id: configId, + spaceId, } = apiResponse.body; expect(id).not.empty(); expect(configId).not.empty(); + expect(spaceId).not.empty(); expect([createdAt, updatedAt].map((d) => moment(d).isValid())).eql([true, true]); return { - rawBody: apiResponse.body, + rawBody: omit(apiResponse.body, ['spaceId']), body: { ...omit(apiResponse.body, [ 'created_at', @@ -89,6 +91,7 @@ export class SyntheticsMonitorTestService { 'id', 'config_id', 'form_monitor_type', + 'spaceId', ]), }, };