Skip to content

Commit

Permalink
[8.x] [Synthetics] Public API's refactor (#190531) (#193863)
Browse files Browse the repository at this point in the history
# Backport

This will backport the following commits from `main` to `8.x`:
- [[Synthetics] Public API's refactor
(#190531)](#190531)

<!--- Backport version: 9.4.3 -->

### Questions ?
Please refer to the [Backport tool
documentation](https://github.com/sqren/backport)

<!--BACKPORT
[{"author":{"name":"Shahzad","email":"[email protected]"},"sourceCommit":{"committedDate":"2024-09-24T12:37:24Z","message":"[Synthetics]
Public API's refactor (#190531)\n\n## Summary\r\n\r\nPublic API's
refactor !!\r\n\r\nFixes #189906
!!\r\n\r\n\r\n### Testing \r\n\r\nPlease test HTTP, TCP, ICMP and
Browser monitors creation and updates\r\nvia
API\r\n\r\n----------------\r\n\r\n### params field \r\n\r\nreturn
`params` always as an object `{}`\r\n\r\n----------------\r\n\r\n###
`ssl` field \r\n\r\nssl field isn't returned anymore for browser
monitor\r\n\r\nand is returned always as an object for other monitor
types as in\r\n```\r\n\r\n \"ssl\": {\r\n \"certificate_authorities\":
\"\",\r\n \"certificate\": \"\",\r\n \"key\": \"i am a key\",\r\n
\"key_passphrase\": \"\",\r\n \"verification_mode\": \"full\",\r\n
\"supported_protocols\": [\r\n \"TLSv1.1\",\r\n \"TLSv1.2\",\r\n
\"TLSv1.3\"\r\n ]\r\n },\r\n```\r\n\r\n----------------\r\n\r\n###
`response` field \r\nalso returned as nested object\r\n```\r\n
\"response\": {\r\n \"include_body\": \"on_error\",\r\n
\"include_headers\": true,\r\n \"include_body_max_bytes\": \"1024\"\r\n
},\r\n```\r\n-----\r\n### `check` field\r\nis also returned as nested
object now\r\n\r\n```\r\n \"check\": {\r\n \"response.body.negative\":
[],\r\n \"response.body.positive\": [],\r\n \"response.json\": [],\r\n
\"response.headers\": {},\r\n \"response.status\": [],\r\n
\"request.body\": {\r\n \"value\": \"\",\r\n \"type\": \"text\"\r\n
},\r\n \"request.headers\": {},\r\n \"request.method\": \"GET\"\r\n
}\r\n```\r\n\r\n-----\r\n\r\n\r\n###
`retest_on_failure`\r\n\r\nretest_on_failure is always returned now,
max_attempts abstraction is\r\nomitted\r\n\r\n-----\r\n\r\n###
`max_redirects`\r\nalways return `max_redirects` as a
number\r\n\r\n---------\r\n\r\nCo-authored-by: Justin Kambic
<[email protected]>\r\nCo-authored-by: kibanamachine
<[email protected]>\r\nCo-authored-by:
Elastic Machine
<[email protected]>","sha":"a333373d9e9f63725c11dcf41793235b58ecd470","branchLabelMapping":{"^v9.0.0$":"main","^v8.16.0$":"8.x","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:skip","v9.0.0","backport:prev-minor","ci:project-deploy-observability","Team:obs-ux-management"],"title":"[Synthetics]
Public API's
refactor","number":190531,"url":"https://github.com/elastic/kibana/pull/190531","mergeCommit":{"message":"[Synthetics]
Public API's refactor (#190531)\n\n## Summary\r\n\r\nPublic API's
refactor !!\r\n\r\nFixes #189906
!!\r\n\r\n\r\n### Testing \r\n\r\nPlease test HTTP, TCP, ICMP and
Browser monitors creation and updates\r\nvia
API\r\n\r\n----------------\r\n\r\n### params field \r\n\r\nreturn
`params` always as an object `{}`\r\n\r\n----------------\r\n\r\n###
`ssl` field \r\n\r\nssl field isn't returned anymore for browser
monitor\r\n\r\nand is returned always as an object for other monitor
types as in\r\n```\r\n\r\n \"ssl\": {\r\n \"certificate_authorities\":
\"\",\r\n \"certificate\": \"\",\r\n \"key\": \"i am a key\",\r\n
\"key_passphrase\": \"\",\r\n \"verification_mode\": \"full\",\r\n
\"supported_protocols\": [\r\n \"TLSv1.1\",\r\n \"TLSv1.2\",\r\n
\"TLSv1.3\"\r\n ]\r\n },\r\n```\r\n\r\n----------------\r\n\r\n###
`response` field \r\nalso returned as nested object\r\n```\r\n
\"response\": {\r\n \"include_body\": \"on_error\",\r\n
\"include_headers\": true,\r\n \"include_body_max_bytes\": \"1024\"\r\n
},\r\n```\r\n-----\r\n### `check` field\r\nis also returned as nested
object now\r\n\r\n```\r\n \"check\": {\r\n \"response.body.negative\":
[],\r\n \"response.body.positive\": [],\r\n \"response.json\": [],\r\n
\"response.headers\": {},\r\n \"response.status\": [],\r\n
\"request.body\": {\r\n \"value\": \"\",\r\n \"type\": \"text\"\r\n
},\r\n \"request.headers\": {},\r\n \"request.method\": \"GET\"\r\n
}\r\n```\r\n\r\n-----\r\n\r\n\r\n###
`retest_on_failure`\r\n\r\nretest_on_failure is always returned now,
max_attempts abstraction is\r\nomitted\r\n\r\n-----\r\n\r\n###
`max_redirects`\r\nalways return `max_redirects` as a
number\r\n\r\n---------\r\n\r\nCo-authored-by: Justin Kambic
<[email protected]>\r\nCo-authored-by: kibanamachine
<[email protected]>\r\nCo-authored-by:
Elastic Machine
<[email protected]>","sha":"a333373d9e9f63725c11dcf41793235b58ecd470"}},"sourceBranch":"main","suggestedTargetBranches":[],"targetPullRequestStates":[{"branch":"main","label":"v9.0.0","branchLabelMappingKey":"^v9.0.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/190531","number":190531,"mergeCommit":{"message":"[Synthetics]
Public API's refactor (#190531)\n\n## Summary\r\n\r\nPublic API's
refactor !!\r\n\r\nFixes #189906
!!\r\n\r\n\r\n### Testing \r\n\r\nPlease test HTTP, TCP, ICMP and
Browser monitors creation and updates\r\nvia
API\r\n\r\n----------------\r\n\r\n### params field \r\n\r\nreturn
`params` always as an object `{}`\r\n\r\n----------------\r\n\r\n###
`ssl` field \r\n\r\nssl field isn't returned anymore for browser
monitor\r\n\r\nand is returned always as an object for other monitor
types as in\r\n```\r\n\r\n \"ssl\": {\r\n \"certificate_authorities\":
\"\",\r\n \"certificate\": \"\",\r\n \"key\": \"i am a key\",\r\n
\"key_passphrase\": \"\",\r\n \"verification_mode\": \"full\",\r\n
\"supported_protocols\": [\r\n \"TLSv1.1\",\r\n \"TLSv1.2\",\r\n
\"TLSv1.3\"\r\n ]\r\n },\r\n```\r\n\r\n----------------\r\n\r\n###
`response` field \r\nalso returned as nested object\r\n```\r\n
\"response\": {\r\n \"include_body\": \"on_error\",\r\n
\"include_headers\": true,\r\n \"include_body_max_bytes\": \"1024\"\r\n
},\r\n```\r\n-----\r\n### `check` field\r\nis also returned as nested
object now\r\n\r\n```\r\n \"check\": {\r\n \"response.body.negative\":
[],\r\n \"response.body.positive\": [],\r\n \"response.json\": [],\r\n
\"response.headers\": {},\r\n \"response.status\": [],\r\n
\"request.body\": {\r\n \"value\": \"\",\r\n \"type\": \"text\"\r\n
},\r\n \"request.headers\": {},\r\n \"request.method\": \"GET\"\r\n
}\r\n```\r\n\r\n-----\r\n\r\n\r\n###
`retest_on_failure`\r\n\r\nretest_on_failure is always returned now,
max_attempts abstraction is\r\nomitted\r\n\r\n-----\r\n\r\n###
`max_redirects`\r\nalways return `max_redirects` as a
number\r\n\r\n---------\r\n\r\nCo-authored-by: Justin Kambic
<[email protected]>\r\nCo-authored-by: kibanamachine
<[email protected]>\r\nCo-authored-by:
Elastic Machine
<[email protected]>","sha":"a333373d9e9f63725c11dcf41793235b58ecd470"}}]}]
BACKPORT-->

Co-authored-by: Shahzad <[email protected]>
  • Loading branch information
kibanamachine and shahzad31 authored Sep 24, 2024
1 parent e294ce9 commit 4686f35
Show file tree
Hide file tree
Showing 50 changed files with 767 additions and 442 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,8 @@ export type ICMPFields = t.TypeOf<typeof ICMPFieldsCodec>;
export const HTTPSimpleFieldsCodec = t.intersection([
t.interface({
[ConfigKey.METADATA]: MetadataCodec,
[ConfigKey.MAX_REDIRECTS]: t.string,
// string is for yaml config and number for public api
[ConfigKey.MAX_REDIRECTS]: t.union([t.string, t.number]),
[ConfigKey.URLS]: getNonEmptyStringCodec('url'),
[ConfigKey.PORT]: t.union([t.number, t.null]),
}),
Expand Down Expand Up @@ -318,6 +319,11 @@ export const MonitorFieldsCodec = t.intersection([
BrowserFieldsCodec,
]);

export const MonitorFieldsResultCodec = t.intersection([
MonitorFieldsCodec,
t.interface({ id: t.string, updated_at: t.string, created_at: t.string }),
]);

// Monitor, represents one of (Icmp | Tcp | Http | Browser) decrypted
export const SyntheticsMonitorCodec = t.union([
HTTPFieldsCodec,
Expand All @@ -336,7 +342,7 @@ export const EncryptedSyntheticsMonitorCodec = t.union([

export const SyntheticsMonitorWithIdCodec = t.intersection([
SyntheticsMonitorCodec,
t.interface({ id: t.string }),
t.interface({ id: t.string, updated_at: t.string, created_at: t.string }),
]);

const HeartbeatFieldsCodec = t.intersection([
Expand All @@ -355,7 +361,10 @@ const HeartbeatFieldsCodec = t.intersection([
]);

export const HeartbeatConfigCodec = t.intersection([
SyntheticsMonitorWithIdCodec,
SyntheticsMonitorCodec,
t.interface({
id: t.string,
}),
t.partial({
fields_under_root: t.boolean,
fields: HeartbeatFieldsCodec,
Expand Down Expand Up @@ -400,6 +409,7 @@ export type BrowserFields = t.TypeOf<typeof BrowserFieldsCodec>;
export type BrowserSimpleFields = t.TypeOf<typeof BrowserSimpleFieldsCodec>;
export type BrowserAdvancedFields = t.TypeOf<typeof BrowserAdvancedFieldsCodec>;
export type MonitorFields = t.TypeOf<typeof MonitorFieldsCodec>;
export type MonitorFieldsResult = t.TypeOf<typeof MonitorFieldsResultCodec>;
export type HeartbeatFields = t.TypeOf<typeof HeartbeatFieldsCodec>;
export type SyntheticsMonitor = t.TypeOf<typeof SyntheticsMonitorCodec>;
export type SyntheticsMonitorWithId = t.TypeOf<typeof SyntheticsMonitorWithIdCodec>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ export const ProjectMonitorCodec = t.intersection([
hash: t.string,
namespace: t.string,
retestOnFailure: t.boolean,
labels: t.record(t.string, t.string),
fields: t.record(t.string, t.string),
}),
]);

Expand Down Expand Up @@ -91,14 +91,10 @@ export const ProjectMonitorsResponseCodec = t.intersection([
}),
]);

export type ProjectMonitorThrottlingConfig = t.TypeOf<typeof ProjectMonitorThrottlingConfigCodec>;

export type ProjectMonitor = t.TypeOf<typeof ProjectMonitorCodec>;

export type LegacyProjectMonitorsRequest = t.TypeOf<typeof LegacyProjectMonitorsRequestCodec>;

export type ProjectMonitorsRequest = t.TypeOf<typeof ProjectMonitorsRequestCodec>;

export type ProjectMonitorsResponse = t.TypeOf<typeof ProjectMonitorsResponseCodec>;

export type ProjectMonitorMetaData = t.TypeOf<typeof ProjectMonitorMetaDataCodec>;
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { CA_CERT_PATH } from '@kbn/dev-utils';
import { get } from 'lodash';
import { commonFunctionalServices } from '@kbn/ftr-common-functional-services';
import { commonFunctionalUIServices } from '@kbn/ftr-common-functional-ui-services';

import { readKibanaConfig } from './tasks/read_kibana_config';
const MANIFEST_KEY = 'xpack.uptime.service.manifestUrl';
const SERVICE_PASSWORD = 'xpack.uptime.service.password';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@
*/

import { after, before, expect, journey, step } from '@elastic/synthetics';
import { omit } from 'lodash';
import { SyntheticsMonitor } from '@kbn/synthetics-plugin/common/runtime_types';
import { SyntheticsServices } from './services/synthetics_services';
import { cleanTestMonitors, enableMonitorManagedViaApi } from './services/add_monitor';
import { cleanTestMonitors } from './services/add_monitor';
import { addTestMonitorProject } from './services/add_monitor_project';
import { syntheticsAppPageProvider } from '../page_objects/synthetics_app';

Expand All @@ -23,14 +24,13 @@ journey('ProjectMonitorReadOnly', async ({ page, params }) => {

before(async () => {
await cleanTestMonitors(params);
await enableMonitorManagedViaApi(params.kibanaUrl);
});

step('Go to monitor-management', async () => {
await addTestMonitorProject(params.kibanaUrl, monitorName);

await syntheticsApp.waitForLoadingToFinish();
});

step('Go to monitor-management', async () => {
await syntheticsApp.navigateToMonitorManagement();
});

Expand Down Expand Up @@ -62,11 +62,16 @@ journey('ProjectMonitorReadOnly', async ({ page, params }) => {
// hash is always reset to empty string when monitor is edited
// this ensures that when the monitor is pushed again, the monitor
// config in the process takes precedence
expect(newConfiguration).toEqual({
...originalMonitorConfiguration,
hash: '',
revision: 2,
});
expect(omit(newConfiguration, ['updated_at'])).toEqual(
omit(
{
...originalMonitorConfiguration,
hash: '',
revision: 2,
},
['updated_at']
)
);
});

step('Navigate to edit monitor', async () => {
Expand All @@ -83,29 +88,39 @@ journey('ProjectMonitorReadOnly', async ({ page, params }) => {
// hash is always reset to empty string when monitor is edited
// this ensures that when the monitor is pushed again, the monitor
// config in the process takes precedence
expect(newConfiguration).toEqual({
...originalMonitorConfiguration,
hash: '',
revision: 3,
alert: {
status: {
enabled: !(originalMonitorConfiguration?.alert?.status?.enabled as boolean),
},
tls: {
enabled: originalMonitorConfiguration?.alert?.tls?.enabled as boolean,
expect(omit(newConfiguration, ['updated_at'])).toEqual(
omit(
{
...originalMonitorConfiguration,
hash: '',
revision: 3,
alert: {
status: {
enabled: !(originalMonitorConfiguration?.alert?.status?.enabled as boolean),
},
tls: {
enabled: originalMonitorConfiguration?.alert?.tls?.enabled as boolean,
},
},
enabled: !originalMonitorConfiguration?.enabled,
},
},
enabled: !originalMonitorConfiguration?.enabled,
});
['updated_at']
)
);
});

step('Monitor can be re-pushed and overwrite any changes', async () => {
await addTestMonitorProject(params.kibanaUrl, monitorName);
const repushedConfiguration = await services.getMonitor(monitorId);
expect(repushedConfiguration).toEqual({
...originalMonitorConfiguration,
revision: 4,
});
expect(omit(repushedConfiguration, ['updated_at'])).toEqual(
omit(
{
...originalMonitorConfiguration,
revision: 4,
},
['updated_at']
)
);
});

step('Navigate to edit monitor', async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,23 +17,15 @@ export const addTestMonitorProject = async (
const testData = {
...testProjectMonitorBrowser(name, config),
};
try {
return await axios.put(
kibanaUrl +
SYNTHETICS_API_URLS.SYNTHETICS_MONITORS_PROJECT_UPDATE.replace(
'{projectName}',
projectName
),
testData,
{
auth: { username: 'elastic', password: 'changeme' },
headers: { 'kbn-xsrf': 'true', 'x-elastic-internal-origin': 'synthetics-e2e' },
}
);
} catch (e) {
// eslint-disable-next-line no-console
console.log(e);
}
return await axios.put(
kibanaUrl +
SYNTHETICS_API_URLS.SYNTHETICS_MONITORS_PROJECT_UPDATE.replace('{projectName}', projectName),
testData,
{
auth: { username: 'elastic', password: 'changeme' },
headers: { 'kbn-xsrf': 'true', 'x-elastic-internal-origin': 'synthetics-e2e' },
}
);
};

const testProjectMonitorBrowser = (name: string, config?: Record<string, unknown>) => ({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,9 @@ export class SyntheticsServices {
try {
const { data } = await this.requester.request({
description: 'get monitor by id',
path: SYNTHETICS_API_URLS.GET_SYNTHETICS_MONITOR.replace('{monitorId}', monitorId),
query: {
decrypted: true,
},
path:
SYNTHETICS_API_URLS.GET_SYNTHETICS_MONITOR.replace('{monitorId}', monitorId) +
'?internal=true',
method: 'GET',
});
return data as SyntheticsMonitor;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import {
EncryptedSyntheticsSavedMonitor,
MonitorFields,
Ping,
SyntheticsMonitorWithId,
} from '../../../../../../common/runtime_types';
import { MonitorTypeBadge } from './monitor_type_badge';
import { useDateFormat } from '../../../../../hooks/use_date_format';
Expand All @@ -36,7 +37,7 @@ export interface MonitorDetailsPanelProps {
latestPing?: Ping;
loading: boolean;
configId: string;
monitor: EncryptedSyntheticsSavedMonitor | null;
monitor: SyntheticsMonitorWithId | EncryptedSyntheticsSavedMonitor | null;
hideEnabled?: boolean;
hideLocations?: boolean;
hasBorder?: boolean;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,12 @@ import { LastRefreshed } from '../components/last_refreshed';
import { AutoRefreshButton } from '../components/auto_refresh_button';
import { useSyntheticsSettingsContext } from '../../../contexts';
import { useGetUrlParams } from '../../../hooks';
import { MONITOR_ROUTE, SETTINGS_ROUTE } from '../../../../../../common/constants';
import {
MONITOR_ADD_ROUTE,
MONITOR_EDIT_ROUTE,
MONITOR_ROUTE,
SETTINGS_ROUTE,
} from '../../../../../../common/constants';
import { stringifyUrlParams } from '../../../utils/url_params';
import { InspectorHeaderLink } from './inspector_header_link';
import { ToggleAlertFlyoutButton } from '../../alerts/toggle_alert_flyout_button';
Expand All @@ -41,6 +46,8 @@ export function ActionMenuContent(): React.ReactElement {
}; /* useSelector(monitorStatusSelector) TODO: Implement state for monitor status */

const detailRouteMatch = useRouteMatch(MONITOR_ROUTE);
const isEditRoute = useRouteMatch(MONITOR_EDIT_ROUTE);
const isAddRoute = useRouteMatch(MONITOR_ADD_ROUTE);
const monitorId = selectedMonitor?.monitor?.id;

const syntheticExploratoryViewLink = createExploratoryViewUrl(
Expand Down Expand Up @@ -69,8 +76,12 @@ export function ActionMenuContent(): React.ReactElement {

return (
<EuiHeaderLinks gutterSize="xs">
<LastRefreshed />
<AutoRefreshButton />
{!isEditRoute && !isAddRoute && (
<>
<LastRefreshed />
<AutoRefreshButton />
</>
)}
<ToggleAlertFlyoutButton />

<EuiHeaderLink
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,26 @@ export const formatDefaultFormValues = (monitor?: SyntheticsMonitor) => {
schedule.number = `${schedule.number}s`;
}

const params = monitorWithFormMonitorType[ConfigKey.PARAMS];
if (typeof params !== 'string' && params) {
try {
monitorWithFormMonitorType[ConfigKey.PARAMS] = JSON.stringify(params);
} catch (e) {
// ignore
}
}
const browserMonitor = monitor as BrowserFields;

const pwOptions = browserMonitor[ConfigKey.PLAYWRIGHT_OPTIONS];
if (typeof pwOptions !== 'string' && pwOptions) {
try {
(monitorWithFormMonitorType as BrowserFields)[ConfigKey.PLAYWRIGHT_OPTIONS] =
JSON.stringify(pwOptions);
} catch (e) {
// ignore
}
}

// handle default monitor types from Uptime, which don't contain `ConfigKey.FORM_MONITOR_TYPE`
if (!formMonitorType) {
formMonitorType =
Expand All @@ -81,7 +101,6 @@ export const formatDefaultFormValues = (monitor?: SyntheticsMonitor) => {

switch (formMonitorType) {
case FormMonitorType.MULTISTEP:
const browserMonitor = monitor as BrowserFields;
return {
...monitorWithFormMonitorType,
'source.inline': {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@ export const BROWSER_ADVANCED = (readOnly: boolean) => [
FIELD(readOnly)[ConfigKey.IGNORE_HTTPS_ERRORS],
FIELD(readOnly)[ConfigKey.SYNTHETICS_ARGS],
FIELD(readOnly)[ConfigKey.PLAYWRIGHT_OPTIONS],
FIELD(readOnly)[ConfigKey.PARAMS],
],
},
];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,8 +117,7 @@ const validateHTTP: ValidationLibrary = {
return validateHeaders(headers);
},
[ConfigKey.MAX_REDIRECTS]: ({ [ConfigKey.MAX_REDIRECTS]: value }) =>
(!!value && !`${value}`.match(DIGITS_ONLY)) ||
parseFloat(value as MonitorFields[ConfigKey.MAX_REDIRECTS]) < 0,
(!!value && !`${value}`.match(DIGITS_ONLY)) || parseFloat(value as string) < 0,
[ConfigKey.URLS]: ({ [ConfigKey.URLS]: value }) => !value,
...validateCommon,
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@
*/

import { useFetcher } from '@kbn/observability-shared-plugin/public';
import { fetchSyntheticsMonitor } from '../../../state/monitor_details/api';
import { useGetUrlParams } from '../../../hooks';
import { getDecryptedMonitorAPI } from '../../../state/monitor_management/api';

export const useCloneMonitor = () => {
const { cloneId } = useGetUrlParams();
return useFetcher(() => {
if (!cloneId) return Promise.resolve(undefined);
return getDecryptedMonitorAPI({ id: cloneId });
return fetchSyntheticsMonitor({ monitorId: cloneId });
}, [cloneId]);
};
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@
*/

import { useEffect } from 'react';
import { IHttpFetchError, ResponseErrorBody } from '@kbn/core-http-browser';
import { useGetUrlParams, useUrlParams } from '../../../hooks';
import { IHttpSerializedFetchError } from '../../../state';

export const useMonitorNotFound = (error?: IHttpFetchError<ResponseErrorBody>, id?: string) => {
export const useMonitorNotFound = (error?: IHttpSerializedFetchError | null, id?: string) => {
const { packagePolicyId } = useGetUrlParams();
const updateUrlParams = useUrlParams()[1];

Expand Down
Loading

0 comments on commit 4686f35

Please sign in to comment.