From 55afbba7c020da83f0e72858565eee0a07cb713c Mon Sep 17 00:00:00 2001 From: Shahzad Date: Tue, 28 Mar 2023 17:07:33 +0200 Subject: [PATCH] [Synthetics] Fix usage of synthetics-* index pattern the synthetics app (#152473) Co-authored-by: Dominique Clarke --- .../e2e/journeys/step_duration.journey.ts | 2 +- .../app/observability_status/content.ts | 2 +- .../components/app/section/uptime/index.tsx | 4 +- .../hooks/use_lens_attributes.test.tsx | 1 + .../shared/exploratory_view/labels.ts | 5 ++ .../obsv_exploratory_view.tsx | 10 +++ .../series_editor/columns/date_picker_col.tsx | 3 +- .../shared/exploratory_view/types.ts | 1 + .../observability/public/context/constants.ts | 1 + .../public/context/has_data_context.test.tsx | 30 +++---- .../public/context/has_data_context.tsx | 6 +- .../observability/public/data_handler.test.ts | 6 +- .../overview/components/empty_sections.tsx | 2 +- .../pages/overview/overview.stories.tsx | 18 ++-- .../typings/fetch_overview_data/index.ts | 6 +- .../observability_data_views.ts | 8 ++ .../common/constants/synthetics/rest_api.ts | 5 ++ .../journey_screenshot_dialog.test.tsx | 6 +- .../screenshot/journey_screenshot_dialog.tsx | 21 ++++- ...journey_step_screenshot_container.test.tsx | 5 +- .../journey_step_screenshot_container.tsx | 3 +- .../hooks/use_synthetics_priviliges.tsx | 79 +++++++++++++++++ .../public/apps/synthetics/routes.tsx | 7 +- .../synthetics/state/browser_journey/api.ts | 12 ++- .../synthetics/state/network_events/api.ts | 4 +- .../app/uptime_page_template.tsx | 4 +- .../common/header/action_menu_content.tsx | 2 +- .../monitor_duration/monitor_duration.tsx | 1 - .../monitor_duration_container.tsx | 24 ------ .../waterfall_marker_trend.test.tsx | 2 +- .../check_steps/step_field_trend.tsx | 2 +- x-pack/plugins/synthetics/public/plugin.ts | 2 +- .../server/legacy_uptime/lib/lib.ts | 71 +++++++++------- .../lib/requests/get_journey_screenshot.ts | 2 +- .../pings/journey_screenshot_blocks.test.ts | 39 ++++++++- .../routes/pings/journey_screenshot_blocks.ts | 41 +++++---- .../routes/pings/journey_screenshots.test.ts | 47 ++++++++++- .../routes/pings/journey_screenshots.ts | 67 ++++++++------- .../synthetics/last_successful_check.ts | 84 +++++++++++-------- .../server/legacy_uptime/routes/types.ts | 20 +++-- .../routes/uptime_route_wrapper.ts | 56 ++++++++----- .../plugins/synthetics/server/routes/index.ts | 11 ++- .../network_events/get_network_events.ts | 32 +++++++ .../server/routes/network_events/index.ts | 8 ++ .../routes/pings/journey_screenshot_blocks.ts | 27 ++++++ .../routes/pings/journey_screenshots.ts | 28 +++++++ .../server/routes/pings/journeys.ts | 16 ++-- .../routes/pings/last_successful_check.ts | 30 +++++++ .../server/synthetics_route_wrapper.ts | 75 ++++++++++------- .../authentication/check_has_privilege.ts | 17 +++- 50 files changed, 686 insertions(+), 269 deletions(-) create mode 100644 x-pack/plugins/synthetics/public/apps/synthetics/hooks/use_synthetics_priviliges.tsx create mode 100644 x-pack/plugins/synthetics/server/routes/network_events/get_network_events.ts create mode 100644 x-pack/plugins/synthetics/server/routes/network_events/index.ts create mode 100644 x-pack/plugins/synthetics/server/routes/pings/journey_screenshot_blocks.ts create mode 100644 x-pack/plugins/synthetics/server/routes/pings/journey_screenshots.ts create mode 100644 x-pack/plugins/synthetics/server/routes/pings/last_successful_check.ts diff --git a/x-pack/plugins/observability/e2e/journeys/step_duration.journey.ts b/x-pack/plugins/observability/e2e/journeys/step_duration.journey.ts index 52da0a786fdb6..10cc98fa2da6a 100644 --- a/x-pack/plugins/observability/e2e/journeys/step_duration.journey.ts +++ b/x-pack/plugins/observability/e2e/journeys/step_duration.journey.ts @@ -27,7 +27,7 @@ journey('Exploratory view', async ({ page, params }) => { reportType: 'kpi-over-time', allSeries: [ { - dataType: 'synthetics', + dataType: 'uptime', time: { from: moment().subtract(10, 'y').toISOString(), to: moment().toISOString(), diff --git a/x-pack/plugins/observability/public/components/app/observability_status/content.ts b/x-pack/plugins/observability/public/components/app/observability_status/content.ts index 319c701fd96c2..3081ee2ebf29b 100644 --- a/x-pack/plugins/observability/public/components/app/observability_status/content.ts +++ b/x-pack/plugins/observability/public/components/app/observability_status/content.ts @@ -86,7 +86,7 @@ export const getContent = ( weight: 2, }, { - id: 'synthetics', + id: 'uptime', title: i18n.translate('xpack.observability.statusVisualization.uptime.title', { defaultMessage: 'Uptime', }), diff --git a/x-pack/plugins/observability/public/components/app/section/uptime/index.tsx b/x-pack/plugins/observability/public/components/app/section/uptime/index.tsx index 8e76c1316be21..a7d482d6da2f6 100644 --- a/x-pack/plugins/observability/public/components/app/section/uptime/index.tsx +++ b/x-pack/plugins/observability/public/components/app/section/uptime/index.tsx @@ -53,7 +53,7 @@ export function UptimeSection({ bucketSize }: Props) { const { data, status } = useFetcher( () => { if (bucketSize && absoluteStart && absoluteEnd) { - return getDataHandler('synthetics')?.fetchData({ + return getDataHandler('uptime')?.fetchData({ absoluteTime: { start: absoluteStart, end: absoluteEnd }, relativeTime: { start: relativeStart, end: relativeEnd }, timeZone, @@ -75,7 +75,7 @@ export function UptimeSection({ bucketSize }: Props) { ] ); - if (!hasDataMap.synthetics?.hasData) { + if (!hasDataMap.uptime?.hasData) { return null; } diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_lens_attributes.test.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_lens_attributes.test.tsx index 1080c1d51d28d..d98a8a8260cbd 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_lens_attributes.test.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_lens_attributes.test.tsx @@ -46,6 +46,7 @@ describe('useExpViewTimeRange', function () { infra_logs: mockDataView, infra_metrics: mockDataView, synthetics: mockDataView, + uptime: mockDataView, alerts: mockDataView, }, }); diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/labels.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/labels.ts index 7f975c5777d4d..add254e327d18 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/labels.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/labels.ts @@ -8,6 +8,7 @@ import { i18n } from '@kbn/i18n'; export enum DataTypes { SYNTHETICS = 'synthetics', + UPTIME = 'uptime', UX = 'ux', MOBILE = 'mobile', METRICS = 'infra_metrics', @@ -27,6 +28,10 @@ export const DataTypesLabels: Record = { } ), + [DataTypes.UPTIME]: i18n.translate('xpack.observability.overview.exploratoryView.uptimeLabel', { + defaultMessage: 'Uptime', + }), + [DataTypes.METRICS]: i18n.translate('xpack.observability.overview.exploratoryView.metricsLabel', { defaultMessage: 'Metrics', }), diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/obsv_exploratory_view.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/obsv_exploratory_view.tsx index fb30cb1734642..9b3f7b470dd32 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/obsv_exploratory_view.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/obsv_exploratory_view.tsx @@ -38,6 +38,10 @@ import { getLogsKPIConfig } from './configurations/infra_logs/kpi_over_time_conf import { getSingleMetricConfig } from './configurations/rum/single_metric_config'; export const dataTypes: Array<{ id: AppDataType; label: string }> = [ + { + id: DataTypes.UPTIME, + label: DataTypesLabels[DataTypes.UPTIME], + }, { id: DataTypes.SYNTHETICS, label: DataTypesLabels[DataTypes.SYNTHETICS], @@ -85,6 +89,12 @@ export const obsvReportConfigMap = { getSyntheticsSingleMetricConfig, getSyntheticsHeatmapConfig, ], + [DataTypes.UPTIME]: [ + getSyntheticsKPIConfig, + getSyntheticsDistributionConfig, + getSyntheticsSingleMetricConfig, + getSyntheticsHeatmapConfig, + ], [DataTypes.MOBILE]: [ getMobileKPIConfig, getMobileKPIDistributionConfig, diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/date_picker_col.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/date_picker_col.tsx index 7a45920cfd83f..cc6c76755689b 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/date_picker_col.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/date_picker_col.tsx @@ -27,7 +27,8 @@ interface Props { const AddDataComponents: Record = { mobile: MobileAddData, ux: UXAddData, - synthetics: SyntheticsAddData, + uptime: SyntheticsAddData, + synthetics: null, apm: null, infra_logs: null, infra_metrics: null, diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/types.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/types.ts index 243aefafbc22b..690f00e19fec1 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/types.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/types.ts @@ -149,6 +149,7 @@ interface FormatType extends SerializedFieldFormat { export type AppDataType = | 'synthetics' + | 'uptime' | 'ux' | 'infra_logs' | 'infra_metrics' diff --git a/x-pack/plugins/observability/public/context/constants.ts b/x-pack/plugins/observability/public/context/constants.ts index 695febb81aff6..962622b128ded 100644 --- a/x-pack/plugins/observability/public/context/constants.ts +++ b/x-pack/plugins/observability/public/context/constants.ts @@ -8,6 +8,7 @@ export const ALERT_APP = 'alert'; export const UX_APP = 'ux'; export const SYNTHETICS_APP = 'synthetics'; +export const UPTIME_APP = 'uptime'; export const APM_APP = 'apm'; export const INFRA_LOGS_APP = 'infra_logs'; export const INFRA_METRICS_APP = 'infra_metrics'; diff --git a/x-pack/plugins/observability/public/context/has_data_context.test.tsx b/x-pack/plugins/observability/public/context/has_data_context.test.tsx index 1cac519680272..6d6a99e9b8642 100644 --- a/x-pack/plugins/observability/public/context/has_data_context.test.tsx +++ b/x-pack/plugins/observability/public/context/has_data_context.test.tsx @@ -37,7 +37,7 @@ function unregisterAll() { unregisterDataHandler({ appName: 'apm' }); unregisterDataHandler({ appName: 'infra_logs' }); unregisterDataHandler({ appName: 'infra_metrics' }); - unregisterDataHandler({ appName: 'synthetics' }); + unregisterDataHandler({ appName: 'uptime' }); unregisterDataHandler({ appName: 'ux' }); } @@ -73,7 +73,7 @@ describe('HasDataContextProvider', () => { expect(result.current).toEqual({ hasDataMap: { apm: { hasData: undefined, status: 'success' }, - synthetics: { hasData: undefined, status: 'success' }, + uptime: { hasData: undefined, status: 'success' }, infra_logs: { hasData: undefined, status: 'success' }, infra_metrics: { hasData: undefined, status: 'success' }, ux: { hasData: undefined, status: 'success' }, @@ -98,7 +98,7 @@ describe('HasDataContextProvider', () => { }, { appName: 'infra_metrics', hasData: async () => ({ hasData: false }) }, { - appName: 'synthetics', + appName: 'uptime', hasData: async () => ({ hasData: false }), }, { @@ -128,7 +128,7 @@ describe('HasDataContextProvider', () => { expect(result.current).toEqual({ hasDataMap: { apm: { hasData: false, status: 'success' }, - synthetics: { + uptime: { hasData: false, status: 'success', }, @@ -161,7 +161,7 @@ describe('HasDataContextProvider', () => { hasData: async () => ({ hasData: false, indices: 'metric-*' }), }, { - appName: 'synthetics', + appName: 'uptime', hasData: async () => ({ hasData: false, indices: 'heartbeat-*, synthetics-*' }), }, { @@ -190,7 +190,7 @@ describe('HasDataContextProvider', () => { expect(result.current).toEqual({ hasDataMap: { apm: { hasData: true, status: 'success' }, - synthetics: { + uptime: { hasData: false, indices: 'heartbeat-*, synthetics-*', status: 'success', @@ -225,7 +225,7 @@ describe('HasDataContextProvider', () => { hasData: async () => ({ hasData: true, indices: 'metric-*' }), }, { - appName: 'synthetics', + appName: 'uptime', hasData: async () => ({ hasData: true, indices: 'heartbeat-*, synthetics-*' }), }, { @@ -257,7 +257,7 @@ describe('HasDataContextProvider', () => { hasData: true, status: 'success', }, - synthetics: { + uptime: { hasData: true, indices: 'heartbeat-*, synthetics-*', status: 'success', @@ -309,7 +309,7 @@ describe('HasDataContextProvider', () => { expect(result.current).toEqual({ hasDataMap: { apm: { hasData: true, indices: sampleAPMIndices, status: 'success' }, - synthetics: { hasData: undefined, status: 'success' }, + uptime: { hasData: undefined, status: 'success' }, infra_logs: { hasData: undefined, status: 'success' }, infra_metrics: { hasData: undefined, status: 'success' }, ux: { hasData: undefined, status: 'success' }, @@ -358,7 +358,7 @@ describe('HasDataContextProvider', () => { indices: sampleAPMIndices, status: 'success', }, - synthetics: { hasData: undefined, status: 'success' }, + uptime: { hasData: undefined, status: 'success' }, infra_logs: { hasData: undefined, status: 'success' }, infra_metrics: { hasData: undefined, status: 'success' }, ux: { hasData: undefined, status: 'success' }, @@ -391,7 +391,7 @@ describe('HasDataContextProvider', () => { hasData: async () => ({ hasData: true, indices: 'metric-*' }), }, { - appName: 'synthetics', + appName: 'uptime', hasData: async () => ({ hasData: true, indices: 'heartbeat-*, synthetics-*' }), }, { @@ -420,7 +420,7 @@ describe('HasDataContextProvider', () => { expect(result.current).toEqual({ hasDataMap: { apm: { hasData: undefined, status: 'failure' }, - synthetics: { + uptime: { hasData: true, indices: 'heartbeat-*, synthetics-*', status: 'success', @@ -465,7 +465,7 @@ describe('HasDataContextProvider', () => { }, }, { - appName: 'synthetics', + appName: 'uptime', hasData: async () => { throw new Error('BOOMMMMM'); }, @@ -498,7 +498,7 @@ describe('HasDataContextProvider', () => { expect(result.current).toEqual({ hasDataMap: { apm: { hasData: undefined, status: 'failure' }, - synthetics: { hasData: undefined, status: 'failure' }, + uptime: { hasData: undefined, status: 'failure' }, infra_logs: { hasData: undefined, status: 'failure' }, infra_metrics: { hasData: undefined, status: 'failure' }, ux: { hasData: undefined, status: 'failure' }, @@ -544,7 +544,7 @@ describe('HasDataContextProvider', () => { expect(result.current).toEqual({ hasDataMap: { apm: { hasData: undefined, status: 'success' }, - synthetics: { hasData: undefined, status: 'success' }, + uptime: { hasData: undefined, status: 'success' }, infra_logs: { hasData: undefined, status: 'success' }, infra_metrics: { hasData: undefined, status: 'success' }, ux: { hasData: undefined, status: 'success' }, diff --git a/x-pack/plugins/observability/public/context/has_data_context.tsx b/x-pack/plugins/observability/public/context/has_data_context.tsx index 775b401b50866..dda9ca4ef37fe 100644 --- a/x-pack/plugins/observability/public/context/has_data_context.tsx +++ b/x-pack/plugins/observability/public/context/has_data_context.tsx @@ -15,7 +15,7 @@ import { APM_APP, INFRA_LOGS_APP, INFRA_METRICS_APP, - SYNTHETICS_APP, + UPTIME_APP, UX_APP, } from './constants'; import { getDataHandler } from '../data_handler'; @@ -50,7 +50,7 @@ export const HasDataContext = createContext({} as HasDataContextValue); const apps: DataContextApps[] = [ APM_APP, - SYNTHETICS_APP, + UPTIME_APP, INFRA_LOGS_APP, INFRA_METRICS_APP, UX_APP, @@ -100,7 +100,7 @@ export function HasDataContextProvider({ children }: { children: React.ReactNode serviceName: resultUx?.serviceName as string, }); break; - case SYNTHETICS_APP: + case UPTIME_APP: const resultSy = await getDataHandler(app)?.hasData(); updateState({ hasData: resultSy?.hasData, indices: resultSy?.indices }); diff --git a/x-pack/plugins/observability/public/data_handler.test.ts b/x-pack/plugins/observability/public/data_handler.test.ts index d0cca3b5272e7..736fd3d968bda 100644 --- a/x-pack/plugins/observability/public/data_handler.test.ts +++ b/x-pack/plugins/observability/public/data_handler.test.ts @@ -188,7 +188,7 @@ describe('registerDataHandler', () => { }); describe('Uptime', () => { registerDataHandler({ - appName: 'synthetics', + appName: 'uptime', fetchData: async () => { return { title: 'uptime', @@ -226,13 +226,13 @@ describe('registerDataHandler', () => { }); it('registered data handler', () => { - const dataHandler = getDataHandler('synthetics'); + const dataHandler = getDataHandler('uptime'); expect(dataHandler?.fetchData).toBeDefined(); expect(dataHandler?.hasData).toBeDefined(); }); it('returns data when fetchData is called', async () => { - const dataHandler = getDataHandler('synthetics'); + const dataHandler = getDataHandler('uptime'); const response = await dataHandler?.fetchData(params); expect(response).toEqual({ title: 'uptime', diff --git a/x-pack/plugins/observability/public/pages/overview/components/empty_sections.tsx b/x-pack/plugins/observability/public/pages/overview/components/empty_sections.tsx index 704d0c6432ddd..bc91f20bf3b9c 100644 --- a/x-pack/plugins/observability/public/pages/overview/components/empty_sections.tsx +++ b/x-pack/plugins/observability/public/pages/overview/components/empty_sections.tsx @@ -106,7 +106,7 @@ const getEmptySections = ({ http }: { http: HttpSetup }): ISection[] => { href: http.basePath.prepend('/app/home#/tutorial_directory/metrics'), }, { - id: 'synthetics', + id: 'uptime', title: i18n.translate('xpack.observability.emptySection.apps.uptime.title', { defaultMessage: 'Uptime', }), diff --git a/x-pack/plugins/observability/public/pages/overview/overview.stories.tsx b/x-pack/plugins/observability/public/pages/overview/overview.stories.tsx index b139564019e74..e17a527933806 100644 --- a/x-pack/plugins/observability/public/pages/overview/overview.stories.tsx +++ b/x-pack/plugins/observability/public/pages/overview/overview.stories.tsx @@ -31,7 +31,7 @@ function unregisterAll() { unregisterDataHandler({ appName: 'apm' }); unregisterDataHandler({ appName: 'infra_logs' }); unregisterDataHandler({ appName: 'infra_metrics' }); - unregisterDataHandler({ appName: 'synthetics' }); + unregisterDataHandler({ appName: 'uptime' }); } const sampleAPMIndices = { transaction: 'apm-*' } as ApmIndicesConfig; @@ -235,7 +235,7 @@ storiesOf('app/Overview', module) hasData: async () => ({ hasData: false, indices: 'metric-*' }), }); registerDataHandler({ - appName: 'synthetics', + appName: 'uptime', fetchData: fetchUptimeData, hasData: async () => ({ hasData: false, indices: 'heartbeat-*,synthetics-*' }), }); @@ -323,7 +323,7 @@ storiesOf('app/Overview', module) hasData: async () => ({ hasData: true, indices: 'metric-*' }), }); registerDataHandler({ - appName: 'synthetics', + appName: 'uptime', fetchData: fetchUptimeData, hasData: async () => ({ hasData: true, indices: 'heartbeat-*,synthetics-*' }), }); @@ -349,7 +349,7 @@ storiesOf('app/Overview', module) hasData: async () => ({ hasData: true, indices: 'metric-*' }), }); registerDataHandler({ - appName: 'synthetics', + appName: 'uptime', fetchData: fetchUptimeData, hasData: async () => ({ hasData: true, indices: 'heartbeat-*,synthetics-*' }), }); @@ -377,7 +377,7 @@ storiesOf('app/Overview', module) hasData: async () => ({ hasData: true, indices: 'metric-*' }), }); registerDataHandler({ - appName: 'synthetics', + appName: 'uptime', fetchData: fetchUptimeData, hasData: async () => ({ hasData: true, indices: 'heartbeat-*,synthetics-*' }), }); @@ -402,7 +402,7 @@ storiesOf('app/Overview', module) hasData: async () => ({ hasData: true, indices: 'metric-*' }), }); registerDataHandler({ - appName: 'synthetics', + appName: 'uptime', fetchData: async () => emptyUptimeResponse, hasData: async () => ({ hasData: true, indices: 'heartbeat-*,synthetics-*' }), }); @@ -434,7 +434,7 @@ storiesOf('app/Overview', module) hasData: async () => ({ hasData: true, indices: 'metric-*' }), }); registerDataHandler({ - appName: 'synthetics', + appName: 'uptime', fetchData: async () => { throw new Error('Error fetching Uptime data'); }, @@ -472,7 +472,7 @@ storiesOf('app/Overview', module) }, }); registerDataHandler({ - appName: 'synthetics', + appName: 'uptime', fetchData: fetchUptimeData, // @ts-ignore throws an error instead hasData: async () => { @@ -509,7 +509,7 @@ storiesOf('app/Overview', module) }, }); registerDataHandler({ - appName: 'synthetics', + appName: 'uptime', fetchData: fetchUptimeData, // @ts-ignore throws an error instead hasData: async () => { diff --git a/x-pack/plugins/observability/public/typings/fetch_overview_data/index.ts b/x-pack/plugins/observability/public/typings/fetch_overview_data/index.ts index 64a66dff66e42..78837ed27f800 100644 --- a/x-pack/plugins/observability/public/typings/fetch_overview_data/index.ts +++ b/x-pack/plugins/observability/public/typings/fetch_overview_data/index.ts @@ -76,7 +76,7 @@ export type HasData = ( export type ObservabilityFetchDataPlugins = Exclude< ObservabilityApp, - 'observability-overview' | 'stack_monitoring' | 'uptime' | 'fleet' + 'observability-overview' | 'stack_monitoring' | 'fleet' | 'synthetics' >; export interface DataHandler< @@ -154,7 +154,7 @@ export interface ObservabilityFetchDataResponse { apm: ApmFetchDataResponse; infra_metrics: MetricsFetchDataResponse; infra_logs: LogsFetchDataResponse; - synthetics: UptimeFetchDataResponse; + uptime: UptimeFetchDataResponse; ux: UxFetchDataResponse; } @@ -162,6 +162,6 @@ export interface ObservabilityHasDataResponse { apm: APMHasDataResponse; infra_metrics: InfraMetricsHasDataResponse; infra_logs: InfraLogsHasDataResponse; - synthetics: SyntheticsHasDataResponse; + uptime: SyntheticsHasDataResponse; ux: UXHasDataResponse; } diff --git a/x-pack/plugins/observability/public/utils/observability_data_views/observability_data_views.ts b/x-pack/plugins/observability/public/utils/observability_data_views/observability_data_views.ts index 75851f87fa8d6..f5e8893ce1b28 100644 --- a/x-pack/plugins/observability/public/utils/observability_data_views/observability_data_views.ts +++ b/x-pack/plugins/observability/public/utils/observability_data_views/observability_data_views.ts @@ -33,6 +33,7 @@ const appFieldFormats: Record = { infra_metrics: infraMetricsFieldFormats, ux: rumFieldFormats, apm: apmFieldFormats, + uptime: syntheticsFieldFormats, synthetics: syntheticsFieldFormats, mobile: apmFieldFormats, alerts: null, @@ -43,6 +44,7 @@ const appRuntimeFields: Record = { synthetics: 'synthetics_static_index_pattern_id', + uptime: 'uptime_static_index_pattern_id', apm: 'apm_static_index_pattern_id', ux: 'rum_static_index_pattern_id', infra_logs: 'infra_logs_static_index_pattern_id', @@ -75,6 +78,11 @@ const getAppDataViewId = (app: AppDataType, indices: string) => { export async function getDataTypeIndices(dataType: AppDataType) { switch (dataType) { + case 'synthetics': + return { + hasData: true, + indices: 'synthetics-*', + }; case 'mobile': case 'ux': case 'apm': diff --git a/x-pack/plugins/synthetics/common/constants/synthetics/rest_api.ts b/x-pack/plugins/synthetics/common/constants/synthetics/rest_api.ts index a0875d9df4977..e310762c79146 100644 --- a/x-pack/plugins/synthetics/common/constants/synthetics/rest_api.ts +++ b/x-pack/plugins/synthetics/common/constants/synthetics/rest_api.ts @@ -16,4 +16,9 @@ export enum SYNTHETICS_API_URLS { SYNC_GLOBAL_PARAMS = `/synthetics/sync_global_params`, ENABLE_DEFAULT_ALERTING = `/synthetics/enable_default_alerting`, JOURNEY = `/internal/synthetics/journey/{checkGroup}`, + SYNTHETICS_SUCCESSFUL_CHECK = `/internal/synthetics/synthetics/check/success`, + JOURNEY_SCREENSHOT_BLOCKS = `/internal/synthetics/journey/screenshot/block`, + JOURNEY_FAILED_STEPS = `/internal/synthetics/journeys/failed_steps`, + NETWORK_EVENTS = `/internal/synthetics/network_events`, + JOURNEY_SCREENSHOT = `/internal/synthetics/journey/screenshot/{checkGroup}/{stepIndex}`, } diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/screenshot/journey_screenshot_dialog.test.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/screenshot/journey_screenshot_dialog.test.tsx index eb180b8e362e1..44e6db576d3fb 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/screenshot/journey_screenshot_dialog.test.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/screenshot/journey_screenshot_dialog.test.tsx @@ -56,12 +56,11 @@ describe('JourneyScreenshotDialog', () => { expect(() => render()).not.toThrowError(); }); - it('shows loading indicator when image is loading', () => { + it('shows loading indicator when image is loading', async () => { const { queryByTestId } = render(); expect(queryByTestId('screenshotImageLoadingProgress')).not.toBeInTheDocument(); userEvent.click(queryByTestId('screenshotImageNextButton')); - expect(queryByTestId('screenshotImageLoadingProgress')).toBeInTheDocument(); }); it('respects maxSteps', () => { @@ -69,7 +68,6 @@ describe('JourneyScreenshotDialog', () => { expect(queryByTestId('screenshotImageLoadingProgress')).not.toBeInTheDocument(); userEvent.click(queryByTestId('screenshotImageNextButton')); - expect(queryByTestId('screenshotImageLoadingProgress')).toBeInTheDocument(); expect(queryByTestId('screenshotImageNextButton')).toHaveProperty('disabled'); }); @@ -79,6 +77,6 @@ describe('JourneyScreenshotDialog', () => { 'src', 'http://localhost/test-img-url-1' ); - expect(getByText('First step')).toBeInTheDocument(); + expect(getByText('Step: 1 of 1')).toBeInTheDocument(); }); }); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/screenshot/journey_screenshot_dialog.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/screenshot/journey_screenshot_dialog.tsx index dd3607e8221f5..818541b1f73ea 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/screenshot/journey_screenshot_dialog.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/screenshot/journey_screenshot_dialog.tsx @@ -28,6 +28,7 @@ import { useIsWithinMaxBreakpoint, } from '@elastic/eui'; +import { SYNTHETICS_API_URLS } from '../../../../../../common/constants'; import { SyntheticsSettingsContext } from '../../../contexts'; import { useRetrieveStepImage } from '../monitor_test_result/use_retrieve_step_image'; @@ -54,7 +55,7 @@ export const JourneyScreenshotDialog = ({ const [stepNumber, setStepNumber] = useState(initialStepNumber); const { basePath } = useContext(SyntheticsSettingsContext); - const imgPath = `${basePath}/internal/uptime/journey/screenshot/${checkGroup}/${stepNumber}`; + const imgPath = getScreenshotUrl({ basePath, checkGroup, stepNumber }); const imageResult = useRetrieveStepImage({ hasIntersected: true, @@ -205,6 +206,24 @@ export const JourneyScreenshotDialog = ({ ) : null; }; +export const getScreenshotUrl = ({ + basePath, + checkGroup, + stepNumber, +}: { + basePath: string; + checkGroup?: string; + stepNumber: number; +}) => { + if (!checkGroup) { + return ''; + } + return `${basePath}${SYNTHETICS_API_URLS.JOURNEY_SCREENSHOT.replace( + '{checkGroup}', + checkGroup + ).replace('{stepIndex}', stepNumber.toString())}`; +}; + export const formatScreenshotStepsCount = (stepNumber: number, totalSteps: number) => i18n.translate('xpack.synthetics.monitor.stepOfSteps', { defaultMessage: 'Step: {stepNumber} of {totalSteps}', diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/screenshot/journey_step_screenshot_container.test.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/screenshot/journey_step_screenshot_container.test.tsx index fc194af26038c..9f5e313b78dd3 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/screenshot/journey_step_screenshot_container.test.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/screenshot/journey_step_screenshot_container.test.tsx @@ -11,13 +11,14 @@ import { JourneyStepScreenshotContainer } from './journey_step_screenshot_contai import { render } from '../../../utils/testing'; import * as observabilityPublic from '@kbn/observability-plugin/public'; import * as retrieveHooks from '../monitor_test_result/use_retrieve_step_image'; +import { getScreenshotUrl } from './journey_screenshot_dialog'; jest.mock('@kbn/observability-plugin/public'); jest.setTimeout(10 * 1000); -const imgPath1 = '/internal/uptime/journey/screenshot/test-check-group/1'; -const imgPath2 = '/internal/uptime/journey/screenshot/test-check-group/2'; +const imgPath1 = getScreenshotUrl({ basePath: '', checkGroup: 'test-check-group', stepNumber: 1 }); +const imgPath2 = getScreenshotUrl({ basePath: '', checkGroup: 'test-check-group', stepNumber: 2 }); const testImageDataResult = { [imgPath1]: { attempts: 1, diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/screenshot/journey_step_screenshot_container.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/screenshot/journey_step_screenshot_container.tsx index e7f7d05e1fcf4..f4fd7470545e7 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/screenshot/journey_step_screenshot_container.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/screenshot/journey_step_screenshot_container.tsx @@ -8,6 +8,7 @@ import React, { useContext } from 'react'; import useIntersection from 'react-use/lib/useIntersection'; +import { getScreenshotUrl } from './journey_screenshot_dialog'; import { SyntheticsSettingsContext } from '../../../contexts'; import { useRetrieveStepImage } from '../monitor_test_result/use_retrieve_step_image'; @@ -42,7 +43,7 @@ export const JourneyStepScreenshotContainer = ({ const { basePath } = useContext(SyntheticsSettingsContext); const imgPath = checkGroup - ? `${basePath}/internal/uptime/journey/screenshot/${checkGroup}/${initialStepNumber}` + ? getScreenshotUrl({ basePath, checkGroup, stepNumber: initialStepNumber }) : ''; const intersection = useIntersection(intersectionRef, { diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/hooks/use_synthetics_priviliges.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/hooks/use_synthetics_priviliges.tsx new file mode 100644 index 0000000000000..b1f1d57a33fd7 --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/hooks/use_synthetics_priviliges.tsx @@ -0,0 +1,79 @@ +/* + * 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 { useSelector } from 'react-redux'; +import React from 'react'; +import { + EuiEmptyPrompt, + EuiFlexGroup, + EuiFlexItem, + EuiIcon, + EuiMarkdownFormat, +} from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { css } from '@emotion/react'; +import { i18n } from '@kbn/i18n'; +import { selectOverviewStatus } from '../state/overview_status'; +import { SYNTHETICS_INDEX_PATTERN } from '../../../../common/constants'; + +export const useSyntheticsPrivileges = () => { + const { error } = useSelector(selectOverviewStatus); + + if (error?.body?.message?.startsWith('MissingIndicesPrivileges:')) { + return ( + + + + + + ); + } +}; + +const Unprivileged = ({ unprivilegedIndices }: { unprivilegedIndices: string[] }) => ( + } + title={ +

+ +

+ } + body={ +

+ +

+ } + footer={ + `\n- \`${idx}\``) + } + /> + } + /> +); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/routes.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/routes.tsx index 413680f14d602..d60c4c25d95a1 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/routes.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/routes.tsx @@ -18,6 +18,7 @@ import { APP_WRAPPER_CLASS } from '@kbn/core/public'; import { useKibana } from '@kbn/kibana-react-plugin/public'; import type { LazyObservabilityPageTemplateProps } from '@kbn/observability-plugin/public'; import { useInspectorContext } from '@kbn/observability-plugin/public'; +import { useSyntheticsPrivileges } from './hooks/use_synthetics_priviliges'; import { ClientPluginsStart } from '../../plugin'; import { getMonitorsRoute } from './components/monitors_page/route_config'; import { getMonitorDetailsRoute } from './components/monitor_details/route_config'; @@ -192,6 +193,8 @@ export const PageRouter: FC = () => { apiService.addInspectorRequest = addInspectorRequest; + const isUnPrivileged = useSyntheticsPrivileges(); + return ( {routes.map( @@ -207,12 +210,12 @@ export const PageRouter: FC = () => {
- + {isUnPrivileged || }
diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/browser_journey/api.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/browser_journey/api.ts index 33ed12db4b5d3..b4b352d388768 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/state/browser_journey/api.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/state/browser_journey/api.ts @@ -17,7 +17,7 @@ import { Ping, PingType, } from '../../../../../common/runtime_types'; -import { API_URLS, SYNTHETICS_API_URLS } from '../../../../../common/constants'; +import { SYNTHETICS_API_URLS } from '../../../../../common/constants'; export interface FetchJourneyStepsParams { checkGroup: string; @@ -25,7 +25,7 @@ export interface FetchJourneyStepsParams { } export async function fetchScreenshotBlockSet(params: string[]): Promise { - return apiService.post(API_URLS.JOURNEY_SCREENSHOT_BLOCKS, { + return apiService.post(SYNTHETICS_API_URLS.JOURNEY_SCREENSHOT_BLOCKS, { hashes: params, }); } @@ -45,7 +45,11 @@ export async function fetchJourneysFailedSteps({ }: { checkGroups: string[]; }): Promise { - return apiService.get(API_URLS.JOURNEY_FAILED_STEPS, { checkGroups }, FailedStepsApiResponseType); + return apiService.get( + SYNTHETICS_API_URLS.JOURNEY_FAILED_STEPS, + { checkGroups }, + FailedStepsApiResponseType + ); } export async function fetchLastSuccessfulCheck({ @@ -60,7 +64,7 @@ export async function fetchLastSuccessfulCheck({ location?: string; }): Promise { return await apiService.get( - API_URLS.SYNTHETICS_SUCCESSFUL_CHECK, + SYNTHETICS_API_URLS.SYNTHETICS_SUCCESSFUL_CHECK, { monitorId, timestamp, diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/network_events/api.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/network_events/api.ts index 8c52ebba47dad..7bc509aaacb03 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/state/network_events/api.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/state/network_events/api.ts @@ -9,7 +9,7 @@ import { SyntheticsNetworkEventsApiResponse, SyntheticsNetworkEventsApiResponseType, } from '../../../../../common/runtime_types'; -import { API_URLS } from '../../../../../common/constants'; +import { SYNTHETICS_API_URLS } from '../../../../../common/constants'; import { apiService } from '../../../../utils/api_service'; import { FetchNetworkEventsParams } from './actions'; @@ -17,7 +17,7 @@ export async function fetchNetworkEvents( params: FetchNetworkEventsParams ): Promise { return (await apiService.get( - API_URLS.NETWORK_EVENTS, + SYNTHETICS_API_URLS.NETWORK_EVENTS, { checkGroup: params.checkGroup, stepIndex: params.stepIndex, diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/app/uptime_page_template.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/app/uptime_page_template.tsx index 9e3d0d6ecd384..c1003f969586c 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/app/uptime_page_template.tsx +++ b/x-pack/plugins/synthetics/public/legacy_uptime/app/uptime_page_template.tsx @@ -9,7 +9,7 @@ import React, { useEffect } from 'react'; import { EuiPageHeaderProps, EuiPageTemplateProps } from '@elastic/eui'; import { useKibana } from '@kbn/kibana-react-plugin/public'; import { useInspectorContext } from '@kbn/observability-plugin/public'; -import { CERTIFICATES_ROUTE, OVERVIEW_ROUTE } from '../../../common/constants'; +import { CERTIFICATES_ROUTE, OVERVIEW_ROUTE, SETTINGS_ROUTE } from '../../../common/constants'; import { ClientPluginsStart } from '../../plugin'; import { useNoDataConfig } from './use_no_data_config'; import { EmptyStateLoading } from '../components/overview/empty_state/empty_state_loading'; @@ -42,7 +42,7 @@ export const UptimePageTemplateComponent: React.FC inspectorAdapters.requests.reset(); }, [inspectorAdapters.requests]); - if (error) { + if (error && path !== SETTINGS_ROUTE) { return ; } diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/common/header/action_menu_content.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/common/header/action_menu_content.tsx index a0c7f68d6b67a..ad5551dfc00bf 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/common/header/action_menu_content.tsx +++ b/x-pack/plugins/synthetics/public/legacy_uptime/components/common/header/action_menu_content.tsx @@ -52,7 +52,7 @@ export function ActionMenuContent(): React.ReactElement { reportType: 'kpi-over-time', allSeries: [ { - dataType: 'synthetics', + dataType: 'uptime', seriesType: 'area', selectedMetricField: 'monitor.duration.us', time: { from: dateRangeStart, to: dateRangeEnd }, diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor/monitor_duration/monitor_duration.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor/monitor_duration/monitor_duration.tsx index 822a476f5225a..562cfd8030095 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor/monitor_duration/monitor_duration.tsx +++ b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor/monitor_duration/monitor_duration.tsx @@ -18,7 +18,6 @@ interface DurationChartProps { hasMLJob: boolean; anomalies: AnomalyRecords | null; locationDurationLines: LocationDurationLine[]; - exploratoryViewLink: string; } /** diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor/monitor_duration/monitor_duration_container.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor/monitor_duration/monitor_duration_container.tsx index 32bf47451f957..a6fce56d9269e 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor/monitor_duration/monitor_duration_container.tsx +++ b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor/monitor_duration/monitor_duration_container.tsx @@ -8,7 +8,6 @@ import React, { useContext, useEffect } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import { JobStat } from '@kbn/ml-plugin/public'; -import { createExploratoryViewUrl } from '@kbn/observability-plugin/public'; import { useGetUrlParams } from '../../../hooks'; import { getAnomalyRecordsAction, @@ -25,7 +24,6 @@ import { UptimeRefreshContext } from '../../../contexts'; import { MonitorDurationComponent } from './monitor_duration'; import { MonitorIdParam } from '../../../../../common/types'; import { getMLJobId } from '../../../../../common/lib'; -import { useUptimeSettingsContext } from '../../../contexts/uptime_settings_context'; export const MonitorDuration: React.FC = ({ monitorId }) => { const { dateRangeStart, dateRangeEnd, absoluteDateRangeStart, absoluteDateRangeEnd } = @@ -47,27 +45,6 @@ export const MonitorDuration: React.FC = ({ monitorId }) => { const { lastRefresh } = useContext(UptimeRefreshContext); - const { basePath } = useUptimeSettingsContext(); - - const exploratoryViewLink = createExploratoryViewUrl( - { - reportType: 'kpi-over-time', - allSeries: [ - { - name: `${monitorId}-response-duration`, - time: { from: dateRangeStart, to: dateRangeEnd }, - reportDefinitions: { - 'monitor.id': [monitorId] as string[], - }, - breakdown: 'observer.geo.name', - operationType: 'average', - dataType: 'synthetics', - }, - ], - }, - basePath - ); - useEffect(() => { if (isMLAvailable && hasMLJob) { const anomalyParams = { @@ -96,7 +73,6 @@ export const MonitorDuration: React.FC = ({ monitorId }) => { anomalies={anomalies} hasMLJob={hasMLJob} loading={loading || jobsLoading} - exploratoryViewLink={exploratoryViewLink} locationDurationLines={durationLines?.locationDurationLines ?? []} /> ); diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor/synthetics/waterfall/components/waterfall_marker_trend.test.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor/synthetics/waterfall/components/waterfall_marker_trend.test.tsx index aeca7940ef773..0813108d1d1fa 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor/synthetics/waterfall/components/waterfall_marker_trend.test.tsx +++ b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor/synthetics/waterfall/components/waterfall_marker_trend.test.tsx @@ -98,7 +98,7 @@ describe('', () => { selectedMetricField: 'field', time: { to: '2021-12-03T14:35:41.072Z', from: '2021-12-03T13:47:41.072Z' }, seriesType: 'area', - dataType: 'synthetics', + dataType: 'uptime', reportDefinitions: { 'monitor.name': [null], 'synthetics.step.name.keyword': ['test-name'], diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/synthetics/check_steps/step_field_trend.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/synthetics/check_steps/step_field_trend.tsx index f337a9ba0bed5..4968897941e77 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/synthetics/check_steps/step_field_trend.tsx +++ b/x-pack/plugins/synthetics/public/legacy_uptime/components/synthetics/check_steps/step_field_trend.tsx @@ -51,7 +51,7 @@ export function StepFieldTrend({ selectedMetricField: field, time: getLast48Intervals(activeStep), seriesType: 'area', - dataType: 'synthetics', + dataType: 'uptime', reportDefinitions: { 'monitor.name': [activeStep.monitor.name!], 'synthetics.step.name.keyword': [activeStep.synthetics.step?.name!], diff --git a/x-pack/plugins/synthetics/public/plugin.ts b/x-pack/plugins/synthetics/public/plugin.ts index 2ac4aee91d9d1..77de03480be75 100644 --- a/x-pack/plugins/synthetics/public/plugin.ts +++ b/x-pack/plugins/synthetics/public/plugin.ts @@ -132,7 +132,7 @@ export class UptimePlugin plugins.share.url.locators.create(editMonitorNavigatorParams); plugins.observability.dashboard.register({ - appName: 'synthetics', + appName: 'uptime', hasData: async () => { const dataHelper = await getUptimeDataHelper(); const status = await dataHelper.indexStatus(); diff --git a/x-pack/plugins/synthetics/server/legacy_uptime/lib/lib.ts b/x-pack/plugins/synthetics/server/legacy_uptime/lib/lib.ts index c77f75dae0eaa..cf76c5abc978b 100644 --- a/x-pack/plugins/synthetics/server/legacy_uptime/lib/lib.ts +++ b/x-pack/plugins/synthetics/server/legacy_uptime/lib/lib.ts @@ -51,7 +51,7 @@ export class UptimeEsClient { request?: KibanaRequest; baseESClient: ElasticsearchClient; heartbeatIndices: string; - isInspectorEnabled: boolean | undefined; + isInspectorEnabled?: Promise; inspectableEsQueries: InspectResponse = []; uiSettings?: CoreRequestHandlerContext['uiSettings']; savedObjectsClient: SavedObjectsClientContract; @@ -59,31 +59,28 @@ export class UptimeEsClient { constructor( savedObjectsClient: SavedObjectsClientContract, esClient: ElasticsearchClient, - isDev: boolean = false, - uiSettings?: CoreRequestHandlerContext['uiSettings'], - request?: KibanaRequest + options?: { + isDev?: boolean; + uiSettings?: CoreRequestHandlerContext['uiSettings']; + request?: KibanaRequest; + heartbeatIndices?: string; + } ) { + const { isDev = false, uiSettings, request, heartbeatIndices = '' } = options ?? {}; this.uiSettings = uiSettings; this.baseESClient = esClient; - this.isInspectorEnabled = undefined; this.savedObjectsClient = savedObjectsClient; this.request = request; - this.heartbeatIndices = ''; + this.heartbeatIndices = heartbeatIndices; this.isDev = isDev; this.inspectableEsQueries = []; + this.getInspectEnabled(); } async initSettings() { const self = this; - if (!self.heartbeatIndices) { - const [isInspectorEnabled, dynamicSettings] = await Promise.all([ - getInspectEnabled(self.uiSettings), - savedObjectsAdapter.getUptimeDynamicSettings(self.savedObjectsClient), - ]); - - self.heartbeatIndices = dynamicSettings?.heartbeatIndices || ''; - self.isInspectorEnabled = isInspectorEnabled; - } + const heartbeatIndices = await this.getIndices(); + self.heartbeatIndices = heartbeatIndices || ''; } async search( @@ -123,8 +120,8 @@ export class UptimeEsClient { }) ); } - - if (this.isInspectorEnabled && this.request) { + const isInspectorEnabled = await this.getInspectEnabled(); + if (isInspectorEnabled && this.request) { debugESCall({ startTime, request: this.request, @@ -155,7 +152,9 @@ export class UptimeEsClient { esError = e; } - if (this.isInspectorEnabled && this.request) { + const isInspectorEnabled = await this.getInspectEnabled(); + + if (isInspectorEnabled && this.request) { debugESCall({ startTime, request: this.request, @@ -175,27 +174,39 @@ export class UptimeEsClient { return this.savedObjectsClient; } - getInspectData(path: string) { - const isInspectorEnabled = - (this.isInspectorEnabled || this.isDev) && path !== API_URLS.DYNAMIC_SETTINGS; + async getInspectData(path: string) { + const isInspectorEnabled = await this.getInspectEnabled(); + const showInspectData = + (isInspectorEnabled || this.isDev) && path !== API_URLS.DYNAMIC_SETTINGS; - if (isInspectorEnabled) { + if (showInspectData) { return { _inspect: this.inspectableEsQueries }; } return {}; } -} + async getInspectEnabled() { + if (this.isInspectorEnabled !== undefined) { + return this.isInspectorEnabled; + } -export function createEsParams(params: T): T { - return params; -} + if (!this.uiSettings) { + return false; + } + + this.isInspectorEnabled = this.uiSettings.client.get(enableInspectEsQueries); + } -function getInspectEnabled(uiSettings?: CoreRequestHandlerContext['uiSettings']) { - if (!uiSettings) { - return false; + async getIndices() { + if (this.heartbeatIndices) { + return this.heartbeatIndices; + } + const settings = await savedObjectsAdapter.getUptimeDynamicSettings(this.savedObjectsClient); + return settings?.heartbeatIndices || ''; } +} - return uiSettings.client.get(enableInspectEsQueries); +export function createEsParams(params: T): T { + return params; } /* eslint-disable no-console */ diff --git a/x-pack/plugins/synthetics/server/legacy_uptime/lib/requests/get_journey_screenshot.ts b/x-pack/plugins/synthetics/server/legacy_uptime/lib/requests/get_journey_screenshot.ts index 083f2562586ce..a7cbc0cb50d33 100644 --- a/x-pack/plugins/synthetics/server/legacy_uptime/lib/requests/get_journey_screenshot.ts +++ b/x-pack/plugins/synthetics/server/legacy_uptime/lib/requests/get_journey_screenshot.ts @@ -63,7 +63,7 @@ export const getJourneyScreenshot: UMElasticsearchQueryFn< const screenshotsOrRefs = (result.body.aggregations?.step.image.hits.hits as ResultType[]) ?? null; - if (screenshotsOrRefs.length === 0) return null; + if (!screenshotsOrRefs || screenshotsOrRefs?.length === 0) return null; return { ...screenshotsOrRefs[0]._source, diff --git a/x-pack/plugins/synthetics/server/legacy_uptime/routes/pings/journey_screenshot_blocks.test.ts b/x-pack/plugins/synthetics/server/legacy_uptime/routes/pings/journey_screenshot_blocks.test.ts index e865ad571edbc..93d23932c3ac5 100644 --- a/x-pack/plugins/synthetics/server/legacy_uptime/routes/pings/journey_screenshot_blocks.test.ts +++ b/x-pack/plugins/synthetics/server/legacy_uptime/routes/pings/journey_screenshot_blocks.test.ts @@ -9,11 +9,20 @@ import { createJourneyScreenshotBlocksRoute } from './journey_screenshot_blocks' import { UMServerLibs } from '../../uptime_server'; describe('journey screenshot blocks route', () => { - let handlerContext: unknown; + let handlerContext: any; let libs: UMServerLibs; + const data: any = []; beforeEach(() => { handlerContext = { - uptimeEsClient: jest.fn(), + uptimeEsClient: { + search: jest.fn().mockResolvedValue({ + body: { + hits: { + hits: data, + }, + }, + }), + }, request: { body: { hashes: ['hash1', 'hash2'], @@ -49,6 +58,32 @@ describe('journey screenshot blocks route', () => { }); it('returns blocks for request', async () => { + handlerContext.uptimeEsClient.search = jest.fn().mockResolvedValue({ + body: { + hits: { + hits: [ + { + _id: 'hash1', + _source: { + synthetics: { + blob: 'blob1', + blob_mime: 'image/jpeg', + }, + }, + }, + { + _id: 'hash2', + _source: { + synthetics: { + blob: 'blob2', + blob_mime: 'image/jpeg', + }, + }, + }, + ], + }, + }, + }); const responseData = [ { id: 'hash1', diff --git a/x-pack/plugins/synthetics/server/legacy_uptime/routes/pings/journey_screenshot_blocks.ts b/x-pack/plugins/synthetics/server/legacy_uptime/routes/pings/journey_screenshot_blocks.ts index 83e77fd8c8bd1..483444e62b1f8 100644 --- a/x-pack/plugins/synthetics/server/legacy_uptime/routes/pings/journey_screenshot_blocks.ts +++ b/x-pack/plugins/synthetics/server/legacy_uptime/routes/pings/journey_screenshot_blocks.ts @@ -8,8 +8,9 @@ import * as t from 'io-ts'; import { isRight } from 'fp-ts/lib/Either'; import { schema } from '@kbn/config-schema'; +import { getJourneyScreenshotBlocks } from '../../lib/requests/get_journey_screenshot_blocks'; import { UMServerLibs } from '../../lib/lib'; -import { UMRestApiRouteFactory } from '../types'; +import { RouteContext, UMRestApiRouteFactory, UptimeRouteContext } from '../types'; import { API_URLS } from '../../../../common/constants'; function isStringArray(data: unknown): data is string[] { @@ -24,22 +25,30 @@ export const createJourneyScreenshotBlocksRoute: UMRestApiRouteFactory = (libs: hashes: schema.arrayOf(schema.string()), }), }, - handler: async ({ request, response, uptimeEsClient }) => { - const { hashes: blockIds } = request.body; + handler: async (routeProps) => { + return await journeyScreenshotBlocksHandler(routeProps); + }, +}); - if (!isStringArray(blockIds)) return response.badRequest(); +export const journeyScreenshotBlocksHandler = async ({ + response, + request, + uptimeEsClient, +}: RouteContext | UptimeRouteContext) => { + const { hashes: blockIds } = request.body; - const result = await libs.requests.getJourneyScreenshotBlocks({ - blockIds, - uptimeEsClient, - }); + if (!isStringArray(blockIds)) return response.badRequest(); - if (result.length === 0) { - return response.notFound(); - } + const result = await getJourneyScreenshotBlocks({ + blockIds, + uptimeEsClient, + }); - return response.ok({ - body: result, - }); - }, -}); + if (result.length === 0) { + return response.notFound(); + } + + return response.ok({ + body: result, + }); +}; diff --git a/x-pack/plugins/synthetics/server/legacy_uptime/routes/pings/journey_screenshots.test.ts b/x-pack/plugins/synthetics/server/legacy_uptime/routes/pings/journey_screenshots.test.ts index 4e0f5fc616f88..7c4a7d61173fc 100644 --- a/x-pack/plugins/synthetics/server/legacy_uptime/routes/pings/journey_screenshots.test.ts +++ b/x-pack/plugins/synthetics/server/legacy_uptime/routes/pings/journey_screenshots.test.ts @@ -12,7 +12,15 @@ describe('journey screenshot route', () => { let handlerContext: any; beforeEach(() => { handlerContext = { - uptimeEsClient: jest.fn(), + uptimeEsClient: { + search: jest.fn().mockResolvedValue({ + body: { + hits: { + hits: [], + }, + }, + }), + }, request: { params: { checkGroup: 'check_group', @@ -63,6 +71,18 @@ describe('journey screenshot route', () => { totalSteps: 3, }; + handlerContext.uptimeEsClient.search = jest.fn().mockResolvedValue({ + body: { + hits: { + total: { + value: 3, + }, + hits: [], + }, + aggregations: { step: { image: { hits: { hits: [{ _source: mock }] } } } }, + }, + }); + const route = createJourneyScreenshotRoute({ requests: { getJourneyScreenshot: jest.fn().mockReturnValue(mock), @@ -93,8 +113,20 @@ describe('journey screenshot route', () => { }, type: 'step/screenshot', }, - totalSteps: 3, }; + + handlerContext.uptimeEsClient.search = jest.fn().mockResolvedValue({ + body: { + hits: { + total: { + value: 3, + }, + hits: [], + }, + aggregations: { step: { image: { hits: { hits: [{ _source: mock }] } } } }, + }, + }); + const route = createJourneyScreenshotRoute({ requests: { getJourneyScreenshot: jest.fn().mockReturnValue(mock), @@ -133,6 +165,17 @@ describe('journey screenshot route', () => { type: 'step/screenshot', }, }; + handlerContext.uptimeEsClient.search = jest.fn().mockResolvedValue({ + body: { + hits: { + total: { + value: 3, + }, + hits: [], + }, + aggregations: { step: { image: { hits: { hits: [{ _source: mock }] } } } }, + }, + }); const route = createJourneyScreenshotRoute({ requests: { getJourneyScreenshot: jest.fn().mockReturnValue(mock), diff --git a/x-pack/plugins/synthetics/server/legacy_uptime/routes/pings/journey_screenshots.ts b/x-pack/plugins/synthetics/server/legacy_uptime/routes/pings/journey_screenshots.ts index 6ae3ae1b45662..7c56b3a26fa0c 100644 --- a/x-pack/plugins/synthetics/server/legacy_uptime/routes/pings/journey_screenshots.ts +++ b/x-pack/plugins/synthetics/server/legacy_uptime/routes/pings/journey_screenshots.ts @@ -8,8 +8,11 @@ import { schema } from '@kbn/config-schema'; import { isRefResult, isFullScreenshot } from '../../../../common/runtime_types/ping/synthetics'; import { UMServerLibs } from '../../lib/lib'; -import { ScreenshotReturnTypesUnion } from '../../lib/requests/get_journey_screenshot'; -import { UMRestApiRouteFactory } from '../types'; +import { + getJourneyScreenshot, + ScreenshotReturnTypesUnion, +} from '../../lib/requests/get_journey_screenshot'; +import { RouteContext, UMRestApiRouteFactory, UptimeRouteContext } from '../types'; import { API_URLS } from '../../../../common/constants'; function getSharedHeaders(stepName: string, totalSteps: number) { @@ -29,32 +32,40 @@ export const createJourneyScreenshotRoute: UMRestApiRouteFactory = (libs: UMServ stepIndex: schema.number(), }), }, - handler: async ({ uptimeEsClient, request, response }) => { - const { checkGroup, stepIndex } = request.params; + handler: async (routeProps) => { + return await journeyScreenshotHandler(routeProps); + }, +}); - const result: ScreenshotReturnTypesUnion | null = await libs.requests.getJourneyScreenshot({ - uptimeEsClient, - checkGroup, - stepIndex, - }); +export const journeyScreenshotHandler = async ({ + response, + request, + uptimeEsClient, +}: RouteContext | UptimeRouteContext) => { + const { checkGroup, stepIndex } = request.params; - if (isFullScreenshot(result) && typeof result.synthetics?.blob !== 'undefined') { - return response.ok({ - body: Buffer.from(result.synthetics.blob, 'base64'), - headers: { - 'content-type': result.synthetics.blob_mime || 'image/png', // falls back to 'image/png' for earlier versions of synthetics - ...getSharedHeaders(result.synthetics.step.name, result.totalSteps), - }, - }); - } else if (isRefResult(result)) { - return response.ok({ - body: { - screenshotRef: result, - }, - headers: getSharedHeaders(result.synthetics.step.name, result.totalSteps), - }); - } + const result: ScreenshotReturnTypesUnion | null = await getJourneyScreenshot({ + uptimeEsClient, + checkGroup, + stepIndex, + }); - return response.notFound(); - }, -}); + if (isFullScreenshot(result) && typeof result.synthetics?.blob !== 'undefined') { + return response.ok({ + body: Buffer.from(result.synthetics.blob, 'base64'), + headers: { + 'content-type': result.synthetics.blob_mime || 'image/png', // falls back to 'image/png' for earlier versions of synthetics + ...getSharedHeaders(result.synthetics.step.name, result.totalSteps), + }, + }); + } else if (isRefResult(result)) { + return response.ok({ + body: { + screenshotRef: result, + }, + headers: getSharedHeaders(result.synthetics.step.name, result.totalSteps), + }); + } + + return response.notFound(); +}; diff --git a/x-pack/plugins/synthetics/server/legacy_uptime/routes/synthetics/last_successful_check.ts b/x-pack/plugins/synthetics/server/legacy_uptime/routes/synthetics/last_successful_check.ts index 3edba46aa39e8..5b1cb0fd33bc2 100644 --- a/x-pack/plugins/synthetics/server/legacy_uptime/routes/synthetics/last_successful_check.ts +++ b/x-pack/plugins/synthetics/server/legacy_uptime/routes/synthetics/last_successful_check.ts @@ -6,11 +6,13 @@ */ import { schema } from '@kbn/config-schema'; +import { getJourneyScreenshot } from '../../lib/requests/get_journey_screenshot'; import { isRefResult, isFullScreenshot } from '../../../../common/runtime_types/ping/synthetics'; import { Ping } from '../../../../common/runtime_types/ping/ping'; import { UMServerLibs } from '../../lib/lib'; -import { UMRestApiRouteFactory } from '../types'; +import { RouteContext, UMRestApiRouteFactory, UptimeRouteContext } from '../types'; import { API_URLS } from '../../../../common/constants'; +import { getLastSuccessfulCheck } from '../../lib/requests/get_last_successful_check'; export const createLastSuccessfulCheckRoute: UMRestApiRouteFactory = (libs: UMServerLibs) => ({ method: 'GET', @@ -23,41 +25,49 @@ export const createLastSuccessfulCheckRoute: UMRestApiRouteFactory = (libs: UMSe location: schema.maybe(schema.string()), }), }, - handler: async ({ uptimeEsClient, request, response }) => { - const { timestamp, monitorId, stepIndex, location } = request.query; - - const check: Ping | null = await libs.requests.getLastSuccessfulCheck({ - uptimeEsClient, - monitorId, - timestamp, - location, - }); - - if (check === null) { - return response.notFound(); - } - - if (!check.monitor.check_group) { - return response.ok({ body: check }); - } - - const screenshot = await libs.requests.getJourneyScreenshot({ - uptimeEsClient, - checkGroup: check.monitor.check_group, - stepIndex, - }); - - if (screenshot === null) { - return response.ok({ body: check }); - } - - if (check.synthetics) { - check.synthetics.isScreenshotRef = isRefResult(screenshot); - check.synthetics.isFullScreenshot = isFullScreenshot(screenshot); - } - - return response.ok({ - body: check, - }); + handler: async (routeProps) => { + return await getLastSuccessfulCheckScreenshot(routeProps); }, }); + +export const getLastSuccessfulCheckScreenshot = async ({ + response, + request, + uptimeEsClient, +}: RouteContext | UptimeRouteContext) => { + const { timestamp, monitorId, stepIndex, location } = request.query; + + const check: Ping | null = await getLastSuccessfulCheck({ + uptimeEsClient, + monitorId, + timestamp, + location, + }); + + if (check === null) { + return response.notFound(); + } + + if (!check.monitor.check_group) { + return response.ok({ body: check }); + } + + const screenshot = await getJourneyScreenshot({ + uptimeEsClient, + checkGroup: check.monitor.check_group, + stepIndex, + }); + + if (screenshot === null) { + return response.ok({ body: check }); + } + + if (check.synthetics) { + check.synthetics.isScreenshotRef = isRefResult(screenshot); + check.synthetics.isFullScreenshot = isFullScreenshot(screenshot); + } + + return response.ok({ + body: check, + }); +}; diff --git a/x-pack/plugins/synthetics/server/legacy_uptime/routes/types.ts b/x-pack/plugins/synthetics/server/legacy_uptime/routes/types.ts index 55286f8ea770e..35ab8e9217eb6 100644 --- a/x-pack/plugins/synthetics/server/legacy_uptime/routes/types.ts +++ b/x-pack/plugins/synthetics/server/legacy_uptime/routes/types.ts @@ -88,6 +88,16 @@ export type SyntheticsRouteWrapper = ( syntheticsMonitorClient: SyntheticsMonitorClient ) => UMKibanaRoute; +export interface UptimeRouteContext { + uptimeEsClient: UptimeEsClient; + context: UptimeRequestHandlerContext; + request: SyntheticsRequest; + response: KibanaResponseFactory; + savedObjectsClient: SavedObjectsClientContract; + server: UptimeServerSetup; + subject?: Subject; +} + /** * This is the contract we specify internally for route handling. */ @@ -99,15 +109,7 @@ export type UMRouteHandler = ({ server, savedObjectsClient, subject, -}: { - uptimeEsClient: UptimeEsClient; - context: UptimeRequestHandlerContext; - request: SyntheticsRequest; - response: KibanaResponseFactory; - savedObjectsClient: SavedObjectsClientContract; - server: UptimeServerSetup; - subject?: Subject; -}) => IKibanaResponse | Promise>; +}: UptimeRouteContext) => IKibanaResponse | Promise>; export interface RouteContext { uptimeEsClient: UptimeEsClient; diff --git a/x-pack/plugins/synthetics/server/legacy_uptime/routes/uptime_route_wrapper.ts b/x-pack/plugins/synthetics/server/legacy_uptime/routes/uptime_route_wrapper.ts index b5a025c885dcf..97c204e90ac99 100644 --- a/x-pack/plugins/synthetics/server/legacy_uptime/routes/uptime_route_wrapper.ts +++ b/x-pack/plugins/synthetics/server/legacy_uptime/routes/uptime_route_wrapper.ts @@ -6,6 +6,7 @@ */ import { KibanaResponse } from '@kbn/core-http-router-server-internal'; +import { checkIndicesReadPrivileges } from '../../synthetics_service/authentication/check_has_privilege'; import { UMKibanaRouteWrapper } from './types'; import { isTestUser, UptimeEsClient } from '../lib/lib'; @@ -23,31 +24,46 @@ export const uptimeRouteWrapper: UMKibanaRouteWrapper = (uptimeRoute, server) => const uptimeEsClient = new UptimeEsClient( coreContext.savedObjects.client, esClient.asCurrentUser, - Boolean(server.isDev && !isTestUser(server)), - coreContext.uiSettings, - request + { + request, + uiSettings: coreContext.uiSettings, + isDev: Boolean(server.isDev && !isTestUser(server)), + } ); server.uptimeEsClient = uptimeEsClient; + try { + const res = await uptimeRoute.handler({ + uptimeEsClient, + savedObjectsClient: coreContext.savedObjects.client, + context, + request, + response, + server, + }); - const res = await uptimeRoute.handler({ - uptimeEsClient, - savedObjectsClient: coreContext.savedObjects.client, - context, - request, - response, - server, - }); + if (res instanceof KibanaResponse) { + return res; + } - if (res instanceof KibanaResponse) { - return res; + return response.ok({ + body: { + ...res, + ...(await uptimeEsClient.getInspectData(uptimeRoute.path)), + }, + }); + } catch (e) { + if (e.statusCode === 403) { + const privileges = await checkIndicesReadPrivileges(uptimeEsClient); + if (!privileges.has_all_requested) { + return response.forbidden({ + body: { + message: `MissingIndicesPrivileges: You do not have permission to read from the ${uptimeEsClient.heartbeatIndices} indices. Please contact your administrator.`, + }, + }); + } + } + throw e; } - - return response.ok({ - body: { - ...res, - ...uptimeEsClient.getInspectData(uptimeRoute.path), - }, - }); }, }); diff --git a/x-pack/plugins/synthetics/server/routes/index.ts b/x-pack/plugins/synthetics/server/routes/index.ts index b394c53f20142..b77d1d13247e0 100644 --- a/x-pack/plugins/synthetics/server/routes/index.ts +++ b/x-pack/plugins/synthetics/server/routes/index.ts @@ -5,7 +5,10 @@ * 2.0. */ -import { createJourneyRoute } from './pings/journeys'; +import { createJourneyScreenshotRoute } from './pings/journey_screenshots'; +import { createJourneyScreenshotBlocksRoute } from './pings/journey_screenshot_blocks'; +import { createLastSuccessfulCheckRoute } from './pings/last_successful_check'; +import { createJourneyFailedStepsRoute, createJourneyRoute } from './pings/journeys'; import { updateDefaultAlertingRoute } from './default_alerts/update_default_alert'; import { syncParamsSyntheticsParamsRoute } from './settings/sync_global_params'; import { editSyntheticsParamsRoute } from './settings/edit_param'; @@ -44,6 +47,7 @@ import { getHasIntegrationMonitorsRoute } from './fleet/get_has_integration_moni import { addSyntheticsParamsRoute } from './settings/add_param'; import { enableDefaultAlertingRoute } from './default_alerts/enable_default_alert'; import { getDefaultAlertingRoute } from './default_alerts/get_default_alert'; +import { createNetworkEventsRoute } from './network_events'; import { addPrivateLocationRoute } from './settings/private_locations/add_private_location'; import { deletePrivateLocationRoute } from './settings/private_locations/delete_private_location'; import { getPrivateLocationsRoute } from './settings/private_locations/get_private_locations'; @@ -80,6 +84,11 @@ export const syntheticsAppRestApiRoutes: SyntheticsRestApiRouteFactory[] = [ getDefaultAlertingRoute, updateDefaultAlertingRoute, createJourneyRoute, + createLastSuccessfulCheckRoute, + createJourneyScreenshotBlocksRoute, + createJourneyFailedStepsRoute, + createNetworkEventsRoute, + createJourneyScreenshotRoute, addPrivateLocationRoute, deletePrivateLocationRoute, getPrivateLocationsRoute, diff --git a/x-pack/plugins/synthetics/server/routes/network_events/get_network_events.ts b/x-pack/plugins/synthetics/server/routes/network_events/get_network_events.ts new file mode 100644 index 0000000000000..114cb88d508b1 --- /dev/null +++ b/x-pack/plugins/synthetics/server/routes/network_events/get_network_events.ts @@ -0,0 +1,32 @@ +/* + * 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 { schema } from '@kbn/config-schema'; +import { getNetworkEvents } from '../../legacy_uptime/lib/requests/get_network_events'; +import { SYNTHETICS_API_URLS } from '../../../common/constants'; +import { UMServerLibs } from '../../legacy_uptime/uptime_server'; +import { SyntheticsRestApiRouteFactory } from '../../legacy_uptime/routes'; + +export const createNetworkEventsRoute: SyntheticsRestApiRouteFactory = (libs: UMServerLibs) => ({ + method: 'GET', + path: SYNTHETICS_API_URLS.NETWORK_EVENTS, + validate: { + query: schema.object({ + checkGroup: schema.string(), + stepIndex: schema.number(), + }), + }, + handler: async ({ uptimeEsClient, request }): Promise => { + const { checkGroup, stepIndex } = request.query; + + return await getNetworkEvents({ + uptimeEsClient, + checkGroup, + stepIndex, + }); + }, +}); diff --git a/x-pack/plugins/synthetics/server/routes/network_events/index.ts b/x-pack/plugins/synthetics/server/routes/network_events/index.ts new file mode 100644 index 0000000000000..e2b8c871e17bf --- /dev/null +++ b/x-pack/plugins/synthetics/server/routes/network_events/index.ts @@ -0,0 +1,8 @@ +/* + * 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. + */ + +export { createNetworkEventsRoute } from './get_network_events'; diff --git a/x-pack/plugins/synthetics/server/routes/pings/journey_screenshot_blocks.ts b/x-pack/plugins/synthetics/server/routes/pings/journey_screenshot_blocks.ts new file mode 100644 index 0000000000000..6cf0f92b41b40 --- /dev/null +++ b/x-pack/plugins/synthetics/server/routes/pings/journey_screenshot_blocks.ts @@ -0,0 +1,27 @@ +/* + * 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 { schema } from '@kbn/config-schema'; +import { UMServerLibs } from '../../legacy_uptime/uptime_server'; +import { journeyScreenshotBlocksHandler } from '../../legacy_uptime/routes/pings/journey_screenshot_blocks'; +import { SyntheticsRestApiRouteFactory } from '../../legacy_uptime/routes'; +import { SYNTHETICS_API_URLS } from '../../../common/constants'; + +export const createJourneyScreenshotBlocksRoute: SyntheticsRestApiRouteFactory = ( + libs: UMServerLibs +) => ({ + method: 'POST', + path: SYNTHETICS_API_URLS.JOURNEY_SCREENSHOT_BLOCKS, + validate: { + body: schema.object({ + hashes: schema.arrayOf(schema.string()), + }), + }, + handler: async (routeProps) => { + return await journeyScreenshotBlocksHandler(routeProps); + }, +}); diff --git a/x-pack/plugins/synthetics/server/routes/pings/journey_screenshots.ts b/x-pack/plugins/synthetics/server/routes/pings/journey_screenshots.ts new file mode 100644 index 0000000000000..a92cf73f1711b --- /dev/null +++ b/x-pack/plugins/synthetics/server/routes/pings/journey_screenshots.ts @@ -0,0 +1,28 @@ +/* + * 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 { schema } from '@kbn/config-schema'; +import { SyntheticsRestApiRouteFactory } from '../../legacy_uptime/routes'; +import { SYNTHETICS_API_URLS } from '../../../common/constants'; +import { UMServerLibs } from '../../legacy_uptime/uptime_server'; +import { journeyScreenshotHandler } from '../../legacy_uptime/routes/pings/journey_screenshots'; + +export const createJourneyScreenshotRoute: SyntheticsRestApiRouteFactory = ( + libs: UMServerLibs +) => ({ + method: 'GET', + path: SYNTHETICS_API_URLS.JOURNEY_SCREENSHOT, + validate: { + params: schema.object({ + checkGroup: schema.string(), + stepIndex: schema.number(), + }), + }, + handler: async (routeProps) => { + return await journeyScreenshotHandler(routeProps); + }, +}); diff --git a/x-pack/plugins/synthetics/server/routes/pings/journeys.ts b/x-pack/plugins/synthetics/server/routes/pings/journeys.ts index 9ef10fe6bebb2..212f79bd867d8 100644 --- a/x-pack/plugins/synthetics/server/routes/pings/journeys.ts +++ b/x-pack/plugins/synthetics/server/routes/pings/journeys.ts @@ -6,9 +6,13 @@ */ import { schema } from '@kbn/config-schema'; -import { API_URLS, SYNTHETICS_API_URLS } from '../../../common/constants'; +import { getJourneyFailedSteps } from '../../legacy_uptime/lib/requests/get_journey_failed_steps'; +import { SYNTHETICS_API_URLS } from '../../../common/constants'; import { UMServerLibs } from '../../legacy_uptime/uptime_server'; -import { UMRestApiRouteFactory } from '../../legacy_uptime/routes/types'; +import { + SyntheticsRestApiRouteFactory, + UMRestApiRouteFactory, +} from '../../legacy_uptime/routes/types'; import { getJourneyDetails } from '../../queries/get_journey_details'; export const createJourneyRoute: UMRestApiRouteFactory = (libs: UMServerLibs) => ({ @@ -54,9 +58,11 @@ export const createJourneyRoute: UMRestApiRouteFactory = (libs: UMServerLibs) => }, }); -export const createJourneyFailedStepsRoute: UMRestApiRouteFactory = (libs: UMServerLibs) => ({ +export const createJourneyFailedStepsRoute: SyntheticsRestApiRouteFactory = ( + libs: UMServerLibs +) => ({ method: 'GET', - path: API_URLS.JOURNEY_FAILED_STEPS, + path: SYNTHETICS_API_URLS.JOURNEY_FAILED_STEPS, validate: { query: schema.object({ checkGroups: schema.arrayOf(schema.string()), @@ -65,7 +71,7 @@ export const createJourneyFailedStepsRoute: UMRestApiRouteFactory = (libs: UMSer handler: async ({ uptimeEsClient, request, response }): Promise => { const { checkGroups } = request.query; try { - const result = await libs.requests.getJourneyFailedSteps({ + const result = await getJourneyFailedSteps({ uptimeEsClient, checkGroups, }); diff --git a/x-pack/plugins/synthetics/server/routes/pings/last_successful_check.ts b/x-pack/plugins/synthetics/server/routes/pings/last_successful_check.ts new file mode 100644 index 0000000000000..7ebc25adec373 --- /dev/null +++ b/x-pack/plugins/synthetics/server/routes/pings/last_successful_check.ts @@ -0,0 +1,30 @@ +/* + * 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 { schema } from '@kbn/config-schema'; +import { getLastSuccessfulCheckScreenshot } from '../../legacy_uptime/routes/synthetics/last_successful_check'; +import { SyntheticsRestApiRouteFactory } from '../../legacy_uptime/routes'; +import { UMServerLibs } from '../../legacy_uptime/uptime_server'; +import { SYNTHETICS_API_URLS } from '../../../common/constants'; + +export const createLastSuccessfulCheckRoute: SyntheticsRestApiRouteFactory = ( + libs: UMServerLibs +) => ({ + method: 'GET', + path: SYNTHETICS_API_URLS.SYNTHETICS_SUCCESSFUL_CHECK, + validate: { + query: schema.object({ + monitorId: schema.string(), + stepIndex: schema.number(), + timestamp: schema.string(), + location: schema.maybe(schema.string()), + }), + }, + handler: async (routeProps) => { + return await getLastSuccessfulCheckScreenshot(routeProps); + }, +}); diff --git a/x-pack/plugins/synthetics/server/synthetics_route_wrapper.ts b/x-pack/plugins/synthetics/server/synthetics_route_wrapper.ts index 3e3eb72700ed2..3a13b228ba035 100644 --- a/x-pack/plugins/synthetics/server/synthetics_route_wrapper.ts +++ b/x-pack/plugins/synthetics/server/synthetics_route_wrapper.ts @@ -5,6 +5,8 @@ * 2.0. */ import { KibanaResponse } from '@kbn/core-http-router-server-internal'; +import { checkIndicesReadPrivileges } from './synthetics_service/authentication/check_has_privilege'; +import { SYNTHETICS_INDEX_PATTERN } from '../common/constants'; import { isTestUser, UptimeEsClient } from './legacy_uptime/lib/lib'; import { syntheticsServiceApiKey } from './legacy_uptime/lib/saved_objects/service_api_key'; import { SyntheticsRouteWrapper, SyntheticsStreamingRouteHandler } from './legacy_uptime/routes'; @@ -29,13 +31,11 @@ export const syntheticsRouteWrapper: SyntheticsRouteWrapper = ( // specifically needed for the synthetics service api key generation server.authSavedObjectsClient = savedObjectsClient; - const uptimeEsClient = new UptimeEsClient( - savedObjectsClient, - esClient.asCurrentUser, - false, - coreContext.uiSettings, - request - ); + const uptimeEsClient = new UptimeEsClient(savedObjectsClient, esClient.asCurrentUser, { + request, + isDev: false, + uiSettings: coreContext.uiSettings, + }); server.uptimeEsClient = uptimeEsClient; @@ -62,35 +62,48 @@ export const syntheticsRouteWrapper: SyntheticsRouteWrapper = ( // specifically needed for the synthetics service api key generation server.authSavedObjectsClient = savedObjectsClient; - const uptimeEsClient = new UptimeEsClient( - savedObjectsClient, - esClient.asCurrentUser, - Boolean(server.isDev) && !isTestUser(server), + const uptimeEsClient = new UptimeEsClient(savedObjectsClient, esClient.asCurrentUser, { + request, uiSettings, - request - ); + isDev: Boolean(server.isDev) && !isTestUser(server), + heartbeatIndices: SYNTHETICS_INDEX_PATTERN, + }); server.uptimeEsClient = uptimeEsClient; - const res = await uptimeRoute.handler({ - uptimeEsClient, - savedObjectsClient, - context, - request, - response, - server, - syntheticsMonitorClient, - }); + try { + const res = await uptimeRoute.handler({ + uptimeEsClient, + savedObjectsClient, + context, + request, + response, + server, + syntheticsMonitorClient, + }); + if (res instanceof KibanaResponse) { + return res; + } - if (res instanceof KibanaResponse) { - return res; + return response.ok({ + body: { + ...res, + ...(await uptimeEsClient.getInspectData(uptimeRoute.path)), + }, + }); + } catch (e) { + if (e.statusCode === 403) { + const privileges = await checkIndicesReadPrivileges(uptimeEsClient); + if (!privileges.has_all_requested) { + return response.forbidden({ + body: { + message: + 'MissingIndicesPrivileges: You do not have permission to read from the synthetics-* indices. Please contact your administrator.', + }, + }); + } + } + throw e; } - - return response.ok({ - body: { - ...res, - ...uptimeEsClient.getInspectData(uptimeRoute.path), - }, - }); }, }); diff --git a/x-pack/plugins/synthetics/server/synthetics_service/authentication/check_has_privilege.ts b/x-pack/plugins/synthetics/server/synthetics_service/authentication/check_has_privilege.ts index 56b7ce8b79c62..7a2dcb5446725 100644 --- a/x-pack/plugins/synthetics/server/synthetics_service/authentication/check_has_privilege.ts +++ b/x-pack/plugins/synthetics/server/synthetics_service/authentication/check_has_privilege.ts @@ -5,9 +5,11 @@ * 2.0. */ +import { SecurityIndexPrivilege } from '@elastic/elasticsearch/lib/api/types'; import { UptimeServerSetup } from '../../legacy_uptime/lib/adapters'; import { getFakeKibanaRequest } from '../utils/fake_kibana_request'; -import { serviceApiKeyPrivileges } from '../get_api_key'; +import { serviceApiKeyPrivileges, syntheticsIndex } from '../get_api_key'; +import { UptimeEsClient } from '../../legacy_uptime/lib/lib'; export const checkHasPrivileges = async ( server: UptimeServerSetup, @@ -22,3 +24,16 @@ export const checkHasPrivileges = async ( }, }); }; + +export const checkIndicesReadPrivileges = async (uptimeEsClient: UptimeEsClient) => { + return await uptimeEsClient.baseESClient.security.hasPrivileges({ + body: { + index: [ + { + names: [syntheticsIndex], + privileges: ['read'] as SecurityIndexPrivilege[], + }, + ], + }, + }); +};