[Synthetics] Monitor edit page 404 state (#153847)

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
Co-authored-by: Abdul Wahab Zahid <awahab07@yahoo.com>
This commit is contained in:
Shahzad 2023-04-03 19:42:57 +02:00 committed by GitHub
parent ef4afdd64d
commit fcfd414ee1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 182 additions and 4 deletions

View file

@ -21,4 +21,5 @@ export enum SYNTHETICS_API_URLS {
JOURNEY_FAILED_STEPS = `/internal/synthetics/journeys/failed_steps`,
NETWORK_EVENTS = `/internal/synthetics/network_events`,
JOURNEY_SCREENSHOT = `/internal/synthetics/journey/screenshot/{checkGroup}/{stepIndex}`,
DELETE_PACKAGE_POLICY = `/internal/synthetics/monitor/policy/{packagePolicyId}`,
}

View file

@ -50,7 +50,9 @@ journey('AlertingDefaults', async ({ page, params }) => {
step(
'Fill text=Webhook URLCreate a Slack Webhook URL(opens in a new tab or window) >> input[type="text"]',
async () => {
await page.click(byTestId('webhookButton'));
if (await page.isVisible(byTestId('webhookButton'))) {
await page.click(byTestId('webhookButton'));
}
await page.fill(
'text=Webhook URLCreate a Slack Webhook URL(opens in a new tab or window) >> input[type="text"]',
'https://www.slack.com'

View file

@ -81,8 +81,12 @@ journey('AddPrivateLocationMonitor', async ({ page, params }) => {
});
step('Integration edit button leads to correct Synthetics edit page', async () => {
const url = page.url();
const policyId = url.split('edit-integration/').pop();
const btn = await page.locator(byTestId('syntheticsEditMonitorButton'));
expect(await btn.getAttribute('href')).toBe(`/app/synthetics/edit-monitor/${monitorId}`);
expect(await btn.getAttribute('href')).toBe(
`/app/synthetics/edit-monitor/${monitorId}?packagePolicyId=${policyId}`
);
await page.click('text="Edit in Synthetics"');
await page.waitForSelector('h1:has-text("Edit Monitor")');

View file

@ -0,0 +1,80 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import React, { useEffect, useState } from 'react';
import { EuiButton, EuiCallOut, EuiLink, EuiSpacer } from '@elastic/eui';
import { useFetcher } from '@kbn/observability-plugin/public';
import { FormattedMessage } from '@kbn/i18n-react';
import { useGetUrlParams, useUrlParams } from '../../hooks';
import { deletePackagePolicy } from '../../state/monitor_management/api';
import { MonitorNotFoundPage } from '../monitor_details/monitor_not_found_page';
export const EditMonitorNotFound: React.FC = () => {
return (
<>
<LeftoverIntegrationFound />
<EuiSpacer size="m" />
<MonitorNotFoundPage />
</>
);
};
const LeftoverIntegrationFound: React.FC = () => {
const { packagePolicyId } = useGetUrlParams();
const updateUrlParams = useUrlParams()[1];
const [isDeleting, setIsDeleting] = useState(false);
const { data, loading } = useFetcher(() => {
if (!packagePolicyId || !isDeleting) return;
return deletePackagePolicy(packagePolicyId);
}, [isDeleting, packagePolicyId]);
useEffect(() => {
if (isDeleting && data && !loading) {
updateUrlParams({ packagePolicyId: undefined }, true);
setIsDeleting(false);
}
}, [data, isDeleting, loading, updateUrlParams]);
if (!packagePolicyId) return null;
return (
<EuiCallOut title="Leftover integration found" color="warning" iconType="help">
<p>
<FormattedMessage
id="xpack.synthetics.leftOver.errors.title"
defaultMessage="Please click on the button below to delete the integration. Normally this should not happen.
Since the monitor has been deleted, the integration was supposed to be deleted automatically. If
this happens often, report it by "
/>
<EuiLink
data-test-subj="syntheticsLeftoverIntegrationFoundCreatingAnIssueLink"
href="https://github.com/elastic/kibana/issues/new/choose"
>
<FormattedMessage
id="xpack.synthetics.leftOver.errors.createIssue"
defaultMessage="creating an issue."
/>
</EuiLink>
</p>
<EuiButton
data-test-subj="syntheticsUseMonitorNotFoundDeleteIntegrationButton"
color="danger"
isLoading={loading && isDeleting}
onClick={() => {
setIsDeleting(true);
}}
>
<FormattedMessage
id="xpack.synthetics.leftOver.errors.delete"
defaultMessage="Delete integration"
/>
</EuiButton>
</EuiCallOut>
);
};

View file

@ -0,0 +1,24 @@
/*
* 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 { useEffect } from 'react';
import { IHttpFetchError, ResponseErrorBody } from '@kbn/core-http-browser';
import { useGetUrlParams, useUrlParams } from '../../../hooks';
export const useMonitorNotFound = (error?: IHttpFetchError<ResponseErrorBody>, id?: string) => {
const { packagePolicyId } = useGetUrlParams();
const updateUrlParams = useUrlParams()[1];
useEffect(() => {
if (id && packagePolicyId && !error) {
updateUrlParams({ packagePolicyId: undefined }, true);
}
}, [error, id, packagePolicyId, updateUrlParams]);
if (!error) return null;
return error.body?.statusCode === 404;
};

View file

@ -11,6 +11,8 @@ import { useDispatch, useSelector } from 'react-redux';
import { EuiEmptyPrompt } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { useTrackPageview, useFetcher } from '@kbn/observability-plugin/public';
import { IHttpFetchError, ResponseErrorBody } from '@kbn/core-http-browser';
import { EditMonitorNotFound } from './edit_monitor_not_found';
import { LoadingState } from '../monitors_page/overview/overview/monitor_detail_flyout';
import { ConfigKey, SourceType } from '../../../../../common/runtime_types';
import { getServiceLocations, selectServiceLocationsState } from '../../state';
@ -22,6 +24,7 @@ import { MonitorDetailsLinkPortal } from './monitor_details_portal';
import { useMonitorAddEditBreadcrumbs } from './use_breadcrumbs';
import { getMonitorAPI } from '../../state/monitor_management/api';
import { EDIT_MONITOR_STEPS } from './steps/step_config';
import { useMonitorNotFound } from './hooks/use_monitor_not_found';
export const MonitorEditPage: React.FC = () => {
useTrackPageview({ app: 'synthetics', path: 'edit-monitor' });
@ -41,6 +44,15 @@ export const MonitorEditPage: React.FC = () => {
return getMonitorAPI({ id: monitorId });
}, []);
const monitorNotFoundError = useMonitorNotFound(
error as IHttpFetchError<ResponseErrorBody>,
data?.id
);
if (monitorNotFoundError) {
return <EditMonitorNotFound />;
}
const isReadOnly = data?.attributes[ConfigKey.MONITOR_SOURCE_TYPE] === SourceType.PROJECT;
const projectId = data?.attributes[ConfigKey.PROJECT_ID];

View file

@ -12,7 +12,7 @@ import {
SyntheticsMonitor,
SyntheticsMonitorWithId,
} from '../../../../../common/runtime_types';
import { API_URLS } from '../../../../../common/constants';
import { API_URLS, SYNTHETICS_API_URLS } from '../../../../../common/constants';
import { DecryptedSyntheticsMonitorSavedObject } from '../../../../../common/types';
export const createMonitorAPI = async ({
@ -46,3 +46,13 @@ export const fetchServiceAPIKey = async (): Promise<{
}> => {
return await apiService.get(API_URLS.SYNTHETICS_APIKEY);
};
export const deletePackagePolicy = async (
packagePolicyId: string
): Promise<{
apiKey: { encoded: string };
}> => {
return await apiService.delete(
SYNTHETICS_API_URLS.DELETE_PACKAGE_POLICY.replace('{packagePolicyId}', packagePolicyId)
);
};

View file

@ -36,6 +36,7 @@ export interface SyntheticsUrlParams {
schedules?: string[];
groupBy?: MonitorOverviewState['groupBy']['field'];
groupOrderBy?: MonitorOverviewState['groupBy']['order'];
packagePolicyId?: string;
}
const { ABSOLUTE_DATE_RANGE_START, ABSOLUTE_DATE_RANGE_END, SEARCH, FILTERS, STATUS_FILTER } =
@ -93,9 +94,11 @@ export const getSupportedUrlParams = (params: {
schedules,
groupBy,
groupOrderBy,
packagePolicyId,
} = filteredParams;
return {
packagePolicyId: packagePolicyId || undefined,
groupBy: groupBy as MonitorOverviewState['groupBy']['field'],
groupOrderBy: groupOrderBy as MonitorOverviewState['groupBy']['order'],
pagination,

View file

@ -45,7 +45,11 @@ export const SyntheticsPolicyEditExtensionWrapper = memo<PackagePolicyEditExtens
return (
<EuiCallOut>
<p>{EDIT_IN_SYNTHETICS_DESC}</p>
<EuiButton isLoading={!url} href={url} data-test-subj="syntheticsEditMonitorButton">
<EuiButton
isLoading={!url}
href={url + `?packagePolicyId=${currentPolicy.id}`}
data-test-subj="syntheticsEditMonitorButton"
>
{EDIT_IN_SYNTHETICS_LABEL}
</EuiButton>
</EuiCallOut>

View file

@ -5,6 +5,7 @@
* 2.0.
*/
import { deletePackagePolicyRoute } from './monitor_cruds/delete_integration';
import { createJourneyScreenshotRoute } from './pings/journey_screenshots';
import { createJourneyScreenshotBlocksRoute } from './pings/journey_screenshot_blocks';
import { createLastSuccessfulCheckRoute } from './pings/last_successful_check';
@ -89,6 +90,7 @@ export const syntheticsAppRestApiRoutes: SyntheticsRestApiRouteFactory[] = [
createJourneyFailedStepsRoute,
createNetworkEventsRoute,
createJourneyScreenshotRoute,
deletePackagePolicyRoute,
addPrivateLocationRoute,
deletePrivateLocationRoute,
getPrivateLocationsRoute,

View file

@ -0,0 +1,36 @@
/*
* 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/types';
import { SYNTHETICS_API_URLS } from '../../../common/constants';
export const deletePackagePolicyRoute: SyntheticsRestApiRouteFactory = () => ({
method: 'DELETE',
path: SYNTHETICS_API_URLS.DELETE_PACKAGE_POLICY,
validate: {
params: schema.object({
packagePolicyId: schema.string({ minLength: 1, maxLength: 1024 }),
}),
},
handler: async ({ request, savedObjectsClient, server, uptimeEsClient }): Promise<any> => {
const { packagePolicyId } = request.params;
const response = await server.fleet.packagePolicyService.delete(
savedObjectsClient,
uptimeEsClient.baseESClient,
[packagePolicyId],
{
force: true,
}
);
if (response?.[0].success) {
return response;
} else {
throw new Error(response?.[0].body?.message);
}
},
});