diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/getting_started/getting_started_page.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/getting_started/getting_started_page.tsx index c63faf20904a4..f42b781089aea 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/getting_started/getting_started_page.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/getting_started/getting_started_page.tsx @@ -29,6 +29,7 @@ import { setAddingNewPrivateLocation, getAgentPoliciesAction, selectAgentPolicies, + cleanMonitorListState, } from '../../state'; import { MONITOR_ADD_ROUTE } from '../../../../../common/constants/ui'; import { PrivateLocation } from '../../../../../common/runtime_types'; @@ -46,6 +47,9 @@ export const GettingStartedPage = () => { if (canReadAgentPolicies) { dispatch(getAgentPoliciesAction.get()); } + return () => { + dispatch(cleanMonitorListState()); + }; }, [canReadAgentPolicies, dispatch]); useBreadcrumbs([{ text: MONITORING_OVERVIEW_LABEL }]); // No extra breadcrumbs on overview diff --git a/x-pack/plugins/synthetics/server/synthetics_service/service_api_client.test.ts b/x-pack/plugins/synthetics/server/synthetics_service/service_api_client.test.ts index b65d4a30e0b62..22ec8e8a3213e 100644 --- a/x-pack/plugins/synthetics/server/synthetics_service/service_api_client.test.ts +++ b/x-pack/plugins/synthetics/server/synthetics_service/service_api_client.test.ts @@ -177,7 +177,6 @@ describe('callAPI', () => { 1, { isEdit: undefined, - runOnce: undefined, monitors: testMonitors.filter((monitor: any) => monitor.locations.some((loc: any) => loc.id === 'us_central') ), @@ -192,7 +191,6 @@ describe('callAPI', () => { 2, { isEdit: undefined, - runOnce: undefined, monitors: testMonitors.filter((monitor: any) => monitor.locations.some((loc: any) => loc.id === 'us_central_qa') ), @@ -207,7 +205,6 @@ describe('callAPI', () => { 3, { isEdit: undefined, - runOnce: undefined, monitors: testMonitors.filter((monitor: any) => monitor.locations.some((loc: any) => loc.id === 'us_central_staging') ), @@ -284,15 +281,15 @@ describe('callAPI', () => { }); expect(logger.debug).toHaveBeenNthCalledWith( 2, - 'Successfully called service location https://service.dev with method POST with 4 monitors ' + 'Successfully called service location https://service.devundefined with method POST with 4 monitors' ); expect(logger.debug).toHaveBeenNthCalledWith( 4, - 'Successfully called service location https://qa.service.elstc.co with method POST with 4 monitors ' + 'Successfully called service location https://qa.service.elstc.coundefined with method POST with 4 monitors' ); expect(logger.debug).toHaveBeenNthCalledWith( 6, - 'Successfully called service location https://qa.service.stg.co with method POST with 1 monitors ' + 'Successfully called service location https://qa.service.stg.coundefined with method POST with 1 monitors' ); }); @@ -320,7 +317,6 @@ describe('callAPI', () => { coreStart: mockCoreStart, } as UptimeServerSetup ); - apiClient.locations = testLocations; const output = { hosts: ['https://localhost:9200'], api_key: '12345' }; @@ -354,6 +350,110 @@ describe('callAPI', () => { url: 'https://service.dev/monitors', }); }); + + it('Calls the `/run` endpoint when calling `runOnce`', async () => { + const axiosSpy = (axios as jest.MockedFunction).mockResolvedValue({ + status: 200, + statusText: 'ok', + headers: {}, + config: {}, + data: { allowed: true, signupUrl: 'http://localhost:666/example' }, + }); + + const apiClient = new ServiceAPIClient( + logger, + { + manifestUrl: 'http://localhost:8080/api/manifest', + tls: { certificate: 'test-certificate', key: 'test-key' } as any, + }, + { isDev: true, stackVersion: '8.7.0' } as UptimeServerSetup + ); + + apiClient.locations = testLocations; + + const output = { hosts: ['https://localhost:9200'], api_key: '12345' }; + + await apiClient.runOnce({ + monitors: testMonitors, + output, + licenseLevel: 'trial', + }); + + expect(axiosSpy).toHaveBeenNthCalledWith(1, { + data: { + monitors: request1, + is_edit: undefined, + output, + stack_version: '8.7.0', + license_level: 'trial', + }, + headers: { + 'x-kibana-version': '8.7.0', + }, + httpsAgent: expect.objectContaining({ + options: { + rejectUnauthorized: true, + path: null, + cert: 'test-certificate', + key: 'test-key', + }, + }), + method: 'POST', + url: 'https://service.dev/run', + }); + }); + + it('Calls the `/monitors/sync` endpoint when calling `syncMonitors`', async () => { + const axiosSpy = (axios as jest.MockedFunction).mockResolvedValue({ + status: 200, + statusText: 'ok', + headers: {}, + config: {}, + data: { allowed: true, signupUrl: 'http://localhost:666/example' }, + }); + + const apiClient = new ServiceAPIClient( + logger, + { + manifestUrl: 'http://localhost:8080/api/manifest', + tls: { certificate: 'test-certificate', key: 'test-key' } as any, + }, + { isDev: true, stackVersion: '8.7.0' } as UptimeServerSetup + ); + + apiClient.locations = testLocations; + + const output = { hosts: ['https://localhost:9200'], api_key: '12345' }; + + await apiClient.syncMonitors({ + monitors: testMonitors, + output, + licenseLevel: 'trial', + }); + + expect(axiosSpy).toHaveBeenNthCalledWith(1, { + data: { + monitors: request1, + is_edit: undefined, + output, + stack_version: '8.7.0', + license_level: 'trial', + }, + headers: { + 'x-kibana-version': '8.7.0', + }, + httpsAgent: expect.objectContaining({ + options: { + rejectUnauthorized: true, + path: null, + cert: 'test-certificate', + key: 'test-key', + }, + }), + method: 'PUT', + url: 'https://service.dev/monitors/sync', + }); + }); }); const testLocations: PublicLocations = [ diff --git a/x-pack/plugins/synthetics/server/synthetics_service/service_api_client.ts b/x-pack/plugins/synthetics/server/synthetics_service/service_api_client.ts index 362fffea3a5a8..bfc872f3680a8 100644 --- a/x-pack/plugins/synthetics/server/synthetics_service/service_api_client.ts +++ b/x-pack/plugins/synthetics/server/synthetics_service/service_api_client.ts @@ -25,7 +25,7 @@ export interface ServiceData { hosts: string[]; api_key: string; }; - runOnce?: boolean; + endpoint?: 'monitors' | 'runOnce' | 'sync'; isEdit?: boolean; licenseLevel: string; } @@ -81,7 +81,7 @@ export class ServiceAPIClient { } async post(data: ServiceData) { - return this.callAPI('PUT', data); + return this.callAPI('POST', data); } async put(data: ServiceData) { @@ -93,7 +93,11 @@ export class ServiceAPIClient { } async runOnce(data: ServiceData) { - return this.callAPI('POST', { ...data, runOnce: true }); + return this.callAPI('POST', { ...data, endpoint: 'runOnce' }); + } + + async syncMonitors(data: ServiceData) { + return this.callAPI('PUT', { ...data, endpoint: 'sync' }); } addVersionHeader(req: AxiosRequestConfig) { @@ -112,7 +116,7 @@ export class ServiceAPIClient { const url = this.locations[Math.floor(Math.random() * this.locations.length)].url; /* url is required for service locations, but omitted for private locations. - /* this.locations is only service locations */ + /* this.locations is only service locations */ const httpsAgent = this.getHttpsAgent(url); if (httpsAgent) { @@ -138,7 +142,7 @@ export class ServiceAPIClient { async callAPI( method: 'POST' | 'PUT' | 'DELETE', - { monitors: allMonitors, output, runOnce, isEdit, licenseLevel }: ServiceData + { monitors: allMonitors, output, endpoint, isEdit, licenseLevel }: ServiceData ) { if (this.username === TEST_SERVICE_USERNAME) { // we don't want to call service while local integration tests are running @@ -154,25 +158,24 @@ export class ServiceAPIClient { locations?.find((loc) => loc.id === id && loc.isServiceManaged) ); if (locMonitors.length > 0) { + const promise = this.callServiceEndpoint( + { monitors: locMonitors, isEdit, endpoint, output, licenseLevel }, + method, + url + ); promises.push( - rxjsFrom( - this.callServiceEndpoint( - { monitors: locMonitors, isEdit, runOnce, output, licenseLevel }, - method, - url - ) - ).pipe( + rxjsFrom(promise).pipe( tap((result) => { this.logger.debug(result.data); this.logger.debug( - `Successfully called service location ${url} with method ${method} with ${locMonitors.length} monitors ` + `Successfully called service location ${url}${result.request?.path} with method ${method} with ${locMonitors.length} monitors` ); }), catchError((err: AxiosError<{ reason: string; status: number }>) => { pushErrors.push({ locationId: id, error: err.response?.data! }); const reason = err.response?.data?.reason ?? ''; - err.message = `Failed to call service location ${url} with method ${method} with ${locMonitors.length} monitors: ${err.message}, ${reason}`; + err.message = `Failed to call service location ${url}${err.request?.path} with method ${method} with ${locMonitors.length} monitors: ${err.message}, ${reason}`; this.logger.error(err); sendErrorTelemetryEvents(this.logger, this.server.telemetry, { reason: err.response?.data?.reason, @@ -197,19 +200,34 @@ export class ServiceAPIClient { } async callServiceEndpoint( - { monitors, output, runOnce, isEdit, licenseLevel }: ServiceData, + { monitors, output, endpoint = 'monitors', isEdit, licenseLevel }: ServiceData, method: 'POST' | 'PUT' | 'DELETE', - url: string + baseUrl: string ) { // don't need to pass locations to heartbeat const monitorsStreams = monitors.map(({ locations, ...rest }) => convertToDataStreamFormat(rest) ); + let url = baseUrl; + switch (endpoint) { + case 'monitors': + url += '/monitors'; + break; + case 'runOnce': + url += '/run'; + break; + case 'sync': + url += '/monitors/sync'; + break; + } + + const authHeader = this.authorization ? { Authorization: this.authorization } : undefined; + return axios( this.addVersionHeader({ method, - url: url + (runOnce ? '/run' : '/monitors'), + url, data: { monitors: monitorsStreams, output, @@ -217,12 +235,8 @@ export class ServiceAPIClient { is_edit: isEdit, license_level: licenseLevel, }, - headers: this.authorization - ? { - Authorization: this.authorization, - } - : undefined, - httpsAgent: this.getHttpsAgent(url), + headers: authHeader, + httpsAgent: this.getHttpsAgent(baseUrl), }) ); } diff --git a/x-pack/plugins/synthetics/server/synthetics_service/synthetics_service.ts b/x-pack/plugins/synthetics/server/synthetics_service/synthetics_service.ts index 10b20e819abb0..dd3af249aa3e7 100644 --- a/x-pack/plugins/synthetics/server/synthetics_service/synthetics_service.ts +++ b/x-pack/plugins/synthetics/server/synthetics_service/synthetics_service.ts @@ -369,7 +369,7 @@ export class SyntheticsService { this.logger.debug(`${monitors.length} monitors will be pushed to synthetics service.`); - service.syncErrors = await this.apiClient.put({ + service.syncErrors = await this.apiClient.syncMonitors({ monitors, output, licenseLevel: license.type,