mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[Uptime] Remove legacy monitor management (#154471)
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
c39031e3da
commit
3ebc372cc2
251 changed files with 200 additions and 20395 deletions
|
@ -140,7 +140,7 @@ export const getCertsRequestBody = ({
|
|||
field: 'tls.server.hash.sha256',
|
||||
inner_hits: {
|
||||
_source: {
|
||||
includes: ['monitor.id', 'monitor.name', 'url.full'],
|
||||
includes: ['monitor.id', 'monitor.name', 'url.full', 'config_id'],
|
||||
},
|
||||
collapse: {
|
||||
field: 'monitor.id',
|
||||
|
@ -180,6 +180,7 @@ export const processCertsResult = (result: CertificatesResults): CertResult => {
|
|||
return {
|
||||
name: monitorPing?.monitor.name,
|
||||
id: monitorPing?.monitor.id,
|
||||
configId: monitorPing?.config_id,
|
||||
url: monitorPing?.url?.full,
|
||||
};
|
||||
});
|
||||
|
|
|
@ -29,6 +29,7 @@ export type GetCertsParams = t.TypeOf<typeof GetCertsParamsType>;
|
|||
export const CertMonitorType = t.partial({
|
||||
name: t.string,
|
||||
id: t.string,
|
||||
configId: t.string,
|
||||
url: t.string,
|
||||
});
|
||||
|
||||
|
|
|
@ -6,14 +6,8 @@
|
|||
*/
|
||||
|
||||
export * from './data_view_permissions';
|
||||
export * from './read_only_user';
|
||||
export * from './alerts';
|
||||
export * from './uptime.journey';
|
||||
export * from './step_duration.journey';
|
||||
export * from './monitor_details.journey';
|
||||
export * from './monitor_name.journey';
|
||||
export * from './monitor_management.journey';
|
||||
export * from './monitor_management_enablement.journey';
|
||||
export * from './monitor_details';
|
||||
export * from './locations';
|
||||
export * from './private_locations';
|
||||
|
|
|
@ -1,54 +0,0 @@
|
|||
/*
|
||||
* 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 { v4 as uuidv4 } from 'uuid';
|
||||
import { journey, step, expect, after, Page } from '@elastic/synthetics';
|
||||
import { recordVideo } from '../../helpers/record_video';
|
||||
import { monitorManagementPageProvider } from '../../page_objects/uptime/monitor_management';
|
||||
|
||||
journey('MonitorDetails', async ({ page, params }: { page: Page; params: any }) => {
|
||||
recordVideo(page);
|
||||
|
||||
const uptime = monitorManagementPageProvider({ page, kibanaUrl: params.kibanaUrl });
|
||||
const name = `Test monitor ${uuidv4()}`;
|
||||
|
||||
after(async () => {
|
||||
await uptime.enableMonitorManagement(false);
|
||||
});
|
||||
|
||||
step('Go to monitor-management', async () => {
|
||||
await uptime.navigateToMonitorManagement(true);
|
||||
});
|
||||
|
||||
step('create basic monitor', async () => {
|
||||
await uptime.enableMonitorManagement();
|
||||
await uptime.clickAddMonitor();
|
||||
await uptime.createBasicHTTPMonitorDetails({
|
||||
name,
|
||||
locations: ['US Central'],
|
||||
apmServiceName: 'synthetics',
|
||||
url: 'https://www.google.com',
|
||||
});
|
||||
await uptime.confirmAndSave();
|
||||
});
|
||||
|
||||
step('navigate to monitor details page', async () => {
|
||||
await uptime.assertText({ text: name });
|
||||
await Promise.all([page.waitForNavigation(), page.click(`text=${name}`)]);
|
||||
await uptime.assertText({ text: name });
|
||||
const url = await page.textContent('[data-test-subj="monitor-page-url"]');
|
||||
const type = await page.textContent('[data-test-subj="monitor-page-type"]');
|
||||
expect(url).toEqual('https://www.google.com(opens in a new tab or window)');
|
||||
expect(type).toEqual('HTTP');
|
||||
});
|
||||
|
||||
step('delete monitor', async () => {
|
||||
await uptime.navigateToMonitorManagement();
|
||||
const isSuccessful = await uptime.deleteMonitors();
|
||||
expect(isSuccessful).toBeTruthy();
|
||||
});
|
||||
});
|
|
@ -48,7 +48,7 @@ journey('MonitorPingRedirects', async ({ page, params }: { page: Page; params: a
|
|||
await delay(5000);
|
||||
});
|
||||
|
||||
step('go to monitor-management', async () => {
|
||||
step('go to overview page', async () => {
|
||||
await monitorDetails.navigateToOverviewPage({
|
||||
dateRangeEnd: testMonitor.end,
|
||||
dateRangeStart: testMonitor.start,
|
||||
|
|
|
@ -1,269 +0,0 @@
|
|||
/*
|
||||
* 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 { v4 as uuidv4 } from 'uuid';
|
||||
import { journey, step, expect, after, Page } from '@elastic/synthetics';
|
||||
import { byTestId } from '../../helpers/utils';
|
||||
import { recordVideo } from '../../helpers/record_video';
|
||||
import { monitorManagementPageProvider } from '../../page_objects/uptime/monitor_management';
|
||||
import { DataStream } from '../../../common/runtime_types/monitor_management';
|
||||
|
||||
const customLocation = process.env.SYNTHETICS_TEST_LOCATION;
|
||||
|
||||
const basicMonitorDetails = {
|
||||
location: customLocation || 'US Central',
|
||||
schedule: '3',
|
||||
};
|
||||
const httpName = `http monitor ${uuidv4()}`;
|
||||
const icmpName = `icmp monitor ${uuidv4()}`;
|
||||
const tcpName = `tcp monitor ${uuidv4()}`;
|
||||
const browserName = `browser monitor ${uuidv4()}`;
|
||||
|
||||
const configuration = {
|
||||
[DataStream.HTTP]: {
|
||||
monitorConfig: {
|
||||
...basicMonitorDetails,
|
||||
name: httpName,
|
||||
url: 'https://elastic.co',
|
||||
locations: [basicMonitorDetails.location],
|
||||
apmServiceName: 'Sample APM Service',
|
||||
},
|
||||
monitorDetails: {
|
||||
...basicMonitorDetails,
|
||||
name: httpName,
|
||||
url: 'https://elastic.co',
|
||||
},
|
||||
},
|
||||
[DataStream.TCP]: {
|
||||
monitorConfig: {
|
||||
...basicMonitorDetails,
|
||||
name: tcpName,
|
||||
host: 'smtp.gmail.com:587',
|
||||
locations: [basicMonitorDetails.location],
|
||||
apmServiceName: 'Sample APM Service',
|
||||
},
|
||||
monitorDetails: {
|
||||
...basicMonitorDetails,
|
||||
name: tcpName,
|
||||
host: 'smtp.gmail.com:587',
|
||||
},
|
||||
},
|
||||
[DataStream.ICMP]: {
|
||||
monitorConfig: {
|
||||
...basicMonitorDetails,
|
||||
name: icmpName,
|
||||
host: '1.1.1.1',
|
||||
locations: [basicMonitorDetails.location],
|
||||
apmServiceName: 'Sample APM Service',
|
||||
},
|
||||
monitorDetails: {
|
||||
...basicMonitorDetails,
|
||||
name: icmpName,
|
||||
hosts: '1.1.1.1',
|
||||
},
|
||||
},
|
||||
[DataStream.BROWSER]: {
|
||||
monitorConfig: {
|
||||
...basicMonitorDetails,
|
||||
schedule: '10',
|
||||
name: browserName,
|
||||
inlineScript: 'step("test step", () => {})',
|
||||
locations: [basicMonitorDetails.location],
|
||||
apmServiceName: 'Sample APM Service',
|
||||
},
|
||||
monitorDetails: {
|
||||
...basicMonitorDetails,
|
||||
schedule: '10',
|
||||
name: browserName,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const createMonitorJourney = ({
|
||||
monitorName,
|
||||
monitorType,
|
||||
monitorConfig,
|
||||
monitorDetails,
|
||||
}: {
|
||||
monitorName: string;
|
||||
monitorType: DataStream;
|
||||
monitorConfig: Record<string, string | string[]>;
|
||||
monitorDetails: Record<string, string>;
|
||||
}) => {
|
||||
journey(
|
||||
`MonitorManagement-monitor-${monitorType}`,
|
||||
async ({ page, params }: { page: Page; params: any }) => {
|
||||
recordVideo(page);
|
||||
|
||||
const uptime = monitorManagementPageProvider({ page, kibanaUrl: params.kibanaUrl });
|
||||
const isRemote = process.env.SYNTHETICS_REMOTE_ENABLED;
|
||||
|
||||
after(async () => {
|
||||
await uptime.navigateToMonitorManagement();
|
||||
await uptime.enableMonitorManagement(false);
|
||||
});
|
||||
|
||||
step('Go to monitor-management', async () => {
|
||||
await uptime.navigateToMonitorManagement(true);
|
||||
});
|
||||
|
||||
step(`create ${monitorType} monitor`, async () => {
|
||||
await uptime.enableMonitorManagement();
|
||||
await uptime.clickAddMonitor();
|
||||
await uptime.createMonitor({ monitorConfig, monitorType });
|
||||
const isSuccessful = await uptime.confirmAndSave();
|
||||
expect(isSuccessful).toBeTruthy();
|
||||
});
|
||||
|
||||
step(`view ${monitorType} details in Monitor Management UI`, async () => {
|
||||
await uptime.navigateToMonitorManagement();
|
||||
const hasFailure = await uptime.findMonitorConfiguration(monitorDetails);
|
||||
expect(hasFailure).toBeFalsy();
|
||||
});
|
||||
|
||||
if (isRemote) {
|
||||
step('view results in overview page', async () => {
|
||||
await uptime.navigateToOverviewPage();
|
||||
await page.waitForSelector(`text=${monitorName}`, { timeout: 160 * 1000 });
|
||||
});
|
||||
}
|
||||
|
||||
step('delete monitor', async () => {
|
||||
await uptime.navigateToMonitorManagement();
|
||||
const isSuccessful = await uptime.deleteMonitors();
|
||||
expect(isSuccessful).toBeTruthy();
|
||||
});
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
Object.keys(configuration).forEach((type) => {
|
||||
createMonitorJourney({
|
||||
monitorType: type as DataStream,
|
||||
monitorName: `${type} monitor`,
|
||||
monitorConfig: configuration[type as DataStream].monitorConfig,
|
||||
monitorDetails: configuration[type as DataStream].monitorDetails,
|
||||
});
|
||||
});
|
||||
|
||||
journey('Monitor Management breadcrumbs', async ({ page, params }: { page: Page; params: any }) => {
|
||||
recordVideo(page);
|
||||
const uptime = monitorManagementPageProvider({ page, kibanaUrl: params.kibanaUrl });
|
||||
const defaultMonitorDetails = {
|
||||
name: `Sample monitor ${uuidv4()}`,
|
||||
location: 'US Central',
|
||||
schedule: '3',
|
||||
apmServiceName: 'service',
|
||||
};
|
||||
|
||||
after(async () => {
|
||||
await uptime.enableMonitorManagement(false);
|
||||
});
|
||||
|
||||
step('Go to monitor-management', async () => {
|
||||
await uptime.navigateToMonitorManagement(true);
|
||||
});
|
||||
|
||||
step('Check breadcrumb', async () => {
|
||||
const lastBreadcrumb = await (await uptime.findByTestSubj('"breadcrumb last"')).textContent();
|
||||
expect(lastBreadcrumb).toEqual('Monitor Management');
|
||||
});
|
||||
|
||||
step('check breadcrumbs', async () => {
|
||||
await uptime.enableMonitorManagement();
|
||||
await uptime.clickAddMonitor();
|
||||
const breadcrumbs = await page.$$('[data-test-subj="breadcrumb"]');
|
||||
expect(await breadcrumbs[1].textContent()).toEqual('Monitor Management');
|
||||
const lastBreadcrumb = await (await uptime.findByTestSubj('"breadcrumb last"')).textContent();
|
||||
expect(lastBreadcrumb).toEqual('Add monitor');
|
||||
});
|
||||
|
||||
step('create monitor http monitor', async () => {
|
||||
const monitorDetails = {
|
||||
...defaultMonitorDetails,
|
||||
url: 'https://elastic.co',
|
||||
locations: [basicMonitorDetails.location],
|
||||
};
|
||||
await uptime.createBasicHTTPMonitorDetails(monitorDetails);
|
||||
const isSuccessful = await uptime.confirmAndSave();
|
||||
expect(isSuccessful).toBeTruthy();
|
||||
});
|
||||
|
||||
step('edit http monitor and check breadcrumb', async () => {
|
||||
await uptime.editMonitor();
|
||||
// breadcrumb is available before edit page is loaded, make sure its edit view
|
||||
await page.waitForSelector(byTestId('monitorManagementMonitorName'), { timeout: 60 * 1000 });
|
||||
const breadcrumbs = await page.$$('[data-test-subj=breadcrumb]');
|
||||
expect(await breadcrumbs[1].textContent()).toEqual('Monitor Management');
|
||||
const lastBreadcrumb = await (await uptime.findByTestSubj('"breadcrumb last"')).textContent();
|
||||
expect(lastBreadcrumb).toEqual('Edit monitor');
|
||||
});
|
||||
|
||||
step('delete monitor', async () => {
|
||||
await uptime.navigateToMonitorManagement();
|
||||
const isSuccessful = await uptime.deleteMonitors();
|
||||
expect(isSuccessful).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
journey(
|
||||
'MonitorManagement-case-insensitive sort',
|
||||
async ({ page, params }: { page: Page; params: any }) => {
|
||||
recordVideo(page);
|
||||
const uptime = monitorManagementPageProvider({ page, kibanaUrl: params.kibanaUrl });
|
||||
|
||||
const sortedMonitors = [
|
||||
Object.assign({}, configuration[DataStream.ICMP].monitorConfig, {
|
||||
name: `A ${uuidv4()}`,
|
||||
}),
|
||||
Object.assign({}, configuration[DataStream.ICMP].monitorConfig, {
|
||||
name: `B ${uuidv4()}`,
|
||||
}),
|
||||
Object.assign({}, configuration[DataStream.ICMP].monitorConfig, {
|
||||
name: `aa ${uuidv4()}`,
|
||||
}),
|
||||
];
|
||||
|
||||
after(async () => {
|
||||
await uptime.navigateToMonitorManagement();
|
||||
await uptime.deleteMonitors();
|
||||
await uptime.enableMonitorManagement(false);
|
||||
});
|
||||
|
||||
step('Go to monitor-management', async () => {
|
||||
await uptime.navigateToMonitorManagement(true);
|
||||
});
|
||||
|
||||
for (const monitorConfig of sortedMonitors) {
|
||||
step(`create monitor ${monitorConfig.name}`, async () => {
|
||||
await uptime.enableMonitorManagement();
|
||||
await uptime.clickAddMonitor();
|
||||
await uptime.createMonitor({ monitorConfig, monitorType: DataStream.ICMP });
|
||||
const isSuccessful = await uptime.confirmAndSave();
|
||||
expect(isSuccessful).toBeTruthy();
|
||||
});
|
||||
}
|
||||
|
||||
step(`list monitors in Monitor Management UI`, async () => {
|
||||
await uptime.navigateToMonitorManagement();
|
||||
await Promise.all(
|
||||
sortedMonitors.map((monitor) =>
|
||||
page.waitForSelector(`text=${monitor.name}`, { timeout: 160 * 1000 })
|
||||
)
|
||||
);
|
||||
|
||||
// Get first cell value from monitor table -> monitor name
|
||||
const rows = page.locator('tbody tr td:first-child div.euiTableCellContent');
|
||||
expect(await rows.count()).toEqual(sortedMonitors.length);
|
||||
|
||||
const expectedSort = sortedMonitors
|
||||
.map((mn) => mn.name)
|
||||
.sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase()));
|
||||
expect(await rows.allTextContents()).toEqual(expectedSort);
|
||||
});
|
||||
}
|
||||
);
|
|
@ -1,56 +0,0 @@
|
|||
/*
|
||||
* 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 { journey, step, expect, after, Page } from '@elastic/synthetics';
|
||||
import { recordVideo } from '../../helpers/record_video';
|
||||
import { monitorManagementPageProvider } from '../../page_objects/uptime/monitor_management';
|
||||
|
||||
journey(
|
||||
'Monitor Management-enablement-superuser',
|
||||
async ({ page, params }: { page: Page; params: any }) => {
|
||||
recordVideo(page);
|
||||
|
||||
const uptime = monitorManagementPageProvider({ page, kibanaUrl: params.kibanaUrl });
|
||||
|
||||
after(async () => {
|
||||
await uptime.enableMonitorManagement(false);
|
||||
});
|
||||
|
||||
step('Go to monitor-management', async () => {
|
||||
await uptime.navigateToMonitorManagement(true);
|
||||
});
|
||||
|
||||
step('check add monitor button', async () => {
|
||||
expect(await uptime.checkIsEnabled()).toBe(false);
|
||||
});
|
||||
|
||||
step('enable Monitor Management', async () => {
|
||||
await uptime.enableMonitorManagement();
|
||||
expect(await uptime.checkIsEnabled()).toBe(true);
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
journey(
|
||||
'MonitorManagement-enablement-obs-admin',
|
||||
async ({ page, params }: { page: Page; params: any }) => {
|
||||
recordVideo(page);
|
||||
|
||||
const uptime = monitorManagementPageProvider({ page, kibanaUrl: params.kibanaUrl });
|
||||
|
||||
step('Go to monitor-management', async () => {
|
||||
await uptime.navigateToMonitorManagement(true);
|
||||
});
|
||||
|
||||
step('check add monitor button', async () => {
|
||||
expect(await uptime.checkIsEnabled()).toBe(false);
|
||||
});
|
||||
|
||||
step('check that enabled toggle does not appear', async () => {
|
||||
expect(await page.$(`[data-test-subj=syntheticsEnableSwitch]`)).toBeFalsy();
|
||||
});
|
||||
}
|
||||
);
|
|
@ -1,68 +0,0 @@
|
|||
/*
|
||||
* 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 { v4 as uuidv4 } from 'uuid';
|
||||
import { journey, step, expect, Page } from '@elastic/synthetics';
|
||||
import { byTestId } from '../../helpers/utils';
|
||||
import { recordVideo } from '../../helpers/record_video';
|
||||
import { monitorManagementPageProvider } from '../../page_objects/uptime/monitor_management';
|
||||
|
||||
journey(`MonitorName`, async ({ page, params }: { page: Page; params: any }) => {
|
||||
recordVideo(page);
|
||||
|
||||
const name = `Test monitor ${uuidv4()}`;
|
||||
const uptime = monitorManagementPageProvider({ page, kibanaUrl: params.kibanaUrl });
|
||||
|
||||
const createBasicMonitor = async () => {
|
||||
await uptime.createBasicHTTPMonitorDetails({
|
||||
name,
|
||||
locations: ['US Central'],
|
||||
apmServiceName: 'synthetics',
|
||||
url: 'https://www.google.com',
|
||||
});
|
||||
};
|
||||
|
||||
step('Go to monitor-management', async () => {
|
||||
await uptime.navigateToMonitorManagement(true);
|
||||
});
|
||||
|
||||
step('create basic monitor', async () => {
|
||||
await uptime.enableMonitorManagement();
|
||||
await uptime.clickAddMonitor();
|
||||
await createBasicMonitor();
|
||||
await uptime.confirmAndSave();
|
||||
});
|
||||
|
||||
step(`shows error if name already exists`, async () => {
|
||||
await uptime.navigateToAddMonitor();
|
||||
await uptime.createBasicHTTPMonitorDetails({
|
||||
name,
|
||||
locations: ['US Central'],
|
||||
apmServiceName: 'synthetics',
|
||||
url: 'https://www.google.com',
|
||||
});
|
||||
|
||||
await uptime.assertText({ text: 'Monitor name already exists.' });
|
||||
|
||||
expect(await page.isEnabled(byTestId('monitorTestNowRunBtn'))).toBeFalsy();
|
||||
});
|
||||
|
||||
step(`form becomes valid after change`, async () => {
|
||||
await uptime.createBasicMonitorDetails({
|
||||
name: 'Test monitor 2',
|
||||
locations: ['US Central'],
|
||||
apmServiceName: 'synthetics',
|
||||
});
|
||||
|
||||
expect(await page.isEnabled(byTestId('monitorTestNowRunBtn'))).toBeTruthy();
|
||||
});
|
||||
|
||||
step('delete monitor', async () => {
|
||||
await uptime.navigateToMonitorManagement();
|
||||
await uptime.deleteMonitors();
|
||||
await uptime.enableMonitorManagement(false);
|
||||
});
|
||||
});
|
|
@ -1,98 +0,0 @@
|
|||
/*
|
||||
* 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 { v4 as uuidv4 } from 'uuid';
|
||||
import { journey, step, expect, before } from '@elastic/synthetics';
|
||||
import { TIMEOUT_60_SEC, byTestId } from '../../../helpers/utils';
|
||||
import { recordVideo } from '../../../helpers/record_video';
|
||||
import { cleanTestMonitors } from '../../synthetics/services/add_monitor';
|
||||
import { monitorManagementPageProvider } from '../../../page_objects/uptime/monitor_management';
|
||||
|
||||
journey('AddPrivateLocationMonitor', async ({ page, params }) => {
|
||||
recordVideo(page);
|
||||
|
||||
page.setDefaultTimeout(TIMEOUT_60_SEC.timeout);
|
||||
const kibanaUrl = params.kibanaUrl;
|
||||
|
||||
const uptime = monitorManagementPageProvider({ page, kibanaUrl });
|
||||
const monitorName = `Private location monitor ${uuidv4()}`;
|
||||
|
||||
let monitorId: string;
|
||||
|
||||
before(async () => {
|
||||
await cleanTestMonitors(params);
|
||||
page.on('request', (evt) => {
|
||||
if (
|
||||
evt.resourceType() === 'fetch' &&
|
||||
evt.url().includes('/internal/uptime/service/monitors?preserve_namespace=true')
|
||||
) {
|
||||
evt
|
||||
.response()
|
||||
?.then((res) => res?.json())
|
||||
.then((res) => {
|
||||
monitorId = res.id;
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
step('Go to monitor-management', async () => {
|
||||
await uptime.navigateToMonitorManagement();
|
||||
});
|
||||
|
||||
step('login to Kibana', async () => {
|
||||
await uptime.loginToKibana();
|
||||
const invalid = await page.locator(`text=Username or password is incorrect. Please try again.`);
|
||||
expect(await invalid.isVisible()).toBeFalsy();
|
||||
});
|
||||
|
||||
step('enable management', async () => {
|
||||
await uptime.enableMonitorManagement();
|
||||
});
|
||||
|
||||
step('Click text=Add monitor', async () => {
|
||||
await page.click('text=Add monitor');
|
||||
expect(page.url()).toBe(`${kibanaUrl}/app/uptime/add-monitor`);
|
||||
await uptime.waitForLoadingToFinish();
|
||||
|
||||
await page.click('input[name="name"]');
|
||||
await page.fill('input[name="name"]', monitorName);
|
||||
await page.click('label:has-text("Test private location Private")', TIMEOUT_60_SEC);
|
||||
await page.selectOption('select', 'http');
|
||||
await page.click(byTestId('syntheticsUrlField'));
|
||||
await page.fill(byTestId('syntheticsUrlField'), 'https://www.google.com');
|
||||
|
||||
await page.click('text=Save monitor');
|
||||
|
||||
await page.click(`text=${monitorName}`);
|
||||
|
||||
await page.click('[data-test-subj="superDatePickerApplyTimeButton"]');
|
||||
});
|
||||
|
||||
step('Integration cannot be edited in Fleet', async () => {
|
||||
await page.goto(`${kibanaUrl}/app/integrations/detail/synthetics/policies`);
|
||||
await page.waitForSelector('h1:has-text("Elastic Synthetics")');
|
||||
await page.click(`text=${monitorName}`);
|
||||
await page.waitForSelector('h1:has-text("Edit Elastic Synthetics integration")');
|
||||
await page.waitForSelector('text="This package policy is managed by the Synthetics app."');
|
||||
});
|
||||
|
||||
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}?packagePolicyId=${policyId}`
|
||||
);
|
||||
await page.click('text="Edit in Synthetics"');
|
||||
|
||||
await page.waitForSelector('h1:has-text("Edit Monitor")');
|
||||
await page.waitForSelector('h2:has-text("Monitor details")');
|
||||
expect(await page.inputValue('[data-test-subj="syntheticsMonitorConfigName"]')).toBe(
|
||||
monitorName
|
||||
);
|
||||
});
|
||||
});
|
|
@ -1,9 +0,0 @@
|
|||
/*
|
||||
* 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 * from './manage_locations';
|
||||
export * from './add_monitor_private_location';
|
|
@ -1,80 +0,0 @@
|
|||
/*
|
||||
* 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 { journey, step, expect } from '@elastic/synthetics';
|
||||
import { byTestId, TIMEOUT_60_SEC } from '../../../helpers/utils';
|
||||
import { recordVideo } from '../../../helpers/record_video';
|
||||
import { monitorManagementPageProvider } from '../../../page_objects/uptime/monitor_management';
|
||||
|
||||
journey('ManagePrivateLocation', async ({ page, params: { kibanaUrl } }) => {
|
||||
recordVideo(page);
|
||||
|
||||
const uptime = monitorManagementPageProvider({ page, kibanaUrl });
|
||||
|
||||
step('Go to monitor-management', async () => {
|
||||
await uptime.navigateToMonitorManagement();
|
||||
});
|
||||
|
||||
step('login to Kibana', async () => {
|
||||
await uptime.loginToKibana();
|
||||
const invalid = await page.locator(`text=Username or password is incorrect. Please try again.`);
|
||||
expect(await invalid.isVisible()).toBeFalsy();
|
||||
});
|
||||
|
||||
step('enable management', async () => {
|
||||
await uptime.enableMonitorManagement();
|
||||
});
|
||||
|
||||
step('Open manage location', async () => {
|
||||
await page.click('button:has-text("Private locations")');
|
||||
});
|
||||
|
||||
step('Add two agent policies', async () => {
|
||||
await page.click('text=Create agent policy');
|
||||
|
||||
await addAgentPolicy('Fleet test policy');
|
||||
await page.click('text=Create agent policy');
|
||||
|
||||
await addAgentPolicy('Fleet test policy 2');
|
||||
await page.goBack({ waitUntil: 'networkidle' });
|
||||
await page.goBack({ waitUntil: 'networkidle' });
|
||||
await page.goBack({ waitUntil: 'networkidle' });
|
||||
});
|
||||
|
||||
step('Add new private location', async () => {
|
||||
await page.waitForTimeout(30 * 1000);
|
||||
await page.click('button:has-text("Close")');
|
||||
|
||||
await page.click('button:has-text("Private locations")');
|
||||
await page.click(byTestId('addPrivateLocationButton'));
|
||||
|
||||
await addPrivateLocation('Test private location', 'Fleet test policy');
|
||||
});
|
||||
|
||||
step('Add another location', async () => {
|
||||
await page.click(byTestId('addPrivateLocationButton'), TIMEOUT_60_SEC);
|
||||
|
||||
await page.click('[aria-label="Select agent policy"]');
|
||||
await page.isDisabled(`button[role="option"]:has-text("Fleet test policyAgents: 0")`);
|
||||
|
||||
await addPrivateLocation('Test private location 2', 'Fleet test policy 2');
|
||||
});
|
||||
|
||||
const addPrivateLocation = async (name: string, policy: string) => {
|
||||
await page.click('[aria-label="Location name"]');
|
||||
await page.fill('[aria-label="Location name"]', name);
|
||||
await page.click('[aria-label="Select agent policy"]');
|
||||
await page.click(`button[role="option"]:has-text("${policy}Agents: 0")`);
|
||||
await page.click('button:has-text("Save")');
|
||||
};
|
||||
|
||||
const addAgentPolicy = async (name: string) => {
|
||||
await page.click('[placeholder="Choose a name"]');
|
||||
await page.fill('[placeholder="Choose a name"]', name);
|
||||
await page.click('text=Collect system logs and metrics');
|
||||
await page.click('div[role="dialog"] button:has-text("Create agent policy")');
|
||||
};
|
||||
});
|
|
@ -1,8 +0,0 @@
|
|||
/*
|
||||
* 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 * from './monitor_management';
|
|
@ -1,32 +0,0 @@
|
|||
/*
|
||||
* 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 { expect, journey, Page, step } from '@elastic/synthetics';
|
||||
import { byTestId } from '../../../helpers/utils';
|
||||
import { recordVideo } from '../../../helpers/record_video';
|
||||
import { monitorManagementPageProvider } from '../../../page_objects/uptime/monitor_management';
|
||||
|
||||
journey(
|
||||
'Monitor Management read only user',
|
||||
async ({ page, params }: { page: Page; params: any }) => {
|
||||
recordVideo(page);
|
||||
|
||||
const uptime = monitorManagementPageProvider({ page, kibanaUrl: params.kibanaUrl });
|
||||
|
||||
step('Go to monitor-management', async () => {
|
||||
await uptime.navigateToMonitorManagement(false);
|
||||
});
|
||||
|
||||
step('login to Kibana', async () => {
|
||||
await uptime.loginToKibana('viewer', 'changeme');
|
||||
});
|
||||
|
||||
step('Adding monitor is disabled', async () => {
|
||||
expect(await page.isEnabled(byTestId('syntheticsAddMonitorBtn'))).toBeFalsy();
|
||||
});
|
||||
}
|
||||
);
|
|
@ -32,7 +32,7 @@ journey('StepsDuration', async ({ page, params }) => {
|
|||
});
|
||||
|
||||
step('Go to monitor details', async () => {
|
||||
await page.click('button:has-text("test-monitor - inline")');
|
||||
await page.click('text="test-monitor - inline"');
|
||||
expect(page.url()).toBe(`${baseUrl}/monitor/dGVzdC1tb25pdG9yLWlubGluZQ==/?${queryParams}`);
|
||||
});
|
||||
|
||||
|
|
|
@ -6,8 +6,9 @@
|
|||
*/
|
||||
|
||||
import { Page } from '@elastic/synthetics';
|
||||
import { byTestId, delay } from '../../helpers/utils';
|
||||
import { monitorManagementPageProvider } from './monitor_management';
|
||||
import { utilsPageProvider } from '../utils';
|
||||
import { byTestId, delay, getQuerystring } from '../../helpers/utils';
|
||||
import { loginPageProvider } from '../login';
|
||||
|
||||
interface AlertType {
|
||||
id: string;
|
||||
|
@ -15,9 +16,21 @@ interface AlertType {
|
|||
}
|
||||
|
||||
export function monitorDetailsPageProvider({ page, kibanaUrl }: { page: Page; kibanaUrl: string }) {
|
||||
return {
|
||||
...monitorManagementPageProvider({ page, kibanaUrl }),
|
||||
const remoteKibanaUrl = process.env.SYNTHETICS_REMOTE_KIBANA_URL;
|
||||
const isRemote = Boolean(process.env.SYNTHETICS_REMOTE_ENABLED);
|
||||
const remoteUsername = process.env.SYNTHETICS_REMOTE_KIBANA_USERNAME;
|
||||
const remotePassword = process.env.SYNTHETICS_REMOTE_KIBANA_PASSWORD;
|
||||
|
||||
const basePath = isRemote ? remoteKibanaUrl : kibanaUrl;
|
||||
const overview = `${basePath}/app/uptime`;
|
||||
return {
|
||||
...loginPageProvider({
|
||||
page,
|
||||
isRemote,
|
||||
username: isRemote ? remoteUsername : 'elastic',
|
||||
password: isRemote ? remotePassword : 'changeme',
|
||||
}),
|
||||
...utilsPageProvider({ page }),
|
||||
async navigateToMonitorDetails(monitorId: string) {
|
||||
await page.click(byTestId(`monitor-page-link-${monitorId}`));
|
||||
},
|
||||
|
@ -104,8 +117,8 @@ export function monitorDetailsPageProvider({ page, kibanaUrl }: { page: Page; ki
|
|||
},
|
||||
|
||||
async selectAlertThreshold(threshold: string) {
|
||||
await this.clickByTestSubj('uptimeAnomalySeverity');
|
||||
await this.clickByTestSubj('anomalySeveritySelect');
|
||||
await page.click(byTestId('uptimeAnomalySeverity'));
|
||||
await page.click(byTestId('anomalySeveritySelect'));
|
||||
await page.click(`text=${threshold}`);
|
||||
},
|
||||
|
||||
|
@ -125,5 +138,10 @@ export function monitorDetailsPageProvider({ page, kibanaUrl }: { page: Page; ki
|
|||
await page.waitForSelector('text=Rule successfully disabled!');
|
||||
await this.closeAnomalyDetectionMenu();
|
||||
},
|
||||
async navigateToOverviewPage(options?: object) {
|
||||
await page.goto(`${overview}${options ? `?${getQuerystring(options)}` : ''}`, {
|
||||
waitUntil: 'networkidle',
|
||||
});
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,301 +0,0 @@
|
|||
/*
|
||||
* 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 { expect, Page } from '@elastic/synthetics';
|
||||
import { getQuerystring, TIMEOUT_60_SEC } from '../../helpers/utils';
|
||||
import { DataStream } from '../../../common/runtime_types/monitor_management';
|
||||
import { loginPageProvider } from '../login';
|
||||
import { utilsPageProvider } from '../utils';
|
||||
|
||||
export function monitorManagementPageProvider({
|
||||
page,
|
||||
kibanaUrl,
|
||||
}: {
|
||||
page: Page;
|
||||
kibanaUrl: string;
|
||||
}) {
|
||||
const remoteKibanaUrl = process.env.SYNTHETICS_REMOTE_KIBANA_URL;
|
||||
const remoteUsername = process.env.SYNTHETICS_REMOTE_KIBANA_USERNAME;
|
||||
const remotePassword = process.env.SYNTHETICS_REMOTE_KIBANA_PASSWORD;
|
||||
const isRemote = Boolean(process.env.SYNTHETICS_REMOTE_ENABLED);
|
||||
const basePath = isRemote ? remoteKibanaUrl : kibanaUrl;
|
||||
const monitorManagement = `${basePath}/app/uptime/manage-monitors/all`;
|
||||
const addMonitor = `${basePath}/app/uptime/add-monitor`;
|
||||
const overview = `${basePath}/app/uptime`;
|
||||
return {
|
||||
...loginPageProvider({
|
||||
page,
|
||||
isRemote,
|
||||
username: isRemote ? remoteUsername : 'elastic',
|
||||
password: isRemote ? remotePassword : 'changeme',
|
||||
}),
|
||||
...utilsPageProvider({ page }),
|
||||
|
||||
async navigateToMonitorManagement(doLogin = false) {
|
||||
await page.goto(monitorManagement, {
|
||||
waitUntil: 'networkidle',
|
||||
});
|
||||
if (doLogin) {
|
||||
await this.loginToKibana();
|
||||
}
|
||||
await this.waitForMonitorManagementLoadingToFinish();
|
||||
},
|
||||
|
||||
async waitForMonitorManagementLoadingToFinish() {
|
||||
while (true) {
|
||||
if ((await page.$(this.byTestId('uptimeLoader'))) === null) break;
|
||||
await page.waitForTimeout(5 * 1000);
|
||||
}
|
||||
},
|
||||
|
||||
async enableMonitorManagement(shouldEnable: boolean = true) {
|
||||
const isEnabled = await this.checkIsEnabled();
|
||||
if (isEnabled === shouldEnable) {
|
||||
return;
|
||||
}
|
||||
const [toggle, button] = await Promise.all([
|
||||
page.$(this.byTestId('syntheticsEnableSwitch')),
|
||||
page.$(this.byTestId('syntheticsEnableButton')),
|
||||
]);
|
||||
|
||||
if (toggle === null && button === null) {
|
||||
return null;
|
||||
}
|
||||
if (toggle) {
|
||||
if (isEnabled !== shouldEnable) {
|
||||
await toggle.click();
|
||||
}
|
||||
} else {
|
||||
await button?.click();
|
||||
}
|
||||
if (shouldEnable) {
|
||||
await this.findByText('Monitor Management enabled successfully.');
|
||||
} else {
|
||||
await this.findByText('Monitor Management disabled successfully.');
|
||||
}
|
||||
},
|
||||
|
||||
async getEnableToggle() {
|
||||
return await this.findByTestSubj('syntheticsEnableSwitch');
|
||||
},
|
||||
|
||||
async getEnableButton() {
|
||||
return await this.findByTestSubj('syntheticsEnableSwitch');
|
||||
},
|
||||
|
||||
async getAddMonitorButton() {
|
||||
return await this.findByTestSubj('syntheticsAddMonitorBtn');
|
||||
},
|
||||
|
||||
async checkIsEnabled() {
|
||||
await page.waitForTimeout(5 * 1000);
|
||||
const addMonitorBtn = await this.getAddMonitorButton();
|
||||
const isDisabled = await addMonitorBtn.isDisabled();
|
||||
return !isDisabled;
|
||||
},
|
||||
|
||||
async navigateToAddMonitor() {
|
||||
await page.goto(addMonitor, {
|
||||
waitUntil: 'networkidle',
|
||||
});
|
||||
},
|
||||
|
||||
async navigateToOverviewPage(options?: object) {
|
||||
await page.goto(`${overview}${options ? `?${getQuerystring(options)}` : ''}`, {
|
||||
waitUntil: 'networkidle',
|
||||
});
|
||||
},
|
||||
|
||||
async clickAddMonitor() {
|
||||
const isEnabled = await this.checkIsEnabled();
|
||||
expect(isEnabled).toBe(true);
|
||||
await page.click('text=Add monitor');
|
||||
},
|
||||
|
||||
async deleteMonitors() {
|
||||
let isSuccessful: boolean = false;
|
||||
await page.waitForSelector('[data-test-subj="monitorManagementDeleteMonitor"]');
|
||||
while (true) {
|
||||
if ((await page.$(this.byTestId('monitorManagementDeleteMonitor'))) === null) break;
|
||||
await page.click(this.byTestId('monitorManagementDeleteMonitor'), { delay: 800 });
|
||||
await page.waitForSelector('[data-test-subj="confirmModalTitleText"]');
|
||||
await this.clickByTestSubj('confirmModalConfirmButton');
|
||||
isSuccessful = Boolean(await this.findByTestSubj('uptimeDeleteMonitorSuccess'));
|
||||
await page.waitForTimeout(5 * 1000);
|
||||
}
|
||||
return isSuccessful;
|
||||
},
|
||||
|
||||
async editMonitor() {
|
||||
await page.click(this.byTestId('monitorManagementEditMonitor'), { delay: 800 });
|
||||
},
|
||||
|
||||
async findMonitorConfiguration(monitorConfig: Record<string, string>) {
|
||||
const values = Object.values(monitorConfig);
|
||||
|
||||
for (let i = 0; i < values.length; i++) {
|
||||
await this.findByText(values[i]);
|
||||
}
|
||||
},
|
||||
|
||||
async selectMonitorType(monitorType: string) {
|
||||
await this.selectByTestSubj('syntheticsMonitorTypeField', monitorType);
|
||||
},
|
||||
|
||||
async ensureIsOnMonitorConfigPage() {
|
||||
await page.isVisible('[data-test-subj=monitorSettingsSection]');
|
||||
},
|
||||
|
||||
async confirmAndSave(isEditPage?: boolean) {
|
||||
await this.ensureIsOnMonitorConfigPage();
|
||||
if (isEditPage) {
|
||||
await page.click('text=Update monitor');
|
||||
} else {
|
||||
await page.click('text=Save monitor');
|
||||
}
|
||||
return await this.findByText('Monitor added successfully.');
|
||||
},
|
||||
|
||||
async fillCodeEditor(value: string) {
|
||||
await page.fill('[data-test-subj=codeEditorContainer] textarea', value);
|
||||
},
|
||||
|
||||
async selectLocations({ locations }: { locations: string[] }) {
|
||||
for (let i = 0; i < locations.length; i++) {
|
||||
await page.check(`text=${locations[i]}`, TIMEOUT_60_SEC);
|
||||
}
|
||||
},
|
||||
|
||||
async createBasicMonitorDetails({
|
||||
name,
|
||||
apmServiceName,
|
||||
locations,
|
||||
}: {
|
||||
name: string;
|
||||
apmServiceName: string;
|
||||
locations: string[];
|
||||
}) {
|
||||
await this.fillByTestSubj('monitorManagementMonitorName', name);
|
||||
await this.fillByTestSubj('syntheticsAPMServiceName', apmServiceName);
|
||||
await this.selectLocations({ locations });
|
||||
},
|
||||
|
||||
async createBasicHTTPMonitorDetails({
|
||||
name,
|
||||
url,
|
||||
apmServiceName,
|
||||
locations,
|
||||
}: {
|
||||
name: string;
|
||||
url: string;
|
||||
apmServiceName: string;
|
||||
locations: string[];
|
||||
}) {
|
||||
await this.selectMonitorType('http');
|
||||
await this.createBasicMonitorDetails({ name, apmServiceName, locations });
|
||||
await this.fillByTestSubj('syntheticsUrlField', url);
|
||||
},
|
||||
|
||||
async createBasicTCPMonitorDetails({
|
||||
name,
|
||||
host,
|
||||
apmServiceName,
|
||||
locations,
|
||||
}: {
|
||||
name: string;
|
||||
host: string;
|
||||
apmServiceName: string;
|
||||
locations: string[];
|
||||
}) {
|
||||
await this.selectMonitorType('tcp');
|
||||
await this.createBasicMonitorDetails({ name, apmServiceName, locations });
|
||||
await this.fillByTestSubj('syntheticsTCPHostField', host);
|
||||
},
|
||||
|
||||
async createBasicICMPMonitorDetails({
|
||||
name,
|
||||
host,
|
||||
apmServiceName,
|
||||
locations,
|
||||
}: {
|
||||
name: string;
|
||||
host: string;
|
||||
apmServiceName: string;
|
||||
locations: string[];
|
||||
}) {
|
||||
await this.selectMonitorType('icmp');
|
||||
await this.createBasicMonitorDetails({ name, apmServiceName, locations });
|
||||
await this.fillByTestSubj('syntheticsICMPHostField', host);
|
||||
},
|
||||
|
||||
async createBasicBrowserMonitorDetails(
|
||||
{
|
||||
name,
|
||||
inlineScript,
|
||||
zipUrl,
|
||||
folder,
|
||||
params,
|
||||
username,
|
||||
password,
|
||||
apmServiceName,
|
||||
locations,
|
||||
}: {
|
||||
name: string;
|
||||
inlineScript?: string;
|
||||
zipUrl?: string;
|
||||
folder?: string;
|
||||
params?: string;
|
||||
username?: string;
|
||||
password?: string;
|
||||
apmServiceName: string;
|
||||
locations: string[];
|
||||
},
|
||||
isInline: boolean = false
|
||||
) {
|
||||
await this.selectMonitorType('browser');
|
||||
await this.createBasicMonitorDetails({ name, apmServiceName, locations });
|
||||
if (isInline && inlineScript) {
|
||||
await this.clickByTestSubj('syntheticsSourceTab__inline');
|
||||
await this.fillCodeEditor(inlineScript);
|
||||
return;
|
||||
}
|
||||
await this.fillByTestSubj('syntheticsBrowserZipUrl', zipUrl || '');
|
||||
await this.fillByTestSubj('syntheticsBrowserZipUrlFolder', folder || '');
|
||||
await this.fillByTestSubj('syntheticsBrowserZipUrlUsername', username || '');
|
||||
await this.fillByTestSubj('syntheticsBrowserZipUrlPassword', password || '');
|
||||
await this.fillCodeEditor(params || '');
|
||||
},
|
||||
|
||||
async createMonitor({
|
||||
monitorConfig,
|
||||
monitorType,
|
||||
}: {
|
||||
monitorConfig: Record<string, string | string[]>;
|
||||
monitorType: DataStream;
|
||||
}) {
|
||||
switch (monitorType) {
|
||||
case DataStream.HTTP:
|
||||
// @ts-ignore
|
||||
await this.createBasicHTTPMonitorDetails(monitorConfig);
|
||||
break;
|
||||
case DataStream.TCP:
|
||||
// @ts-ignore
|
||||
await this.createBasicTCPMonitorDetails(monitorConfig);
|
||||
break;
|
||||
case DataStream.ICMP:
|
||||
// @ts-ignore
|
||||
await this.createBasicICMPMonitorDetails(monitorConfig);
|
||||
break;
|
||||
case DataStream.BROWSER:
|
||||
// @ts-ignore
|
||||
await this.createBasicBrowserMonitorDetails(monitorConfig, true);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* 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 from 'react';
|
||||
import { EuiButton, EuiCallOut, EuiLink } from '@elastic/eui';
|
||||
import { InvalidApiKeyCalloutCallout } from './invalid_api_key_callout';
|
||||
import * as labels from './labels';
|
||||
import { useEnablement } from '../../../hooks';
|
||||
|
||||
export const DisabledCallout = ({ total }: { total: number }) => {
|
||||
const { enablement, enableSynthetics, invalidApiKeyError, loading } = useEnablement();
|
||||
|
||||
const showDisableCallout = !enablement.isEnabled && total > 0;
|
||||
|
||||
if (invalidApiKeyError) {
|
||||
return <InvalidApiKeyCalloutCallout />;
|
||||
}
|
||||
|
||||
if (!showDisableCallout) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<EuiCallOut title={labels.CALLOUT_MANAGEMENT_DISABLED} color="warning" iconType="help">
|
||||
<p>{labels.CALLOUT_MANAGEMENT_DESCRIPTION}</p>
|
||||
{enablement.canEnable || loading ? (
|
||||
<EuiButton
|
||||
data-test-subj="syntheticsMonitorManagementPageButton"
|
||||
fill
|
||||
color="primary"
|
||||
onClick={() => {
|
||||
enableSynthetics();
|
||||
}}
|
||||
isLoading={loading}
|
||||
>
|
||||
{labels.SYNTHETICS_ENABLE_LABEL}
|
||||
</EuiButton>
|
||||
) : (
|
||||
<p>
|
||||
{labels.CALLOUT_MANAGEMENT_CONTACT_ADMIN}{' '}
|
||||
<EuiLink data-test-subj="syntheticsMonitorManagementPageLink" href="#" target="_blank">
|
||||
{labels.LEARN_MORE_LABEL}
|
||||
</EuiLink>
|
||||
</p>
|
||||
)}
|
||||
</EuiCallOut>
|
||||
);
|
||||
};
|
|
@ -8,20 +8,16 @@
|
|||
import React from 'react';
|
||||
import { EuiButton, EuiCallOut, EuiLink, EuiSpacer } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { useEnablement } from '../../components/monitor_management/hooks/use_enablement';
|
||||
import { useEnablement } from '../../../hooks';
|
||||
|
||||
export const InvalidApiKeyCalloutCallout = () => {
|
||||
const { enablement, enableSynthetics, invalidApiKeyError } = useEnablement();
|
||||
|
||||
if (!invalidApiKeyError || !enablement.isEnabled) {
|
||||
return null;
|
||||
}
|
||||
const { enablement, enableSynthetics, loading } = useEnablement();
|
||||
|
||||
return (
|
||||
<>
|
||||
<EuiCallOut title={API_KEY_MISSING} color="warning" iconType="help">
|
||||
<p>{CALLOUT_MANAGEMENT_DESCRIPTION}</p>
|
||||
{enablement.canEnable ? (
|
||||
{enablement.canEnable || loading ? (
|
||||
<EuiButton
|
||||
data-test-subj="syntheticsInvalidApiKeyCalloutCalloutButton"
|
||||
fill
|
||||
|
@ -29,6 +25,7 @@ export const InvalidApiKeyCalloutCallout = () => {
|
|||
onClick={() => {
|
||||
enableSynthetics();
|
||||
}}
|
||||
isLoading={loading}
|
||||
>
|
||||
{SYNTHETICS_ENABLE_LABEL}
|
||||
</EuiButton>
|
|
@ -7,9 +7,9 @@
|
|||
|
||||
import React from 'react';
|
||||
import { Redirect } from 'react-router-dom';
|
||||
import { EuiButton, EuiCallOut, EuiLink, EuiSpacer } from '@elastic/eui';
|
||||
import { useTrackPageview } from '@kbn/observability-plugin/public';
|
||||
|
||||
import { DisabledCallout } from './management/disabled_callout';
|
||||
import { useOverviewStatus } from './hooks/use_overview_status';
|
||||
import { GETTING_STARTED_ROUTE } from '../../../../../common/constants';
|
||||
|
||||
|
@ -33,9 +33,8 @@ const MonitorManagementPage: React.FC = () => {
|
|||
|
||||
const {
|
||||
error: enablementError,
|
||||
enablement: { isEnabled, canEnable },
|
||||
enablement: { isEnabled },
|
||||
loading: enablementLoading,
|
||||
enableSynthetics,
|
||||
} = useEnablement();
|
||||
|
||||
useOverviewStatus({ scopeStatusByLocation: false });
|
||||
|
@ -59,37 +58,7 @@ const MonitorManagementPage: React.FC = () => {
|
|||
errorTitle={labels.ERROR_HEADING_LABEL}
|
||||
errorBody={labels.ERROR_HEADING_BODY}
|
||||
>
|
||||
{!isEnabled && syntheticsMonitors.length > 0 ? (
|
||||
<>
|
||||
<EuiCallOut title={labels.CALLOUT_MANAGEMENT_DISABLED} color="warning" iconType="help">
|
||||
<p>{labels.CALLOUT_MANAGEMENT_DESCRIPTION}</p>
|
||||
{canEnable ? (
|
||||
<EuiButton
|
||||
data-test-subj="syntheticsMonitorManagementPageButton"
|
||||
fill
|
||||
color="primary"
|
||||
onClick={() => {
|
||||
enableSynthetics();
|
||||
}}
|
||||
>
|
||||
{labels.SYNTHETICS_ENABLE_LABEL}
|
||||
</EuiButton>
|
||||
) : (
|
||||
<p>
|
||||
{labels.CALLOUT_MANAGEMENT_CONTACT_ADMIN}{' '}
|
||||
<EuiLink
|
||||
data-test-subj="syntheticsMonitorManagementPageLink"
|
||||
href="#"
|
||||
target="_blank"
|
||||
>
|
||||
{labels.LEARN_MORE_LABEL}
|
||||
</EuiLink>
|
||||
</p>
|
||||
)}
|
||||
</EuiCallOut>
|
||||
<EuiSpacer size="s" />
|
||||
</>
|
||||
) : null}
|
||||
<DisabledCallout total={absoluteTotal} />
|
||||
<MonitorListContainer isEnabled={isEnabled} monitorListProps={monitorListProps} />
|
||||
</Loader>
|
||||
{showEmptyState && <EnablementEmptyState />}
|
||||
|
|
|
@ -9,6 +9,7 @@ import { EuiFlexGroup, EuiSpacer, EuiFlexItem } from '@elastic/eui';
|
|||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { useTrackPageview } from '@kbn/observability-plugin/public';
|
||||
import { Redirect, useLocation } from 'react-router-dom';
|
||||
import { DisabledCallout } from '../management/disabled_callout';
|
||||
import { FilterGroup } from '../common/monitor_filters/filter_group';
|
||||
import { OverviewAlerts } from './overview/overview_alerts';
|
||||
import { useEnablement } from '../../../hooks';
|
||||
|
@ -99,6 +100,7 @@ export const OverviewPage: React.FC = () => {
|
|||
|
||||
return (
|
||||
<>
|
||||
<DisabledCallout total={absoluteTotal} />
|
||||
<EuiFlexGroup gutterSize="s" wrap={true}>
|
||||
<EuiFlexItem>
|
||||
<SearchField />
|
||||
|
|
|
@ -20,10 +20,10 @@ export function useEnablement() {
|
|||
const { loading, error, enablement } = useSelector(selectSyntheticsEnablement);
|
||||
|
||||
useEffect(() => {
|
||||
if (!enablement) {
|
||||
if (!enablement && !loading) {
|
||||
dispatch(getSyntheticsEnablement());
|
||||
}
|
||||
}, [dispatch, enablement]);
|
||||
}, [dispatch, enablement, loading]);
|
||||
|
||||
return {
|
||||
enablement: {
|
||||
|
|
|
@ -24,7 +24,9 @@ export const disableSyntheticsFailure = createAction<IHttpSerializedFetchError>(
|
|||
);
|
||||
|
||||
export const enableSynthetics = createAction('[SYNTHETICS_ENABLEMENT] ENABLE');
|
||||
export const enableSyntheticsSuccess = createAction<{}>('[SYNTHETICS_ENABLEMENT] ENABLE SUCCESS');
|
||||
export const enableSyntheticsSuccess = createAction<MonitorManagementEnablementResult>(
|
||||
'[SYNTHETICS_ENABLEMENT] ENABLE SUCCESS'
|
||||
);
|
||||
export const enableSyntheticsFailure = createAction<IHttpSerializedFetchError>(
|
||||
'[SYNTHETICS_ENABLEMENT] ENABLE FAILURE'
|
||||
);
|
||||
|
|
|
@ -25,6 +25,6 @@ export const fetchDisableSynthetics = async (): Promise<{}> => {
|
|||
return await apiService.delete(API_URLS.SYNTHETICS_ENABLEMENT);
|
||||
};
|
||||
|
||||
export const fetchEnableSynthetics = async (): Promise<{}> => {
|
||||
export const fetchEnableSynthetics = async (): Promise<MonitorManagementEnablementResult> => {
|
||||
return await apiService.post(API_URLS.SYNTHETICS_ENABLEMENT);
|
||||
};
|
||||
|
|
|
@ -68,17 +68,12 @@ export const syntheticsEnablementReducer = createReducer(initialState, (builder)
|
|||
|
||||
.addCase(enableSynthetics, (state) => {
|
||||
state.loading = true;
|
||||
state.enablement = null;
|
||||
})
|
||||
.addCase(enableSyntheticsSuccess, (state, action) => {
|
||||
state.loading = false;
|
||||
state.error = null;
|
||||
state.enablement = {
|
||||
canEnable: state.enablement?.canEnable ?? false,
|
||||
areApiKeysEnabled: state.enablement?.areApiKeysEnabled ?? false,
|
||||
canManageApiKeys: state.enablement?.canManageApiKeys ?? false,
|
||||
isValidApiKey: state.enablement?.isValidApiKey ?? false,
|
||||
isEnabled: true,
|
||||
};
|
||||
state.enablement = action.payload;
|
||||
})
|
||||
.addCase(enableSyntheticsFailure, (state, action) => {
|
||||
state.loading = false;
|
||||
|
|
|
@ -6,18 +6,14 @@ exports[`CertMonitors renders expected elements for valid props 1`] = `
|
|||
<span
|
||||
class="euiToolTipAnchor emotion-euiToolTipAnchor-inlineBlock"
|
||||
>
|
||||
<button
|
||||
<a
|
||||
class="euiLink emotion-euiLink-primary"
|
||||
data-test-subj="syntheticsMonitorPageLinkLink"
|
||||
type="button"
|
||||
data-test-subj="monitor-page-link-bad-ssl-dashboard"
|
||||
href="/monitor/YmFkLXNzbC1kYXNoYm9hcmQ="
|
||||
rel="noreferrer"
|
||||
>
|
||||
<a
|
||||
data-test-subj="monitor-page-link-bad-ssl-dashboard"
|
||||
href="/monitor/YmFkLXNzbC1kYXNoYm9hcmQ="
|
||||
>
|
||||
bad-ssl-dashboard
|
||||
</a>
|
||||
</button>
|
||||
bad-ssl-dashboard
|
||||
</a>
|
||||
</span>
|
||||
</span>
|
||||
<span>
|
||||
|
@ -25,18 +21,14 @@ exports[`CertMonitors renders expected elements for valid props 1`] = `
|
|||
<span
|
||||
class="euiToolTipAnchor emotion-euiToolTipAnchor-inlineBlock"
|
||||
>
|
||||
<button
|
||||
<a
|
||||
class="euiLink emotion-euiLink-primary"
|
||||
data-test-subj="syntheticsMonitorPageLinkLink"
|
||||
type="button"
|
||||
data-test-subj="monitor-page-link-elastic-co"
|
||||
href="/monitor/ZWxhc3RpYy1jbw=="
|
||||
rel="noreferrer"
|
||||
>
|
||||
<a
|
||||
data-test-subj="monitor-page-link-elastic-co"
|
||||
href="/monitor/ZWxhc3RpYy1jbw=="
|
||||
>
|
||||
elastic
|
||||
</a>
|
||||
</button>
|
||||
elastic
|
||||
</a>
|
||||
</span>
|
||||
</span>
|
||||
<span>
|
||||
|
@ -44,18 +36,14 @@ exports[`CertMonitors renders expected elements for valid props 1`] = `
|
|||
<span
|
||||
class="euiToolTipAnchor emotion-euiToolTipAnchor-inlineBlock"
|
||||
>
|
||||
<button
|
||||
<a
|
||||
class="euiLink emotion-euiLink-primary"
|
||||
data-test-subj="syntheticsMonitorPageLinkLink"
|
||||
type="button"
|
||||
data-test-subj="monitor-page-link-extended-validation"
|
||||
href="/monitor/ZXh0ZW5kZWQtdmFsaWRhdGlvbg=="
|
||||
rel="noreferrer"
|
||||
>
|
||||
<a
|
||||
data-test-subj="monitor-page-link-extended-validation"
|
||||
href="/monitor/ZXh0ZW5kZWQtdmFsaWRhdGlvbg=="
|
||||
>
|
||||
extended-validation
|
||||
</a>
|
||||
</button>
|
||||
extended-validation
|
||||
</a>
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
|
|
|
@ -21,7 +21,7 @@ export const CertMonitors: React.FC<Props> = ({ monitors }) => {
|
|||
<span key={mon.id}>
|
||||
{ind > 0 && ', '}
|
||||
<EuiToolTip content={mon.url}>
|
||||
<MonitorPageLink monitorId={mon.id!} linkParameters={''}>
|
||||
<MonitorPageLink monitorId={mon.id!} configId={mon.configId} linkParameters={''}>
|
||||
{mon.name || mon.id}
|
||||
</MonitorPageLink>
|
||||
</EuiToolTip>
|
||||
|
|
|
@ -29,9 +29,7 @@ describe('CertificateList', () => {
|
|||
/>
|
||||
);
|
||||
|
||||
expect(
|
||||
getByText('No Certificates found. Note: Certificates are only visible for Heartbeat 7.8+')
|
||||
).toBeInTheDocument();
|
||||
expect(getByText('No Certificates found.')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders certificates list', () => {
|
||||
|
|
|
@ -67,8 +67,8 @@ export const COPY_FINGERPRINT = i18n.translate('xpack.synthetics.certs.list.copy
|
|||
defaultMessage: 'Click to copy fingerprint value',
|
||||
});
|
||||
|
||||
export const NO_CERTS_AVAILABLE = i18n.translate('xpack.synthetics.certs.list.empty', {
|
||||
defaultMessage: 'No Certificates found. Note: Certificates are only visible for Heartbeat 7.8+',
|
||||
export const NO_CERTS_AVAILABLE = i18n.translate('xpack.synthetics.certs.list.noCerts', {
|
||||
defaultMessage: 'No Certificates found.',
|
||||
});
|
||||
|
||||
export const LOADING_CERTIFICATES = i18n.translate('xpack.synthetics.certificates.loading', {
|
||||
|
|
|
@ -1,23 +1,15 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`MonitorPageLink component renders a help link when link parameters present 1`] = `
|
||||
<EuiLink
|
||||
data-test-subj="syntheticsMonitorPageLinkLink"
|
||||
>
|
||||
<Link
|
||||
data-test-subj="monitor-page-link-bad-ssl"
|
||||
to="/monitor/YmFkLXNzbA==/selectedPingStatus=down"
|
||||
/>
|
||||
</EuiLink>
|
||||
<ReactRouterEuiLink
|
||||
data-test-subj="monitor-page-link-bad-ssl"
|
||||
to="/monitor/YmFkLXNzbA==/selectedPingStatus=down"
|
||||
/>
|
||||
`;
|
||||
|
||||
exports[`MonitorPageLink component renders the link properly 1`] = `
|
||||
<EuiLink
|
||||
data-test-subj="syntheticsMonitorPageLinkLink"
|
||||
>
|
||||
<Link
|
||||
data-test-subj="monitor-page-link-bad-ssl"
|
||||
to="/monitor/YmFkLXNzbA=="
|
||||
/>
|
||||
</EuiLink>
|
||||
<ReactRouterEuiLink
|
||||
data-test-subj="monitor-page-link-bad-ssl"
|
||||
to="/monitor/YmFkLXNzbA=="
|
||||
/>
|
||||
`;
|
||||
|
|
|
@ -5,34 +5,29 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { EuiHeaderLink } from '@elastic/eui';
|
||||
import { EuiHeaderLink, EuiToolTip } from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import React from 'react';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
|
||||
import { MONITOR_MANAGEMENT_ROUTE } from '../../../../../common/constants';
|
||||
|
||||
export const ManageMonitorsBtn = () => {
|
||||
const history = useHistory();
|
||||
|
||||
return (
|
||||
<EuiHeaderLink
|
||||
aria-label={NAVIGATE_LABEL}
|
||||
color="text"
|
||||
data-test-subj="syntheticsManagementPageLink"
|
||||
href={history.createHref({
|
||||
pathname: MONITOR_MANAGEMENT_ROUTE,
|
||||
})}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.page_header.manageMonitors"
|
||||
defaultMessage="Monitor Management"
|
||||
/>
|
||||
</EuiHeaderLink>
|
||||
<EuiToolTip content={NAVIGATE_LABEL}>
|
||||
<EuiHeaderLink
|
||||
aria-label={NAVIGATE_LABEL}
|
||||
color="text"
|
||||
data-test-subj="syntheticsManagementPageLink"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.page_header.manageMonitors"
|
||||
defaultMessage="Monitor Management"
|
||||
/>
|
||||
</EuiHeaderLink>
|
||||
</EuiToolTip>
|
||||
);
|
||||
};
|
||||
|
||||
const NAVIGATE_LABEL = i18n.translate('xpack.synthetics.page_header.manageLink.label', {
|
||||
defaultMessage: 'Navigate to the Uptime Monitor Management page',
|
||||
const NAVIGATE_LABEL = i18n.translate('xpack.synthetics.page_header.manageLink.not', {
|
||||
defaultMessage:
|
||||
'Monitor Management is no longer available in Uptime, use the Synthetics app instead.',
|
||||
});
|
||||
|
|
|
@ -7,13 +7,15 @@
|
|||
|
||||
import React, { FC } from 'react';
|
||||
import { EuiLink } from '@elastic/eui';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { useKibana } from '@kbn/kibana-react-plugin/public';
|
||||
import { ReactRouterEuiLink } from './react_router_helpers';
|
||||
|
||||
interface DetailPageLinkProps {
|
||||
/**
|
||||
* MonitorId to be used to redirect to detail page
|
||||
*/
|
||||
monitorId: string;
|
||||
configId?: string;
|
||||
/**
|
||||
* Link parameters usually filter states
|
||||
*/
|
||||
|
@ -23,19 +25,31 @@ interface DetailPageLinkProps {
|
|||
export const MonitorPageLink: FC<DetailPageLinkProps> = ({
|
||||
children,
|
||||
monitorId,
|
||||
configId,
|
||||
linkParameters,
|
||||
}) => {
|
||||
const basePath = useKibana().services.http?.basePath.get();
|
||||
if (configId) {
|
||||
return (
|
||||
<EuiLink
|
||||
data-test-subj="syntheticsMonitorPageLinkLink"
|
||||
href={`${basePath}/app/synthetics/monitor/${configId}`}
|
||||
>
|
||||
{children}
|
||||
</EuiLink>
|
||||
);
|
||||
}
|
||||
|
||||
const getLocationTo = () => {
|
||||
// encode monitorId param as 64 base string to make it a valid URL, since it can be a url
|
||||
return linkParameters
|
||||
? `/monitor/${btoa(monitorId)}/${linkParameters}`
|
||||
: `/monitor/${btoa(monitorId)}`;
|
||||
};
|
||||
|
||||
return (
|
||||
<EuiLink data-test-subj="syntheticsMonitorPageLinkLink">
|
||||
<Link data-test-subj={`monitor-page-link-${monitorId}`} to={getLocationTo()}>
|
||||
{children}
|
||||
</Link>
|
||||
</EuiLink>
|
||||
<ReactRouterEuiLink data-test-subj={`monitor-page-link-${monitorId}`} to={getLocationTo()}>
|
||||
{children}
|
||||
</ReactRouterEuiLink>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,109 +0,0 @@
|
|||
/*
|
||||
* 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 { fireEvent } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { render } from '../../../lib/helper/rtl_helpers';
|
||||
import { BrowserAdvancedFields } from './advanced_fields';
|
||||
import {
|
||||
ConfigKey,
|
||||
BrowserAdvancedFields as BrowserAdvancedFieldsType,
|
||||
BrowserSimpleFields,
|
||||
Validation,
|
||||
DataStream,
|
||||
} from '../types';
|
||||
import {
|
||||
BrowserAdvancedFieldsContextProvider,
|
||||
BrowserSimpleFieldsContextProvider,
|
||||
defaultBrowserAdvancedFields as defaultConfig,
|
||||
defaultBrowserSimpleFields,
|
||||
} from '../contexts';
|
||||
import { validate as centralValidation } from '../../monitor_management/validation';
|
||||
import { __IntlProvider as IntlProvider } from '@kbn/i18n-react';
|
||||
|
||||
const defaultValidation = centralValidation[DataStream.BROWSER];
|
||||
|
||||
jest.mock('@elastic/eui/lib/services/accessibility/html_id_generator', () => ({
|
||||
...jest.requireActual('@elastic/eui/lib/services/accessibility/html_id_generator'),
|
||||
htmlIdGenerator: () => () => `id-${Math.random()}`,
|
||||
}));
|
||||
|
||||
describe('<BrowserAdvancedFields />', () => {
|
||||
const WrappedComponent = ({
|
||||
defaultValues = defaultConfig,
|
||||
defaultSimpleFields = defaultBrowserSimpleFields,
|
||||
validate = defaultValidation,
|
||||
children,
|
||||
onFieldBlur,
|
||||
}: {
|
||||
defaultValues?: BrowserAdvancedFieldsType;
|
||||
defaultSimpleFields?: BrowserSimpleFields;
|
||||
validate?: Validation;
|
||||
children?: React.ReactNode;
|
||||
onFieldBlur?: (field: ConfigKey) => void;
|
||||
}) => {
|
||||
return (
|
||||
<IntlProvider locale="en">
|
||||
<BrowserSimpleFieldsContextProvider defaultValues={defaultSimpleFields}>
|
||||
<BrowserAdvancedFieldsContextProvider defaultValues={defaultValues}>
|
||||
<BrowserAdvancedFields validate={validate} onFieldBlur={onFieldBlur}>
|
||||
{children}
|
||||
</BrowserAdvancedFields>
|
||||
</BrowserAdvancedFieldsContextProvider>
|
||||
</BrowserSimpleFieldsContextProvider>
|
||||
</IntlProvider>
|
||||
);
|
||||
};
|
||||
|
||||
it('renders BrowserAdvancedFields', () => {
|
||||
const { getByLabelText } = render(<WrappedComponent />);
|
||||
|
||||
const syntheticsArgs = getByLabelText('Synthetics args');
|
||||
const screenshots = getByLabelText('Screenshot options') as HTMLInputElement;
|
||||
expect(screenshots.value).toEqual(defaultConfig[ConfigKey.SCREENSHOTS]);
|
||||
expect(syntheticsArgs).toBeInTheDocument();
|
||||
});
|
||||
|
||||
describe('handles changing fields', () => {
|
||||
it('for screenshot options', () => {
|
||||
const { getByLabelText } = render(<WrappedComponent />);
|
||||
|
||||
const screenshots = getByLabelText('Screenshot options') as HTMLInputElement;
|
||||
userEvent.selectOptions(screenshots, ['off']);
|
||||
expect(screenshots.value).toEqual('off');
|
||||
});
|
||||
|
||||
it('calls onFieldBlur after change', () => {
|
||||
const onFieldBlur = jest.fn();
|
||||
const { getByLabelText } = render(<WrappedComponent onFieldBlur={onFieldBlur} />);
|
||||
|
||||
const screenshots = getByLabelText('Screenshot options') as HTMLInputElement;
|
||||
userEvent.selectOptions(screenshots, ['off']);
|
||||
fireEvent.blur(screenshots);
|
||||
expect(onFieldBlur).toHaveBeenCalledWith(ConfigKey.SCREENSHOTS);
|
||||
});
|
||||
});
|
||||
|
||||
it('does not display filter options (zip url has been deprecated)', () => {
|
||||
const { queryByText } = render(<WrappedComponent />);
|
||||
|
||||
expect(
|
||||
queryByText(
|
||||
/Use these options to apply the selected monitor settings to a subset of the tests in your suite./
|
||||
)
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders upstream fields', () => {
|
||||
const upstreamFieldsText = 'Monitor Advanced field section';
|
||||
const { getByText } = render(<WrappedComponent>{upstreamFieldsText}</WrappedComponent>);
|
||||
|
||||
const upstream = getByText(upstreamFieldsText) as HTMLInputElement;
|
||||
expect(upstream).toBeInTheDocument();
|
||||
});
|
||||
});
|
|
@ -1,162 +0,0 @@
|
|||
/*
|
||||
* 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, { memo, useCallback } from 'react';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { EuiAccordion, EuiSelect, EuiCheckbox, EuiFormRow, EuiSpacer } from '@elastic/eui';
|
||||
import { ComboBox } from '../combo_box';
|
||||
import { DescribedFormGroupWithWrap } from '../common/described_form_group_with_wrap';
|
||||
|
||||
import { useBrowserAdvancedFieldsContext } from '../contexts';
|
||||
|
||||
import { ConfigKey, Validation, ScreenshotOption } from '../types';
|
||||
|
||||
import { OptionalLabel } from '../optional_label';
|
||||
import { ThrottlingFields } from './throttling_fields';
|
||||
|
||||
interface Props {
|
||||
validate: Validation;
|
||||
children?: React.ReactNode;
|
||||
minColumnWidth?: string;
|
||||
onFieldBlur?: (field: ConfigKey) => void;
|
||||
}
|
||||
|
||||
export const BrowserAdvancedFields = memo<Props>(
|
||||
({ validate, children, minColumnWidth, onFieldBlur }) => {
|
||||
const { fields, setFields } = useBrowserAdvancedFieldsContext();
|
||||
|
||||
const handleInputChange = useCallback(
|
||||
({ value, configKey }: { value: unknown; configKey: ConfigKey }) => {
|
||||
setFields((prevFields) => ({ ...prevFields, [configKey]: value }));
|
||||
},
|
||||
[setFields]
|
||||
);
|
||||
|
||||
return (
|
||||
<EuiAccordion
|
||||
id="syntheticsIntegrationBrowserAdvancedOptions"
|
||||
buttonContent="Advanced Browser options"
|
||||
data-test-subj="syntheticsBrowserAdvancedFieldsAccordion"
|
||||
>
|
||||
<EuiSpacer size="m" />
|
||||
<DescribedFormGroupWithWrap
|
||||
minColumnWidth={minColumnWidth}
|
||||
title={
|
||||
<h4>
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.createPackagePolicy.stepConfigure.browserAdvancedSettings.title"
|
||||
defaultMessage="Synthetics agent options"
|
||||
/>
|
||||
</h4>
|
||||
}
|
||||
description={
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.createPackagePolicy.stepConfigure.browserAdvancedSettings.description"
|
||||
defaultMessage="Provide fine-tuned configuration for the synthetics agent."
|
||||
/>
|
||||
}
|
||||
>
|
||||
<EuiSpacer size="s" />
|
||||
<EuiFormRow
|
||||
helpText={
|
||||
<>
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.createPackagePolicy.stepConfigure.browserAdvancedSettings.ignoreHttpsErrors.helpText"
|
||||
defaultMessage="Set this option to true to disable TLS/SSL validation in the synthetics browser. This is useful for testing sites that use self-signed certs."
|
||||
/>
|
||||
</>
|
||||
}
|
||||
data-test-subj="syntheticsBrowserIgnoreHttpsErrors"
|
||||
>
|
||||
<EuiCheckbox
|
||||
id="syntheticsBrowserIgnoreHttpsErrorsCheckbox"
|
||||
checked={fields[ConfigKey.IGNORE_HTTPS_ERRORS]}
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.createPackagePolicy.stepConfigure.browserAdvancedSettings.ignoreHttpsErrors.label"
|
||||
defaultMessage="Ignore HTTPS errors"
|
||||
/>
|
||||
}
|
||||
onChange={(event) =>
|
||||
handleInputChange({
|
||||
value: event.target.checked,
|
||||
configKey: ConfigKey.IGNORE_HTTPS_ERRORS,
|
||||
})
|
||||
}
|
||||
onBlur={() => onFieldBlur?.(ConfigKey.IGNORE_HTTPS_ERRORS)}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
|
||||
<EuiFormRow
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.createPackagePolicy.stepConfigure.browserAdvancedSettings.screenshots.label"
|
||||
defaultMessage="Screenshot options"
|
||||
/>
|
||||
}
|
||||
labelAppend={<OptionalLabel />}
|
||||
helpText={
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.createPackagePolicy.stepConfigure.browserAdvancedSettings.screenshots.helpText"
|
||||
defaultMessage="Set this option to manage the screenshots captured by the synthetics agent."
|
||||
/>
|
||||
}
|
||||
>
|
||||
<EuiSelect
|
||||
options={requestMethodOptions}
|
||||
value={fields[ConfigKey.SCREENSHOTS]}
|
||||
onChange={(event) =>
|
||||
handleInputChange({
|
||||
value: event.target.value,
|
||||
configKey: ConfigKey.SCREENSHOTS,
|
||||
})
|
||||
}
|
||||
onBlur={() => onFieldBlur?.(ConfigKey.SCREENSHOTS)}
|
||||
data-test-subj="syntheticsBrowserScreenshots"
|
||||
/>
|
||||
</EuiFormRow>
|
||||
<EuiFormRow
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.createPackagePolicy.stepConfigure.browserAdvancedSettings.syntheticsArgs.label"
|
||||
defaultMessage="Synthetics args"
|
||||
/>
|
||||
}
|
||||
labelAppend={<OptionalLabel />}
|
||||
helpText={
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.createPackagePolicy.stepConfigure.browserAdvancedSettings.syntheticsArgs.helpText"
|
||||
defaultMessage="Extra arguments to pass to the synthetics agent package. Takes a list of strings. This is useful in rare scenarios, and should not ordinarily need to be set."
|
||||
/>
|
||||
}
|
||||
>
|
||||
<ComboBox
|
||||
selectedOptions={fields[ConfigKey.SYNTHETICS_ARGS]}
|
||||
onChange={(value) =>
|
||||
handleInputChange({ value, configKey: ConfigKey.SYNTHETICS_ARGS })
|
||||
}
|
||||
onBlur={() => onFieldBlur?.(ConfigKey.SYNTHETICS_ARGS)}
|
||||
data-test-subj="syntheticsBrowserSyntheticsArgs"
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</DescribedFormGroupWithWrap>
|
||||
|
||||
<ThrottlingFields
|
||||
validate={validate}
|
||||
minColumnWidth={minColumnWidth}
|
||||
onFieldBlur={onFieldBlur}
|
||||
/>
|
||||
{children}
|
||||
</EuiAccordion>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
const requestMethodOptions = Object.values(ScreenshotOption).map((option) => ({
|
||||
value: option,
|
||||
text: option.replace(/-/g, ' '),
|
||||
}));
|
|
@ -1,125 +0,0 @@
|
|||
/*
|
||||
* 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 from 'react';
|
||||
import { fireEvent, waitFor } from '@testing-library/react';
|
||||
import { render } from '../../../lib/helper/rtl_helpers';
|
||||
import { ScriptRecorderFields } from './script_recorder_fields';
|
||||
import { PolicyConfigContextProvider } from '../contexts';
|
||||
|
||||
jest.mock('@elastic/eui/lib/services/accessibility/html_id_generator', () => ({
|
||||
...jest.requireActual('@elastic/eui/lib/services/accessibility/html_id_generator'),
|
||||
htmlIdGenerator: () => () => `id-${Math.random()}`,
|
||||
}));
|
||||
|
||||
const onChange = jest.fn();
|
||||
|
||||
describe('<ScriptRecorderFields />', () => {
|
||||
let file: File;
|
||||
const testScript = 'step(() => {})';
|
||||
const WrappedComponent = ({
|
||||
isEditable = true,
|
||||
script = '',
|
||||
fileName = '',
|
||||
}: {
|
||||
isEditable?: boolean;
|
||||
script?: string;
|
||||
fileName?: string;
|
||||
}) => {
|
||||
return (
|
||||
<PolicyConfigContextProvider isEditable={isEditable}>
|
||||
<ScriptRecorderFields script={script} fileName={fileName} onChange={onChange} />
|
||||
</PolicyConfigContextProvider>
|
||||
);
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
file = new File([testScript], 'samplescript.js', { type: 'text/javascript' });
|
||||
});
|
||||
|
||||
it('renders ScriptRecorderFields', () => {
|
||||
const { getByText, queryByText } = render(<WrappedComponent />);
|
||||
|
||||
const downloadLink = getByText('Download the Elastic Synthetics Recorder');
|
||||
|
||||
expect(downloadLink).toBeInTheDocument();
|
||||
expect(downloadLink).toHaveAttribute('target', '_blank');
|
||||
|
||||
expect(queryByText('Show script')).not.toBeInTheDocument();
|
||||
expect(queryByText('Remove script')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('handles uploading files', async () => {
|
||||
const { getByTestId } = render(<WrappedComponent />);
|
||||
|
||||
const uploader = getByTestId('syntheticsFleetScriptRecorderUploader');
|
||||
|
||||
fireEvent.change(uploader, {
|
||||
target: { files: [file] },
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(onChange).toBeCalledWith({ scriptText: testScript, fileName: 'samplescript.js' });
|
||||
});
|
||||
});
|
||||
|
||||
it('shows user errors for invalid file types', async () => {
|
||||
const { getByTestId, getByText } = render(<WrappedComponent />);
|
||||
file = new File(['journey(() => {})'], 'samplescript.js', { type: 'text/javascript' });
|
||||
|
||||
let uploader = getByTestId('syntheticsFleetScriptRecorderUploader') as HTMLInputElement;
|
||||
|
||||
fireEvent.change(uploader, {
|
||||
target: { files: [file] },
|
||||
});
|
||||
|
||||
uploader = getByTestId('syntheticsFleetScriptRecorderUploader') as HTMLInputElement;
|
||||
|
||||
await waitFor(() => {
|
||||
expect(onChange).not.toBeCalled();
|
||||
expect(
|
||||
getByText(
|
||||
'Error uploading file. Please upload a .js file generated by the Elastic Synthetics Recorder in inline script format.'
|
||||
)
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('shows show script button when script is available', () => {
|
||||
const { getByText, queryByText } = render(<WrappedComponent script={testScript} />);
|
||||
|
||||
const showScriptBtn = getByText('Show script');
|
||||
|
||||
expect(queryByText(testScript)).not.toBeInTheDocument();
|
||||
|
||||
fireEvent.click(showScriptBtn);
|
||||
|
||||
expect(getByText(testScript)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('shows show remove script button when script is available and isEditable is true', async () => {
|
||||
const { getByText, getByTestId } = render(
|
||||
<WrappedComponent script={testScript} isEditable={true} />
|
||||
);
|
||||
|
||||
const showScriptBtn = getByText('Show script');
|
||||
fireEvent.click(showScriptBtn);
|
||||
|
||||
expect(getByText(testScript)).toBeInTheDocument();
|
||||
|
||||
fireEvent.click(getByTestId('euiFlyoutCloseButton'));
|
||||
|
||||
const removeScriptBtn = getByText('Remove script');
|
||||
|
||||
fireEvent.click(removeScriptBtn);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(onChange).toBeCalledWith({ scriptText: '', fileName: '' });
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,140 +0,0 @@
|
|||
/*
|
||||
* 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, { useState, useCallback } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import {
|
||||
EuiLink,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiFlyout,
|
||||
EuiFlyoutHeader,
|
||||
EuiFormRow,
|
||||
EuiCodeBlock,
|
||||
EuiTitle,
|
||||
EuiButton,
|
||||
EuiSpacer,
|
||||
EuiText,
|
||||
} from '@elastic/eui';
|
||||
import { usePolicyConfigContext } from '../contexts/policy_config_context';
|
||||
import { Uploader } from './uploader';
|
||||
|
||||
interface Props {
|
||||
onChange: ({ scriptText, fileName }: { scriptText: string; fileName: string }) => void;
|
||||
script: string;
|
||||
fileName?: string;
|
||||
}
|
||||
|
||||
export function ScriptRecorderFields({ onChange, script, fileName }: Props) {
|
||||
const [showScript, setShowScript] = useState(false);
|
||||
const { isEditable } = usePolicyConfigContext();
|
||||
|
||||
const handleUpload = useCallback(
|
||||
({ scriptText, fileName: fileNameT }: { scriptText: string; fileName: string }) => {
|
||||
onChange({ scriptText, fileName: fileNameT });
|
||||
},
|
||||
[onChange]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<EuiSpacer size="m" />
|
||||
<EuiLink
|
||||
data-test-subj="syntheticsScriptRecorderFieldsDownloadTheElasticSyntheticsRecorderLink"
|
||||
href="https://github.com/elastic/synthetics-recorder/releases/"
|
||||
target="_blank"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.browser.recorderLink"
|
||||
defaultMessage="Download the Elastic Synthetics Recorder"
|
||||
/>
|
||||
</EuiLink>
|
||||
<EuiSpacer size="m" />
|
||||
{isEditable && script ? (
|
||||
<EuiFormRow label="Testing script">
|
||||
<EuiText size="s">
|
||||
<strong>{fileName}</strong>
|
||||
</EuiText>
|
||||
</EuiFormRow>
|
||||
) : (
|
||||
<Uploader onUpload={handleUpload} />
|
||||
)}
|
||||
{script && (
|
||||
<>
|
||||
<EuiSpacer size="m" />
|
||||
<EuiFlexGroup gutterSize="s">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton
|
||||
data-test-subj="syntheticsScriptRecorderFieldsShowScriptButton"
|
||||
onClick={() => setShowScript(true)}
|
||||
iconType="editorCodeBlock"
|
||||
iconSide="right"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.browser.showScriptLabel"
|
||||
defaultMessage="Show script"
|
||||
/>
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
{isEditable && (
|
||||
<EuiButton
|
||||
data-test-subj="syntheticsScriptRecorderFieldsRemoveScriptButton"
|
||||
onClick={() => onChange({ scriptText: '', fileName: '' })}
|
||||
iconType="trash"
|
||||
iconSide="right"
|
||||
color="danger"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.browser.removeScriptLabel"
|
||||
defaultMessage="Remove script"
|
||||
/>
|
||||
</EuiButton>
|
||||
)}
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</>
|
||||
)}
|
||||
{showScript && (
|
||||
<EuiFlyout
|
||||
ownFocus
|
||||
onClose={() => setShowScript(false)}
|
||||
aria-labelledby="syntheticsBrowserScriptBlockHeader"
|
||||
closeButtonProps={{ 'aria-label': CLOSE_BUTTON_LABEL }}
|
||||
>
|
||||
<EuiFlyoutHeader hasBorder>
|
||||
<EuiTitle size="m">
|
||||
<span id="syntheticsBrowserScriptBlockHeader">
|
||||
{fileName || PLACEHOLDER_FILE_NAME}
|
||||
</span>
|
||||
</EuiTitle>
|
||||
</EuiFlyoutHeader>
|
||||
<div style={{ height: '100%' }}>
|
||||
<EuiCodeBlock language="js" overflowHeight={'100%'} fontSize="m" isCopyable>
|
||||
{script}
|
||||
</EuiCodeBlock>
|
||||
</div>
|
||||
</EuiFlyout>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
const PLACEHOLDER_FILE_NAME = i18n.translate(
|
||||
'xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.browser.scriptRecorder.mockFileName',
|
||||
{
|
||||
defaultMessage: 'test_script.js',
|
||||
}
|
||||
);
|
||||
|
||||
const CLOSE_BUTTON_LABEL = i18n.translate(
|
||||
'xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.browser.scriptRecorder.closeButtonLabel',
|
||||
{
|
||||
defaultMessage: 'Close script flyout',
|
||||
}
|
||||
);
|
|
@ -1,110 +0,0 @@
|
|||
/*
|
||||
* 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, { memo, useMemo, useCallback } from 'react';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { EuiFormRow } from '@elastic/eui';
|
||||
import { Validation } from '../types';
|
||||
import { ConfigKey, MonitorFields } from '../types';
|
||||
import { useBrowserSimpleFieldsContext } from '../contexts';
|
||||
import { ScheduleField } from '../schedule_field';
|
||||
import { SourceField } from './source_field';
|
||||
import { SimpleFieldsWrapper } from '../common/simple_fields_wrapper';
|
||||
|
||||
interface Props {
|
||||
validate: Validation;
|
||||
onFieldBlur: (field: ConfigKey) => void; // To propagate blurred state up to parents
|
||||
}
|
||||
|
||||
export const BrowserSimpleFields = memo<Props>(({ validate, onFieldBlur }) => {
|
||||
const { fields, setFields, defaultValues } = useBrowserSimpleFieldsContext();
|
||||
const handleInputChange = useCallback(
|
||||
({ value, configKey }: { value: unknown; configKey: ConfigKey }) => {
|
||||
setFields((prevFields) => ({ ...prevFields, [configKey]: value }));
|
||||
},
|
||||
[setFields]
|
||||
);
|
||||
const onChangeSourceField = useCallback(
|
||||
({ inlineScript, params, isGeneratedScript, fileName }) => {
|
||||
setFields((prevFields) => ({
|
||||
...prevFields,
|
||||
[ConfigKey.SOURCE_INLINE]: inlineScript,
|
||||
[ConfigKey.PARAMS]: params,
|
||||
[ConfigKey.METADATA]: {
|
||||
...prevFields[ConfigKey.METADATA],
|
||||
script_source: {
|
||||
is_generated_script: isGeneratedScript,
|
||||
file_name: fileName,
|
||||
},
|
||||
},
|
||||
}));
|
||||
},
|
||||
[setFields]
|
||||
);
|
||||
|
||||
return (
|
||||
<SimpleFieldsWrapper
|
||||
fields={fields}
|
||||
validate={validate}
|
||||
onInputChange={handleInputChange}
|
||||
onFieldBlur={onFieldBlur}
|
||||
>
|
||||
<EuiFormRow
|
||||
id="syntheticsFleetScheduleField--number syntheticsFleetScheduleField--unit"
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.monitorInterval"
|
||||
defaultMessage="Frequency"
|
||||
/>
|
||||
}
|
||||
isInvalid={!!validate[ConfigKey.SCHEDULE]?.(fields as Partial<MonitorFields>)}
|
||||
error={
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.monitorInterval.error"
|
||||
defaultMessage="Monitor frequency is required"
|
||||
/>
|
||||
}
|
||||
>
|
||||
<ScheduleField
|
||||
onChange={(schedule) =>
|
||||
handleInputChange({
|
||||
value: schedule,
|
||||
configKey: ConfigKey.SCHEDULE,
|
||||
})
|
||||
}
|
||||
onBlur={() => onFieldBlur(ConfigKey.SCHEDULE)}
|
||||
number={fields[ConfigKey.SCHEDULE].number}
|
||||
unit={fields[ConfigKey.SCHEDULE].unit}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
<EuiFormRow
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.browser.sourceType.label"
|
||||
defaultMessage="Source Type"
|
||||
/>
|
||||
}
|
||||
>
|
||||
<SourceField
|
||||
validate={validate}
|
||||
onChange={onChangeSourceField}
|
||||
onFieldBlur={onFieldBlur}
|
||||
defaultConfig={useMemo(
|
||||
() => ({
|
||||
inlineScript: defaultValues[ConfigKey.SOURCE_INLINE],
|
||||
params: defaultValues[ConfigKey.PARAMS],
|
||||
isGeneratedScript:
|
||||
defaultValues[ConfigKey.METADATA].script_source?.is_generated_script,
|
||||
fileName: defaultValues[ConfigKey.METADATA].script_source?.file_name,
|
||||
}),
|
||||
[defaultValues]
|
||||
)}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</SimpleFieldsWrapper>
|
||||
);
|
||||
});
|
|
@ -1,92 +0,0 @@
|
|||
/*
|
||||
* 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 'jest-canvas-mock';
|
||||
|
||||
import React from 'react';
|
||||
import { fireEvent, screen } from '@testing-library/react';
|
||||
import { render } from '../../../lib/helper/rtl_helpers';
|
||||
import { IPolicyConfigContextProvider } from '../contexts/policy_config_context';
|
||||
import { SourceField, Props } from './source_field';
|
||||
import { BrowserSimpleFieldsContextProvider, PolicyConfigContextProvider } from '../contexts';
|
||||
|
||||
jest.mock('@elastic/eui/lib/services/accessibility/html_id_generator', () => ({
|
||||
...jest.requireActual('@elastic/eui/lib/services/accessibility/html_id_generator'),
|
||||
htmlIdGenerator: () => () => `id-${Math.random()}`,
|
||||
}));
|
||||
|
||||
// ensures that fields appropriately match to their label
|
||||
jest.mock('@elastic/eui/lib/services/accessibility', () => ({
|
||||
...jest.requireActual('@elastic/eui/lib/services/accessibility'),
|
||||
useGeneratedHtmlId: () => `id-${Math.random()}`,
|
||||
}));
|
||||
|
||||
jest.mock('@kbn/kibana-react-plugin/public', () => {
|
||||
const original = jest.requireActual('@kbn/kibana-react-plugin/public');
|
||||
return {
|
||||
...original,
|
||||
// Mocking CodeEditor, which uses React Monaco under the hood
|
||||
CodeEditor: (props: any) => (
|
||||
<>
|
||||
<input
|
||||
data-test-subj={props['data-test-subj'] || 'mockCodeEditor'}
|
||||
data-currentvalue={props.value}
|
||||
id={props.id}
|
||||
onChange={props.onChange}
|
||||
/>
|
||||
</>
|
||||
),
|
||||
};
|
||||
});
|
||||
|
||||
const onChange = jest.fn();
|
||||
const onBlur = jest.fn();
|
||||
|
||||
describe('<SourceField />', () => {
|
||||
const WrappedComponent = ({
|
||||
defaultConfig,
|
||||
}: Omit<IPolicyConfigContextProvider, 'children'> & Partial<Props>) => {
|
||||
return (
|
||||
<PolicyConfigContextProvider>
|
||||
<BrowserSimpleFieldsContextProvider>
|
||||
<SourceField onChange={onChange} onFieldBlur={onBlur} defaultConfig={defaultConfig} />
|
||||
</BrowserSimpleFieldsContextProvider>
|
||||
</PolicyConfigContextProvider>
|
||||
);
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('selects inline script by default', () => {
|
||||
render(<WrappedComponent />);
|
||||
|
||||
expect(
|
||||
screen.getByText('Runs Synthetic test scripts that are defined inline.')
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('does not show ZipUrl source type', async () => {
|
||||
render(<WrappedComponent />);
|
||||
|
||||
expect(screen.queryByTestId('syntheticsSourceTab__zipUrl')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('shows params for all source types', async () => {
|
||||
const { getByText, getByTestId } = render(<WrappedComponent />);
|
||||
|
||||
const inlineTab = getByTestId('syntheticsSourceTab__inline');
|
||||
fireEvent.click(inlineTab);
|
||||
|
||||
expect(getByText('Parameters')).toBeInTheDocument();
|
||||
|
||||
const recorder = getByTestId('syntheticsSourceTab__scriptRecorder');
|
||||
fireEvent.click(recorder);
|
||||
|
||||
expect(getByText('Parameters')).toBeInTheDocument();
|
||||
});
|
||||
});
|
|
@ -1,231 +0,0 @@
|
|||
/*
|
||||
* 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 styled from 'styled-components';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import {
|
||||
EuiCode,
|
||||
EuiTabbedContent,
|
||||
EuiFormRow,
|
||||
EuiSpacer,
|
||||
EuiBetaBadge,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
} from '@elastic/eui';
|
||||
import { OptionalLabel } from '../optional_label';
|
||||
import { CodeEditor } from '../code_editor';
|
||||
import { ScriptRecorderFields } from './script_recorder_fields';
|
||||
import { ConfigKey, MonacoEditorLangId, Validation } from '../types';
|
||||
|
||||
enum SourceType {
|
||||
INLINE = 'syntheticsBrowserInlineConfig',
|
||||
SCRIPT_RECORDER = 'syntheticsBrowserScriptRecorderConfig',
|
||||
}
|
||||
|
||||
interface SourceConfig {
|
||||
inlineScript: string;
|
||||
params: string;
|
||||
isGeneratedScript?: boolean;
|
||||
fileName?: string;
|
||||
}
|
||||
|
||||
export interface Props {
|
||||
onChange: (sourceConfig: SourceConfig) => void;
|
||||
onFieldBlur: (field: ConfigKey) => void;
|
||||
defaultConfig?: SourceConfig;
|
||||
validate?: Validation;
|
||||
}
|
||||
|
||||
export const defaultValues = {
|
||||
inlineScript: '',
|
||||
params: '',
|
||||
isGeneratedScript: false,
|
||||
fileName: '',
|
||||
};
|
||||
|
||||
const getDefaultTab = (defaultConfig: SourceConfig) => {
|
||||
if (defaultConfig.inlineScript && defaultConfig.isGeneratedScript) {
|
||||
return SourceType.SCRIPT_RECORDER;
|
||||
} else {
|
||||
return SourceType.INLINE;
|
||||
}
|
||||
};
|
||||
|
||||
export const SourceField = ({
|
||||
onChange,
|
||||
onFieldBlur,
|
||||
defaultConfig = defaultValues,
|
||||
validate,
|
||||
}: Props) => {
|
||||
const [sourceType, setSourceType] = useState<SourceType>(getDefaultTab(defaultConfig));
|
||||
const [config, setConfig] = useState<SourceConfig>(defaultConfig);
|
||||
|
||||
useEffect(() => {
|
||||
onChange(config);
|
||||
}, [config, onChange]);
|
||||
|
||||
const isSourceInlineInvalid =
|
||||
validate?.[ConfigKey.SOURCE_INLINE]?.({
|
||||
[ConfigKey.SOURCE_INLINE]: config.inlineScript,
|
||||
}) ?? false;
|
||||
|
||||
const params = (
|
||||
<EuiFormRow
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.params.label"
|
||||
defaultMessage="Parameters"
|
||||
/>
|
||||
}
|
||||
labelAppend={<OptionalLabel />}
|
||||
helpText={
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.params.helpText"
|
||||
defaultMessage="Use JSON to define parameters that can be referenced in your script with {code}"
|
||||
values={{ code: <EuiCode>params.value</EuiCode> }}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<CodeEditor
|
||||
ariaLabel={i18n.translate(
|
||||
'xpack.synthetics.createPackagePolicy.stepConfigure.requestBody.codeEditor.json.ariaLabel',
|
||||
{
|
||||
defaultMessage: 'JSON code editor',
|
||||
}
|
||||
)}
|
||||
id="jsonParamsEditor"
|
||||
languageId={MonacoEditorLangId.JSON}
|
||||
onChange={(code) => {
|
||||
setConfig((prevConfig) => ({ ...prevConfig, params: code }));
|
||||
onFieldBlur(ConfigKey.PARAMS);
|
||||
}}
|
||||
value={config.params}
|
||||
data-test-subj="syntheticsBrowserParams"
|
||||
/>
|
||||
</EuiFormRow>
|
||||
);
|
||||
|
||||
const tabs = [
|
||||
{
|
||||
id: 'syntheticsBrowserInlineConfig',
|
||||
name: (
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.browser.inlineScript.label"
|
||||
defaultMessage="Inline script"
|
||||
/>
|
||||
),
|
||||
'data-test-subj': `syntheticsSourceTab__inline`,
|
||||
content: (
|
||||
<>
|
||||
<EuiFormRow
|
||||
isInvalid={isSourceInlineInvalid}
|
||||
error={
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.browser.inlineScript.error"
|
||||
defaultMessage="Script is required"
|
||||
/>
|
||||
}
|
||||
helpText={
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.browser.inlineScript.helpText"
|
||||
defaultMessage="Runs Synthetic test scripts that are defined inline."
|
||||
/>
|
||||
}
|
||||
>
|
||||
<CodeEditor
|
||||
ariaLabel={i18n.translate(
|
||||
'xpack.synthetics.createPackagePolicy.stepConfigure.requestBody.codeEditor.javascript.ariaLabel',
|
||||
{
|
||||
defaultMessage: 'JavaScript code editor',
|
||||
}
|
||||
)}
|
||||
id="javascript"
|
||||
languageId={MonacoEditorLangId.JAVASCRIPT}
|
||||
onChange={(code) => {
|
||||
setConfig((prevConfig) => ({ ...prevConfig, inlineScript: code }));
|
||||
onFieldBlur(ConfigKey.SOURCE_INLINE);
|
||||
}}
|
||||
value={config.inlineScript}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
{params}
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'syntheticsBrowserScriptRecorderConfig',
|
||||
name: (
|
||||
<EuiFlexGroup responsive={false} alignItems="center" gutterSize="xs">
|
||||
<EuiFlexItem grow={false}>
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.createPackagePolicy.stepConfigure.browser.scriptRecorder.label"
|
||||
defaultMessage="Script recorder"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<StyledBetaBadgeWrapper grow={false}>
|
||||
<EuiBetaBadge
|
||||
label={i18n.translate(
|
||||
'xpack.synthetics.createPackagePolicy.stepConfigure.browser.scriptRecorder.experimentalLabel',
|
||||
{
|
||||
defaultMessage: 'Tech preview',
|
||||
}
|
||||
)}
|
||||
iconType="beaker"
|
||||
tooltipContent={i18n.translate(
|
||||
'xpack.synthetics.createPackagePolicy.stepConfigure.browser.scriptRecorder.experimentalTooltip',
|
||||
{
|
||||
defaultMessage:
|
||||
'Preview the quickest way to create Elastic Synthetics monitoring scripts with our Elastic Synthetics Recorder',
|
||||
}
|
||||
)}
|
||||
/>
|
||||
</StyledBetaBadgeWrapper>
|
||||
</EuiFlexGroup>
|
||||
),
|
||||
'data-test-subj': 'syntheticsSourceTab__scriptRecorder',
|
||||
content: (
|
||||
<>
|
||||
<ScriptRecorderFields
|
||||
onChange={({ scriptText, fileName }) =>
|
||||
setConfig((prevConfig) => ({
|
||||
...prevConfig,
|
||||
inlineScript: scriptText,
|
||||
isGeneratedScript: true,
|
||||
fileName,
|
||||
}))
|
||||
}
|
||||
script={config.inlineScript}
|
||||
fileName={config.fileName}
|
||||
/>
|
||||
<EuiSpacer size="s" />
|
||||
{params}
|
||||
</>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<EuiTabbedContent
|
||||
tabs={tabs}
|
||||
initialSelectedTab={tabs.find((tab) => tab.id === sourceType)}
|
||||
autoFocus="selected"
|
||||
onTabClick={(tab) => {
|
||||
if (tab.id !== sourceType) {
|
||||
setConfig(defaultValues);
|
||||
}
|
||||
setSourceType(tab.id as SourceType);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const StyledBetaBadgeWrapper = styled(EuiFlexItem)`
|
||||
.euiToolTipAnchor {
|
||||
display: flex;
|
||||
}
|
||||
`;
|
|
@ -1,361 +0,0 @@
|
|||
/*
|
||||
* 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 { fireEvent } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { render } from '../../../lib/helper/rtl_helpers';
|
||||
import { ThrottlingFields } from './throttling_fields';
|
||||
import {
|
||||
DataStream,
|
||||
BrowserAdvancedFields,
|
||||
BrowserSimpleFields,
|
||||
Validation,
|
||||
ConfigKey,
|
||||
BandwidthLimitKey,
|
||||
} from '../types';
|
||||
import {
|
||||
BrowserAdvancedFieldsContextProvider,
|
||||
BrowserSimpleFieldsContextProvider,
|
||||
PolicyConfigContextProvider,
|
||||
IPolicyConfigContextProvider,
|
||||
defaultPolicyConfigValues,
|
||||
defaultBrowserAdvancedFields as defaultConfig,
|
||||
defaultBrowserSimpleFields,
|
||||
} from '../contexts';
|
||||
import { validate as centralValidation } from '../../monitor_management/validation';
|
||||
import { __IntlProvider as IntlProvider } from '@kbn/i18n-react';
|
||||
|
||||
const defaultValidation = centralValidation[DataStream.BROWSER];
|
||||
|
||||
jest.mock('@elastic/eui/lib/services/accessibility/html_id_generator', () => ({
|
||||
...jest.requireActual('@elastic/eui/lib/services/accessibility/html_id_generator'),
|
||||
htmlIdGenerator: () => () => `id-${Math.random()}`,
|
||||
}));
|
||||
|
||||
describe('<ThrottlingFields />', () => {
|
||||
const defaultLocation = {
|
||||
id: 'test',
|
||||
label: 'Test',
|
||||
geo: { lat: 1, lon: 2 },
|
||||
url: 'https://example.com',
|
||||
isServiceManaged: true,
|
||||
};
|
||||
|
||||
const WrappedComponent = ({
|
||||
defaultValues = defaultConfig,
|
||||
defaultSimpleFields = defaultBrowserSimpleFields,
|
||||
policyConfigOverrides = {},
|
||||
validate = defaultValidation,
|
||||
onFieldBlur,
|
||||
}: {
|
||||
defaultValues?: BrowserAdvancedFields;
|
||||
defaultSimpleFields?: BrowserSimpleFields;
|
||||
policyConfigOverrides?: Partial<IPolicyConfigContextProvider>;
|
||||
validate?: Validation;
|
||||
onFieldBlur?: (field: ConfigKey) => void;
|
||||
}) => {
|
||||
const policyConfigValues = { ...defaultPolicyConfigValues, ...policyConfigOverrides };
|
||||
|
||||
return (
|
||||
<IntlProvider locale="en">
|
||||
<BrowserSimpleFieldsContextProvider defaultValues={defaultSimpleFields}>
|
||||
<BrowserAdvancedFieldsContextProvider defaultValues={defaultValues}>
|
||||
<PolicyConfigContextProvider {...policyConfigValues}>
|
||||
<ThrottlingFields validate={validate} onFieldBlur={onFieldBlur} />
|
||||
</PolicyConfigContextProvider>
|
||||
</BrowserAdvancedFieldsContextProvider>
|
||||
</BrowserSimpleFieldsContextProvider>
|
||||
</IntlProvider>
|
||||
);
|
||||
};
|
||||
|
||||
it('renders ThrottlingFields', () => {
|
||||
const { getByLabelText, getByTestId } = render(<WrappedComponent />);
|
||||
|
||||
const enableSwitch = getByTestId('syntheticsBrowserIsThrottlingEnabled');
|
||||
const downloadSpeed = getByLabelText('Download Speed');
|
||||
const uploadSpeed = getByLabelText('Upload Speed');
|
||||
const latency = getByLabelText('Latency');
|
||||
|
||||
expect(enableSwitch).toBeChecked();
|
||||
expect(downloadSpeed).toBeInTheDocument();
|
||||
expect(uploadSpeed).toBeInTheDocument();
|
||||
expect(latency).toBeInTheDocument();
|
||||
});
|
||||
|
||||
describe('handles changing fields', () => {
|
||||
it('for the enable switch', () => {
|
||||
const { getByTestId } = render(<WrappedComponent />);
|
||||
|
||||
const enableSwitch = getByTestId('syntheticsBrowserIsThrottlingEnabled');
|
||||
userEvent.click(enableSwitch);
|
||||
expect(enableSwitch).not.toBeChecked();
|
||||
});
|
||||
|
||||
it('for the download option', () => {
|
||||
const { getByLabelText } = render(<WrappedComponent />);
|
||||
|
||||
const downloadSpeed = getByLabelText('Download Speed') as HTMLInputElement;
|
||||
userEvent.clear(downloadSpeed);
|
||||
userEvent.type(downloadSpeed, '1337');
|
||||
expect(downloadSpeed.value).toEqual('1337');
|
||||
});
|
||||
|
||||
it('for the upload option', () => {
|
||||
const { getByLabelText } = render(<WrappedComponent />);
|
||||
|
||||
const uploadSpeed = getByLabelText('Upload Speed') as HTMLInputElement;
|
||||
userEvent.clear(uploadSpeed);
|
||||
userEvent.type(uploadSpeed, '1338');
|
||||
expect(uploadSpeed.value).toEqual('1338');
|
||||
});
|
||||
|
||||
it('for the latency option', () => {
|
||||
const { getByLabelText } = render(<WrappedComponent />);
|
||||
const latency = getByLabelText('Latency') as HTMLInputElement;
|
||||
userEvent.clear(latency);
|
||||
userEvent.type(latency, '1339');
|
||||
expect(latency.value).toEqual('1339');
|
||||
});
|
||||
});
|
||||
|
||||
describe('calls onBlur on fields', () => {
|
||||
const onFieldBlur = jest.fn();
|
||||
|
||||
afterEach(() => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
it('for the enable switch', () => {
|
||||
const { getByTestId } = render(<WrappedComponent onFieldBlur={onFieldBlur} />);
|
||||
|
||||
const enableSwitch = getByTestId('syntheticsBrowserIsThrottlingEnabled');
|
||||
fireEvent.focus(enableSwitch);
|
||||
fireEvent.blur(enableSwitch);
|
||||
expect(onFieldBlur).toHaveBeenCalledWith(ConfigKey.IS_THROTTLING_ENABLED);
|
||||
});
|
||||
|
||||
it('for throttling inputs', () => {
|
||||
const { getByLabelText } = render(<WrappedComponent onFieldBlur={onFieldBlur} />);
|
||||
|
||||
const downloadSpeed = getByLabelText('Download Speed') as HTMLInputElement;
|
||||
const uploadSpeed = getByLabelText('Upload Speed') as HTMLInputElement;
|
||||
const latency = getByLabelText('Latency') as HTMLInputElement;
|
||||
|
||||
fireEvent.blur(downloadSpeed);
|
||||
fireEvent.blur(uploadSpeed);
|
||||
fireEvent.blur(latency);
|
||||
|
||||
expect(onFieldBlur).toHaveBeenCalledWith(ConfigKey.DOWNLOAD_SPEED);
|
||||
expect(onFieldBlur).toHaveBeenCalledWith(ConfigKey.UPLOAD_SPEED);
|
||||
expect(onFieldBlur).toHaveBeenCalledWith(ConfigKey.LATENCY);
|
||||
});
|
||||
});
|
||||
|
||||
describe('validates changing fields', () => {
|
||||
it('disallows negative/zero download speeds', () => {
|
||||
const { getByLabelText, queryByText } = render(<WrappedComponent />);
|
||||
|
||||
const downloadSpeed = getByLabelText('Download Speed') as HTMLInputElement;
|
||||
userEvent.clear(downloadSpeed);
|
||||
userEvent.type(downloadSpeed, '-1337');
|
||||
expect(queryByText('Download speed must be greater than zero.')).toBeInTheDocument();
|
||||
|
||||
userEvent.clear(downloadSpeed);
|
||||
userEvent.type(downloadSpeed, '0');
|
||||
expect(queryByText('Download speed must be greater than zero.')).toBeInTheDocument();
|
||||
|
||||
userEvent.clear(downloadSpeed);
|
||||
userEvent.type(downloadSpeed, '1');
|
||||
expect(queryByText('Download speed must be greater than zero.')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('disallows negative/zero upload speeds', () => {
|
||||
const { getByLabelText, queryByText } = render(<WrappedComponent />);
|
||||
|
||||
const uploadSpeed = getByLabelText('Upload Speed') as HTMLInputElement;
|
||||
userEvent.clear(uploadSpeed);
|
||||
userEvent.type(uploadSpeed, '-1337');
|
||||
expect(queryByText('Upload speed must be greater than zero.')).toBeInTheDocument();
|
||||
|
||||
userEvent.clear(uploadSpeed);
|
||||
userEvent.type(uploadSpeed, '0');
|
||||
expect(queryByText('Upload speed must be greater than zero.')).toBeInTheDocument();
|
||||
|
||||
userEvent.clear(uploadSpeed);
|
||||
userEvent.type(uploadSpeed, '1');
|
||||
expect(queryByText('Upload speed must be greater than zero.')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('disallows negative latency values', () => {
|
||||
const { getByLabelText, queryByText } = render(<WrappedComponent />);
|
||||
|
||||
const latency = getByLabelText('Latency') as HTMLInputElement;
|
||||
userEvent.clear(latency);
|
||||
userEvent.type(latency, '-1337');
|
||||
expect(queryByText('Latency must not be negative.')).toBeInTheDocument();
|
||||
|
||||
userEvent.clear(latency);
|
||||
userEvent.type(latency, '0');
|
||||
expect(queryByText('Latency must not be negative.')).not.toBeInTheDocument();
|
||||
|
||||
userEvent.clear(latency);
|
||||
userEvent.type(latency, '1');
|
||||
expect(queryByText('Latency must not be negative.')).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe('throttling warnings', () => {
|
||||
const throttling = {
|
||||
[BandwidthLimitKey.DOWNLOAD]: 100,
|
||||
[BandwidthLimitKey.UPLOAD]: 50,
|
||||
};
|
||||
|
||||
const defaultLocations = [defaultLocation];
|
||||
|
||||
it('shows automatic throttling warnings only when throttling is disabled', () => {
|
||||
const { getByTestId, queryByText } = render(
|
||||
<WrappedComponent
|
||||
policyConfigOverrides={{ throttling, defaultLocations, runsOnService: true }}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(queryByText('Automatic cap')).not.toBeInTheDocument();
|
||||
expect(
|
||||
queryByText(
|
||||
"When disabling throttling, your monitor will still have its bandwidth capped by the configurations of the Synthetics Nodes in which it's running."
|
||||
)
|
||||
).not.toBeInTheDocument();
|
||||
|
||||
const enableSwitch = getByTestId('syntheticsBrowserIsThrottlingEnabled');
|
||||
userEvent.click(enableSwitch);
|
||||
|
||||
expect(queryByText('Automatic cap')).toBeInTheDocument();
|
||||
expect(
|
||||
queryByText(
|
||||
"When disabling throttling, your monitor will still have its bandwidth capped by the configurations of the Synthetics Nodes in which it's running."
|
||||
)
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("shows throttling warnings when exceeding the node's download limits", () => {
|
||||
const { getByLabelText, queryByText } = render(
|
||||
<WrappedComponent
|
||||
policyConfigOverrides={{ throttling, defaultLocations, runsOnService: true }}
|
||||
/>
|
||||
);
|
||||
|
||||
const downloadLimit = throttling[BandwidthLimitKey.DOWNLOAD];
|
||||
|
||||
const download = getByLabelText('Download Speed') as HTMLInputElement;
|
||||
userEvent.clear(download);
|
||||
userEvent.type(download, String(downloadLimit + 1));
|
||||
|
||||
expect(
|
||||
queryByText(
|
||||
`You have exceeded the download limit for Synthetic Nodes. The download value can't be larger than ${downloadLimit}Mbps.`
|
||||
)
|
||||
).toBeInTheDocument();
|
||||
|
||||
expect(
|
||||
queryByText("You've exceeded the Synthetics Node bandwidth limits")
|
||||
).toBeInTheDocument();
|
||||
|
||||
expect(
|
||||
queryByText(
|
||||
'When using throttling values larger than a Synthetics Node bandwidth limit, your monitor will still have its bandwidth capped.'
|
||||
)
|
||||
).toBeInTheDocument();
|
||||
|
||||
userEvent.clear(download);
|
||||
userEvent.type(download, String(downloadLimit - 1));
|
||||
expect(
|
||||
queryByText(
|
||||
`You have exceeded the download limit for Synthetic Nodes. The download value can't be larger than ${downloadLimit}Mbps.`
|
||||
)
|
||||
).not.toBeInTheDocument();
|
||||
|
||||
expect(
|
||||
queryByText("You've exceeded the Synthetics Node bandwidth limits")
|
||||
).not.toBeInTheDocument();
|
||||
|
||||
expect(
|
||||
queryByText(
|
||||
'When using throttling values larger than a Synthetics Node bandwidth limit, your monitor will still have its bandwidth capped.'
|
||||
)
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("shows throttling warnings when exceeding the node's upload limits", () => {
|
||||
const { getByLabelText, queryByText } = render(
|
||||
<WrappedComponent
|
||||
policyConfigOverrides={{ throttling, defaultLocations, runsOnService: true }}
|
||||
/>
|
||||
);
|
||||
|
||||
const uploadLimit = throttling[BandwidthLimitKey.UPLOAD];
|
||||
|
||||
const upload = getByLabelText('Upload Speed') as HTMLInputElement;
|
||||
userEvent.clear(upload);
|
||||
userEvent.type(upload, String(uploadLimit + 1));
|
||||
|
||||
expect(
|
||||
queryByText(
|
||||
`You have exceeded the upload limit for Synthetic Nodes. The upload value can't be larger than ${uploadLimit}Mbps.`
|
||||
)
|
||||
).toBeInTheDocument();
|
||||
|
||||
expect(
|
||||
queryByText("You've exceeded the Synthetics Node bandwidth limits")
|
||||
).toBeInTheDocument();
|
||||
|
||||
expect(
|
||||
queryByText(
|
||||
'When using throttling values larger than a Synthetics Node bandwidth limit, your monitor will still have its bandwidth capped.'
|
||||
)
|
||||
).toBeInTheDocument();
|
||||
|
||||
userEvent.clear(upload);
|
||||
userEvent.type(upload, String(uploadLimit - 1));
|
||||
expect(
|
||||
queryByText(
|
||||
`You have exceeded the upload limit for Synthetic Nodes. The upload value can't be larger than ${uploadLimit}Mbps.`
|
||||
)
|
||||
).not.toBeInTheDocument();
|
||||
|
||||
expect(
|
||||
queryByText("You've exceeded the Synthetics Node bandwidth limits")
|
||||
).not.toBeInTheDocument();
|
||||
|
||||
expect(
|
||||
queryByText(
|
||||
'When using throttling values larger than a Synthetics Node bandwidth limit, your monitor will still have its bandwidth capped.'
|
||||
)
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('only displays download, upload, and latency fields with throttling is on', () => {
|
||||
const { getByLabelText, getByTestId } = render(<WrappedComponent />);
|
||||
|
||||
const enableSwitch = getByTestId('syntheticsBrowserIsThrottlingEnabled');
|
||||
const downloadSpeed = getByLabelText('Download Speed');
|
||||
const uploadSpeed = getByLabelText('Upload Speed');
|
||||
const latency = getByLabelText('Latency');
|
||||
|
||||
expect(downloadSpeed).toBeInTheDocument();
|
||||
expect(uploadSpeed).toBeInTheDocument();
|
||||
expect(latency).toBeInTheDocument();
|
||||
|
||||
userEvent.click(enableSwitch);
|
||||
|
||||
expect(downloadSpeed).not.toBeInTheDocument();
|
||||
expect(uploadSpeed).not.toBeInTheDocument();
|
||||
expect(latency).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
|
@ -1,293 +0,0 @@
|
|||
/*
|
||||
* 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, { memo, useCallback } from 'react';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import {
|
||||
EuiSwitch,
|
||||
EuiSpacer,
|
||||
EuiFormRow,
|
||||
EuiFieldNumber,
|
||||
EuiText,
|
||||
EuiCallOut,
|
||||
} from '@elastic/eui';
|
||||
import { DescribedFormGroupWithWrap } from '../common/described_form_group_with_wrap';
|
||||
|
||||
import { OptionalLabel } from '../optional_label';
|
||||
import { useBrowserAdvancedFieldsContext, usePolicyConfigContext } from '../contexts';
|
||||
import { Validation, ConfigKey, BandwidthLimitKey } from '../types';
|
||||
|
||||
interface Props {
|
||||
validate?: Validation;
|
||||
minColumnWidth?: string;
|
||||
onFieldBlur?: (field: ConfigKey) => void;
|
||||
readOnly?: boolean;
|
||||
}
|
||||
|
||||
type ThrottlingConfigs =
|
||||
| ConfigKey.IS_THROTTLING_ENABLED
|
||||
| ConfigKey.DOWNLOAD_SPEED
|
||||
| ConfigKey.UPLOAD_SPEED
|
||||
| ConfigKey.LATENCY;
|
||||
|
||||
export const ThrottlingDisabledCallout = () => {
|
||||
return (
|
||||
<EuiCallOut
|
||||
title={
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.createPackagePolicy.stepConfigure.browserAdvancedSettings.throttling.automatic_node_cap.title"
|
||||
defaultMessage="Automatic cap"
|
||||
/>
|
||||
}
|
||||
color="warning"
|
||||
iconType="warning"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.createPackagePolicy.stepConfigure.browserAdvancedSettings.throttling.automatic_node_cap.message"
|
||||
defaultMessage="When disabling throttling, your monitor will still have its bandwidth capped by the configurations of the Synthetics Nodes in which it's running."
|
||||
/>
|
||||
</EuiCallOut>
|
||||
);
|
||||
};
|
||||
|
||||
export const ThrottlingExceededCallout = () => {
|
||||
return (
|
||||
<EuiCallOut
|
||||
title={
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.createPackagePolicy.stepConfigure.browserAdvancedSettings.throttling.exceeded_throttling.title"
|
||||
defaultMessage="You've exceeded the Synthetics Node bandwidth limits"
|
||||
/>
|
||||
}
|
||||
color="warning"
|
||||
iconType="warning"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.createPackagePolicy.stepConfigure.browserAdvancedSettings.throttling.exceeded_throttling.message"
|
||||
defaultMessage="When using throttling values larger than a Synthetics Node bandwidth limit, your monitor will still have its bandwidth capped."
|
||||
/>
|
||||
</EuiCallOut>
|
||||
);
|
||||
};
|
||||
|
||||
export const ThrottlingExceededMessage = ({
|
||||
throttlingField,
|
||||
limit,
|
||||
}: {
|
||||
throttlingField: string;
|
||||
limit: number;
|
||||
}) => {
|
||||
return (
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.createPackagePolicy.stepConfigure.browserAdvancedSettings.throttling.throttling_exceeded.message"
|
||||
defaultMessage="You have exceeded the { throttlingField } limit for Synthetic Nodes. The { throttlingField } value can't be larger than { limit }Mbps."
|
||||
values={{ throttlingField, limit }}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const ThrottlingFields = memo<Props>(
|
||||
({ validate, minColumnWidth, onFieldBlur, readOnly = false }) => {
|
||||
const { fields, setFields } = useBrowserAdvancedFieldsContext();
|
||||
const { runsOnService, throttling } = usePolicyConfigContext();
|
||||
|
||||
const maxDownload = throttling[BandwidthLimitKey.DOWNLOAD];
|
||||
const maxUpload = throttling[BandwidthLimitKey.UPLOAD];
|
||||
|
||||
const handleInputChange = useCallback(
|
||||
({ value, configKey }: { value: unknown; configKey: ThrottlingConfigs }) => {
|
||||
setFields((prevFields) => ({ ...prevFields, [configKey]: value }));
|
||||
},
|
||||
[setFields]
|
||||
);
|
||||
|
||||
const exceedsDownloadLimits =
|
||||
runsOnService && parseFloat(fields[ConfigKey.DOWNLOAD_SPEED]) > maxDownload;
|
||||
const exceedsUploadLimits =
|
||||
runsOnService && parseFloat(fields[ConfigKey.UPLOAD_SPEED]) > maxUpload;
|
||||
const isThrottlingEnabled = fields[ConfigKey.IS_THROTTLING_ENABLED];
|
||||
|
||||
const throttlingInputs = isThrottlingEnabled ? (
|
||||
<>
|
||||
<EuiSpacer size="m" />
|
||||
<EuiFormRow
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.createPackagePolicy.stepConfigure.browserAdvancedSettings.throttling.download.label"
|
||||
defaultMessage="Download Speed"
|
||||
/>
|
||||
}
|
||||
labelAppend={<OptionalLabel />}
|
||||
isInvalid={
|
||||
(validate ? !!validate[ConfigKey.DOWNLOAD_SPEED]?.(fields) : false) ||
|
||||
exceedsDownloadLimits
|
||||
}
|
||||
error={
|
||||
exceedsDownloadLimits ? (
|
||||
<ThrottlingExceededMessage throttlingField="download" limit={maxDownload} />
|
||||
) : (
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.createPackagePolicy.stepConfigure.browserAdvancedSettings.throttling.download.error"
|
||||
defaultMessage="Download speed must be greater than zero."
|
||||
/>
|
||||
)
|
||||
}
|
||||
>
|
||||
<EuiFieldNumber
|
||||
min={0}
|
||||
step={0.001}
|
||||
value={fields[ConfigKey.DOWNLOAD_SPEED]}
|
||||
onChange={(event) => {
|
||||
handleInputChange({
|
||||
value: event.target.value,
|
||||
configKey: ConfigKey.DOWNLOAD_SPEED,
|
||||
});
|
||||
}}
|
||||
onBlur={() => onFieldBlur?.(ConfigKey.DOWNLOAD_SPEED)}
|
||||
data-test-subj="syntheticsBrowserDownloadSpeed"
|
||||
append={
|
||||
<EuiText size="xs">
|
||||
<strong>Mbps</strong>
|
||||
</EuiText>
|
||||
}
|
||||
readOnly={readOnly}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
<EuiFormRow
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.createPackagePolicy.stepConfigure.browserAdvancedSettings.throttling.upload.label"
|
||||
defaultMessage="Upload Speed"
|
||||
/>
|
||||
}
|
||||
labelAppend={<OptionalLabel />}
|
||||
isInvalid={
|
||||
(validate ? !!validate[ConfigKey.UPLOAD_SPEED]?.(fields) : false) || exceedsUploadLimits
|
||||
}
|
||||
error={
|
||||
exceedsUploadLimits ? (
|
||||
<ThrottlingExceededMessage throttlingField="upload" limit={maxUpload} />
|
||||
) : (
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.createPackagePolicy.stepConfigure.browserAdvancedSettings.throttling.upload.error"
|
||||
defaultMessage="Upload speed must be greater than zero."
|
||||
/>
|
||||
)
|
||||
}
|
||||
>
|
||||
<EuiFieldNumber
|
||||
min={0}
|
||||
step={0.001}
|
||||
value={fields[ConfigKey.UPLOAD_SPEED]}
|
||||
onChange={(event) =>
|
||||
handleInputChange({
|
||||
value: event.target.value,
|
||||
configKey: ConfigKey.UPLOAD_SPEED,
|
||||
})
|
||||
}
|
||||
onBlur={() => onFieldBlur?.(ConfigKey.UPLOAD_SPEED)}
|
||||
data-test-subj="syntheticsBrowserUploadSpeed"
|
||||
append={
|
||||
<EuiText size="xs">
|
||||
<strong>Mbps</strong>
|
||||
</EuiText>
|
||||
}
|
||||
readOnly={readOnly}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
<EuiFormRow
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.createPackagePolicy.stepConfigure.browserAdvancedSettings.throttling.latency.label"
|
||||
defaultMessage="Latency"
|
||||
/>
|
||||
}
|
||||
labelAppend={<OptionalLabel />}
|
||||
isInvalid={validate ? !!validate[ConfigKey.LATENCY]?.(fields) : false}
|
||||
error={
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.createPackagePolicy.stepConfigure.browserAdvancedSettings.throttling.latency.error"
|
||||
defaultMessage="Latency must not be negative."
|
||||
/>
|
||||
}
|
||||
>
|
||||
<EuiFieldNumber
|
||||
min={0}
|
||||
value={fields[ConfigKey.LATENCY]}
|
||||
onChange={(event) =>
|
||||
handleInputChange({
|
||||
value: event.target.value,
|
||||
configKey: ConfigKey.LATENCY,
|
||||
})
|
||||
}
|
||||
onBlur={() => onFieldBlur?.(ConfigKey.LATENCY)}
|
||||
data-test-subj="syntheticsBrowserLatency"
|
||||
append={
|
||||
<EuiText size="xs">
|
||||
<strong>ms</strong>
|
||||
</EuiText>
|
||||
}
|
||||
readOnly={readOnly}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<EuiSpacer />
|
||||
<ThrottlingDisabledCallout />
|
||||
</>
|
||||
);
|
||||
|
||||
return (
|
||||
<DescribedFormGroupWithWrap
|
||||
minColumnWidth={minColumnWidth}
|
||||
title={
|
||||
<h4>
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.createPackagePolicy.stepConfigure.browserAdvancedSettings.throttling.title"
|
||||
defaultMessage="Throttling options"
|
||||
/>
|
||||
</h4>
|
||||
}
|
||||
description={
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.createPackagePolicy.stepConfigure.browserAdvancedSettings.throttling.description"
|
||||
defaultMessage="Control the monitor's download and upload speeds, and its latency to simulate your application's behaviour on slower or laggier networks."
|
||||
/>
|
||||
}
|
||||
>
|
||||
<EuiSwitch
|
||||
id={'uptimeFleetIsThrottlingEnabled'}
|
||||
aria-label="enable throttling configuration"
|
||||
data-test-subj="syntheticsBrowserIsThrottlingEnabled"
|
||||
checked={fields[ConfigKey.IS_THROTTLING_ENABLED]}
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.createPackagePolicy.stepConfigure.browserAdvancedSettings.throttling.switch.description"
|
||||
defaultMessage="Enable throttling"
|
||||
/>
|
||||
}
|
||||
onChange={(event) =>
|
||||
handleInputChange({
|
||||
value: event.target.checked,
|
||||
configKey: ConfigKey.IS_THROTTLING_ENABLED,
|
||||
})
|
||||
}
|
||||
onBlur={() => onFieldBlur?.(ConfigKey.IS_THROTTLING_ENABLED)}
|
||||
disabled={readOnly}
|
||||
/>
|
||||
{isThrottlingEnabled && (exceedsDownloadLimits || exceedsUploadLimits) ? (
|
||||
<>
|
||||
<EuiSpacer />
|
||||
<ThrottlingExceededCallout />
|
||||
</>
|
||||
) : null}
|
||||
{throttlingInputs}
|
||||
</DescribedFormGroupWithWrap>
|
||||
);
|
||||
}
|
||||
);
|
|
@ -1,93 +0,0 @@
|
|||
/*
|
||||
* 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, { useState, useRef } from 'react';
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { EuiFormRow, EuiFilePicker } from '@elastic/eui';
|
||||
|
||||
interface Props {
|
||||
onUpload: ({ scriptText, fileName }: { scriptText: string; fileName: string }) => void;
|
||||
}
|
||||
|
||||
export function Uploader({ onUpload }: Props) {
|
||||
const fileReader = useRef<null | FileReader>(null);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const filePickerRef = useRef<EuiFilePicker>(null);
|
||||
|
||||
const handleFileRead = (fileName: string) => {
|
||||
const content = fileReader?.current?.result as string;
|
||||
|
||||
if (content?.trim().slice(0, 4) !== 'step') {
|
||||
setError(PARSING_ERROR);
|
||||
filePickerRef.current?.removeFiles();
|
||||
return;
|
||||
}
|
||||
|
||||
onUpload({ scriptText: content, fileName });
|
||||
setError(null);
|
||||
};
|
||||
|
||||
const handleFileChosen = (files: FileList | null) => {
|
||||
if (!files || !files.length) {
|
||||
onUpload({ scriptText: '', fileName: '' });
|
||||
return;
|
||||
}
|
||||
if (files.length && !files[0].type.includes('javascript')) {
|
||||
setError(INVALID_FILE_ERROR);
|
||||
filePickerRef.current?.removeFiles();
|
||||
return;
|
||||
}
|
||||
fileReader.current = new FileReader();
|
||||
fileReader.current.onloadend = () => handleFileRead(files[0].name);
|
||||
fileReader.current.readAsText(files[0]);
|
||||
};
|
||||
|
||||
return (
|
||||
<EuiFormRow isInvalid={Boolean(error)} error={error} label={TESTING_SCRIPT_LABEL}>
|
||||
<EuiFilePicker
|
||||
id="syntheticsFleetScriptRecorderUploader"
|
||||
data-test-subj="syntheticsFleetScriptRecorderUploader"
|
||||
ref={filePickerRef}
|
||||
initialPromptText={PROMPT_TEXT}
|
||||
onChange={handleFileChosen}
|
||||
display={'large'}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
);
|
||||
}
|
||||
|
||||
const TESTING_SCRIPT_LABEL = i18n.translate(
|
||||
'xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.browser.uploader.fieldLabel',
|
||||
{
|
||||
defaultMessage: 'Testing script',
|
||||
}
|
||||
);
|
||||
|
||||
const PROMPT_TEXT = i18n.translate(
|
||||
'xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.browser.uploader.label',
|
||||
{
|
||||
defaultMessage: 'Select recorder-generated .js file',
|
||||
}
|
||||
);
|
||||
|
||||
const INVALID_FILE_ERROR = i18n.translate(
|
||||
'xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.browser.uploader.invalidFileError',
|
||||
{
|
||||
defaultMessage:
|
||||
'Invalid file type. Please upload a .js file generated by the Elastic Synthetics Recorder.',
|
||||
}
|
||||
);
|
||||
|
||||
const PARSING_ERROR = i18n.translate(
|
||||
'xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.browser.uploader.parsingError',
|
||||
{
|
||||
defaultMessage:
|
||||
'Error uploading file. Please upload a .js file generated by the Elastic Synthetics Recorder in inline script format.',
|
||||
}
|
||||
);
|
|
@ -1,67 +0,0 @@
|
|||
/*
|
||||
* 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 from 'react';
|
||||
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { EuiPanel } from '@elastic/eui';
|
||||
import { euiStyled } from '@kbn/kibana-react-plugin/common';
|
||||
import { CodeEditor as MonacoCodeEditor } from '@kbn/kibana-react-plugin/public';
|
||||
|
||||
import { MonacoEditorLangId } from './types';
|
||||
|
||||
const CodeEditorContainer = styled(EuiPanel)`
|
||||
padding: 0;
|
||||
`;
|
||||
|
||||
interface Props {
|
||||
ariaLabel: string;
|
||||
id: string;
|
||||
languageId: MonacoEditorLangId;
|
||||
onChange?: (value: string) => void;
|
||||
value: string;
|
||||
readOnly?: boolean;
|
||||
}
|
||||
|
||||
export const CodeEditor = ({
|
||||
ariaLabel,
|
||||
id,
|
||||
languageId,
|
||||
onChange,
|
||||
value,
|
||||
readOnly = false,
|
||||
}: Props) => {
|
||||
return (
|
||||
<CodeEditorContainer borderRadius="none" hasShadow={false} hasBorder={true}>
|
||||
<MonacoCodeContainer
|
||||
id={`${id}-editor`}
|
||||
aria-label={ariaLabel}
|
||||
data-test-subj="codeEditorContainer"
|
||||
>
|
||||
<MonacoCodeEditor
|
||||
languageId={languageId}
|
||||
width="100%"
|
||||
height="250px"
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
options={{
|
||||
renderValidationDecorations: value ? 'on' : 'off',
|
||||
readOnly,
|
||||
}}
|
||||
isCopyable={true}
|
||||
allowFullScreen={true}
|
||||
/>
|
||||
</MonacoCodeContainer>
|
||||
</CodeEditorContainer>
|
||||
);
|
||||
};
|
||||
|
||||
const MonacoCodeContainer = euiStyled.div`
|
||||
& > .kibanaCodeEditor {
|
||||
z-index: 0;
|
||||
}
|
||||
`;
|
|
@ -1,37 +0,0 @@
|
|||
/*
|
||||
* 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 { fireEvent } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import { render } from '../../lib/helper/rtl_helpers';
|
||||
import { ComboBox } from './combo_box';
|
||||
|
||||
describe('<ComboBox />', () => {
|
||||
const onChange = jest.fn();
|
||||
const selectedOptions: string[] = [];
|
||||
|
||||
it('renders ComboBox', () => {
|
||||
const { getByTestId } = render(
|
||||
<ComboBox selectedOptions={selectedOptions} onChange={onChange} />
|
||||
);
|
||||
|
||||
expect(getByTestId('syntheticsFleetComboBox')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('calls onBlur', () => {
|
||||
const onBlur = jest.fn();
|
||||
const { getByTestId } = render(
|
||||
<ComboBox selectedOptions={selectedOptions} onChange={onChange} onBlur={onBlur} />
|
||||
);
|
||||
|
||||
const combobox = getByTestId('syntheticsFleetComboBox');
|
||||
fireEvent.focus(combobox);
|
||||
fireEvent.blur(combobox);
|
||||
|
||||
expect(onBlur).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
|
@ -1,87 +0,0 @@
|
|||
/*
|
||||
* 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, { useState, useCallback } from 'react';
|
||||
import { EuiComboBox, EuiComboBoxOptionOption } from '@elastic/eui';
|
||||
|
||||
export interface Props {
|
||||
onChange: (value: string[]) => void;
|
||||
onBlur?: () => void;
|
||||
selectedOptions: string[];
|
||||
readOnly?: boolean;
|
||||
}
|
||||
|
||||
export const ComboBox = ({
|
||||
onChange,
|
||||
onBlur,
|
||||
selectedOptions,
|
||||
readOnly = false,
|
||||
...props
|
||||
}: Props) => {
|
||||
const [formattedSelectedOptions, setSelectedOptions] = useState<
|
||||
Array<EuiComboBoxOptionOption<string>>
|
||||
>(selectedOptions.map((option) => ({ label: option, key: option })));
|
||||
const [isInvalid, setInvalid] = useState(false);
|
||||
|
||||
const onOptionsChange = useCallback(
|
||||
(options: Array<EuiComboBoxOptionOption<string>>) => {
|
||||
setSelectedOptions(options);
|
||||
const formattedTags = options.map((option) => option.label);
|
||||
onChange(formattedTags);
|
||||
setInvalid(false);
|
||||
},
|
||||
[onChange, setSelectedOptions, setInvalid]
|
||||
);
|
||||
|
||||
const onCreateOption = useCallback(
|
||||
(tag: string) => {
|
||||
const formattedTag = tag.trim();
|
||||
const newOption = {
|
||||
label: formattedTag,
|
||||
};
|
||||
|
||||
onChange([...selectedOptions, formattedTag]);
|
||||
|
||||
// Select the option.
|
||||
setSelectedOptions([...formattedSelectedOptions, newOption]);
|
||||
},
|
||||
[onChange, formattedSelectedOptions, selectedOptions, setSelectedOptions]
|
||||
);
|
||||
|
||||
const onSearchChange = useCallback(
|
||||
(searchValue: string) => {
|
||||
if (!searchValue) {
|
||||
setInvalid(false);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
setInvalid(!isValid(searchValue));
|
||||
},
|
||||
[setInvalid]
|
||||
);
|
||||
|
||||
return (
|
||||
<EuiComboBox<string>
|
||||
data-test-subj="syntheticsFleetComboBox"
|
||||
noSuggestions
|
||||
selectedOptions={formattedSelectedOptions}
|
||||
onCreateOption={onCreateOption}
|
||||
onChange={onOptionsChange}
|
||||
onBlur={() => onBlur?.()}
|
||||
onSearchChange={onSearchChange}
|
||||
isInvalid={isInvalid}
|
||||
isDisabled={readOnly}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const isValid = (value: string) => {
|
||||
// Ensure that the tag is more than whitespace
|
||||
return value.match(/\S+/) !== null;
|
||||
};
|
|
@ -1,140 +0,0 @@
|
|||
/*
|
||||
* 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 { EuiFieldNumber, EuiFieldText, EuiFormRow } from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import React, { useEffect } from 'react';
|
||||
import { ComboBox } from '../combo_box';
|
||||
import { usePolicyConfigContext } from '../contexts';
|
||||
import { OptionalLabel } from '../optional_label';
|
||||
import { CommonFields as CommonFieldsType, ConfigKey, DataStream, Validation } from '../types';
|
||||
|
||||
interface Props {
|
||||
validate: Validation;
|
||||
fields: CommonFieldsType;
|
||||
onChange: ({
|
||||
value,
|
||||
configKey,
|
||||
}: {
|
||||
value: string | string[] | null;
|
||||
configKey: ConfigKey;
|
||||
}) => void;
|
||||
onFieldBlur?: (field: ConfigKey) => void;
|
||||
}
|
||||
|
||||
export function CommonFields({ fields, onChange, onFieldBlur, validate }: Props) {
|
||||
const { monitorType } = usePolicyConfigContext();
|
||||
|
||||
const isBrowser = monitorType === DataStream.BROWSER;
|
||||
|
||||
useEffect(() => {
|
||||
if (monitorType === DataStream.BROWSER) {
|
||||
onChange({
|
||||
value: null,
|
||||
configKey: ConfigKey.TIMEOUT,
|
||||
});
|
||||
}
|
||||
}, [onChange, monitorType]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<EuiFormRow
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.APMServiceName.label"
|
||||
defaultMessage="APM service name"
|
||||
/>
|
||||
}
|
||||
labelAppend={<OptionalLabel />}
|
||||
helpText={
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.APMServiceName.helpText"
|
||||
defaultMessage="APM service name for this monitor. Corresponds to the service.name ECS field. Set this when monitoring an app that is also using APM to enable integrations between Uptime and APM data in Kibana."
|
||||
/>
|
||||
}
|
||||
>
|
||||
<EuiFieldText
|
||||
value={fields[ConfigKey.APM_SERVICE_NAME]}
|
||||
onChange={(event) =>
|
||||
onChange({
|
||||
value: event.target.value,
|
||||
configKey: ConfigKey.APM_SERVICE_NAME,
|
||||
})
|
||||
}
|
||||
onBlur={() => onFieldBlur?.(ConfigKey.APM_SERVICE_NAME)}
|
||||
data-test-subj="syntheticsAPMServiceName"
|
||||
/>
|
||||
</EuiFormRow>
|
||||
{!isBrowser && (
|
||||
<EuiFormRow
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.timeout.label"
|
||||
defaultMessage="Timeout in seconds"
|
||||
/>
|
||||
}
|
||||
isInvalid={!!validate[ConfigKey.TIMEOUT]?.(fields)}
|
||||
error={
|
||||
parseInt(fields[ConfigKey.TIMEOUT] || '', 10) < 0 ? (
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.timeout.moreThanZeroError"
|
||||
defaultMessage="Timeout must be greater than or equal to 0"
|
||||
/>
|
||||
) : (
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.timeout.lessThanIntervalError"
|
||||
defaultMessage="Timeout must be less than the monitor frequency"
|
||||
/>
|
||||
)
|
||||
}
|
||||
helpText={
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.timeout.helpText"
|
||||
defaultMessage="The total time allowed for testing the connection and exchanging data."
|
||||
/>
|
||||
}
|
||||
>
|
||||
<EuiFieldNumber
|
||||
data-test-subj="syntheticsCommonFieldsFieldNumber"
|
||||
min={0}
|
||||
value={fields[ConfigKey.TIMEOUT] || ''}
|
||||
onChange={(event) =>
|
||||
onChange({
|
||||
value: event.target.value,
|
||||
configKey: ConfigKey.TIMEOUT,
|
||||
})
|
||||
}
|
||||
onBlur={() => onFieldBlur?.(ConfigKey.TIMEOUT)}
|
||||
step={'any'}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
)}
|
||||
<EuiFormRow
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.tags.label"
|
||||
defaultMessage="Tags"
|
||||
/>
|
||||
}
|
||||
labelAppend={<OptionalLabel />}
|
||||
helpText={
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.tags.helpText"
|
||||
defaultMessage="A list of tags that will be sent with the monitor event. Press enter to add a new tag. Displayed in Uptime and enables searching by tag."
|
||||
/>
|
||||
}
|
||||
>
|
||||
<ComboBox
|
||||
selectedOptions={fields[ConfigKey.TAGS]}
|
||||
onChange={(value) => onChange({ value, configKey: ConfigKey.TAGS })}
|
||||
onBlur={() => onFieldBlur?.(ConfigKey.TAGS)}
|
||||
data-test-subj="syntheticsTags"
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -1,23 +0,0 @@
|
|||
/*
|
||||
* 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 { EuiDescribedFormGroup } from '@elastic/eui';
|
||||
import { euiStyled } from '@kbn/kibana-react-plugin/common';
|
||||
|
||||
/**
|
||||
* EuiForm group doesn't expose props to control the flex wrapping on flex groups defining form rows.
|
||||
* This override allows to define a minimum column width to which the Described Form's flex rows should wrap.
|
||||
*/
|
||||
export const DescribedFormGroupWithWrap = euiStyled(EuiDescribedFormGroup)<{
|
||||
minColumnWidth?: string;
|
||||
}>`
|
||||
> .euiFlexGroup {
|
||||
${({ minColumnWidth }) => (minColumnWidth ? `flex-wrap: wrap;` : '')}
|
||||
> .euiFlexItem {
|
||||
${({ minColumnWidth }) => (minColumnWidth ? `min-width: ${minColumnWidth};` : '')}
|
||||
}
|
||||
}
|
||||
`;
|
|
@ -1,52 +0,0 @@
|
|||
/*
|
||||
* 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 from 'react';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { EuiFormRow, EuiSwitch } from '@elastic/eui';
|
||||
import { ConfigKey, CommonFields } from '../types';
|
||||
|
||||
interface Props {
|
||||
fields: CommonFields;
|
||||
onChange: ({ value, configKey }: { value: boolean; configKey: ConfigKey }) => void;
|
||||
onBlur?: () => void;
|
||||
readOnly?: boolean;
|
||||
}
|
||||
|
||||
export function Enabled({ fields, onChange, onBlur, readOnly }: Props) {
|
||||
return (
|
||||
<>
|
||||
<EuiFormRow
|
||||
helpText={
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.enabled.helpText"
|
||||
defaultMessage="Switch this configuration off to disable the monitor."
|
||||
/>
|
||||
}
|
||||
>
|
||||
<EuiSwitch
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.enabled.label"
|
||||
defaultMessage="Enabled"
|
||||
/>
|
||||
}
|
||||
data-test-subj="syntheticsEnabled"
|
||||
checked={fields[ConfigKey.ENABLED]}
|
||||
onChange={(event) =>
|
||||
onChange({
|
||||
value: event.target.checked,
|
||||
configKey: ConfigKey.ENABLED,
|
||||
})
|
||||
}
|
||||
onBlur={() => onBlur?.()}
|
||||
disabled={readOnly}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -1,28 +0,0 @@
|
|||
/*
|
||||
* 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 { cronToSecondsNormalizer, jsonToJavascriptNormalizer } from './normalizers';
|
||||
|
||||
describe('normalizers', () => {
|
||||
describe('cronToSecondsNormalizer', () => {
|
||||
it('returns number of seconds from cron formatted seconds', () => {
|
||||
expect(cronToSecondsNormalizer('3s')).toEqual('3');
|
||||
});
|
||||
});
|
||||
|
||||
describe('jsonToJavascriptNormalizer', () => {
|
||||
it('takes a json object string and returns an object', () => {
|
||||
expect(jsonToJavascriptNormalizer('{\n "key": "value"\n}')).toEqual({
|
||||
key: 'value',
|
||||
});
|
||||
});
|
||||
|
||||
it('takes a json array string and returns an array', () => {
|
||||
expect(jsonToJavascriptNormalizer('["tag1","tag2"]')).toEqual(['tag1', 'tag2']);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,102 +0,0 @@
|
|||
/*
|
||||
* 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 { NewPackagePolicyInput } from '@kbn/fleet-plugin/common';
|
||||
import { parseJsonIfString } from '../helpers/parsers';
|
||||
import { CommonFields, ConfigKey, DataStream } from '../types';
|
||||
import {
|
||||
DEFAULT_COMMON_FIELDS,
|
||||
DEFAULT_NAMESPACE_STRING,
|
||||
DEFAULT_FIELDS,
|
||||
} from '../../../../../common/constants/monitor_defaults';
|
||||
|
||||
// TO DO: create a standard input format that all fields resolve to
|
||||
export type Normalizer = (fields: NewPackagePolicyInput['vars']) => unknown;
|
||||
|
||||
// create a type of all the common policy fields, as well as the fleet managed 'name' field
|
||||
export type CommonNormalizerMap = Record<keyof CommonFields | ConfigKey.NAME, Normalizer>;
|
||||
|
||||
/**
|
||||
* Takes a cron formatted seconds and returns just the number of seconds. Assumes that cron is already in seconds format.
|
||||
* @params {string} value (Ex '3s')
|
||||
* @return {string} (Ex '3')
|
||||
*/
|
||||
export const cronToSecondsNormalizer = (value: string) =>
|
||||
value ? value.slice(0, value.length - 1) : null;
|
||||
|
||||
export const jsonToJavascriptNormalizer = (value: string) =>
|
||||
value ? parseJsonIfString(value) : null;
|
||||
|
||||
export function getNormalizer<Fields>(key: string, defaultValues: Fields): Normalizer {
|
||||
return (fields: NewPackagePolicyInput['vars']) =>
|
||||
fields?.[key]?.value ?? defaultValues[key as keyof Fields];
|
||||
}
|
||||
|
||||
export function getJsonToJavascriptNormalizer<Fields>(
|
||||
key: string,
|
||||
defaultValues: Fields
|
||||
): Normalizer {
|
||||
return (fields: NewPackagePolicyInput['vars']) =>
|
||||
jsonToJavascriptNormalizer(fields?.[key]?.value) ?? defaultValues[key as keyof Fields];
|
||||
}
|
||||
|
||||
export function getCronNormalizer<Fields>(key: string, defaultValues: Fields): Normalizer {
|
||||
return (fields: NewPackagePolicyInput['vars']) =>
|
||||
cronToSecondsNormalizer(fields?.[key]?.value) ?? defaultValues[key as keyof Fields];
|
||||
}
|
||||
|
||||
export const getCommonNormalizer = (key: ConfigKey) => {
|
||||
return getNormalizer(key, DEFAULT_COMMON_FIELDS);
|
||||
};
|
||||
|
||||
export const getCommonjsonToJavascriptNormalizer = (key: ConfigKey) => {
|
||||
return getJsonToJavascriptNormalizer(key, DEFAULT_COMMON_FIELDS);
|
||||
};
|
||||
|
||||
export const getCommonCronToSecondsNormalizer = (key: ConfigKey) => {
|
||||
return getCronNormalizer(key, DEFAULT_COMMON_FIELDS);
|
||||
};
|
||||
|
||||
export const commonNormalizers: CommonNormalizerMap = {
|
||||
[ConfigKey.NAME]: (fields) => fields?.[ConfigKey.NAME]?.value ?? '',
|
||||
[ConfigKey.LOCATIONS]: getCommonNormalizer(ConfigKey.LOCATIONS),
|
||||
[ConfigKey.ENABLED]: getCommonNormalizer(ConfigKey.ENABLED),
|
||||
[ConfigKey.ALERT_CONFIG]: getCommonNormalizer(ConfigKey.ENABLED),
|
||||
[ConfigKey.MONITOR_TYPE]: getCommonNormalizer(ConfigKey.MONITOR_TYPE),
|
||||
[ConfigKey.LOCATIONS]: getCommonNormalizer(ConfigKey.LOCATIONS),
|
||||
[ConfigKey.SCHEDULE]: (fields) => {
|
||||
const value = fields?.[ConfigKey.SCHEDULE]?.value;
|
||||
const type = fields?.[ConfigKey.MONITOR_TYPE]?.value as DataStream;
|
||||
if (value) {
|
||||
const fullString = JSON.parse(fields?.[ConfigKey.SCHEDULE]?.value);
|
||||
const fullSchedule = fullString.replace('@every ', '');
|
||||
const unit = fullSchedule.slice(-1);
|
||||
const number = fullSchedule.slice(0, fullSchedule.length - 1);
|
||||
return {
|
||||
unit,
|
||||
number,
|
||||
};
|
||||
} else {
|
||||
return DEFAULT_FIELDS[type][ConfigKey.SCHEDULE];
|
||||
}
|
||||
},
|
||||
[ConfigKey.APM_SERVICE_NAME]: getCommonNormalizer(ConfigKey.APM_SERVICE_NAME),
|
||||
[ConfigKey.CONFIG_ID]: getCommonNormalizer(ConfigKey.CONFIG_ID),
|
||||
[ConfigKey.TAGS]: getCommonjsonToJavascriptNormalizer(ConfigKey.TAGS),
|
||||
[ConfigKey.TIMEOUT]: getCommonCronToSecondsNormalizer(ConfigKey.TIMEOUT),
|
||||
[ConfigKey.NAMESPACE]: (fields) =>
|
||||
fields?.[ConfigKey.NAMESPACE]?.value ?? DEFAULT_NAMESPACE_STRING,
|
||||
[ConfigKey.REVISION]: getCommonNormalizer(ConfigKey.REVISION),
|
||||
[ConfigKey.MONITOR_SOURCE_TYPE]: getCommonNormalizer(ConfigKey.MONITOR_SOURCE_TYPE),
|
||||
[ConfigKey.FORM_MONITOR_TYPE]: getCommonNormalizer(ConfigKey.FORM_MONITOR_TYPE),
|
||||
[ConfigKey.JOURNEY_ID]: getCommonNormalizer(ConfigKey.JOURNEY_ID),
|
||||
[ConfigKey.PROJECT_ID]: getCommonNormalizer(ConfigKey.PROJECT_ID),
|
||||
[ConfigKey.CUSTOM_HEARTBEAT_ID]: getCommonNormalizer(ConfigKey.CUSTOM_HEARTBEAT_ID),
|
||||
[ConfigKey.ORIGINAL_SPACE]: getCommonNormalizer(ConfigKey.ORIGINAL_SPACE),
|
||||
[ConfigKey.CONFIG_HASH]: getCommonNormalizer(ConfigKey.CONFIG_HASH),
|
||||
[ConfigKey.MONITOR_QUERY_ID]: getCommonNormalizer(ConfigKey.MONITOR_QUERY_ID),
|
||||
};
|
|
@ -1,44 +0,0 @@
|
|||
/*
|
||||
* 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 from 'react';
|
||||
import { CommonFields } from './common_fields';
|
||||
import { Enabled } from './enabled';
|
||||
import { CommonFields as CommonFieldsType, ConfigKey, Validation } from '../types';
|
||||
|
||||
interface Props {
|
||||
validate: Validation;
|
||||
onInputChange: ({ value, configKey }: { value: unknown; configKey: ConfigKey }) => void;
|
||||
onFieldBlur?: (field: ConfigKey) => void;
|
||||
children: React.ReactNode;
|
||||
fields: CommonFieldsType;
|
||||
}
|
||||
|
||||
export const SimpleFieldsWrapper = ({
|
||||
validate,
|
||||
onInputChange,
|
||||
onFieldBlur,
|
||||
children,
|
||||
fields,
|
||||
}: Props) => {
|
||||
return (
|
||||
<>
|
||||
<Enabled
|
||||
fields={fields}
|
||||
onChange={onInputChange}
|
||||
onBlur={() => onFieldBlur?.(ConfigKey.ENABLED)}
|
||||
/>
|
||||
{children}
|
||||
<CommonFields
|
||||
fields={fields}
|
||||
validate={validate}
|
||||
onChange={onInputChange}
|
||||
onFieldBlur={onFieldBlur}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -1,411 +0,0 @@
|
|||
/*
|
||||
* 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, memo } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import {
|
||||
EuiCallOut,
|
||||
EuiComboBox,
|
||||
EuiComboBoxOptionOption,
|
||||
EuiFormRow,
|
||||
EuiTextArea,
|
||||
EuiFormFieldset,
|
||||
EuiSelect,
|
||||
EuiScreenReaderOnly,
|
||||
EuiSpacer,
|
||||
EuiFieldPassword,
|
||||
} from '@elastic/eui';
|
||||
|
||||
import { VerificationMode, TLSVersion } from '../types';
|
||||
|
||||
import { OptionalLabel } from '../optional_label';
|
||||
|
||||
type TLSRole = 'client' | 'server';
|
||||
|
||||
export interface TLSConfig {
|
||||
certificateAuthorities?: string;
|
||||
certificate?: string;
|
||||
key?: string;
|
||||
keyPassphrase?: string;
|
||||
verificationMode?: VerificationMode;
|
||||
version?: TLSVersion[];
|
||||
}
|
||||
|
||||
const defaultConfig = {
|
||||
certificateAuthorities: '',
|
||||
certificate: '',
|
||||
key: '',
|
||||
keyPassphrase: '',
|
||||
verificationMode: VerificationMode.STRICT,
|
||||
version: [],
|
||||
};
|
||||
|
||||
interface Props {
|
||||
onChange: (defaultConfig: TLSConfig) => void;
|
||||
defaultValues: TLSConfig;
|
||||
tlsRole: TLSRole;
|
||||
}
|
||||
|
||||
export const TLSOptions: React.FunctionComponent<Props> = memo(
|
||||
({ onChange, defaultValues = defaultConfig, tlsRole }) => {
|
||||
const [verificationVersionInputRef, setVerificationVersionInputRef] =
|
||||
useState<HTMLInputElement | null>(null);
|
||||
const [hasVerificationVersionError, setHasVerificationVersionError] = useState<
|
||||
string | undefined
|
||||
>(undefined);
|
||||
|
||||
const [config, setConfig] = useState<TLSConfig>(defaultValues);
|
||||
|
||||
useEffect(() => {
|
||||
onChange(config);
|
||||
}, [config, onChange]);
|
||||
|
||||
const onVerificationVersionChange = (
|
||||
selectedVersionOptions: Array<EuiComboBoxOptionOption<TLSVersion>>
|
||||
) => {
|
||||
setConfig((prevConfig) => ({
|
||||
...prevConfig,
|
||||
version: selectedVersionOptions.map((option) => option.label as TLSVersion),
|
||||
}));
|
||||
setHasVerificationVersionError(undefined);
|
||||
};
|
||||
|
||||
const onSearchChange = (value: string, hasMatchingOptions?: boolean) => {
|
||||
setHasVerificationVersionError(
|
||||
value.length === 0 || hasMatchingOptions ? undefined : `"${value}" is not a valid option`
|
||||
);
|
||||
};
|
||||
|
||||
const onBlur = () => {
|
||||
if (verificationVersionInputRef) {
|
||||
const { value } = verificationVersionInputRef;
|
||||
setHasVerificationVersionError(
|
||||
value.length === 0 ? undefined : `"${value}" is not a valid option`
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<EuiFormFieldset
|
||||
legend={{
|
||||
children: (
|
||||
<EuiScreenReaderOnly>
|
||||
<span>
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.createPackagePolicy.stepConfigure.certsField.legend"
|
||||
defaultMessage="Certificate settings"
|
||||
/>
|
||||
</span>
|
||||
</EuiScreenReaderOnly>
|
||||
),
|
||||
}}
|
||||
>
|
||||
<EuiFormRow
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.createPackagePolicy.stepConfigure.certsField.verificationMode.label"
|
||||
defaultMessage="Verification mode"
|
||||
/>
|
||||
}
|
||||
helpText={
|
||||
config.verificationMode ? verificationModeHelpText[config.verificationMode] : ''
|
||||
}
|
||||
>
|
||||
<EuiSelect
|
||||
options={verificationModeOptions}
|
||||
value={config.verificationMode}
|
||||
onChange={(event) => {
|
||||
const verificationMode = event.target.value as VerificationMode;
|
||||
setConfig((prevConfig) => ({
|
||||
...prevConfig,
|
||||
verificationMode,
|
||||
}));
|
||||
}}
|
||||
data-test-subj="syntheticsTLSVerificationMode"
|
||||
/>
|
||||
</EuiFormRow>
|
||||
{config.verificationMode === VerificationMode.NONE && (
|
||||
<>
|
||||
<EuiSpacer size="s" />
|
||||
<EuiCallOut
|
||||
title={
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.createPackagePolicy.stepConfigure.certsField.verificationMode.warning.title"
|
||||
defaultMessage="Disabling TLS"
|
||||
/>
|
||||
}
|
||||
color="warning"
|
||||
size="s"
|
||||
>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.createPackagePolicy.stepConfigure.certsField.verificationMode.warning.description"
|
||||
defaultMessage="This mode disables many of the security benefits of SSL/TLS and should only be used
|
||||
after cautious consideration."
|
||||
/>
|
||||
</p>
|
||||
</EuiCallOut>
|
||||
<EuiSpacer size="s" />
|
||||
</>
|
||||
)}
|
||||
<EuiFormRow
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.createPackagePolicy.stepConfigure.certsField.version.label"
|
||||
defaultMessage="Supported TLS protocols"
|
||||
/>
|
||||
}
|
||||
error={hasVerificationVersionError}
|
||||
isInvalid={hasVerificationVersionError !== undefined}
|
||||
>
|
||||
<EuiComboBox
|
||||
placeholder={i18n.translate(
|
||||
'xpack.synthetics.createPackagePolicy.stepConfigure.certsField.version.placeholder',
|
||||
{
|
||||
defaultMessage: 'Select one or more TLS protocols.',
|
||||
}
|
||||
)}
|
||||
options={tlsVersionOptions}
|
||||
selectedOptions={(config.version || []).map((version: TLSVersion) => ({
|
||||
label: version,
|
||||
}))}
|
||||
inputRef={setVerificationVersionInputRef}
|
||||
onChange={onVerificationVersionChange}
|
||||
onSearchChange={onSearchChange}
|
||||
onBlur={onBlur}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
<EuiFormRow
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.createPackagePolicy.stepConfigure.certsField.certificateAuthorities.label"
|
||||
defaultMessage="Certificate authorities"
|
||||
/>
|
||||
}
|
||||
helpText={
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.createPackagePolicy.stepConfigure.certsField.certificateAuthorities.helpText"
|
||||
defaultMessage="PEM formatted custom certificate authorities."
|
||||
/>
|
||||
}
|
||||
labelAppend={<OptionalLabel />}
|
||||
>
|
||||
<EuiTextArea
|
||||
value={config.certificateAuthorities}
|
||||
onChange={(event) => {
|
||||
const certificateAuthorities = event.target.value;
|
||||
setConfig((prevConfig) => ({
|
||||
...prevConfig,
|
||||
certificateAuthorities,
|
||||
}));
|
||||
}}
|
||||
onBlur={(event) => {
|
||||
const certificateAuthorities = event.target.value;
|
||||
setConfig((prevConfig) => ({
|
||||
...prevConfig,
|
||||
certificateAuthorities: certificateAuthorities.trim(),
|
||||
}));
|
||||
}}
|
||||
data-test-subj="syntheticsTLSCA"
|
||||
/>
|
||||
</EuiFormRow>
|
||||
<EuiFormRow
|
||||
label={
|
||||
<>
|
||||
{tlsRoleLabels[tlsRole]}{' '}
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.createPackagePolicy.stepConfigure.certsField.certificate.label"
|
||||
defaultMessage="certificate"
|
||||
/>
|
||||
</>
|
||||
}
|
||||
helpText={
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.createPackagePolicy.stepConfigure.certsField.certificate.helpText"
|
||||
defaultMessage="PEM formatted certificate for TLS client authentication."
|
||||
/>
|
||||
}
|
||||
labelAppend={<OptionalLabel />}
|
||||
>
|
||||
<EuiTextArea
|
||||
value={config.certificate}
|
||||
onChange={(event) => {
|
||||
const certificate = event.target.value;
|
||||
setConfig((prevConfig) => ({
|
||||
...prevConfig,
|
||||
certificate,
|
||||
}));
|
||||
}}
|
||||
onBlur={(event) => {
|
||||
const certificate = event.target.value;
|
||||
setConfig((prevConfig) => ({
|
||||
...prevConfig,
|
||||
certificate: certificate.trim(),
|
||||
}));
|
||||
}}
|
||||
data-test-subj="syntheticsTLSCert"
|
||||
/>
|
||||
</EuiFormRow>
|
||||
<EuiFormRow
|
||||
label={
|
||||
<>
|
||||
{tlsRoleLabels[tlsRole]}{' '}
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.createPackagePolicy.stepConfigure.certsField.certificateKey.label"
|
||||
defaultMessage="key"
|
||||
/>
|
||||
</>
|
||||
}
|
||||
helpText={
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.createPackagePolicy.stepConfigure.certsField.certificateKey.helpText"
|
||||
defaultMessage="PEM formatted certificate key for TLS client authentication."
|
||||
/>
|
||||
}
|
||||
labelAppend={<OptionalLabel />}
|
||||
>
|
||||
<EuiTextArea
|
||||
value={config.key}
|
||||
onChange={(event) => {
|
||||
const key = event.target.value;
|
||||
setConfig((prevConfig) => ({
|
||||
...prevConfig,
|
||||
key,
|
||||
}));
|
||||
}}
|
||||
onBlur={(event) => {
|
||||
const key = event.target.value;
|
||||
setConfig((prevConfig) => ({
|
||||
...prevConfig,
|
||||
key: key.trim(),
|
||||
}));
|
||||
}}
|
||||
data-test-subj="syntheticsTLSCertKey"
|
||||
/>
|
||||
</EuiFormRow>
|
||||
<EuiFormRow
|
||||
label={
|
||||
<>
|
||||
{tlsRoleLabels[tlsRole]}{' '}
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.createPackagePolicy.stepConfigure.certsField.certificateKeyPassphrase.label"
|
||||
defaultMessage="key passphrase"
|
||||
/>
|
||||
</>
|
||||
}
|
||||
helpText={
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.createPackagePolicy.stepConfigure.certsField.certificateKeyPassphrase.helpText"
|
||||
defaultMessage="Certificate key passphrase for TLS client authentication."
|
||||
/>
|
||||
}
|
||||
labelAppend={<OptionalLabel />}
|
||||
>
|
||||
<EuiFieldPassword
|
||||
value={config.keyPassphrase}
|
||||
onChange={(event) => {
|
||||
const keyPassphrase = event.target.value;
|
||||
setConfig((prevConfig) => ({
|
||||
...prevConfig,
|
||||
keyPassphrase,
|
||||
}));
|
||||
}}
|
||||
data-test-subj="syntheticsTLSCertKeyPassphrase"
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</EuiFormFieldset>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
const tlsRoleLabels = {
|
||||
client: (
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.createPackagePolicy.stepConfigure.certsField.tlsRole.client"
|
||||
defaultMessage="Client"
|
||||
/>
|
||||
),
|
||||
server: (
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.createPackagePolicy.stepConfigure.certsField.tlsRole.server"
|
||||
defaultMessage="Server"
|
||||
/>
|
||||
),
|
||||
};
|
||||
|
||||
const verificationModeHelpText = {
|
||||
[VerificationMode.CERTIFICATE]: i18n.translate(
|
||||
'xpack.synthetics.createPackagePolicy.stepConfigure.certsField.verificationMode.certificate.description',
|
||||
{
|
||||
defaultMessage:
|
||||
'Verifies that the provided certificate is signed by a trusted authority (CA), but does not perform any hostname verification.',
|
||||
}
|
||||
),
|
||||
[VerificationMode.FULL]: i18n.translate(
|
||||
'xpack.synthetics.createPackagePolicy.stepConfigure.certsField.verificationMode.full.description',
|
||||
{
|
||||
defaultMessage:
|
||||
'Verifies that the provided certificate is signed by a trusted authority (CA) and also verifies that the server’s hostname (or IP address) matches the names identified within the certificate.',
|
||||
}
|
||||
),
|
||||
[VerificationMode.NONE]: i18n.translate(
|
||||
'xpack.synthetics.createPackagePolicy.stepConfigure.certsField.verificationMode.none.description',
|
||||
{
|
||||
defaultMessage:
|
||||
'Performs no verification of the server’s certificate. It is primarily intended as a temporary diagnostic mechanism when attempting to resolve TLS errors; its use in production environments is strongly discouraged.',
|
||||
}
|
||||
),
|
||||
[VerificationMode.STRICT]: i18n.translate(
|
||||
'xpack.synthetics.createPackagePolicy.stepConfigure.certsField.verificationMode.strict.description',
|
||||
{
|
||||
defaultMessage:
|
||||
'Verifies that the provided certificate is signed by a trusted authority (CA) and also verifies that the server’s hostname (or IP address) matches the names identified within the certificate. If the Subject Alternative Name is empty, it returns an error.',
|
||||
}
|
||||
),
|
||||
};
|
||||
|
||||
const verificationModeLabels = {
|
||||
[VerificationMode.CERTIFICATE]: i18n.translate(
|
||||
'xpack.synthetics.createPackagePolicy.stepConfigure.certsField.verificationMode.certificate.label',
|
||||
{
|
||||
defaultMessage: 'Certificate',
|
||||
}
|
||||
),
|
||||
[VerificationMode.FULL]: i18n.translate(
|
||||
'xpack.synthetics.createPackagePolicy.stepConfigure.certsField.verificationMode.full.label',
|
||||
{
|
||||
defaultMessage: 'Full',
|
||||
}
|
||||
),
|
||||
[VerificationMode.NONE]: i18n.translate(
|
||||
'xpack.synthetics.createPackagePolicy.stepConfigure.certsField.verificationMode.none.label',
|
||||
{
|
||||
defaultMessage: 'None',
|
||||
}
|
||||
),
|
||||
[VerificationMode.STRICT]: i18n.translate(
|
||||
'xpack.synthetics.createPackagePolicy.stepConfigure.certsField.verificationMode.strict.label',
|
||||
{
|
||||
defaultMessage: 'Strict',
|
||||
}
|
||||
),
|
||||
};
|
||||
|
||||
const verificationModeOptions = [
|
||||
{
|
||||
value: VerificationMode.CERTIFICATE,
|
||||
text: verificationModeLabels[VerificationMode.CERTIFICATE],
|
||||
},
|
||||
{ value: VerificationMode.FULL, text: verificationModeLabels[VerificationMode.FULL] },
|
||||
{ value: VerificationMode.NONE, text: verificationModeLabels[VerificationMode.NONE] },
|
||||
{ value: VerificationMode.STRICT, text: verificationModeLabels[VerificationMode.STRICT] },
|
||||
];
|
||||
|
||||
const tlsVersionOptions = Object.values(TLSVersion).map((method) => ({
|
||||
label: method,
|
||||
}));
|
|
@ -1,50 +0,0 @@
|
|||
/*
|
||||
* 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, { createContext, useContext, useMemo, useState } from 'react';
|
||||
import { BrowserSimpleFields } from '../types';
|
||||
import { DEFAULT_BROWSER_SIMPLE_FIELDS } from '../../../../../common/constants/monitor_defaults';
|
||||
|
||||
interface BrowserSimpleFieldsContext {
|
||||
setFields: React.Dispatch<React.SetStateAction<BrowserSimpleFields>>;
|
||||
fields: BrowserSimpleFields;
|
||||
defaultValues: BrowserSimpleFields;
|
||||
}
|
||||
|
||||
interface BrowserSimpleFieldsContextProvider {
|
||||
children: React.ReactNode;
|
||||
defaultValues?: BrowserSimpleFields;
|
||||
}
|
||||
|
||||
export const initialValues: BrowserSimpleFields = DEFAULT_BROWSER_SIMPLE_FIELDS;
|
||||
|
||||
const defaultContext: BrowserSimpleFieldsContext = {
|
||||
setFields: (_fields: React.SetStateAction<BrowserSimpleFields>) => {
|
||||
throw new Error(
|
||||
'setFields was not initialized for Browser Simple Fields, set it when you invoke the context'
|
||||
);
|
||||
},
|
||||
fields: initialValues, // mutable
|
||||
defaultValues: initialValues, // immutable
|
||||
};
|
||||
|
||||
export const BrowserSimpleFieldsContext = createContext(defaultContext);
|
||||
|
||||
export const BrowserSimpleFieldsContextProvider = ({
|
||||
children,
|
||||
defaultValues = initialValues,
|
||||
}: BrowserSimpleFieldsContextProvider) => {
|
||||
const [fields, setFields] = useState<BrowserSimpleFields>(defaultValues);
|
||||
|
||||
const value = useMemo(() => {
|
||||
return { fields, setFields, defaultValues };
|
||||
}, [fields, defaultValues]);
|
||||
|
||||
return <BrowserSimpleFieldsContext.Provider value={value} children={children} />;
|
||||
};
|
||||
|
||||
export const useBrowserSimpleFieldsContext = () => useContext(BrowserSimpleFieldsContext);
|
|
@ -1,50 +0,0 @@
|
|||
/*
|
||||
* 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, { createContext, useContext, useMemo, useState } from 'react';
|
||||
import { BrowserAdvancedFields } from '../types';
|
||||
import { DEFAULT_BROWSER_ADVANCED_FIELDS } from '../../../../../common/constants/monitor_defaults';
|
||||
|
||||
interface BrowserAdvancedFieldsContext {
|
||||
setFields: React.Dispatch<React.SetStateAction<BrowserAdvancedFields>>;
|
||||
fields: BrowserAdvancedFields;
|
||||
defaultValues: BrowserAdvancedFields;
|
||||
}
|
||||
|
||||
interface BrowserAdvancedFieldsContextProvider {
|
||||
children: React.ReactNode;
|
||||
defaultValues?: BrowserAdvancedFields;
|
||||
}
|
||||
|
||||
export const initialValues: BrowserAdvancedFields = DEFAULT_BROWSER_ADVANCED_FIELDS;
|
||||
|
||||
const defaultContext: BrowserAdvancedFieldsContext = {
|
||||
setFields: (_fields: React.SetStateAction<BrowserAdvancedFields>) => {
|
||||
throw new Error(
|
||||
'setFields was not initialized for Browser Advanced Fields, set it when you invoke the context'
|
||||
);
|
||||
},
|
||||
fields: initialValues, // mutable
|
||||
defaultValues: initialValues, // immutable
|
||||
};
|
||||
|
||||
export const BrowserAdvancedFieldsContext = createContext(defaultContext);
|
||||
|
||||
export const BrowserAdvancedFieldsContextProvider = ({
|
||||
children,
|
||||
defaultValues = initialValues,
|
||||
}: BrowserAdvancedFieldsContextProvider) => {
|
||||
const [fields, setFields] = useState<BrowserAdvancedFields>(defaultValues);
|
||||
|
||||
const value = useMemo(() => {
|
||||
return { fields, setFields, defaultValues };
|
||||
}, [fields, defaultValues]);
|
||||
|
||||
return <BrowserAdvancedFieldsContext.Provider value={value} children={children} />;
|
||||
};
|
||||
|
||||
export const useBrowserAdvancedFieldsContext = () => useContext(BrowserAdvancedFieldsContext);
|
|
@ -1,52 +0,0 @@
|
|||
/*
|
||||
* 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, { ReactNode } from 'react';
|
||||
import { BrowserFields, BrowserSimpleFields, BrowserAdvancedFields } from '../types';
|
||||
import {
|
||||
BrowserSimpleFieldsContextProvider,
|
||||
BrowserAdvancedFieldsContextProvider,
|
||||
defaultBrowserSimpleFields,
|
||||
defaultBrowserAdvancedFields,
|
||||
} from '.';
|
||||
import { formatDefaultValues } from '../helpers/context_helpers';
|
||||
|
||||
interface BrowserContextProviderProps {
|
||||
defaultValues?: BrowserFields;
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
export const BrowserContextProvider = ({
|
||||
defaultValues,
|
||||
children,
|
||||
}: BrowserContextProviderProps) => {
|
||||
const simpleKeys = Object.keys(defaultBrowserSimpleFields) as Array<keyof BrowserSimpleFields>;
|
||||
const advancedKeys = Object.keys(defaultBrowserAdvancedFields) as Array<
|
||||
keyof BrowserAdvancedFields
|
||||
>;
|
||||
const formattedDefaultSimpleFields = formatDefaultValues<BrowserSimpleFields>(
|
||||
simpleKeys,
|
||||
defaultValues || {}
|
||||
);
|
||||
const formattedDefaultAdvancedFields = formatDefaultValues<BrowserAdvancedFields>(
|
||||
advancedKeys,
|
||||
defaultValues || {}
|
||||
);
|
||||
const simpleFields: BrowserSimpleFields | undefined = defaultValues
|
||||
? formattedDefaultSimpleFields
|
||||
: undefined;
|
||||
const advancedFields: BrowserAdvancedFields | undefined = defaultValues
|
||||
? formattedDefaultAdvancedFields
|
||||
: undefined;
|
||||
return (
|
||||
<BrowserAdvancedFieldsContextProvider defaultValues={advancedFields}>
|
||||
<BrowserSimpleFieldsContextProvider defaultValues={simpleFields}>
|
||||
{children}
|
||||
</BrowserSimpleFieldsContextProvider>
|
||||
</BrowserAdvancedFieldsContextProvider>
|
||||
);
|
||||
};
|
|
@ -1,50 +0,0 @@
|
|||
/*
|
||||
* 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, { createContext, useContext, useMemo, useState } from 'react';
|
||||
import { HTTPSimpleFields } from '../types';
|
||||
import { DEFAULT_HTTP_SIMPLE_FIELDS } from '../../../../../common/constants/monitor_defaults';
|
||||
|
||||
interface HTTPSimpleFieldsContext {
|
||||
setFields: React.Dispatch<React.SetStateAction<HTTPSimpleFields>>;
|
||||
fields: HTTPSimpleFields;
|
||||
defaultValues: HTTPSimpleFields;
|
||||
}
|
||||
|
||||
interface HTTPSimpleFieldsContextProvider {
|
||||
children: React.ReactNode;
|
||||
defaultValues?: HTTPSimpleFields;
|
||||
}
|
||||
|
||||
export const initialValues: HTTPSimpleFields = DEFAULT_HTTP_SIMPLE_FIELDS;
|
||||
|
||||
const defaultContext: HTTPSimpleFieldsContext = {
|
||||
setFields: (_fields: React.SetStateAction<HTTPSimpleFields>) => {
|
||||
throw new Error(
|
||||
'setFields was not initialized for HTTP Simple Fields, set it when you invoke the context'
|
||||
);
|
||||
},
|
||||
fields: initialValues, // mutable
|
||||
defaultValues: initialValues, // immutable
|
||||
};
|
||||
|
||||
export const HTTPSimpleFieldsContext = createContext(defaultContext);
|
||||
|
||||
export const HTTPSimpleFieldsContextProvider = ({
|
||||
children,
|
||||
defaultValues = initialValues,
|
||||
}: HTTPSimpleFieldsContextProvider) => {
|
||||
const [fields, setFields] = useState<HTTPSimpleFields>(defaultValues);
|
||||
|
||||
const value = useMemo(() => {
|
||||
return { fields, setFields, defaultValues };
|
||||
}, [fields, defaultValues]);
|
||||
|
||||
return <HTTPSimpleFieldsContext.Provider value={value} children={children} />;
|
||||
};
|
||||
|
||||
export const useHTTPSimpleFieldsContext = () => useContext(HTTPSimpleFieldsContext);
|
|
@ -1,48 +0,0 @@
|
|||
/*
|
||||
* 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, { createContext, useContext, useMemo, useState } from 'react';
|
||||
import { HTTPAdvancedFields } from '../types';
|
||||
import { DEFAULT_HTTP_ADVANCED_FIELDS } from '../../../../../common/constants/monitor_defaults';
|
||||
|
||||
interface HTTPAdvancedFieldsContext {
|
||||
setFields: React.Dispatch<React.SetStateAction<HTTPAdvancedFields>>;
|
||||
fields: HTTPAdvancedFields;
|
||||
defaultValues: HTTPAdvancedFields;
|
||||
}
|
||||
|
||||
interface HTTPAdvancedFieldsContextProvider {
|
||||
children: React.ReactNode;
|
||||
defaultValues?: HTTPAdvancedFields;
|
||||
}
|
||||
|
||||
export const initialValues: HTTPAdvancedFields = DEFAULT_HTTP_ADVANCED_FIELDS;
|
||||
|
||||
export const defaultContext: HTTPAdvancedFieldsContext = {
|
||||
setFields: (_fields: React.SetStateAction<HTTPAdvancedFields>) => {
|
||||
throw new Error('setFields was not initialized, set it when you invoke the context');
|
||||
},
|
||||
fields: initialValues,
|
||||
defaultValues: initialValues,
|
||||
};
|
||||
|
||||
export const HTTPAdvancedFieldsContext = createContext(defaultContext);
|
||||
|
||||
export const HTTPAdvancedFieldsContextProvider = ({
|
||||
children,
|
||||
defaultValues = initialValues,
|
||||
}: HTTPAdvancedFieldsContextProvider) => {
|
||||
const [fields, setFields] = useState<HTTPAdvancedFields>(defaultValues);
|
||||
|
||||
const value = useMemo(() => {
|
||||
return { fields, setFields, defaultValues };
|
||||
}, [fields, defaultValues]);
|
||||
|
||||
return <HTTPAdvancedFieldsContext.Provider value={value} children={children} />;
|
||||
};
|
||||
|
||||
export const useHTTPAdvancedFieldsContext = () => useContext(HTTPAdvancedFieldsContext);
|
|
@ -1,45 +0,0 @@
|
|||
/*
|
||||
* 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, { ReactNode } from 'react';
|
||||
import { HTTPFields, HTTPSimpleFields, HTTPAdvancedFields } from '../types';
|
||||
import {
|
||||
HTTPSimpleFieldsContextProvider,
|
||||
HTTPAdvancedFieldsContextProvider,
|
||||
defaultHTTPSimpleFields,
|
||||
defaultHTTPAdvancedFields,
|
||||
} from '.';
|
||||
import { formatDefaultValues } from '../helpers/context_helpers';
|
||||
|
||||
interface HTTPContextProviderProps {
|
||||
defaultValues?: HTTPFields;
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
export const HTTPContextProvider = ({ defaultValues, children }: HTTPContextProviderProps) => {
|
||||
const simpleKeys = Object.keys(defaultHTTPSimpleFields) as Array<keyof HTTPSimpleFields>;
|
||||
const advancedKeys = Object.keys(defaultHTTPAdvancedFields) as Array<keyof HTTPAdvancedFields>;
|
||||
const formattedDefaultHTTPSimpleFields = formatDefaultValues<HTTPSimpleFields>(
|
||||
simpleKeys,
|
||||
defaultValues || {}
|
||||
);
|
||||
const formattedDefaultHTTPAdvancedFields = formatDefaultValues<HTTPAdvancedFields>(
|
||||
advancedKeys,
|
||||
defaultValues || {}
|
||||
);
|
||||
const httpAdvancedFields = defaultValues ? formattedDefaultHTTPAdvancedFields : undefined;
|
||||
const httpSimpleFields: HTTPSimpleFields | undefined = defaultValues
|
||||
? formattedDefaultHTTPSimpleFields
|
||||
: undefined;
|
||||
return (
|
||||
<HTTPAdvancedFieldsContextProvider defaultValues={httpAdvancedFields}>
|
||||
<HTTPSimpleFieldsContextProvider defaultValues={httpSimpleFields}>
|
||||
{children}
|
||||
</HTTPSimpleFieldsContextProvider>
|
||||
</HTTPAdvancedFieldsContextProvider>
|
||||
);
|
||||
};
|
|
@ -1,50 +0,0 @@
|
|||
/*
|
||||
* 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, { createContext, useContext, useMemo, useState } from 'react';
|
||||
import { ICMPSimpleFields } from '../types';
|
||||
import { DEFAULT_ICMP_SIMPLE_FIELDS } from '../../../../../common/constants/monitor_defaults';
|
||||
|
||||
interface ICMPSimpleFieldsContext {
|
||||
setFields: React.Dispatch<React.SetStateAction<ICMPSimpleFields>>;
|
||||
fields: ICMPSimpleFields;
|
||||
defaultValues: ICMPSimpleFields;
|
||||
}
|
||||
|
||||
interface ICMPSimpleFieldsContextProvider {
|
||||
children: React.ReactNode;
|
||||
defaultValues?: ICMPSimpleFields;
|
||||
}
|
||||
|
||||
export const initialValues: ICMPSimpleFields = DEFAULT_ICMP_SIMPLE_FIELDS;
|
||||
|
||||
const defaultContext: ICMPSimpleFieldsContext = {
|
||||
setFields: (_fields: React.SetStateAction<ICMPSimpleFields>) => {
|
||||
throw new Error(
|
||||
'setFields was not initialized for ICMP Simple Fields, set it when you invoke the context'
|
||||
);
|
||||
},
|
||||
fields: initialValues, // mutable
|
||||
defaultValues: initialValues, // immutable
|
||||
};
|
||||
|
||||
export const ICMPSimpleFieldsContext = createContext(defaultContext);
|
||||
|
||||
export const ICMPSimpleFieldsContextProvider = ({
|
||||
children,
|
||||
defaultValues = initialValues,
|
||||
}: ICMPSimpleFieldsContextProvider) => {
|
||||
const [fields, setFields] = useState<ICMPSimpleFields>(defaultValues);
|
||||
|
||||
const value = useMemo(() => {
|
||||
return { fields, setFields, defaultValues };
|
||||
}, [fields, defaultValues]);
|
||||
|
||||
return <ICMPSimpleFieldsContext.Provider value={value} children={children} />;
|
||||
};
|
||||
|
||||
export const useICMPSimpleFieldsContext = () => useContext(ICMPSimpleFieldsContext);
|
|
@ -1,66 +0,0 @@
|
|||
/*
|
||||
* 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 type { IPolicyConfigContextProvider } from './policy_config_context';
|
||||
export {
|
||||
PolicyConfigContext,
|
||||
PolicyConfigContextProvider,
|
||||
initialMonitorTypeValue as defaultPolicyConfig,
|
||||
defaultContext as defaultPolicyConfigValues,
|
||||
usePolicyConfigContext,
|
||||
} from './policy_config_context';
|
||||
export {
|
||||
HTTPSimpleFieldsContext,
|
||||
HTTPSimpleFieldsContextProvider,
|
||||
initialValues as defaultHTTPSimpleFields,
|
||||
useHTTPSimpleFieldsContext,
|
||||
} from './http_context';
|
||||
export {
|
||||
HTTPAdvancedFieldsContext,
|
||||
HTTPAdvancedFieldsContextProvider,
|
||||
initialValues as defaultHTTPAdvancedFields,
|
||||
useHTTPAdvancedFieldsContext,
|
||||
} from './http_context_advanced';
|
||||
export {
|
||||
TCPSimpleFieldsContext,
|
||||
TCPSimpleFieldsContextProvider,
|
||||
initialValues as defaultTCPSimpleFields,
|
||||
useTCPSimpleFieldsContext,
|
||||
} from './tcp_context';
|
||||
export {
|
||||
ICMPSimpleFieldsContext,
|
||||
ICMPSimpleFieldsContextProvider,
|
||||
initialValues as defaultICMPSimpleFields,
|
||||
useICMPSimpleFieldsContext,
|
||||
} from './icmp_context';
|
||||
export {
|
||||
TCPAdvancedFieldsContext,
|
||||
TCPAdvancedFieldsContextProvider,
|
||||
initialValues as defaultTCPAdvancedFields,
|
||||
useTCPAdvancedFieldsContext,
|
||||
} from './tcp_context_advanced';
|
||||
export {
|
||||
BrowserSimpleFieldsContext,
|
||||
BrowserSimpleFieldsContextProvider,
|
||||
initialValues as defaultBrowserSimpleFields,
|
||||
useBrowserSimpleFieldsContext,
|
||||
} from './browser_context';
|
||||
export {
|
||||
BrowserAdvancedFieldsContext,
|
||||
BrowserAdvancedFieldsContextProvider,
|
||||
initialValues as defaultBrowserAdvancedFields,
|
||||
useBrowserAdvancedFieldsContext,
|
||||
} from './browser_context_advanced';
|
||||
export {
|
||||
TLSFieldsContext,
|
||||
TLSFieldsContextProvider,
|
||||
initialValues as defaultTLSFields,
|
||||
useTLSFieldsContext,
|
||||
} from './tls_fields_context';
|
||||
export { HTTPContextProvider } from './http_provider';
|
||||
export { TCPContextProvider } from './tcp_provider';
|
||||
export { BrowserContextProvider } from './browser_provider';
|
||||
export { SyntheticsProviders } from './synthetics_context_providers';
|
|
@ -1,162 +0,0 @@
|
|||
/*
|
||||
* 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, { createContext, useContext, useEffect, useMemo, useState } from 'react';
|
||||
import { useRouteMatch } from 'react-router-dom';
|
||||
import { MONITOR_ADD_ROUTE } from '../../../../../common/constants';
|
||||
import { DEFAULT_NAMESPACE_STRING } from '../../../../../common/constants/monitor_defaults';
|
||||
import {
|
||||
ScheduleUnit,
|
||||
SourceType,
|
||||
MonitorServiceLocations,
|
||||
ThrottlingOptions,
|
||||
DEFAULT_THROTTLING,
|
||||
} from '../../../../../common/runtime_types';
|
||||
import { DataStream } from '../types';
|
||||
|
||||
interface IPolicyConfigContext {
|
||||
setMonitorType: React.Dispatch<React.SetStateAction<DataStream>>;
|
||||
setName: React.Dispatch<React.SetStateAction<string>>;
|
||||
setLocations: React.Dispatch<React.SetStateAction<MonitorServiceLocations>>;
|
||||
setIsTLSEnabled: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
setNamespace: React.Dispatch<React.SetStateAction<string>>;
|
||||
monitorType: DataStream;
|
||||
defaultMonitorType: DataStream;
|
||||
isTLSEnabled?: boolean;
|
||||
runsOnService?: boolean;
|
||||
defaultIsTLSEnabled?: boolean;
|
||||
isEditable?: boolean;
|
||||
defaultName?: string;
|
||||
name?: string;
|
||||
defaultLocations?: MonitorServiceLocations;
|
||||
locations?: MonitorServiceLocations;
|
||||
allowedScheduleUnits?: ScheduleUnit[];
|
||||
defaultNamespace?: string;
|
||||
namespace?: string;
|
||||
throttling: ThrottlingOptions;
|
||||
sourceType?: SourceType;
|
||||
}
|
||||
|
||||
export interface IPolicyConfigContextProvider {
|
||||
children: React.ReactNode;
|
||||
defaultMonitorType?: DataStream;
|
||||
runsOnService?: boolean;
|
||||
defaultIsTLSEnabled?: boolean;
|
||||
defaultName?: string;
|
||||
defaultLocations?: MonitorServiceLocations;
|
||||
defaultNamespace?: string;
|
||||
isEditable?: boolean;
|
||||
allowedScheduleUnits?: ScheduleUnit[];
|
||||
throttling?: ThrottlingOptions;
|
||||
sourceType?: SourceType;
|
||||
}
|
||||
|
||||
export const initialMonitorTypeValue = DataStream.HTTP;
|
||||
|
||||
export const defaultContext: IPolicyConfigContext = {
|
||||
setMonitorType: (_monitorType: React.SetStateAction<DataStream>) => {
|
||||
throw new Error('setMonitorType was not initialized, set it when you invoke the context');
|
||||
},
|
||||
setName: (_name: React.SetStateAction<string>) => {
|
||||
throw new Error('setName was not initialized, set it when you invoke the context');
|
||||
},
|
||||
setLocations: (_locations: React.SetStateAction<MonitorServiceLocations>) => {
|
||||
throw new Error('setLocations was not initialized, set it when you invoke the context');
|
||||
},
|
||||
setIsTLSEnabled: (_isTLSEnabled: React.SetStateAction<boolean>) => {
|
||||
throw new Error('setIsTLSEnabled was not initialized, set it when you invoke the context');
|
||||
},
|
||||
setNamespace: (_namespace: React.SetStateAction<string>) => {
|
||||
throw new Error('setNamespace was not initialized, set it when you invoke the context');
|
||||
},
|
||||
monitorType: initialMonitorTypeValue, // mutable
|
||||
defaultMonitorType: initialMonitorTypeValue, // immutable,
|
||||
runsOnService: false,
|
||||
defaultIsTLSEnabled: false,
|
||||
defaultName: '',
|
||||
defaultLocations: [],
|
||||
isEditable: false,
|
||||
allowedScheduleUnits: [ScheduleUnit.MINUTES, ScheduleUnit.SECONDS],
|
||||
defaultNamespace: DEFAULT_NAMESPACE_STRING,
|
||||
throttling: DEFAULT_THROTTLING,
|
||||
sourceType: SourceType.UI,
|
||||
};
|
||||
|
||||
export const PolicyConfigContext = createContext(defaultContext);
|
||||
|
||||
export function PolicyConfigContextProvider<ExtraFields = unknown>({
|
||||
children,
|
||||
throttling = DEFAULT_THROTTLING,
|
||||
defaultMonitorType = initialMonitorTypeValue,
|
||||
defaultIsTLSEnabled = false,
|
||||
defaultName = '',
|
||||
defaultLocations = [],
|
||||
defaultNamespace = DEFAULT_NAMESPACE_STRING,
|
||||
isEditable = false,
|
||||
runsOnService = false,
|
||||
allowedScheduleUnits = [ScheduleUnit.MINUTES, ScheduleUnit.SECONDS],
|
||||
sourceType,
|
||||
}: IPolicyConfigContextProvider) {
|
||||
const [monitorType, setMonitorType] = useState<DataStream>(defaultMonitorType);
|
||||
const [name, setName] = useState<string>(defaultName);
|
||||
const [locations, setLocations] = useState<MonitorServiceLocations>(defaultLocations);
|
||||
const [isTLSEnabled, setIsTLSEnabled] = useState<boolean>(defaultIsTLSEnabled);
|
||||
const [namespace, setNamespace] = useState<string>(defaultNamespace);
|
||||
|
||||
const isAddMonitorRoute = useRouteMatch(MONITOR_ADD_ROUTE);
|
||||
|
||||
useEffect(() => {
|
||||
if (isAddMonitorRoute?.isExact) {
|
||||
setMonitorType(DataStream.BROWSER);
|
||||
}
|
||||
}, [isAddMonitorRoute?.isExact]);
|
||||
|
||||
const value = useMemo(() => {
|
||||
return {
|
||||
monitorType,
|
||||
setMonitorType,
|
||||
defaultMonitorType,
|
||||
runsOnService,
|
||||
isTLSEnabled,
|
||||
setIsTLSEnabled,
|
||||
defaultIsTLSEnabled,
|
||||
isEditable,
|
||||
defaultName,
|
||||
name,
|
||||
setName,
|
||||
defaultLocations,
|
||||
locations,
|
||||
setLocations,
|
||||
allowedScheduleUnits,
|
||||
namespace,
|
||||
setNamespace,
|
||||
throttling,
|
||||
sourceType,
|
||||
} as IPolicyConfigContext;
|
||||
}, [
|
||||
monitorType,
|
||||
defaultMonitorType,
|
||||
runsOnService,
|
||||
isTLSEnabled,
|
||||
defaultIsTLSEnabled,
|
||||
isEditable,
|
||||
name,
|
||||
defaultName,
|
||||
locations,
|
||||
defaultLocations,
|
||||
allowedScheduleUnits,
|
||||
namespace,
|
||||
throttling,
|
||||
sourceType,
|
||||
]);
|
||||
|
||||
return <PolicyConfigContext.Provider value={value} children={children} />;
|
||||
}
|
||||
|
||||
export function usePolicyConfigContext() {
|
||||
return useContext(PolicyConfigContext);
|
||||
}
|
|
@ -1,53 +0,0 @@
|
|||
/*
|
||||
* 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 from 'react';
|
||||
import { HTTPFields, TCPFields, ICMPFields, BrowserFields, TLSFields } from '../types';
|
||||
import {
|
||||
PolicyConfigContextProvider,
|
||||
TCPContextProvider,
|
||||
ICMPSimpleFieldsContextProvider,
|
||||
HTTPContextProvider,
|
||||
BrowserContextProvider,
|
||||
TLSFieldsContextProvider,
|
||||
} from '.';
|
||||
import { IPolicyConfigContextProvider } from './policy_config_context';
|
||||
interface Props {
|
||||
children: React.ReactNode;
|
||||
httpDefaultValues?: HTTPFields;
|
||||
tcpDefaultValues?: TCPFields;
|
||||
icmpDefaultValues?: ICMPFields;
|
||||
browserDefaultValues?: BrowserFields;
|
||||
tlsDefaultValues?: TLSFields;
|
||||
policyDefaultValues?: Omit<IPolicyConfigContextProvider, 'children'>;
|
||||
}
|
||||
|
||||
export const SyntheticsProviders = ({
|
||||
children,
|
||||
httpDefaultValues,
|
||||
tcpDefaultValues,
|
||||
icmpDefaultValues,
|
||||
browserDefaultValues,
|
||||
tlsDefaultValues,
|
||||
policyDefaultValues,
|
||||
}: Props) => {
|
||||
return (
|
||||
<PolicyConfigContextProvider {...policyDefaultValues}>
|
||||
<HTTPContextProvider defaultValues={httpDefaultValues}>
|
||||
<TCPContextProvider defaultValues={tcpDefaultValues}>
|
||||
<TLSFieldsContextProvider defaultValues={tlsDefaultValues}>
|
||||
<ICMPSimpleFieldsContextProvider defaultValues={icmpDefaultValues}>
|
||||
<BrowserContextProvider defaultValues={browserDefaultValues}>
|
||||
{children}
|
||||
</BrowserContextProvider>
|
||||
</ICMPSimpleFieldsContextProvider>
|
||||
</TLSFieldsContextProvider>
|
||||
</TCPContextProvider>
|
||||
</HTTPContextProvider>
|
||||
</PolicyConfigContextProvider>
|
||||
);
|
||||
};
|
|
@ -1,50 +0,0 @@
|
|||
/*
|
||||
* 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, { createContext, useContext, useMemo, useState } from 'react';
|
||||
import { TCPSimpleFields } from '../types';
|
||||
import { DEFAULT_TCP_SIMPLE_FIELDS } from '../../../../../common/constants/monitor_defaults';
|
||||
|
||||
interface TCPSimpleFieldsContext {
|
||||
setFields: React.Dispatch<React.SetStateAction<TCPSimpleFields>>;
|
||||
fields: TCPSimpleFields;
|
||||
defaultValues: TCPSimpleFields;
|
||||
}
|
||||
|
||||
interface TCPSimpleFieldsContextProvider {
|
||||
children: React.ReactNode;
|
||||
defaultValues?: TCPSimpleFields;
|
||||
}
|
||||
|
||||
export const initialValues: TCPSimpleFields = DEFAULT_TCP_SIMPLE_FIELDS;
|
||||
|
||||
const defaultContext: TCPSimpleFieldsContext = {
|
||||
setFields: (_fields: React.SetStateAction<TCPSimpleFields>) => {
|
||||
throw new Error(
|
||||
'setFields was not initialized for TCP Simple Fields, set it when you invoke the context'
|
||||
);
|
||||
},
|
||||
fields: initialValues, // mutable
|
||||
defaultValues: initialValues, // immutable
|
||||
};
|
||||
|
||||
export const TCPSimpleFieldsContext = createContext(defaultContext);
|
||||
|
||||
export const TCPSimpleFieldsContextProvider = ({
|
||||
children,
|
||||
defaultValues = initialValues,
|
||||
}: TCPSimpleFieldsContextProvider) => {
|
||||
const [fields, setFields] = useState<TCPSimpleFields>(defaultValues);
|
||||
|
||||
const value = useMemo(() => {
|
||||
return { fields, setFields, defaultValues };
|
||||
}, [fields, defaultValues]);
|
||||
|
||||
return <TCPSimpleFieldsContext.Provider value={value} children={children} />;
|
||||
};
|
||||
|
||||
export const useTCPSimpleFieldsContext = () => useContext(TCPSimpleFieldsContext);
|
|
@ -1,48 +0,0 @@
|
|||
/*
|
||||
* 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, { createContext, useContext, useMemo, useState } from 'react';
|
||||
import { TCPAdvancedFields } from '../types';
|
||||
import { DEFAULT_TCP_ADVANCED_FIELDS } from '../../../../../common/constants/monitor_defaults';
|
||||
|
||||
interface TCPAdvancedFieldsContext {
|
||||
setFields: React.Dispatch<React.SetStateAction<TCPAdvancedFields>>;
|
||||
fields: TCPAdvancedFields;
|
||||
defaultValues: TCPAdvancedFields;
|
||||
}
|
||||
|
||||
interface TCPAdvancedFieldsContextProvider {
|
||||
children: React.ReactNode;
|
||||
defaultValues?: TCPAdvancedFields;
|
||||
}
|
||||
|
||||
export const initialValues: TCPAdvancedFields = DEFAULT_TCP_ADVANCED_FIELDS;
|
||||
|
||||
const defaultContext: TCPAdvancedFieldsContext = {
|
||||
setFields: (_fields: React.SetStateAction<TCPAdvancedFields>) => {
|
||||
throw new Error('setFields was not initialized, set it when you invoke the context');
|
||||
},
|
||||
fields: initialValues, // mutable
|
||||
defaultValues: initialValues, // immutable
|
||||
};
|
||||
|
||||
export const TCPAdvancedFieldsContext = createContext(defaultContext);
|
||||
|
||||
export const TCPAdvancedFieldsContextProvider = ({
|
||||
children,
|
||||
defaultValues = initialValues,
|
||||
}: TCPAdvancedFieldsContextProvider) => {
|
||||
const [fields, setFields] = useState<TCPAdvancedFields>(defaultValues);
|
||||
|
||||
const value = useMemo(() => {
|
||||
return { fields, setFields, defaultValues };
|
||||
}, [fields, defaultValues]);
|
||||
|
||||
return <TCPAdvancedFieldsContext.Provider value={value} children={children} />;
|
||||
};
|
||||
|
||||
export const useTCPAdvancedFieldsContext = () => useContext(TCPAdvancedFieldsContext);
|
|
@ -1,47 +0,0 @@
|
|||
/*
|
||||
* 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, { ReactNode } from 'react';
|
||||
import { TCPFields, TCPSimpleFields, TCPAdvancedFields } from '../types';
|
||||
import {
|
||||
TCPSimpleFieldsContextProvider,
|
||||
TCPAdvancedFieldsContextProvider,
|
||||
defaultTCPSimpleFields,
|
||||
defaultTCPAdvancedFields,
|
||||
} from '.';
|
||||
import { formatDefaultValues } from '../helpers/context_helpers';
|
||||
|
||||
interface TCPContextProviderProps {
|
||||
defaultValues?: TCPFields;
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
export const TCPContextProvider = ({ defaultValues, children }: TCPContextProviderProps) => {
|
||||
const simpleKeys = Object.keys(defaultTCPSimpleFields) as Array<keyof TCPSimpleFields>;
|
||||
const advancedKeys = Object.keys(defaultTCPAdvancedFields) as Array<keyof TCPAdvancedFields>;
|
||||
const formattedDefaultSimpleFields = formatDefaultValues<TCPSimpleFields>(
|
||||
simpleKeys,
|
||||
defaultValues || {}
|
||||
);
|
||||
const formattedDefaultAdvancedFields = formatDefaultValues<TCPAdvancedFields>(
|
||||
advancedKeys,
|
||||
defaultValues || {}
|
||||
);
|
||||
const simpleFields: TCPSimpleFields | undefined = defaultValues
|
||||
? formattedDefaultSimpleFields
|
||||
: undefined;
|
||||
const advancedFields: TCPAdvancedFields | undefined = defaultValues
|
||||
? formattedDefaultAdvancedFields
|
||||
: undefined;
|
||||
return (
|
||||
<TCPAdvancedFieldsContextProvider defaultValues={advancedFields}>
|
||||
<TCPSimpleFieldsContextProvider defaultValues={simpleFields}>
|
||||
{children}
|
||||
</TCPSimpleFieldsContextProvider>
|
||||
</TCPAdvancedFieldsContextProvider>
|
||||
);
|
||||
};
|
|
@ -1,48 +0,0 @@
|
|||
/*
|
||||
* 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, { createContext, useContext, useMemo, useState } from 'react';
|
||||
import { TLSFields } from '../types';
|
||||
import { DEFAULT_TLS_FIELDS } from '../../../../../common/constants/monitor_defaults';
|
||||
|
||||
interface TLSFieldsContext {
|
||||
setFields: React.Dispatch<React.SetStateAction<TLSFields>>;
|
||||
fields: TLSFields;
|
||||
defaultValues: TLSFields;
|
||||
}
|
||||
|
||||
interface TLSFieldsContextProvider {
|
||||
children: React.ReactNode;
|
||||
defaultValues?: TLSFields;
|
||||
}
|
||||
|
||||
export const initialValues: TLSFields = DEFAULT_TLS_FIELDS;
|
||||
|
||||
const defaultContext: TLSFieldsContext = {
|
||||
setFields: (_fields: React.SetStateAction<TLSFields>) => {
|
||||
throw new Error('setFields was not initialized, set it when you invoke the context');
|
||||
},
|
||||
fields: initialValues, // mutable
|
||||
defaultValues: initialValues, // immutable
|
||||
};
|
||||
|
||||
export const TLSFieldsContext = createContext(defaultContext);
|
||||
|
||||
export const TLSFieldsContextProvider = ({
|
||||
children,
|
||||
defaultValues = initialValues,
|
||||
}: TLSFieldsContextProvider) => {
|
||||
const [fields, setFields] = useState<TLSFields>(defaultValues);
|
||||
|
||||
const value = useMemo(() => {
|
||||
return { fields, setFields, defaultValues };
|
||||
}, [fields, defaultValues]);
|
||||
|
||||
return <TLSFieldsContext.Provider value={value} children={children} />;
|
||||
};
|
||||
|
||||
export const useTLSFieldsContext = () => useContext(TLSFieldsContext);
|
|
@ -1,393 +0,0 @@
|
|||
/*
|
||||
* 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 'jest-canvas-mock';
|
||||
|
||||
import React from 'react';
|
||||
import { screen, fireEvent, waitFor } from '@testing-library/react';
|
||||
import { render } from '../../lib/helper/rtl_helpers';
|
||||
import {
|
||||
TCPContextProvider,
|
||||
HTTPContextProvider,
|
||||
BrowserContextProvider,
|
||||
ICMPSimpleFieldsContextProvider,
|
||||
PolicyConfigContextProvider,
|
||||
TLSFieldsContextProvider,
|
||||
} from './contexts';
|
||||
import { CustomFields } from './custom_fields';
|
||||
import { ConfigKey, DataStream, ScheduleUnit } from './types';
|
||||
import { validate as centralValidation } from '../monitor_management/validation';
|
||||
import { defaultConfig } from './synthetics_policy_create_extension';
|
||||
|
||||
// ensures that fields appropriately match to their label
|
||||
jest.mock('@elastic/eui/lib/services/accessibility/html_id_generator', () => ({
|
||||
...jest.requireActual('@elastic/eui/lib/services/accessibility/html_id_generator'),
|
||||
htmlIdGenerator: () => () => `id-${Math.random()}`,
|
||||
}));
|
||||
|
||||
// ensures that fields appropriately match to their label
|
||||
jest.mock('@elastic/eui/lib/services/accessibility', () => ({
|
||||
...jest.requireActual('@elastic/eui/lib/services/accessibility'),
|
||||
useGeneratedHtmlId: () => `id-${Math.random()}`,
|
||||
}));
|
||||
|
||||
jest.mock('@kbn/kibana-react-plugin/public', () => {
|
||||
const original = jest.requireActual('@kbn/kibana-react-plugin/public');
|
||||
return {
|
||||
...original,
|
||||
// Mocking CodeEditor, which uses React Monaco under the hood
|
||||
CodeEditor: (props: any) => (
|
||||
<input
|
||||
data-test-subj={props['data-test-subj'] || 'mockCodeEditor'}
|
||||
data-currentvalue={props.value}
|
||||
onChange={(e: any) => {
|
||||
props.onChange(e.jsonContent);
|
||||
}}
|
||||
/>
|
||||
),
|
||||
};
|
||||
});
|
||||
|
||||
const defaultValidation = centralValidation[DataStream.HTTP];
|
||||
|
||||
const defaultHTTPConfig = defaultConfig[DataStream.HTTP];
|
||||
const defaultTCPConfig = defaultConfig[DataStream.TCP];
|
||||
|
||||
describe('<CustomFields />', () => {
|
||||
let onFieldBlurMock: jest.Mock | undefined;
|
||||
|
||||
const WrappedComponent = ({
|
||||
validate = defaultValidation,
|
||||
isEditable = false,
|
||||
dataStreams = [DataStream.HTTP, DataStream.TCP, DataStream.ICMP, DataStream.BROWSER],
|
||||
onFieldBlur = onFieldBlurMock,
|
||||
}) => {
|
||||
return (
|
||||
<HTTPContextProvider>
|
||||
<PolicyConfigContextProvider isEditable={isEditable}>
|
||||
<TCPContextProvider>
|
||||
<BrowserContextProvider>
|
||||
<ICMPSimpleFieldsContextProvider>
|
||||
<TLSFieldsContextProvider>
|
||||
<CustomFields
|
||||
validate={validate}
|
||||
dataStreams={dataStreams}
|
||||
onFieldBlur={onFieldBlur}
|
||||
/>
|
||||
</TLSFieldsContextProvider>
|
||||
</ICMPSimpleFieldsContextProvider>
|
||||
</BrowserContextProvider>
|
||||
</TCPContextProvider>
|
||||
</PolicyConfigContextProvider>
|
||||
</HTTPContextProvider>
|
||||
);
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
onFieldBlurMock = undefined;
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
it('renders CustomFields', async () => {
|
||||
const { getByText, getByLabelText, queryByLabelText } = render(
|
||||
<WrappedComponent onFieldBlur={undefined} />
|
||||
);
|
||||
const monitorType = getByLabelText('Monitor Type') as HTMLInputElement;
|
||||
const url = getByLabelText('URL') as HTMLInputElement;
|
||||
const proxyUrl = getByLabelText('Proxy URL') as HTMLInputElement;
|
||||
const monitorIntervalNumber = getByLabelText('Number') as HTMLInputElement;
|
||||
const monitorIntervalUnit = getByLabelText('Unit') as HTMLInputElement;
|
||||
const apmServiceName = getByLabelText('APM service name') as HTMLInputElement;
|
||||
const maxRedirects = getByLabelText('Max redirects') as HTMLInputElement;
|
||||
const timeout = getByLabelText('Timeout in seconds') as HTMLInputElement;
|
||||
expect(monitorType).toBeInTheDocument();
|
||||
expect(url).toBeInTheDocument();
|
||||
expect(url.value).toEqual(defaultHTTPConfig[ConfigKey.URLS]);
|
||||
expect(proxyUrl).toBeInTheDocument();
|
||||
expect(proxyUrl.value).toEqual(defaultHTTPConfig[ConfigKey.PROXY_URL]);
|
||||
expect(monitorIntervalNumber).toBeInTheDocument();
|
||||
expect(monitorIntervalNumber.value).toEqual(defaultHTTPConfig[ConfigKey.SCHEDULE].number);
|
||||
expect(monitorIntervalUnit).toBeInTheDocument();
|
||||
expect(monitorIntervalUnit.value).toEqual(defaultHTTPConfig[ConfigKey.SCHEDULE].unit);
|
||||
// expect(tags).toBeInTheDocument();
|
||||
expect(apmServiceName).toBeInTheDocument();
|
||||
expect(apmServiceName.value).toEqual(defaultHTTPConfig[ConfigKey.APM_SERVICE_NAME]);
|
||||
expect(maxRedirects).toBeInTheDocument();
|
||||
expect(maxRedirects.value).toEqual(`${defaultHTTPConfig[ConfigKey.MAX_REDIRECTS]}`);
|
||||
expect(timeout).toBeInTheDocument();
|
||||
expect(timeout.value).toEqual(`${defaultHTTPConfig[ConfigKey.TIMEOUT]}`);
|
||||
|
||||
// ensure other monitor type options are not in the DOM
|
||||
expect(queryByLabelText('Host')).not.toBeInTheDocument();
|
||||
expect(queryByLabelText('Wait in seconds')).not.toBeInTheDocument();
|
||||
|
||||
// ensure at least one http advanced option is present
|
||||
const advancedOptionsButton = getByText('Advanced HTTP options');
|
||||
fireEvent.click(advancedOptionsButton);
|
||||
await waitFor(() => {
|
||||
expect(getByLabelText('Request method')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('does not show monitor type dropdown when isEditable is true', async () => {
|
||||
const { queryByLabelText } = render(<WrappedComponent isEditable />);
|
||||
const monitorType = queryByLabelText('Monitor Type') as HTMLInputElement;
|
||||
|
||||
expect(monitorType).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('shows SSL fields when Enable SSL Fields is checked', async () => {
|
||||
const { findByLabelText, queryByLabelText } = render(<WrappedComponent />);
|
||||
const enableSSL = queryByLabelText('Enable TLS configuration') as HTMLInputElement;
|
||||
expect(queryByLabelText('Certificate authorities')).not.toBeInTheDocument();
|
||||
expect(queryByLabelText('Client key')).not.toBeInTheDocument();
|
||||
expect(queryByLabelText('Client certificate')).not.toBeInTheDocument();
|
||||
expect(queryByLabelText('Client key passphrase')).not.toBeInTheDocument();
|
||||
expect(queryByLabelText('Verification mode')).not.toBeInTheDocument();
|
||||
|
||||
// ensure at least one http advanced option is present
|
||||
fireEvent.click(enableSSL);
|
||||
|
||||
const ca = (await findByLabelText('Certificate authorities')) as HTMLInputElement;
|
||||
const clientKey = (await findByLabelText('Client key')) as HTMLInputElement;
|
||||
const clientKeyPassphrase = (await findByLabelText(
|
||||
'Client key passphrase'
|
||||
)) as HTMLInputElement;
|
||||
const clientCertificate = (await findByLabelText('Client certificate')) as HTMLInputElement;
|
||||
const verificationMode = (await findByLabelText('Verification mode')) as HTMLInputElement;
|
||||
expect(ca).toBeInTheDocument();
|
||||
expect(clientKey).toBeInTheDocument();
|
||||
expect(clientKeyPassphrase).toBeInTheDocument();
|
||||
expect(clientCertificate).toBeInTheDocument();
|
||||
expect(verificationMode).toBeInTheDocument();
|
||||
|
||||
await waitFor(() => {
|
||||
expect(ca.value).toEqual(defaultHTTPConfig[ConfigKey.TLS_CERTIFICATE_AUTHORITIES]);
|
||||
expect(clientKey.value).toEqual(defaultHTTPConfig[ConfigKey.TLS_KEY]);
|
||||
expect(clientKeyPassphrase.value).toEqual(defaultHTTPConfig[ConfigKey.TLS_KEY_PASSPHRASE]);
|
||||
expect(clientCertificate.value).toEqual(defaultHTTPConfig[ConfigKey.TLS_CERTIFICATE]);
|
||||
expect(verificationMode.value).toEqual(defaultHTTPConfig[ConfigKey.TLS_VERIFICATION_MODE]);
|
||||
});
|
||||
});
|
||||
|
||||
it('handles updating each field (besides TLS)', async () => {
|
||||
const { getByLabelText } = render(<WrappedComponent />);
|
||||
const url = getByLabelText('URL') as HTMLInputElement;
|
||||
const proxyUrl = getByLabelText('Proxy URL') as HTMLInputElement;
|
||||
const monitorIntervalNumber = getByLabelText('Number') as HTMLInputElement;
|
||||
const monitorIntervalUnit = getByLabelText('Unit') as HTMLInputElement;
|
||||
const apmServiceName = getByLabelText('APM service name') as HTMLInputElement;
|
||||
const maxRedirects = getByLabelText('Max redirects') as HTMLInputElement;
|
||||
const timeout = getByLabelText('Timeout in seconds') as HTMLInputElement;
|
||||
|
||||
fireEvent.change(url, { target: { value: 'http://elastic.co' } });
|
||||
fireEvent.change(proxyUrl, { target: { value: 'http://proxy.co' } });
|
||||
fireEvent.change(monitorIntervalNumber, { target: { value: '1' } });
|
||||
fireEvent.change(monitorIntervalUnit, { target: { value: ScheduleUnit.MINUTES } });
|
||||
fireEvent.change(apmServiceName, { target: { value: 'APM Service' } });
|
||||
fireEvent.change(maxRedirects, { target: { value: '2' } });
|
||||
fireEvent.change(timeout, { target: { value: '3' } });
|
||||
|
||||
expect(url.value).toEqual('http://elastic.co');
|
||||
expect(proxyUrl.value).toEqual('http://proxy.co');
|
||||
expect(monitorIntervalNumber.value).toEqual('1');
|
||||
expect(monitorIntervalUnit.value).toEqual(ScheduleUnit.MINUTES);
|
||||
expect(apmServiceName.value).toEqual('APM Service');
|
||||
expect(maxRedirects.value).toEqual('2');
|
||||
expect(timeout.value).toEqual('3');
|
||||
});
|
||||
|
||||
it('handles switching monitor type', () => {
|
||||
const { getByText, queryByText, getByLabelText, queryByLabelText } = render(
|
||||
<WrappedComponent />
|
||||
);
|
||||
const monitorType = getByLabelText('Monitor Type') as HTMLInputElement;
|
||||
expect(monitorType).toBeInTheDocument();
|
||||
expect(monitorType.value).toEqual(defaultHTTPConfig[ConfigKey.MONITOR_TYPE]);
|
||||
fireEvent.change(monitorType, { target: { value: DataStream.TCP } });
|
||||
|
||||
// expect tcp fields to be in the DOM
|
||||
const host = getByLabelText('Host:Port') as HTMLInputElement;
|
||||
|
||||
expect(host).toBeInTheDocument();
|
||||
expect(host.value).toEqual(defaultTCPConfig[ConfigKey.HOSTS]);
|
||||
|
||||
// expect HTTP fields not to be in the DOM
|
||||
expect(queryByLabelText('URL')).not.toBeInTheDocument();
|
||||
expect(queryByLabelText('Max redirects')).not.toBeInTheDocument();
|
||||
|
||||
// expect tls options to be available for TCP
|
||||
// here we must getByText because EUI will generate duplicate aria-labelledby
|
||||
// values within the test-env generator used, and that will conflict with other
|
||||
// automatically generated labels. See:
|
||||
// https://github.com/elastic/eui/blob/91b416dcd51e116edb2cb4a2cac4c306512e28c7/src/services/accessibility/html_id_generator.testenv.ts#L12
|
||||
expect(queryByText(/Enable TLS configuration/)).toBeInTheDocument();
|
||||
|
||||
// ensure at least one tcp advanced option is present
|
||||
let advancedOptionsButton = getByText('Advanced TCP options');
|
||||
fireEvent.click(advancedOptionsButton);
|
||||
|
||||
expect(queryByLabelText('Request method')).not.toBeInTheDocument();
|
||||
expect(getByLabelText('Request payload')).toBeInTheDocument();
|
||||
|
||||
fireEvent.change(monitorType, { target: { value: DataStream.ICMP } });
|
||||
|
||||
// expect ICMP fields to be in the DOM
|
||||
expect(getByLabelText('Wait in seconds')).toBeInTheDocument();
|
||||
|
||||
// expect tls options not to be available for ICMP
|
||||
expect(queryByText(/Enable TLS configuration/)).not.toBeInTheDocument();
|
||||
|
||||
// expect TCP fields not to be in the DOM
|
||||
expect(queryByLabelText('Proxy URL')).not.toBeInTheDocument();
|
||||
|
||||
fireEvent.change(monitorType, { target: { value: DataStream.BROWSER } });
|
||||
|
||||
// expect browser fields to be in the DOM
|
||||
expect(
|
||||
screen.getByText('Runs Synthetic test scripts that are defined inline.')
|
||||
).toBeInTheDocument();
|
||||
|
||||
expect(
|
||||
getByText(/To create a "Browser" monitor, please ensure you are using the/)
|
||||
).toBeInTheDocument();
|
||||
|
||||
// ensure at least one browser advanced option is present
|
||||
advancedOptionsButton = getByText('Advanced Browser options');
|
||||
fireEvent.click(advancedOptionsButton);
|
||||
expect(getByLabelText('Screenshot options')).toBeInTheDocument();
|
||||
|
||||
// expect ICMP fields not to be in the DOM
|
||||
expect(queryByLabelText('Wait in seconds')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('does not show timeout for browser monitors', () => {
|
||||
const { getByLabelText, queryByLabelText } = render(<WrappedComponent />);
|
||||
const monitorType = getByLabelText('Monitor Type') as HTMLInputElement;
|
||||
let timeout = getByLabelText('Timeout in seconds') as HTMLInputElement;
|
||||
expect(monitorType).toBeInTheDocument();
|
||||
expect(monitorType.value).toEqual(defaultHTTPConfig[ConfigKey.MONITOR_TYPE]);
|
||||
expect(timeout.value).toEqual(defaultHTTPConfig[ConfigKey.TIMEOUT]);
|
||||
|
||||
// change to browser monitor
|
||||
fireEvent.change(monitorType, { target: { value: DataStream.BROWSER } });
|
||||
|
||||
// expect timeout not to be in the DOM
|
||||
expect(queryByLabelText('Timeout in seconds')).not.toBeInTheDocument();
|
||||
|
||||
// change back to HTTP
|
||||
fireEvent.change(monitorType, { target: { value: DataStream.HTTP } });
|
||||
|
||||
// expect timeout value to be present with the correct value
|
||||
timeout = getByLabelText('Timeout in seconds') as HTMLInputElement;
|
||||
expect(timeout.value).toEqual(defaultHTTPConfig[ConfigKey.TIMEOUT]);
|
||||
});
|
||||
|
||||
it('shows resolve hostnames locally field when proxy url is filled for tcp monitors', () => {
|
||||
const { getByLabelText, queryByLabelText } = render(<WrappedComponent />);
|
||||
const monitorType = getByLabelText('Monitor Type') as HTMLInputElement;
|
||||
fireEvent.change(monitorType, { target: { value: DataStream.TCP } });
|
||||
|
||||
expect(queryByLabelText('Resolve hostnames locally')).not.toBeInTheDocument();
|
||||
|
||||
const proxyUrl = getByLabelText('Proxy URL') as HTMLInputElement;
|
||||
|
||||
fireEvent.change(proxyUrl, { target: { value: 'sampleProxyUrl' } });
|
||||
|
||||
expect(getByLabelText('Resolve hostnames locally')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('handles validation', () => {
|
||||
const { getByText, getByLabelText, queryByText } = render(<WrappedComponent />);
|
||||
|
||||
const url = getByLabelText('URL') as HTMLInputElement;
|
||||
const monitorIntervalNumber = getByLabelText('Number') as HTMLInputElement;
|
||||
const maxRedirects = getByLabelText('Max redirects') as HTMLInputElement;
|
||||
const timeout = getByLabelText('Timeout in seconds') as HTMLInputElement;
|
||||
|
||||
// create errors
|
||||
fireEvent.change(monitorIntervalNumber, { target: { value: '-1' } });
|
||||
fireEvent.change(maxRedirects, { target: { value: '-1' } });
|
||||
fireEvent.change(timeout, { target: { value: '-1' } });
|
||||
|
||||
const urlError = getByText('URL is required');
|
||||
const monitorIntervalError = getByText('Monitor frequency is required');
|
||||
const maxRedirectsError = getByText('Max redirects must be 0 or greater');
|
||||
const timeoutError = getByText('Timeout must be greater than or equal to 0');
|
||||
|
||||
expect(urlError).toBeInTheDocument();
|
||||
expect(monitorIntervalError).toBeInTheDocument();
|
||||
expect(maxRedirectsError).toBeInTheDocument();
|
||||
expect(timeoutError).toBeInTheDocument();
|
||||
|
||||
// resolve errors
|
||||
fireEvent.change(url, { target: { value: 'http://elastic.co' } });
|
||||
fireEvent.change(monitorIntervalNumber, { target: { value: '1' } });
|
||||
fireEvent.change(maxRedirects, { target: { value: '1' } });
|
||||
fireEvent.change(timeout, { target: { value: '1' } });
|
||||
|
||||
expect(queryByText('URL is required')).not.toBeInTheDocument();
|
||||
expect(queryByText('Monitor frequency is required')).not.toBeInTheDocument();
|
||||
expect(queryByText('Max redirects must be 0 or greater')).not.toBeInTheDocument();
|
||||
expect(queryByText('Timeout must be greater than or equal to 0')).not.toBeInTheDocument();
|
||||
|
||||
// create more errors
|
||||
fireEvent.change(monitorIntervalNumber, { target: { value: '1' } }); // 1 minute
|
||||
fireEvent.change(timeout, { target: { value: '611' } }); // timeout cannot be more than monitor interval
|
||||
|
||||
const timeoutError2 = getByText('Timeout must be less than the monitor frequency');
|
||||
|
||||
expect(timeoutError2).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('does not show monitor options that are not contained in datastreams', async () => {
|
||||
const { getByText, queryByText, queryByLabelText } = render(
|
||||
<WrappedComponent dataStreams={[DataStream.HTTP, DataStream.TCP, DataStream.ICMP]} />
|
||||
);
|
||||
|
||||
const monitorType = queryByLabelText('Monitor Type') as HTMLInputElement;
|
||||
|
||||
// resolve errors
|
||||
fireEvent.click(monitorType);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(getByText('HTTP')).toBeInTheDocument();
|
||||
expect(getByText('TCP')).toBeInTheDocument();
|
||||
expect(getByText('ICMP')).toBeInTheDocument();
|
||||
expect(queryByText('Browser (Beta)')).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('allows monitors to be disabled', async () => {
|
||||
const { queryByLabelText } = render(
|
||||
<WrappedComponent dataStreams={[DataStream.HTTP, DataStream.TCP, DataStream.ICMP]} />
|
||||
);
|
||||
|
||||
const enabled = queryByLabelText('Enabled') as HTMLInputElement;
|
||||
expect(enabled).toBeChecked();
|
||||
|
||||
fireEvent.click(enabled);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(enabled).not.toBeChecked();
|
||||
});
|
||||
});
|
||||
|
||||
it('calls onFieldBlur on fields', () => {
|
||||
onFieldBlurMock = jest.fn();
|
||||
const { queryByLabelText } = render(
|
||||
<WrappedComponent
|
||||
dataStreams={[DataStream.HTTP, DataStream.TCP, DataStream.ICMP]}
|
||||
onFieldBlur={onFieldBlurMock}
|
||||
/>
|
||||
);
|
||||
|
||||
const monitorTypeSelect = queryByLabelText('Monitor Type') as HTMLInputElement;
|
||||
fireEvent.click(monitorTypeSelect);
|
||||
fireEvent.blur(monitorTypeSelect);
|
||||
expect(onFieldBlurMock).toHaveBeenCalledWith(ConfigKey.MONITOR_TYPE);
|
||||
});
|
||||
});
|
|
@ -1,253 +0,0 @@
|
|||
/*
|
||||
* 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, { useMemo, memo } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import {
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiForm,
|
||||
EuiFormRow,
|
||||
EuiSelect,
|
||||
EuiSpacer,
|
||||
EuiSwitch,
|
||||
EuiCallOut,
|
||||
EuiCode,
|
||||
EuiLink,
|
||||
} from '@elastic/eui';
|
||||
import { DescribedFormGroupWithWrap } from './common/described_form_group_with_wrap';
|
||||
import { ConfigKey, DataStream, Validation } from './types';
|
||||
import { usePolicyConfigContext } from './contexts';
|
||||
import { TLSFields } from './tls_fields';
|
||||
import { HTTPSimpleFields } from './http/simple_fields';
|
||||
import { HTTPAdvancedFields } from './http/advanced_fields';
|
||||
import { TCPSimpleFields } from './tcp/simple_fields';
|
||||
import { TCPAdvancedFields } from './tcp/advanced_fields';
|
||||
import { ICMPSimpleFields } from './icmp/simple_fields';
|
||||
import { BrowserSimpleFields } from './browser/simple_fields';
|
||||
import { BrowserAdvancedFields } from './browser/advanced_fields';
|
||||
import { ICMPAdvancedFields } from './icmp/advanced_fields';
|
||||
|
||||
interface Props {
|
||||
validate: Validation;
|
||||
dataStreams?: DataStream[];
|
||||
children?: React.ReactNode;
|
||||
appendAdvancedFields?: React.ReactNode;
|
||||
minColumnWidth?: string;
|
||||
onFieldBlur?: (field: ConfigKey) => void;
|
||||
}
|
||||
|
||||
const dataStreamToString = [
|
||||
{
|
||||
value: DataStream.BROWSER,
|
||||
text: i18n.translate(
|
||||
'xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.browserLabel',
|
||||
{
|
||||
defaultMessage: 'Browser (Beta)',
|
||||
}
|
||||
),
|
||||
},
|
||||
{ value: DataStream.HTTP, text: 'HTTP' },
|
||||
{ value: DataStream.TCP, text: 'TCP' },
|
||||
{ value: DataStream.ICMP, text: 'ICMP' },
|
||||
];
|
||||
|
||||
export const CustomFields = memo<Props>(
|
||||
({ validate, dataStreams = [], children, appendAdvancedFields, minColumnWidth, onFieldBlur }) => {
|
||||
const { monitorType, setMonitorType, isTLSEnabled, setIsTLSEnabled, isEditable } =
|
||||
usePolicyConfigContext();
|
||||
|
||||
const isHTTP = monitorType === DataStream.HTTP;
|
||||
const isTCP = monitorType === DataStream.TCP;
|
||||
const isBrowser = monitorType === DataStream.BROWSER;
|
||||
const isICMP = monitorType === DataStream.ICMP;
|
||||
|
||||
const dataStreamOptions = useMemo(() => {
|
||||
return dataStreamToString.filter((dataStream) => dataStreams.includes(dataStream.value));
|
||||
}, [dataStreams]);
|
||||
|
||||
const renderSimpleFields = (type: DataStream) => {
|
||||
switch (type) {
|
||||
case DataStream.HTTP:
|
||||
return (
|
||||
<HTTPSimpleFields validate={validate} onFieldBlur={(field) => onFieldBlur?.(field)} />
|
||||
);
|
||||
case DataStream.ICMP:
|
||||
return (
|
||||
<ICMPSimpleFields validate={validate} onFieldBlur={(field) => onFieldBlur?.(field)} />
|
||||
);
|
||||
case DataStream.TCP:
|
||||
return (
|
||||
<TCPSimpleFields validate={validate} onFieldBlur={(field) => onFieldBlur?.(field)} />
|
||||
);
|
||||
case DataStream.BROWSER:
|
||||
return (
|
||||
<BrowserSimpleFields
|
||||
validate={validate}
|
||||
onFieldBlur={(field) => onFieldBlur?.(field)}
|
||||
/>
|
||||
);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const isWithInUptime = window.location.pathname.includes('/app/uptime');
|
||||
|
||||
return (
|
||||
<EuiForm component="form">
|
||||
<DescribedFormGroupWithWrap
|
||||
minColumnWidth={minColumnWidth}
|
||||
title={
|
||||
<h4>
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSectionTitle"
|
||||
defaultMessage="Monitor settings"
|
||||
/>
|
||||
</h4>
|
||||
}
|
||||
description={
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSectionDescription"
|
||||
defaultMessage="Configure your monitor with the following options."
|
||||
/>
|
||||
}
|
||||
data-test-subj="monitorSettingsSection"
|
||||
>
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem>
|
||||
{children}
|
||||
{!isEditable && (
|
||||
<EuiFormRow
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.monitorType"
|
||||
defaultMessage="Monitor Type"
|
||||
/>
|
||||
}
|
||||
isInvalid={
|
||||
!!validate[ConfigKey.MONITOR_TYPE]?.({
|
||||
[ConfigKey.MONITOR_TYPE]: monitorType as DataStream,
|
||||
})
|
||||
}
|
||||
error={
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.monitorType.error"
|
||||
defaultMessage="Monitor type is required"
|
||||
/>
|
||||
}
|
||||
>
|
||||
<EuiSelect
|
||||
options={dataStreamOptions}
|
||||
value={monitorType}
|
||||
onChange={(event) => setMonitorType(event.target.value as DataStream)}
|
||||
onBlur={() => onFieldBlur?.(ConfigKey.MONITOR_TYPE)}
|
||||
data-test-subj="syntheticsMonitorTypeField"
|
||||
/>
|
||||
</EuiFormRow>
|
||||
)}
|
||||
<EuiSpacer size="s" />
|
||||
{isBrowser && !isWithInUptime && (
|
||||
<EuiCallOut
|
||||
title={
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.monitorType.browser.warning.title"
|
||||
defaultMessage="Requirement"
|
||||
/>
|
||||
}
|
||||
size="s"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.monitorType.browser.warning.content"
|
||||
defaultMessage='To create a "Browser" monitor, please ensure you are using the {agent} Docker container, which contains the dependencies to run these monitors. For more information, please visit our {link}.'
|
||||
values={{
|
||||
agent: <EuiCode>elastic-agent-complete</EuiCode>,
|
||||
link: (
|
||||
<EuiLink
|
||||
data-test-subj="syntheticsCustomFieldsSyntheticsDocumentationLink"
|
||||
target="_blank"
|
||||
href="https://www.elastic.co/guide/en/observability/current/synthetics-quickstart-fleet.html"
|
||||
external
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.monitorType.browser.warning.link"
|
||||
defaultMessage="synthetics documentation"
|
||||
/>
|
||||
</EuiLink>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</EuiCallOut>
|
||||
)}
|
||||
<EuiSpacer size="s" />
|
||||
{renderSimpleFields(monitorType)}
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</DescribedFormGroupWithWrap>
|
||||
{(isHTTP || isTCP) && (
|
||||
<DescribedFormGroupWithWrap
|
||||
minColumnWidth={minColumnWidth}
|
||||
title={
|
||||
<h4>
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.createPackagePolicy.stepConfigure.tlsSettings.label"
|
||||
defaultMessage="TLS settings"
|
||||
/>
|
||||
</h4>
|
||||
}
|
||||
description={
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.createPackagePolicy.stepConfigure.tlsSettings.description"
|
||||
defaultMessage="Configure TLS options, including verification mode, certificate authorities, and client certificates."
|
||||
/>
|
||||
}
|
||||
id="uptimeFleetIsTLSEnabled"
|
||||
>
|
||||
<EuiSwitch
|
||||
id="uptimeFleetIsTLSEnabled"
|
||||
data-test-subj="syntheticsIsTLSEnabled"
|
||||
checked={!!isTLSEnabled}
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.createPackagePolicy.stepConfigure.certificateSettings.enableSSLSettings.label"
|
||||
defaultMessage="Enable TLS configuration"
|
||||
/>
|
||||
}
|
||||
onChange={(event) => setIsTLSEnabled(event.target.checked)}
|
||||
/>
|
||||
<TLSFields />
|
||||
</DescribedFormGroupWithWrap>
|
||||
)}
|
||||
<EuiSpacer size="m" />
|
||||
{isHTTP && (
|
||||
<HTTPAdvancedFields
|
||||
validate={validate}
|
||||
minColumnWidth={minColumnWidth}
|
||||
onFieldBlur={onFieldBlur}
|
||||
>
|
||||
{appendAdvancedFields}
|
||||
</HTTPAdvancedFields>
|
||||
)}
|
||||
{isTCP && (
|
||||
<TCPAdvancedFields minColumnWidth={minColumnWidth} onFieldBlur={onFieldBlur}>
|
||||
{appendAdvancedFields}
|
||||
</TCPAdvancedFields>
|
||||
)}
|
||||
{isBrowser && (
|
||||
<BrowserAdvancedFields
|
||||
validate={validate}
|
||||
minColumnWidth={minColumnWidth}
|
||||
onFieldBlur={onFieldBlur}
|
||||
>
|
||||
{appendAdvancedFields}
|
||||
</BrowserAdvancedFields>
|
||||
)}
|
||||
{isICMP && <ICMPAdvancedFields>{appendAdvancedFields}</ICMPAdvancedFields>}
|
||||
</EuiForm>
|
||||
);
|
||||
}
|
||||
);
|
|
@ -1,109 +0,0 @@
|
|||
/*
|
||||
* 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 from 'react';
|
||||
import { fireEvent, waitFor } from '@testing-library/react';
|
||||
import { render } from '../../lib/helper/rtl_helpers';
|
||||
import { HeaderField, contentTypes } from './header_field';
|
||||
import { Mode } from './types';
|
||||
|
||||
describe('<HeaderField />', () => {
|
||||
const onChange = jest.fn();
|
||||
const onBlur = jest.fn();
|
||||
const defaultValue = {};
|
||||
|
||||
afterEach(() => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
it('renders HeaderField', () => {
|
||||
const { getByText, getByTestId } = render(
|
||||
<HeaderField defaultValue={{ sample: 'header' }} onChange={onChange} />
|
||||
);
|
||||
|
||||
expect(getByText('Key')).toBeInTheDocument();
|
||||
expect(getByText('Value')).toBeInTheDocument();
|
||||
const key = getByTestId('keyValuePairsKey0') as HTMLInputElement;
|
||||
const value = getByTestId('keyValuePairsValue0') as HTMLInputElement;
|
||||
expect(key.value).toEqual('sample');
|
||||
expect(value.value).toEqual('header');
|
||||
});
|
||||
|
||||
it('calls onBlur', () => {
|
||||
const { getByTestId } = render(
|
||||
<HeaderField defaultValue={{ sample: 'header' }} onChange={onChange} onBlur={onBlur} />
|
||||
);
|
||||
|
||||
const key = getByTestId('keyValuePairsKey0') as HTMLInputElement;
|
||||
const value = getByTestId('keyValuePairsValue0') as HTMLInputElement;
|
||||
|
||||
fireEvent.blur(key);
|
||||
fireEvent.blur(value);
|
||||
|
||||
expect(onBlur).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it('formats headers and handles onChange', async () => {
|
||||
const { getByTestId, getByText } = render(
|
||||
<HeaderField defaultValue={defaultValue} onChange={onChange} />
|
||||
);
|
||||
const addHeader = getByText('Add header');
|
||||
fireEvent.click(addHeader);
|
||||
const key = getByTestId('keyValuePairsKey0') as HTMLInputElement;
|
||||
const value = getByTestId('keyValuePairsValue0') as HTMLInputElement;
|
||||
const newKey = 'sampleKey';
|
||||
const newValue = 'sampleValue';
|
||||
fireEvent.change(key, { target: { value: newKey } });
|
||||
fireEvent.change(value, { target: { value: newValue } });
|
||||
|
||||
await waitFor(() => {
|
||||
expect(onChange).toBeCalledWith({
|
||||
[newKey]: newValue,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('handles deleting headers', async () => {
|
||||
const { getByTestId, getByText, getByLabelText } = render(
|
||||
<HeaderField defaultValue={{ sampleKey: 'sampleValue' }} onChange={onChange} />
|
||||
);
|
||||
const addHeader = getByText('Add header');
|
||||
|
||||
fireEvent.click(addHeader);
|
||||
|
||||
const key = getByTestId('keyValuePairsKey0') as HTMLInputElement;
|
||||
const value = getByTestId('keyValuePairsValue0') as HTMLInputElement;
|
||||
const newKey = 'sampleKey';
|
||||
const newValue = 'sampleValue';
|
||||
fireEvent.change(key, { target: { value: newKey } });
|
||||
fireEvent.change(value, { target: { value: newValue } });
|
||||
|
||||
await waitFor(() => {
|
||||
expect(onChange).toBeCalledWith({
|
||||
[newKey]: newValue,
|
||||
});
|
||||
});
|
||||
|
||||
const deleteBtn = getByLabelText('Delete item number 2, sampleKey:sampleValue');
|
||||
|
||||
// uncheck
|
||||
fireEvent.click(deleteBtn);
|
||||
});
|
||||
|
||||
it('handles content mode', async () => {
|
||||
const contentMode: Mode = Mode.PLAINTEXT;
|
||||
render(
|
||||
<HeaderField defaultValue={defaultValue} onChange={onChange} contentMode={contentMode} />
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(onChange).toBeCalledWith({
|
||||
'Content-Type': contentTypes[Mode.PLAINTEXT],
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,77 +0,0 @@
|
|||
/*
|
||||
* 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 { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { ContentType, Mode } from './types';
|
||||
|
||||
import { KeyValuePairsField, Pair } from './key_value_field';
|
||||
|
||||
interface Props {
|
||||
contentMode?: Mode;
|
||||
defaultValue: Record<string, string>;
|
||||
onChange: (value: Record<string, string>) => void;
|
||||
onBlur?: () => void;
|
||||
'data-test-subj'?: string;
|
||||
}
|
||||
|
||||
export const HeaderField = ({
|
||||
contentMode,
|
||||
defaultValue,
|
||||
onChange,
|
||||
onBlur,
|
||||
'data-test-subj': dataTestSubj,
|
||||
}: Props) => {
|
||||
const defaultValueKeys = Object.keys(defaultValue).filter((key) => key !== 'Content-Type'); // Content-Type is a secret header we hide from the user
|
||||
const formattedDefaultValues: Pair[] = [
|
||||
...defaultValueKeys.map<Pair>((key) => {
|
||||
return [key || '', defaultValue[key] || '']; // key, value
|
||||
}),
|
||||
];
|
||||
const [headers, setHeaders] = useState<Pair[]>(formattedDefaultValues);
|
||||
|
||||
useEffect(() => {
|
||||
const formattedHeaders = headers.reduce((acc: Record<string, string>, header) => {
|
||||
const [key, value] = header;
|
||||
if (key) {
|
||||
return {
|
||||
...acc,
|
||||
[key]: value,
|
||||
};
|
||||
}
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
if (contentMode) {
|
||||
onChange({ 'Content-Type': contentTypes[contentMode], ...formattedHeaders });
|
||||
} else {
|
||||
onChange(formattedHeaders);
|
||||
}
|
||||
}, [contentMode, headers, onChange]);
|
||||
|
||||
return (
|
||||
<KeyValuePairsField
|
||||
addPairControlLabel={
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.createPackagePolicy.stepConfigure.headerField.addHeader.label"
|
||||
defaultMessage="Add header"
|
||||
/>
|
||||
}
|
||||
defaultPairs={headers}
|
||||
onChange={setHeaders}
|
||||
onBlur={() => onBlur?.()}
|
||||
data-test-subj={dataTestSubj}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const contentTypes: Record<Mode, ContentType> = {
|
||||
[Mode.JSON]: ContentType.JSON,
|
||||
[Mode.PLAINTEXT]: ContentType.TEXT,
|
||||
[Mode.XML]: ContentType.XML,
|
||||
[Mode.FORM]: ContentType.FORM,
|
||||
};
|
|
@ -1,17 +0,0 @@
|
|||
/*
|
||||
* 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 function formatDefaultValues<Fields>(
|
||||
keys: Array<keyof Fields>,
|
||||
defaultValues: Partial<Fields>
|
||||
) {
|
||||
return keys.reduce((acc: any, currentValue) => {
|
||||
const key = currentValue as keyof Fields;
|
||||
acc[key] = defaultValues?.[key];
|
||||
return acc;
|
||||
}, {}) as Fields;
|
||||
}
|
|
@ -1,14 +0,0 @@
|
|||
/*
|
||||
* 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 function parseJsonIfString<T>(value: T) {
|
||||
if (typeof value === 'string') {
|
||||
return JSON.parse(value);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
|
@ -1,119 +0,0 @@
|
|||
/*
|
||||
* 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 { useMemo } from 'react';
|
||||
import {
|
||||
PolicyConfig,
|
||||
DataStream,
|
||||
ConfigKey,
|
||||
HTTPFields,
|
||||
TCPFields,
|
||||
ICMPFields,
|
||||
BrowserFields,
|
||||
} from '../types';
|
||||
import {
|
||||
usePolicyConfigContext,
|
||||
useTCPSimpleFieldsContext,
|
||||
useTCPAdvancedFieldsContext,
|
||||
useICMPSimpleFieldsContext,
|
||||
useHTTPSimpleFieldsContext,
|
||||
useHTTPAdvancedFieldsContext,
|
||||
useTLSFieldsContext,
|
||||
useBrowserSimpleFieldsContext,
|
||||
useBrowserAdvancedFieldsContext,
|
||||
} from '../contexts';
|
||||
import { DEFAULT_FIELDS } from '../../../../../common/constants/monitor_defaults';
|
||||
|
||||
export const defaultConfig: PolicyConfig = DEFAULT_FIELDS;
|
||||
|
||||
export const usePolicy = (fleetPolicyName: string = '') => {
|
||||
const {
|
||||
isTLSEnabled,
|
||||
name: monitorName, // the monitor name can come from two different places, either from fleet or from uptime
|
||||
locations,
|
||||
namespace,
|
||||
} = usePolicyConfigContext();
|
||||
const { fields: httpSimpleFields } = useHTTPSimpleFieldsContext();
|
||||
const { fields: tcpSimpleFields } = useTCPSimpleFieldsContext();
|
||||
const { fields: icmpSimpleFields } = useICMPSimpleFieldsContext();
|
||||
const { fields: browserSimpleFields } = useBrowserSimpleFieldsContext();
|
||||
const { fields: httpAdvancedFields } = useHTTPAdvancedFieldsContext();
|
||||
const { fields: tcpAdvancedFields } = useTCPAdvancedFieldsContext();
|
||||
const { fields: browserAdvancedFields } = useBrowserAdvancedFieldsContext();
|
||||
const { fields: tlsFields } = useTLSFieldsContext();
|
||||
|
||||
const metadata = useMemo(
|
||||
() => ({
|
||||
is_tls_enabled: isTLSEnabled,
|
||||
}),
|
||||
[isTLSEnabled]
|
||||
);
|
||||
|
||||
/* TODO add locations to policy config for synthetics service */
|
||||
const policyConfig: PolicyConfig = useMemo(
|
||||
() => ({
|
||||
[DataStream.HTTP]: {
|
||||
...httpSimpleFields,
|
||||
...httpAdvancedFields,
|
||||
...tlsFields,
|
||||
[ConfigKey.METADATA]: {
|
||||
...httpSimpleFields[ConfigKey.METADATA],
|
||||
...metadata,
|
||||
},
|
||||
[ConfigKey.NAME]: fleetPolicyName || monitorName,
|
||||
[ConfigKey.LOCATIONS]: locations,
|
||||
[ConfigKey.NAMESPACE]: namespace,
|
||||
} as HTTPFields,
|
||||
[DataStream.TCP]: {
|
||||
...tcpSimpleFields,
|
||||
...tcpAdvancedFields,
|
||||
...tlsFields,
|
||||
[ConfigKey.METADATA]: {
|
||||
...tcpSimpleFields[ConfigKey.METADATA],
|
||||
...metadata,
|
||||
},
|
||||
[ConfigKey.NAME]: fleetPolicyName || monitorName,
|
||||
[ConfigKey.LOCATIONS]: locations,
|
||||
[ConfigKey.NAMESPACE]: namespace,
|
||||
} as TCPFields,
|
||||
[DataStream.ICMP]: {
|
||||
...icmpSimpleFields,
|
||||
[ConfigKey.NAME]: fleetPolicyName || monitorName,
|
||||
[ConfigKey.LOCATIONS]: locations,
|
||||
[ConfigKey.NAMESPACE]: namespace,
|
||||
} as ICMPFields,
|
||||
[DataStream.BROWSER]: {
|
||||
...browserSimpleFields,
|
||||
...browserAdvancedFields,
|
||||
[ConfigKey.METADATA]: {
|
||||
...browserSimpleFields[ConfigKey.METADATA],
|
||||
...metadata,
|
||||
},
|
||||
[ConfigKey.NAME]: fleetPolicyName || monitorName,
|
||||
[ConfigKey.LOCATIONS]: locations,
|
||||
[ConfigKey.NAMESPACE]: namespace,
|
||||
} as BrowserFields,
|
||||
}),
|
||||
[
|
||||
metadata,
|
||||
httpSimpleFields,
|
||||
httpAdvancedFields,
|
||||
tcpSimpleFields,
|
||||
tcpAdvancedFields,
|
||||
icmpSimpleFields,
|
||||
browserSimpleFields,
|
||||
browserAdvancedFields,
|
||||
tlsFields,
|
||||
fleetPolicyName,
|
||||
monitorName,
|
||||
locations,
|
||||
namespace,
|
||||
]
|
||||
);
|
||||
|
||||
return policyConfig;
|
||||
};
|
|
@ -1,157 +0,0 @@
|
|||
/*
|
||||
* 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 { fireEvent } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import { render } from '../../../lib/helper/rtl_helpers';
|
||||
import {
|
||||
defaultHTTPAdvancedFields as defaultConfig,
|
||||
HTTPAdvancedFieldsContextProvider,
|
||||
} from '../contexts';
|
||||
import {
|
||||
ConfigKey,
|
||||
DataStream,
|
||||
HTTPAdvancedFields as HTTPAdvancedFieldsType,
|
||||
HTTPMethod,
|
||||
Validation,
|
||||
} from '../types';
|
||||
import { validate as centralValidation } from '../../monitor_management/validation';
|
||||
import { HTTPAdvancedFields } from './advanced_fields';
|
||||
|
||||
jest.mock('@elastic/eui/lib/services/accessibility/html_id_generator', () => ({
|
||||
htmlIdGenerator: () => () => `id-${Math.random()}`,
|
||||
}));
|
||||
|
||||
jest.mock('@kbn/kibana-react-plugin/public', () => {
|
||||
const original = jest.requireActual('@kbn/kibana-react-plugin/public');
|
||||
return {
|
||||
...original,
|
||||
// Mocking CodeEditor, which uses React Monaco under the hood
|
||||
CodeEditor: (props: any) => (
|
||||
<input
|
||||
data-test-subj={props['data-test-subj'] || 'mockCodeEditor'}
|
||||
data-currentvalue={props.value}
|
||||
onChange={(e: any) => {
|
||||
props.onChange(e.jsonContent);
|
||||
}}
|
||||
/>
|
||||
),
|
||||
};
|
||||
});
|
||||
|
||||
const defaultValidation = centralValidation[DataStream.HTTP];
|
||||
|
||||
describe('<HTTPAdvancedFields />', () => {
|
||||
const onFieldBlur = jest.fn();
|
||||
|
||||
const WrappedComponent = ({
|
||||
defaultValues,
|
||||
validate = defaultValidation,
|
||||
children,
|
||||
}: {
|
||||
defaultValues?: HTTPAdvancedFieldsType;
|
||||
validate?: Validation;
|
||||
children?: React.ReactNode;
|
||||
}) => {
|
||||
return (
|
||||
<HTTPAdvancedFieldsContextProvider defaultValues={defaultValues}>
|
||||
<HTTPAdvancedFields validate={validate} onFieldBlur={onFieldBlur}>
|
||||
{children}
|
||||
</HTTPAdvancedFields>
|
||||
</HTTPAdvancedFieldsContextProvider>
|
||||
);
|
||||
};
|
||||
|
||||
it('renders HTTPAdvancedFields', () => {
|
||||
const { getByText, getByLabelText } = render(<WrappedComponent />);
|
||||
|
||||
const requestMethod = getByLabelText('Request method') as HTMLInputElement;
|
||||
const requestHeaders = getByText('Request headers');
|
||||
const requestBody = getByText('Request body');
|
||||
const indexResponseBody = getByLabelText('Index response body') as HTMLInputElement;
|
||||
const indexResponseBodySelect = getByLabelText(
|
||||
'Response body index policy'
|
||||
) as HTMLInputElement;
|
||||
const indexResponseHeaders = getByLabelText('Index response headers') as HTMLInputElement;
|
||||
const proxyUrl = getByLabelText('Proxy URL') as HTMLInputElement;
|
||||
const responseHeadersContain = getByText('Check response headers contain');
|
||||
const responseStatusEquals = getByText('Check response status equals');
|
||||
const responseBodyContains = getByText('Check response body contains');
|
||||
const responseBodyDoesNotContain = getByText('Check response body does not contain');
|
||||
const username = getByLabelText('Username') as HTMLInputElement;
|
||||
const password = getByLabelText('Password') as HTMLInputElement;
|
||||
expect(requestMethod).toBeInTheDocument();
|
||||
expect(requestMethod.value).toEqual(defaultConfig[ConfigKey.REQUEST_METHOD_CHECK]);
|
||||
expect(requestHeaders).toBeInTheDocument();
|
||||
expect(requestBody).toBeInTheDocument();
|
||||
expect(indexResponseBody).toBeInTheDocument();
|
||||
expect(indexResponseBody.checked).toBe(true);
|
||||
expect(indexResponseBodySelect).toBeInTheDocument();
|
||||
expect(indexResponseBodySelect.value).toEqual(defaultConfig[ConfigKey.RESPONSE_BODY_INDEX]);
|
||||
expect(indexResponseHeaders).toBeInTheDocument();
|
||||
expect(indexResponseHeaders.checked).toBe(true);
|
||||
expect(proxyUrl).toBeInTheDocument();
|
||||
expect(proxyUrl.value).toEqual(defaultConfig[ConfigKey.PROXY_URL]);
|
||||
expect(responseStatusEquals).toBeInTheDocument();
|
||||
expect(responseBodyContains).toBeInTheDocument();
|
||||
expect(responseBodyDoesNotContain).toBeInTheDocument();
|
||||
expect(responseHeadersContain).toBeInTheDocument();
|
||||
expect(username).toBeInTheDocument();
|
||||
expect(username.value).toBe(defaultConfig[ConfigKey.USERNAME]);
|
||||
expect(password).toBeInTheDocument();
|
||||
expect(password.value).toBe(defaultConfig[ConfigKey.PASSWORD]);
|
||||
});
|
||||
|
||||
it('handles changing fields', () => {
|
||||
const { getByText, getByLabelText } = render(<WrappedComponent />);
|
||||
|
||||
const username = getByLabelText('Username') as HTMLInputElement;
|
||||
const password = getByLabelText('Password') as HTMLInputElement;
|
||||
const proxyUrl = getByLabelText('Proxy URL') as HTMLInputElement;
|
||||
const requestMethod = getByLabelText('Request method') as HTMLInputElement;
|
||||
const requestHeaders = getByText('Request headers');
|
||||
const indexResponseBody = getByLabelText('Index response body') as HTMLInputElement;
|
||||
const indexResponseHeaders = getByLabelText('Index response headers') as HTMLInputElement;
|
||||
|
||||
fireEvent.change(username, { target: { value: 'username' } });
|
||||
fireEvent.change(password, { target: { value: 'password' } });
|
||||
fireEvent.change(proxyUrl, { target: { value: 'proxyUrl' } });
|
||||
fireEvent.change(requestMethod, { target: { value: HTTPMethod.POST } });
|
||||
fireEvent.click(indexResponseBody);
|
||||
fireEvent.click(indexResponseHeaders);
|
||||
|
||||
expect(username.value).toEqual('username');
|
||||
expect(password.value).toEqual('password');
|
||||
expect(proxyUrl.value).toEqual('proxyUrl');
|
||||
expect(requestMethod.value).toEqual(HTTPMethod.POST);
|
||||
expect(requestHeaders).toBeInTheDocument();
|
||||
expect(indexResponseBody.checked).toBe(false);
|
||||
expect(indexResponseHeaders.checked).toBe(false);
|
||||
});
|
||||
|
||||
it('calls onBlur', () => {
|
||||
const { getByLabelText } = render(<WrappedComponent />);
|
||||
|
||||
const username = getByLabelText('Username') as HTMLInputElement;
|
||||
const requestMethod = getByLabelText('Request method') as HTMLInputElement;
|
||||
const indexResponseBody = getByLabelText('Index response body') as HTMLInputElement;
|
||||
|
||||
[username, requestMethod, indexResponseBody].forEach((field) => fireEvent.blur(field));
|
||||
|
||||
expect(onFieldBlur).toHaveBeenCalledWith(ConfigKey.USERNAME);
|
||||
expect(onFieldBlur).toHaveBeenCalledWith(ConfigKey.REQUEST_METHOD_CHECK);
|
||||
expect(onFieldBlur).toHaveBeenCalledWith(ConfigKey.RESPONSE_BODY_INDEX);
|
||||
});
|
||||
|
||||
it('renders upstream fields', () => {
|
||||
const upstreamFieldsText = 'Monitor Advanced field section';
|
||||
const { getByText } = render(<WrappedComponent>{upstreamFieldsText}</WrappedComponent>);
|
||||
|
||||
const upstream = getByText(upstreamFieldsText) as HTMLInputElement;
|
||||
expect(upstream).toBeInTheDocument();
|
||||
});
|
||||
});
|
|
@ -1,492 +0,0 @@
|
|||
/*
|
||||
* 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, { useCallback, memo } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import {
|
||||
EuiAccordion,
|
||||
EuiCode,
|
||||
EuiFieldText,
|
||||
EuiFormRow,
|
||||
EuiSelect,
|
||||
EuiCheckbox,
|
||||
EuiSpacer,
|
||||
EuiFieldPassword,
|
||||
} from '@elastic/eui';
|
||||
import { DescribedFormGroupWithWrap } from '../common/described_form_group_with_wrap';
|
||||
|
||||
import { useHTTPAdvancedFieldsContext } from '../contexts';
|
||||
|
||||
import { ConfigKey, HTTPMethod, Validation } from '../types';
|
||||
|
||||
import { OptionalLabel } from '../optional_label';
|
||||
import { HeaderField } from '../header_field';
|
||||
import { RequestBodyField } from '../request_body_field';
|
||||
import { ResponseBodyIndexField } from '../index_response_body_field';
|
||||
import { ComboBox } from '../combo_box';
|
||||
|
||||
interface Props {
|
||||
validate: Validation;
|
||||
children?: React.ReactNode;
|
||||
minColumnWidth?: string;
|
||||
onFieldBlur?: (field: ConfigKey) => void;
|
||||
}
|
||||
|
||||
export const HTTPAdvancedFields = memo<Props>(
|
||||
({ validate, children, minColumnWidth, onFieldBlur }) => {
|
||||
const { fields, setFields } = useHTTPAdvancedFieldsContext();
|
||||
const handleInputChange = useCallback(
|
||||
({ value, configKey }: { value: unknown; configKey: ConfigKey }) => {
|
||||
setFields((prevFields) => ({ ...prevFields, [configKey]: value }));
|
||||
},
|
||||
[setFields]
|
||||
);
|
||||
|
||||
return (
|
||||
<EuiAccordion
|
||||
id="uptimeFleetHttpAdvancedOptions"
|
||||
buttonContent={
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.createPackagePolicy.stepConfigure.httpAdvancedOptions"
|
||||
defaultMessage="Advanced HTTP options"
|
||||
/>
|
||||
}
|
||||
data-test-subj="syntheticsHTTPAdvancedFieldsAccordion"
|
||||
>
|
||||
<EuiSpacer size="xl" />
|
||||
<DescribedFormGroupWithWrap
|
||||
minColumnWidth={minColumnWidth}
|
||||
title={
|
||||
<h4>
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.createPackagePolicy.stepConfigure.httpAdvancedOptions.requestConfiguration.title"
|
||||
defaultMessage="Request configuration"
|
||||
/>
|
||||
</h4>
|
||||
}
|
||||
description={
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.createPackagePolicy.stepConfigure.httpAdvancedOptions.requestConfiguration.description"
|
||||
defaultMessage="Configure an optional request to send to the remote host including method, body, and headers."
|
||||
/>
|
||||
}
|
||||
data-test-subj="httpAdvancedFieldsSection"
|
||||
>
|
||||
<EuiSpacer size="s" />
|
||||
<EuiFormRow
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.username.label"
|
||||
defaultMessage="Username"
|
||||
/>
|
||||
}
|
||||
labelAppend={<OptionalLabel />}
|
||||
helpText={
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.username.helpText"
|
||||
defaultMessage="Username for authenticating with the server."
|
||||
/>
|
||||
}
|
||||
>
|
||||
<EuiFieldText
|
||||
value={fields[ConfigKey.USERNAME]}
|
||||
onChange={(event) =>
|
||||
handleInputChange({
|
||||
value: event.target.value,
|
||||
configKey: ConfigKey.USERNAME,
|
||||
})
|
||||
}
|
||||
onBlur={() => onFieldBlur?.(ConfigKey.USERNAME)}
|
||||
data-test-subj="syntheticsUsername"
|
||||
/>
|
||||
</EuiFormRow>
|
||||
<EuiFormRow
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.password.label"
|
||||
defaultMessage="Password"
|
||||
/>
|
||||
}
|
||||
labelAppend={<OptionalLabel />}
|
||||
helpText={
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.password.helpText"
|
||||
defaultMessage="Password for authenticating with the server."
|
||||
/>
|
||||
}
|
||||
>
|
||||
<EuiFieldPassword
|
||||
value={fields[ConfigKey.PASSWORD]}
|
||||
onChange={(event) =>
|
||||
handleInputChange({
|
||||
value: event.target.value,
|
||||
configKey: ConfigKey.PASSWORD,
|
||||
})
|
||||
}
|
||||
onBlur={() => onFieldBlur?.(ConfigKey.PASSWORD)}
|
||||
data-test-subj="syntheticsPassword"
|
||||
/>
|
||||
</EuiFormRow>
|
||||
<EuiFormRow
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.proxyURL.http.label"
|
||||
defaultMessage="Proxy URL"
|
||||
/>
|
||||
}
|
||||
labelAppend={<OptionalLabel />}
|
||||
helpText={
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.proxyUrl.http.helpText"
|
||||
defaultMessage="HTTP proxy URL."
|
||||
/>
|
||||
}
|
||||
>
|
||||
<EuiFieldText
|
||||
value={fields[ConfigKey.PROXY_URL]}
|
||||
onChange={(event) =>
|
||||
handleInputChange({
|
||||
value: event.target.value,
|
||||
configKey: ConfigKey.PROXY_URL,
|
||||
})
|
||||
}
|
||||
onBlur={() => onFieldBlur?.(ConfigKey.PROXY_URL)}
|
||||
data-test-subj="syntheticsProxyUrl"
|
||||
/>
|
||||
</EuiFormRow>
|
||||
<EuiFormRow
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.createPackagePolicy.stepConfigure.httpAdvancedOptions.requestConfiguration.requestMethod.label"
|
||||
defaultMessage="Request method"
|
||||
/>
|
||||
}
|
||||
helpText={
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.createPackagePolicy.stepConfigure.httpAdvancedOptions.requestMethod.helpText"
|
||||
defaultMessage="The HTTP method to use."
|
||||
/>
|
||||
}
|
||||
>
|
||||
<EuiSelect
|
||||
options={requestMethodOptions}
|
||||
value={fields[ConfigKey.REQUEST_METHOD_CHECK]}
|
||||
onChange={(event) =>
|
||||
handleInputChange({
|
||||
value: event.target.value,
|
||||
configKey: ConfigKey.REQUEST_METHOD_CHECK,
|
||||
})
|
||||
}
|
||||
onBlur={() => onFieldBlur?.(ConfigKey.REQUEST_METHOD_CHECK)}
|
||||
data-test-subj="syntheticsRequestMethod"
|
||||
/>
|
||||
</EuiFormRow>
|
||||
<EuiFormRow
|
||||
fullWidth
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.createPackagePolicy.stepConfigure.httpAdvancedOptions.requestConfiguration.requestHeaders"
|
||||
defaultMessage="Request headers"
|
||||
/>
|
||||
}
|
||||
labelAppend={<OptionalLabel />}
|
||||
isInvalid={!!validate[ConfigKey.REQUEST_HEADERS_CHECK]?.(fields)}
|
||||
error={
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.createPackagePolicy.stepConfigure.httpAdvancedOptions.requestHeadersField.error"
|
||||
defaultMessage="Header key must be a valid HTTP token."
|
||||
/>
|
||||
}
|
||||
helpText={
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.createPackagePolicy.stepConfigure.httpAdvancedOptions.requestHeadersField.helpText"
|
||||
defaultMessage="A dictionary of additional HTTP headers to send. By default the client will set the User-Agent header to identify itself."
|
||||
/>
|
||||
}
|
||||
>
|
||||
<HeaderField
|
||||
contentMode={
|
||||
fields[ConfigKey.REQUEST_BODY_CHECK].value
|
||||
? fields[ConfigKey.REQUEST_BODY_CHECK].type
|
||||
: undefined
|
||||
} // only pass contentMode if the request body is truthy
|
||||
defaultValue={fields[ConfigKey.REQUEST_HEADERS_CHECK]}
|
||||
onChange={useCallback(
|
||||
(value) =>
|
||||
handleInputChange({
|
||||
value,
|
||||
configKey: ConfigKey.REQUEST_HEADERS_CHECK,
|
||||
}),
|
||||
[handleInputChange]
|
||||
)}
|
||||
onBlur={() => onFieldBlur?.(ConfigKey.REQUEST_HEADERS_CHECK)}
|
||||
data-test-subj="syntheticsRequestHeaders"
|
||||
/>
|
||||
</EuiFormRow>
|
||||
<EuiFormRow
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.createPackagePolicy.stepConfigure.httpAdvancedOptions.requestConfiguration.requestBody"
|
||||
defaultMessage="Request body"
|
||||
/>
|
||||
}
|
||||
labelAppend={<OptionalLabel />}
|
||||
helpText={
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.createPackagePolicy.stepConfigure.httpAdvancedOptions.requestBody.helpText"
|
||||
defaultMessage="Request body content."
|
||||
/>
|
||||
}
|
||||
fullWidth
|
||||
>
|
||||
<RequestBodyField
|
||||
value={fields[ConfigKey.REQUEST_BODY_CHECK].value}
|
||||
type={fields[ConfigKey.REQUEST_BODY_CHECK].type}
|
||||
onChange={useCallback(
|
||||
(value) =>
|
||||
handleInputChange({
|
||||
value,
|
||||
configKey: ConfigKey.REQUEST_BODY_CHECK,
|
||||
}),
|
||||
[handleInputChange]
|
||||
)}
|
||||
onBlur={() => onFieldBlur?.(ConfigKey.REQUEST_BODY_CHECK)}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</DescribedFormGroupWithWrap>
|
||||
<EuiSpacer size="xl" />
|
||||
<DescribedFormGroupWithWrap
|
||||
minColumnWidth={minColumnWidth}
|
||||
title={
|
||||
<h4>
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.createPackagePolicy.stepConfigure.httpAdvancedOptions.responseConfiguration.title"
|
||||
defaultMessage="Response configuration"
|
||||
/>
|
||||
</h4>
|
||||
}
|
||||
description={
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.createPackagePolicy.stepConfigure.httpAdvancedOptions.responseConfiguration.description"
|
||||
defaultMessage="Control the indexing of the HTTP response contents."
|
||||
/>
|
||||
}
|
||||
>
|
||||
<EuiSpacer size="s" />
|
||||
<EuiFormRow
|
||||
helpText={
|
||||
<>
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.createPackagePolicy.stepConfigure.httpAdvancedOptions.indexResponseHeaders.helpText"
|
||||
defaultMessage="Controls the indexing of the HTTP response headers to "
|
||||
/>
|
||||
<EuiCode>http.response.body.headers</EuiCode>
|
||||
</>
|
||||
}
|
||||
data-test-subj="syntheticsIndexResponseHeaders"
|
||||
>
|
||||
<EuiCheckbox
|
||||
id={'uptimeFleetIndexResponseHeaders'}
|
||||
checked={fields[ConfigKey.RESPONSE_HEADERS_INDEX]}
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.createPackagePolicy.stepConfigure.httpAdvancedOptions.responseConfig.indexResponseHeaders"
|
||||
defaultMessage="Index response headers"
|
||||
/>
|
||||
}
|
||||
onChange={(event) =>
|
||||
handleInputChange({
|
||||
value: event.target.checked,
|
||||
configKey: ConfigKey.RESPONSE_HEADERS_INDEX,
|
||||
})
|
||||
}
|
||||
onBlur={() => onFieldBlur?.(ConfigKey.RESPONSE_HEADERS_INDEX)}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
<EuiFormRow
|
||||
helpText={
|
||||
<>
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.createPackagePolicy.stepConfigure.httpAdvancedOptions.indexResponseBody.helpText"
|
||||
defaultMessage="Controls the indexing of the HTTP response body contents to "
|
||||
/>
|
||||
<EuiCode>http.response.body.contents</EuiCode>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<ResponseBodyIndexField
|
||||
defaultValue={fields[ConfigKey.RESPONSE_BODY_INDEX]}
|
||||
onChange={useCallback(
|
||||
(policy) =>
|
||||
handleInputChange({ value: policy, configKey: ConfigKey.RESPONSE_BODY_INDEX }),
|
||||
[handleInputChange]
|
||||
)}
|
||||
onBlur={() => onFieldBlur?.(ConfigKey.RESPONSE_BODY_INDEX)}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</DescribedFormGroupWithWrap>
|
||||
<DescribedFormGroupWithWrap
|
||||
minColumnWidth={minColumnWidth}
|
||||
title={
|
||||
<h4>
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.createPackagePolicy.stepConfigure.httpAdvancedOptions.responseChecks.title"
|
||||
defaultMessage="Response checks"
|
||||
/>
|
||||
</h4>
|
||||
}
|
||||
description={
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.createPackagePolicy.stepConfigure.httpAdvancedOptions.responseChecks.description"
|
||||
defaultMessage="Configure the expected HTTP response."
|
||||
/>
|
||||
}
|
||||
>
|
||||
<EuiFormRow
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.createPackagePolicy.stepConfigure.httpAdvancedOptions.responseChecks.responseStatusCheck.label"
|
||||
defaultMessage="Check response status equals"
|
||||
/>
|
||||
}
|
||||
labelAppend={<OptionalLabel />}
|
||||
isInvalid={!!validate[ConfigKey.RESPONSE_STATUS_CHECK]?.(fields)}
|
||||
error={
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.createPackagePolicy.stepConfigure.httpAdvancedOptions.responseChecks.responseStatusCheck.error"
|
||||
defaultMessage="Status code must contain digits only."
|
||||
/>
|
||||
}
|
||||
helpText={i18n.translate(
|
||||
'xpack.synthetics.createPackagePolicy.stepConfigure.httpAdvancedOptions.responseChecks.responseStatusCheck.helpText',
|
||||
{
|
||||
defaultMessage:
|
||||
'A list of expected status codes. Press enter to add a new code. 4xx and 5xx codes are considered down by default. Other codes are considered up.',
|
||||
}
|
||||
)}
|
||||
>
|
||||
<ComboBox
|
||||
selectedOptions={fields[ConfigKey.RESPONSE_STATUS_CHECK]}
|
||||
onChange={(value) =>
|
||||
handleInputChange({
|
||||
value,
|
||||
configKey: ConfigKey.RESPONSE_STATUS_CHECK,
|
||||
})
|
||||
}
|
||||
onBlur={() => onFieldBlur?.(ConfigKey.RESPONSE_STATUS_CHECK)}
|
||||
data-test-subj="syntheticsResponseStatusCheck"
|
||||
/>
|
||||
</EuiFormRow>
|
||||
<EuiFormRow
|
||||
fullWidth
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.createPackagePolicy.stepConfigure.httpAdvancedOptions.responseChecks.checkResponseHeadersContain"
|
||||
defaultMessage="Check response headers contain"
|
||||
/>
|
||||
}
|
||||
labelAppend={<OptionalLabel />}
|
||||
isInvalid={!!validate[ConfigKey.RESPONSE_HEADERS_CHECK]?.(fields)}
|
||||
error={[
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.createPackagePolicy.stepConfigure.httpAdvancedOptions.responseHeadersField.error"
|
||||
defaultMessage="Header key must be a valid HTTP token."
|
||||
/>,
|
||||
]}
|
||||
helpText={
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.createPackagePolicy.stepConfigure.httpAdvancedOptions.responseHeadersField.helpText"
|
||||
defaultMessage="A list of expected response headers."
|
||||
/>
|
||||
}
|
||||
>
|
||||
<HeaderField
|
||||
defaultValue={fields[ConfigKey.RESPONSE_HEADERS_CHECK]}
|
||||
onChange={useCallback(
|
||||
(value) =>
|
||||
handleInputChange({
|
||||
value,
|
||||
configKey: ConfigKey.RESPONSE_HEADERS_CHECK,
|
||||
}),
|
||||
[handleInputChange]
|
||||
)}
|
||||
onBlur={() => onFieldBlur?.(ConfigKey.RESPONSE_HEADERS_CHECK)}
|
||||
data-test-subj="syntheticsResponseHeaders"
|
||||
/>
|
||||
</EuiFormRow>
|
||||
<EuiFormRow
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.createPackagePolicy.stepConfigure.httpAdvancedOptions.responseCheckPositive.label"
|
||||
defaultMessage="Check response body contains"
|
||||
/>
|
||||
}
|
||||
labelAppend={<OptionalLabel />}
|
||||
helpText={i18n.translate(
|
||||
'xpack.synthetics.createPackagePolicy.stepConfigure.httpAdvancedOptions.responseBodyCheckPositive.helpText',
|
||||
{
|
||||
defaultMessage:
|
||||
'A list of regular expressions to match the body output. Press enter to add a new expression. Only a single expression needs to match.',
|
||||
}
|
||||
)}
|
||||
>
|
||||
<ComboBox
|
||||
selectedOptions={fields[ConfigKey.RESPONSE_BODY_CHECK_POSITIVE]}
|
||||
onChange={useCallback(
|
||||
(value) =>
|
||||
handleInputChange({
|
||||
value,
|
||||
configKey: ConfigKey.RESPONSE_BODY_CHECK_POSITIVE,
|
||||
}),
|
||||
[handleInputChange]
|
||||
)}
|
||||
onBlur={() => onFieldBlur?.(ConfigKey.RESPONSE_BODY_CHECK_POSITIVE)}
|
||||
data-test-subj="syntheticsResponseBodyCheckPositive"
|
||||
/>
|
||||
</EuiFormRow>
|
||||
<EuiFormRow
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.createPackagePolicy.stepConfigure.httpAdvancedOptions.responseCheckNegative.label"
|
||||
defaultMessage="Check response body does not contain"
|
||||
/>
|
||||
}
|
||||
labelAppend={<OptionalLabel />}
|
||||
helpText={i18n.translate(
|
||||
'xpack.synthetics.createPackagePolicy.stepConfigure.httpAdvancedOptions.responseBodyCheckNegative.helpText',
|
||||
{
|
||||
defaultMessage:
|
||||
'A list of regular expressions to match the the body output negatively. Press enter to add a new expression. Return match failed if single expression matches.',
|
||||
}
|
||||
)}
|
||||
>
|
||||
<ComboBox
|
||||
selectedOptions={fields[ConfigKey.RESPONSE_BODY_CHECK_NEGATIVE]}
|
||||
onChange={useCallback(
|
||||
(value) =>
|
||||
handleInputChange({
|
||||
value,
|
||||
configKey: ConfigKey.RESPONSE_BODY_CHECK_NEGATIVE,
|
||||
}),
|
||||
[handleInputChange]
|
||||
)}
|
||||
onBlur={() => onFieldBlur?.(ConfigKey.RESPONSE_BODY_CHECK_NEGATIVE)}
|
||||
data-test-subj="syntheticsResponseBodyCheckNegative"
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</DescribedFormGroupWithWrap>
|
||||
{children}
|
||||
</EuiAccordion>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
const requestMethodOptions = Object.values(HTTPMethod).map((method) => ({
|
||||
value: method,
|
||||
text: method,
|
||||
}));
|
|
@ -1,127 +0,0 @@
|
|||
/*
|
||||
* 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 { EuiFieldNumber, EuiFieldText, EuiFormRow } from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import React, { memo, useCallback } from 'react';
|
||||
import { SimpleFieldsWrapper } from '../common/simple_fields_wrapper';
|
||||
import { useHTTPSimpleFieldsContext } from '../contexts';
|
||||
import { OptionalLabel } from '../optional_label';
|
||||
import { ScheduleField } from '../schedule_field';
|
||||
import { ConfigKey, Validation } from '../types';
|
||||
|
||||
interface Props {
|
||||
validate: Validation;
|
||||
onFieldBlur: (field: ConfigKey) => void; // To propagate blurred state up to parents
|
||||
}
|
||||
|
||||
export const HTTPSimpleFields = memo<Props>(({ validate, onFieldBlur }) => {
|
||||
const { fields, setFields } = useHTTPSimpleFieldsContext();
|
||||
const handleInputChange = useCallback(
|
||||
({ value, configKey }: { value: unknown; configKey: ConfigKey }) => {
|
||||
setFields((prevFields) => ({ ...prevFields, [configKey]: value }));
|
||||
},
|
||||
[setFields]
|
||||
);
|
||||
|
||||
return (
|
||||
<SimpleFieldsWrapper
|
||||
fields={fields}
|
||||
validate={validate}
|
||||
onInputChange={handleInputChange}
|
||||
onFieldBlur={onFieldBlur}
|
||||
>
|
||||
<EuiFormRow
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.URL"
|
||||
defaultMessage="URL"
|
||||
/>
|
||||
}
|
||||
isInvalid={!!validate[ConfigKey.URLS]?.(fields)}
|
||||
error={
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.URL.error"
|
||||
defaultMessage="URL is required"
|
||||
/>
|
||||
}
|
||||
>
|
||||
<EuiFieldText
|
||||
value={fields[ConfigKey.URLS]}
|
||||
onChange={(event) =>
|
||||
handleInputChange({ value: event.target.value, configKey: ConfigKey.URLS })
|
||||
}
|
||||
onBlur={() => onFieldBlur(ConfigKey.URLS)}
|
||||
data-test-subj="syntheticsUrlField"
|
||||
/>
|
||||
</EuiFormRow>
|
||||
<EuiFormRow
|
||||
id="syntheticsFleetScheduleField"
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.monitorInterval"
|
||||
defaultMessage="Frequency"
|
||||
/>
|
||||
}
|
||||
isInvalid={!!validate[ConfigKey.SCHEDULE]?.(fields)}
|
||||
error={
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.monitorInterval.error"
|
||||
defaultMessage="Monitor frequency is required"
|
||||
/>
|
||||
}
|
||||
>
|
||||
<ScheduleField
|
||||
onChange={(schedule) =>
|
||||
handleInputChange({
|
||||
value: schedule,
|
||||
configKey: ConfigKey.SCHEDULE,
|
||||
})
|
||||
}
|
||||
onBlur={() => onFieldBlur(ConfigKey.SCHEDULE)}
|
||||
number={fields[ConfigKey.SCHEDULE].number}
|
||||
unit={fields[ConfigKey.SCHEDULE].unit}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
<EuiFormRow
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.maxRedirects"
|
||||
defaultMessage="Max redirects"
|
||||
/>
|
||||
}
|
||||
isInvalid={!!validate[ConfigKey.MAX_REDIRECTS]?.(fields)}
|
||||
error={
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.maxRedirects.error"
|
||||
defaultMessage="Max redirects must be 0 or greater"
|
||||
/>
|
||||
}
|
||||
labelAppend={<OptionalLabel />}
|
||||
helpText={
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.maxRedirects.helpText"
|
||||
defaultMessage="The total number of redirections to follow."
|
||||
/>
|
||||
}
|
||||
>
|
||||
<EuiFieldNumber
|
||||
data-test-subj="syntheticsHTTPSimpleFieldsFieldNumber"
|
||||
min={0}
|
||||
value={fields[ConfigKey.MAX_REDIRECTS]}
|
||||
onChange={(event) =>
|
||||
handleInputChange({
|
||||
value: event.target.value,
|
||||
configKey: ConfigKey.MAX_REDIRECTS,
|
||||
})
|
||||
}
|
||||
onBlur={() => onFieldBlur(ConfigKey.MAX_REDIRECTS)}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</SimpleFieldsWrapper>
|
||||
);
|
||||
});
|
|
@ -1,33 +0,0 @@
|
|||
/*
|
||||
* 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 from 'react';
|
||||
import { render } from '../../../lib/helper/rtl_helpers';
|
||||
import { ICMPAdvancedFields } from './advanced_fields';
|
||||
|
||||
// ensures fields and labels map appropriately
|
||||
jest.mock('@elastic/eui/lib/services/accessibility/html_id_generator', () => ({
|
||||
htmlIdGenerator: () => () => `id-${Math.random()}`,
|
||||
}));
|
||||
|
||||
describe('<ICMPAdvancedFields />', () => {
|
||||
const WrappedComponent = ({ children }: { children?: React.ReactNode }) => (
|
||||
<ICMPAdvancedFields>{children}</ICMPAdvancedFields>
|
||||
);
|
||||
|
||||
it('renders upstream fields', () => {
|
||||
const upstreamFieldsText = 'Monitor Advanced field section';
|
||||
const { getByText, getByTestId } = render(
|
||||
<WrappedComponent>{upstreamFieldsText}</WrappedComponent>
|
||||
);
|
||||
|
||||
const upstream = getByText(upstreamFieldsText) as HTMLInputElement;
|
||||
const accordion = getByTestId('syntheticsICMPAdvancedFieldsAccordion') as HTMLInputElement;
|
||||
expect(upstream).toBeInTheDocument();
|
||||
expect(accordion).toBeInTheDocument();
|
||||
});
|
||||
});
|
|
@ -1,32 +0,0 @@
|
|||
/*
|
||||
* 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 { EuiAccordion, EuiSpacer } from '@elastic/eui';
|
||||
import React from 'react';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
|
||||
export const ICMPAdvancedFields = ({ children }: { children?: React.ReactNode }) => {
|
||||
if (!!children) {
|
||||
return (
|
||||
<EuiAccordion
|
||||
id="uptimeFleetIcmpAdvancedOptions"
|
||||
buttonContent={
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.createPackagePolicy.stepConfigure.icmpAdvancedOptions"
|
||||
defaultMessage="Advanced ICMP options"
|
||||
/>
|
||||
}
|
||||
data-test-subj="syntheticsICMPAdvancedFieldsAccordion"
|
||||
>
|
||||
<EuiSpacer size="xl" />
|
||||
{children}
|
||||
</EuiAccordion>
|
||||
);
|
||||
}
|
||||
|
||||
return <></>;
|
||||
};
|
|
@ -1,131 +0,0 @@
|
|||
/*
|
||||
* 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, { memo, useCallback } from 'react';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { EuiFormRow, EuiFieldText, EuiFieldNumber } from '@elastic/eui';
|
||||
import { ConfigKey, Validation } from '../types';
|
||||
import { useICMPSimpleFieldsContext } from '../contexts';
|
||||
import { OptionalLabel } from '../optional_label';
|
||||
import { ScheduleField } from '../schedule_field';
|
||||
import { SimpleFieldsWrapper } from '../common/simple_fields_wrapper';
|
||||
|
||||
interface Props {
|
||||
validate: Validation;
|
||||
onFieldBlur: (field: ConfigKey) => void; // To propagate blurred state up to parents
|
||||
}
|
||||
|
||||
export const ICMPSimpleFields = memo<Props>(({ validate, onFieldBlur }) => {
|
||||
const { fields, setFields } = useICMPSimpleFieldsContext();
|
||||
const handleInputChange = useCallback(
|
||||
({ value, configKey }: { value: unknown; configKey: ConfigKey }) => {
|
||||
setFields((prevFields) => ({ ...prevFields, [configKey]: value }));
|
||||
},
|
||||
[setFields]
|
||||
);
|
||||
|
||||
return (
|
||||
<SimpleFieldsWrapper
|
||||
fields={fields}
|
||||
validate={validate}
|
||||
onInputChange={handleInputChange}
|
||||
onFieldBlur={onFieldBlur}
|
||||
>
|
||||
<EuiFormRow
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.icmp.hosts"
|
||||
defaultMessage="Host"
|
||||
/>
|
||||
}
|
||||
isInvalid={!!validate[ConfigKey.HOSTS]?.(fields)}
|
||||
error={
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.icmp.hosts.error"
|
||||
defaultMessage="Host is required"
|
||||
/>
|
||||
}
|
||||
>
|
||||
<EuiFieldText
|
||||
value={fields[ConfigKey.HOSTS]}
|
||||
onChange={(event) =>
|
||||
handleInputChange({
|
||||
value: event.target.value,
|
||||
configKey: ConfigKey.HOSTS,
|
||||
})
|
||||
}
|
||||
onBlur={() => onFieldBlur(ConfigKey.HOSTS)}
|
||||
data-test-subj="syntheticsICMPHostField"
|
||||
/>
|
||||
</EuiFormRow>
|
||||
<EuiFormRow
|
||||
id="syntheticsFleetScheduleField--number syntheticsFleetScheduleField--unit"
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.monitorInterval"
|
||||
defaultMessage="Frequency"
|
||||
/>
|
||||
}
|
||||
isInvalid={!!validate[ConfigKey.SCHEDULE]?.(fields)}
|
||||
error={
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.monitorInterval.error"
|
||||
defaultMessage="Monitor frequency is required"
|
||||
/>
|
||||
}
|
||||
>
|
||||
<ScheduleField
|
||||
onChange={(schedule) =>
|
||||
handleInputChange({
|
||||
value: schedule,
|
||||
configKey: ConfigKey.SCHEDULE,
|
||||
})
|
||||
}
|
||||
onBlur={() => onFieldBlur(ConfigKey.SCHEDULE)}
|
||||
number={fields[ConfigKey.SCHEDULE].number}
|
||||
unit={fields[ConfigKey.SCHEDULE].unit}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
<EuiFormRow
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.wait.label"
|
||||
defaultMessage="Wait in seconds"
|
||||
/>
|
||||
}
|
||||
isInvalid={!!validate[ConfigKey.WAIT]?.(fields)}
|
||||
error={
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.wait.error"
|
||||
defaultMessage="Wait must be 0 or greater"
|
||||
/>
|
||||
}
|
||||
labelAppend={<OptionalLabel />}
|
||||
helpText={
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.wait.helpText"
|
||||
defaultMessage="The duration to wait before emitting another ICMP Echo Request if no response is received."
|
||||
/>
|
||||
}
|
||||
>
|
||||
<EuiFieldNumber
|
||||
data-test-subj="syntheticsICMPSimpleFieldsFieldNumber"
|
||||
min={0}
|
||||
value={fields[ConfigKey.WAIT]}
|
||||
onChange={(event) =>
|
||||
handleInputChange({
|
||||
value: event.target.value,
|
||||
configKey: ConfigKey.WAIT,
|
||||
})
|
||||
}
|
||||
onBlur={() => onFieldBlur(ConfigKey.WAIT)}
|
||||
step={'any'}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</SimpleFieldsWrapper>
|
||||
);
|
||||
});
|
|
@ -1,115 +0,0 @@
|
|||
/*
|
||||
* 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 from 'react';
|
||||
import { fireEvent, waitFor } from '@testing-library/react';
|
||||
import { render } from '../../lib/helper/rtl_helpers';
|
||||
import { ResponseBodyIndexField } from './index_response_body_field';
|
||||
import { ResponseBodyIndexPolicy } from './types';
|
||||
|
||||
describe('<ResponseBodyIndexField/>', () => {
|
||||
const defaultDefaultValue = ResponseBodyIndexPolicy.ON_ERROR;
|
||||
const onChange = jest.fn();
|
||||
const onBlur = jest.fn();
|
||||
const WrappedComponent = ({ defaultValue = defaultDefaultValue }) => {
|
||||
return (
|
||||
<ResponseBodyIndexField defaultValue={defaultValue} onChange={onChange} onBlur={onBlur} />
|
||||
);
|
||||
};
|
||||
|
||||
afterEach(() => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
it('renders ResponseBodyIndexField', () => {
|
||||
const { getByText, getByTestId } = render(<WrappedComponent />);
|
||||
const select = getByTestId('indexResponseBodyFieldSelect') as HTMLInputElement;
|
||||
expect(select.value).toEqual(defaultDefaultValue);
|
||||
expect(getByText('On error')).toBeInTheDocument();
|
||||
expect(getByText('Index response body')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('handles select change', async () => {
|
||||
const { getByText, getByTestId } = render(<WrappedComponent />);
|
||||
const select = getByTestId('indexResponseBodyFieldSelect') as HTMLInputElement;
|
||||
const newPolicy = ResponseBodyIndexPolicy.ALWAYS;
|
||||
expect(select.value).toEqual(defaultDefaultValue);
|
||||
|
||||
fireEvent.change(select, { target: { value: newPolicy } });
|
||||
|
||||
await waitFor(() => {
|
||||
expect(select.value).toBe(newPolicy);
|
||||
expect(getByText('Always')).toBeInTheDocument();
|
||||
expect(onChange).toBeCalledWith(newPolicy);
|
||||
});
|
||||
});
|
||||
|
||||
it('calls onBlur', async () => {
|
||||
const { getByTestId } = render(<WrappedComponent />);
|
||||
const select = getByTestId('indexResponseBodyFieldSelect') as HTMLInputElement;
|
||||
const newPolicy = ResponseBodyIndexPolicy.ALWAYS;
|
||||
|
||||
fireEvent.change(select, { target: { value: newPolicy } });
|
||||
fireEvent.blur(select);
|
||||
|
||||
expect(onBlur).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('handles checkbox change', async () => {
|
||||
const { getByTestId, getByLabelText } = render(<WrappedComponent />);
|
||||
const checkbox = getByLabelText('Index response body') as HTMLInputElement;
|
||||
const select = getByTestId('indexResponseBodyFieldSelect') as HTMLInputElement;
|
||||
const newPolicy = ResponseBodyIndexPolicy.NEVER;
|
||||
expect(checkbox.checked).toBe(true);
|
||||
|
||||
fireEvent.click(checkbox);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(checkbox.checked).toBe(false);
|
||||
expect(select).not.toBeInTheDocument();
|
||||
expect(onChange).toBeCalledWith(newPolicy);
|
||||
});
|
||||
|
||||
fireEvent.click(checkbox);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(checkbox.checked).toBe(true);
|
||||
expect(select).not.toBeInTheDocument();
|
||||
expect(onChange).toBeCalledWith(defaultDefaultValue);
|
||||
});
|
||||
});
|
||||
|
||||
it('handles ResponseBodyIndexPolicy.NEVER as a default value', async () => {
|
||||
const { queryByTestId, getByTestId, getByLabelText } = render(
|
||||
<WrappedComponent defaultValue={ResponseBodyIndexPolicy.NEVER} />
|
||||
);
|
||||
const checkbox = getByLabelText('Index response body') as HTMLInputElement;
|
||||
expect(checkbox.checked).toBe(false);
|
||||
expect(
|
||||
queryByTestId('indexResponseBodyFieldSelect') as HTMLInputElement
|
||||
).not.toBeInTheDocument();
|
||||
|
||||
fireEvent.click(checkbox);
|
||||
const select = getByTestId('indexResponseBodyFieldSelect') as HTMLInputElement;
|
||||
|
||||
await waitFor(() => {
|
||||
expect(checkbox.checked).toBe(true);
|
||||
expect(select).toBeInTheDocument();
|
||||
expect(select.value).toEqual(ResponseBodyIndexPolicy.ON_ERROR);
|
||||
// switches back to on error policy when checkbox is checked
|
||||
expect(onChange).toBeCalledWith(ResponseBodyIndexPolicy.ON_ERROR);
|
||||
});
|
||||
|
||||
const newPolicy = ResponseBodyIndexPolicy.ALWAYS;
|
||||
fireEvent.change(select, { target: { value: newPolicy } });
|
||||
|
||||
await waitFor(() => {
|
||||
expect(select.value).toEqual(newPolicy);
|
||||
expect(onChange).toBeCalledWith(newPolicy);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,101 +0,0 @@
|
|||
/*
|
||||
* 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 { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
|
||||
import { EuiCheckbox, EuiFlexGroup, EuiFlexItem, EuiSelect } from '@elastic/eui';
|
||||
import { ResponseBodyIndexPolicy } from './types';
|
||||
|
||||
interface Props {
|
||||
defaultValue: ResponseBodyIndexPolicy;
|
||||
onChange: (responseBodyIndexPolicy: ResponseBodyIndexPolicy) => void;
|
||||
onBlur?: () => void;
|
||||
}
|
||||
|
||||
export const ResponseBodyIndexField = ({ defaultValue, onChange, onBlur }: Props) => {
|
||||
const [policy, setPolicy] = useState<ResponseBodyIndexPolicy>(
|
||||
defaultValue !== ResponseBodyIndexPolicy.NEVER ? defaultValue : ResponseBodyIndexPolicy.ON_ERROR
|
||||
);
|
||||
const [checked, setChecked] = useState<boolean>(defaultValue !== ResponseBodyIndexPolicy.NEVER);
|
||||
|
||||
useEffect(() => {
|
||||
if (checked) {
|
||||
setPolicy(policy);
|
||||
onChange(policy);
|
||||
} else {
|
||||
onChange(ResponseBodyIndexPolicy.NEVER);
|
||||
}
|
||||
}, [checked, policy, setPolicy, onChange]);
|
||||
|
||||
useEffect(() => {
|
||||
onChange(policy);
|
||||
}, [onChange, policy]);
|
||||
|
||||
return (
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem data-test-subj="syntheticsIndexResponseBody">
|
||||
<EuiCheckbox
|
||||
id="uptimeFleetIndexResponseBody"
|
||||
checked={checked}
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.createPackagePolicy.stepConfigure.httpAdvancedOptions.responseConfig.indexResponseBody"
|
||||
defaultMessage="Index response body"
|
||||
/>
|
||||
}
|
||||
onChange={(event) => {
|
||||
const checkedEvent = event.target.checked;
|
||||
setChecked(checkedEvent);
|
||||
}}
|
||||
onBlur={() => onBlur?.()}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
{checked && (
|
||||
<EuiFlexItem>
|
||||
<EuiSelect
|
||||
aria-label={i18n.translate(
|
||||
'xpack.synthetics.createPackagePolicy.stepConfigure.httpAdvancedOptions.responseConfig.responseBodyIndexPolicy',
|
||||
{
|
||||
defaultMessage: 'Response body index policy',
|
||||
}
|
||||
)}
|
||||
data-test-subj="indexResponseBodyFieldSelect"
|
||||
options={responseBodyIndexPolicyOptions}
|
||||
value={policy}
|
||||
onChange={(event) => {
|
||||
setPolicy(event.target.value as ResponseBodyIndexPolicy);
|
||||
}}
|
||||
onBlur={() => onBlur?.()}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
};
|
||||
|
||||
const responseBodyIndexPolicyOptions = [
|
||||
{
|
||||
value: ResponseBodyIndexPolicy.ALWAYS,
|
||||
text: i18n.translate(
|
||||
'xpack.synthetics.createPackagePolicy.stepConfigure.responseBodyIndex.always',
|
||||
{
|
||||
defaultMessage: 'Always',
|
||||
}
|
||||
),
|
||||
},
|
||||
{
|
||||
value: ResponseBodyIndexPolicy.ON_ERROR,
|
||||
text: i18n.translate(
|
||||
'xpack.synthetics.createPackagePolicy.stepConfigure.responseBodyIndex.onError',
|
||||
{
|
||||
defaultMessage: 'On error',
|
||||
}
|
||||
),
|
||||
},
|
||||
];
|
|
@ -1,89 +0,0 @@
|
|||
/*
|
||||
* 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 userEvent from '@testing-library/user-event';
|
||||
import React from 'react';
|
||||
import { fireEvent, waitFor } from '@testing-library/react';
|
||||
import { render } from '../../lib/helper/rtl_helpers';
|
||||
import { KeyValuePairsField, Pair } from './key_value_field';
|
||||
|
||||
describe('<KeyValuePairsField />', () => {
|
||||
const onChange = jest.fn();
|
||||
const onBlur = jest.fn();
|
||||
const defaultDefaultValue = [['', '']] as Pair[];
|
||||
const WrappedComponent = ({
|
||||
defaultValue = defaultDefaultValue,
|
||||
addPairControlLabel = 'Add pair',
|
||||
}) => {
|
||||
return (
|
||||
<KeyValuePairsField
|
||||
defaultPairs={defaultValue}
|
||||
onChange={onChange}
|
||||
onBlur={onBlur}
|
||||
addPairControlLabel={addPairControlLabel}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
afterEach(() => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
it('renders KeyValuePairsField', () => {
|
||||
const { getByText } = render(<WrappedComponent />);
|
||||
expect(getByText('Key')).toBeInTheDocument();
|
||||
expect(getByText('Value')).toBeInTheDocument();
|
||||
|
||||
expect(getByText('Add pair')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('calls onBlur', () => {
|
||||
const { getByText, getByTestId } = render(<WrappedComponent />);
|
||||
const addPair = getByText('Add pair');
|
||||
fireEvent.click(addPair);
|
||||
|
||||
const keyInput = getByTestId('keyValuePairsKey0') as HTMLInputElement;
|
||||
const valueInput = getByTestId('keyValuePairsValue0') as HTMLInputElement;
|
||||
|
||||
userEvent.type(keyInput, 'some-key');
|
||||
userEvent.type(valueInput, 'some-value');
|
||||
fireEvent.blur(valueInput);
|
||||
|
||||
expect(onBlur).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it('handles adding and editing a new row', async () => {
|
||||
const { getByTestId, queryByTestId, getByText } = render(
|
||||
<WrappedComponent defaultValue={[]} />
|
||||
);
|
||||
|
||||
expect(queryByTestId('keyValuePairsKey0')).not.toBeInTheDocument();
|
||||
expect(queryByTestId('keyValuePairsValue0')).not.toBeInTheDocument(); // check that only one row exists
|
||||
|
||||
const addPair = getByText('Add pair');
|
||||
|
||||
fireEvent.click(addPair);
|
||||
|
||||
const newRowKey = getByTestId('keyValuePairsKey0') as HTMLInputElement;
|
||||
const newRowValue = getByTestId('keyValuePairsValue0') as HTMLInputElement;
|
||||
|
||||
await waitFor(() => {
|
||||
expect(newRowKey.value).toEqual('');
|
||||
expect(newRowValue.value).toEqual('');
|
||||
expect(onChange).toBeCalledWith([[newRowKey.value, newRowValue.value]]);
|
||||
});
|
||||
|
||||
fireEvent.change(newRowKey, { target: { value: 'newKey' } });
|
||||
fireEvent.change(newRowValue, { target: { value: 'newValue' } });
|
||||
|
||||
await waitFor(() => {
|
||||
expect(newRowKey.value).toEqual('newKey');
|
||||
expect(newRowValue.value).toEqual('newValue');
|
||||
expect(onChange).toBeCalledWith([[newRowKey.value, newRowValue.value]]);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,201 +0,0 @@
|
|||
/*
|
||||
* 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, { Fragment, useCallback, useEffect, useState } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import {
|
||||
EuiButton,
|
||||
EuiButtonIcon,
|
||||
EuiFieldText,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiFormControlLayoutDelimited,
|
||||
EuiFormLabel,
|
||||
EuiFormFieldset,
|
||||
EuiSpacer,
|
||||
} from '@elastic/eui';
|
||||
|
||||
const StyledFieldset = styled(EuiFormFieldset)`
|
||||
&&& {
|
||||
legend {
|
||||
width: calc(100% - 52px); // right margin + flex item padding
|
||||
margin-right: 40px;
|
||||
}
|
||||
.euiFlexGroup {
|
||||
margin-left: 0;
|
||||
}
|
||||
.euiFlexItem {
|
||||
margin-left: 0;
|
||||
padding-left: 12px;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledField = styled(EuiFieldText)`
|
||||
text-align: left;
|
||||
`;
|
||||
|
||||
export type Pair = [
|
||||
string, // key
|
||||
string // value
|
||||
];
|
||||
|
||||
interface Props {
|
||||
addPairControlLabel: string | React.ReactElement;
|
||||
defaultPairs: Pair[];
|
||||
onChange: (pairs: Pair[]) => void;
|
||||
onBlur?: () => void;
|
||||
'data-test-subj'?: string;
|
||||
}
|
||||
|
||||
export const KeyValuePairsField = ({
|
||||
addPairControlLabel,
|
||||
defaultPairs,
|
||||
onChange,
|
||||
onBlur,
|
||||
'data-test-subj': dataTestSubj,
|
||||
}: Props) => {
|
||||
const [pairs, setPairs] = useState<Pair[]>(defaultPairs);
|
||||
|
||||
const handleOnChange = useCallback(
|
||||
(event: React.ChangeEvent<HTMLInputElement>, index: number, isKey: boolean) => {
|
||||
const targetValue = event.target.value;
|
||||
|
||||
setPairs((prevPairs) => {
|
||||
const newPairs = [...prevPairs];
|
||||
const [prevKey, prevValue] = prevPairs[index];
|
||||
newPairs[index] = isKey ? [targetValue, prevValue] : [prevKey, targetValue];
|
||||
return newPairs;
|
||||
});
|
||||
},
|
||||
[setPairs]
|
||||
);
|
||||
|
||||
const handleAddPair = useCallback(() => {
|
||||
setPairs((prevPairs) => [['', ''], ...prevPairs]);
|
||||
}, [setPairs]);
|
||||
|
||||
const handleDeletePair = useCallback(
|
||||
(index: number) => {
|
||||
setPairs((prevPairs) => {
|
||||
const newPairs = [...prevPairs];
|
||||
newPairs.splice(index, 1);
|
||||
return [...newPairs];
|
||||
});
|
||||
},
|
||||
[setPairs]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
onChange(pairs);
|
||||
}, [onChange, pairs]);
|
||||
|
||||
return (
|
||||
<div data-test-subj={dataTestSubj}>
|
||||
<EuiSpacer size="s" />
|
||||
<EuiFlexGroup justifyContent="flexEnd">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton
|
||||
iconType="plus"
|
||||
onClick={handleAddPair}
|
||||
data-test-subj={`${dataTestSubj}__button`}
|
||||
>
|
||||
{addPairControlLabel}
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<EuiSpacer size="s" />
|
||||
<StyledFieldset
|
||||
legend={
|
||||
!!pairs.length
|
||||
? {
|
||||
children: (
|
||||
<EuiFlexGroup responsive={false}>
|
||||
<EuiFlexItem>
|
||||
{
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.keyValuePairsField.key.label"
|
||||
defaultMessage="Key"
|
||||
/>
|
||||
}
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
{
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.keyValuePairsField.value.label"
|
||||
defaultMessage="Value"
|
||||
/>
|
||||
}
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
),
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
{pairs.map((pair, index) => {
|
||||
const [key, value] = pair;
|
||||
return (
|
||||
<Fragment key={index}>
|
||||
<EuiSpacer size="xs" />
|
||||
<EuiFormControlLayoutDelimited
|
||||
fullWidth
|
||||
append={
|
||||
<EuiFormLabel>
|
||||
<EuiButtonIcon
|
||||
iconType="trash"
|
||||
aria-label={i18n.translate(
|
||||
'xpack.synthetics.keyValuePairsField.deleteItem.label',
|
||||
{
|
||||
defaultMessage: 'Delete item number {index}, {key}:{value}',
|
||||
values: { index: index + 1, key, value },
|
||||
}
|
||||
)}
|
||||
onClick={() => handleDeletePair(index)}
|
||||
/>
|
||||
</EuiFormLabel>
|
||||
}
|
||||
startControl={
|
||||
<StyledField
|
||||
aria-label={i18n.translate(
|
||||
'xpack.synthetics.keyValuePairsField.key.ariaLabel',
|
||||
{
|
||||
defaultMessage: 'Key',
|
||||
}
|
||||
)}
|
||||
data-test-subj={`keyValuePairsKey${index}`}
|
||||
value={key}
|
||||
onChange={(event) => handleOnChange(event, index, true)}
|
||||
onBlur={() => onBlur?.()}
|
||||
/>
|
||||
}
|
||||
endControl={
|
||||
<StyledField
|
||||
aria-label={i18n.translate(
|
||||
'xpack.synthetics.keyValuePairsField.value.ariaLabel',
|
||||
{
|
||||
defaultMessage: 'Value',
|
||||
}
|
||||
)}
|
||||
data-test-subj={`keyValuePairsValue${index}`}
|
||||
value={value}
|
||||
onChange={(event) => handleOnChange(event, index, false)}
|
||||
onBlur={() => onBlur?.()}
|
||||
/>
|
||||
}
|
||||
delimiter=":"
|
||||
/>
|
||||
<EuiSpacer size="xs" />
|
||||
</Fragment>
|
||||
);
|
||||
})}
|
||||
</StyledFieldset>
|
||||
</div>
|
||||
);
|
||||
};
|
|
@ -1,20 +0,0 @@
|
|||
/*
|
||||
* 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 from 'react';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { EuiText } from '@elastic/eui';
|
||||
|
||||
export const OptionalLabel = () => {
|
||||
return (
|
||||
<EuiText size="xs" color="subdued">
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.createPackagePolicy.stepConfigure.inputVarFieldOptionalLabel"
|
||||
defaultMessage="Optional"
|
||||
/>
|
||||
</EuiText>
|
||||
);
|
||||
};
|
|
@ -1,86 +0,0 @@
|
|||
/*
|
||||
* 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 'jest-canvas-mock';
|
||||
|
||||
import React, { useState, useCallback } from 'react';
|
||||
import { fireEvent, waitFor } from '@testing-library/react';
|
||||
import { render } from '../../lib/helper/rtl_helpers';
|
||||
import { RequestBodyField } from './request_body_field';
|
||||
import { Mode } from './types';
|
||||
|
||||
jest.mock('@elastic/eui/lib/services/accessibility/html_id_generator', () => ({
|
||||
htmlIdGenerator: () => () => `id-${Math.random()}`,
|
||||
}));
|
||||
|
||||
jest.mock('@kbn/kibana-react-plugin/public', () => {
|
||||
const original = jest.requireActual('@kbn/kibana-react-plugin/public');
|
||||
return {
|
||||
...original,
|
||||
// Mocking CodeEditor, which uses React Monaco under the hood
|
||||
CodeEditor: (props: any) => (
|
||||
<input
|
||||
data-test-subj={props['data-test-subj'] || 'mockCodeEditor'}
|
||||
data-currentvalue={props.value}
|
||||
onChange={(e: any) => {
|
||||
props.onChange(e.jsonContent);
|
||||
}}
|
||||
/>
|
||||
),
|
||||
};
|
||||
});
|
||||
|
||||
describe('<RequestBodyField />', () => {
|
||||
const defaultMode = Mode.PLAINTEXT;
|
||||
const defaultValue = 'sample value';
|
||||
const WrappedComponent = () => {
|
||||
const [config, setConfig] = useState({
|
||||
type: defaultMode,
|
||||
value: defaultValue,
|
||||
});
|
||||
|
||||
return (
|
||||
<RequestBodyField
|
||||
type={config.type}
|
||||
value={config.value}
|
||||
onChange={useCallback(
|
||||
(code) => setConfig({ type: code.type as Mode, value: code.value }),
|
||||
[setConfig]
|
||||
)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
it('renders RequestBodyField', () => {
|
||||
const { getByText, getByLabelText } = render(<WrappedComponent />);
|
||||
|
||||
expect(getByText('Form')).toBeInTheDocument();
|
||||
expect(getByText('Text')).toBeInTheDocument();
|
||||
expect(getByText('XML')).toBeInTheDocument();
|
||||
expect(getByText('JSON')).toBeInTheDocument();
|
||||
expect(getByLabelText('Text code editor')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('handles changing code editor mode', async () => {
|
||||
const { getByText, getByLabelText, queryByText, queryByLabelText } = render(
|
||||
<WrappedComponent />
|
||||
);
|
||||
|
||||
// currently text code editor is displayed
|
||||
expect(getByLabelText('Text code editor')).toBeInTheDocument();
|
||||
expect(queryByText('Key')).not.toBeInTheDocument();
|
||||
|
||||
const formButton = getByText('Form').closest('button');
|
||||
if (formButton) {
|
||||
fireEvent.click(formButton);
|
||||
}
|
||||
await waitFor(() => {
|
||||
expect(getByText('Add form field')).toBeInTheDocument();
|
||||
expect(queryByLabelText('Text code editor')).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,206 +0,0 @@
|
|||
/*
|
||||
* 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, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { stringify, parse } from 'query-string';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { EuiTabbedContent } from '@elastic/eui';
|
||||
import { Mode, MonacoEditorLangId } from './types';
|
||||
import { KeyValuePairsField, Pair } from './key_value_field';
|
||||
import { CodeEditor } from './code_editor';
|
||||
|
||||
interface Props {
|
||||
onChange: (requestBody: { type: Mode; value: string }) => void;
|
||||
onBlur?: () => void;
|
||||
type: Mode;
|
||||
value: string;
|
||||
}
|
||||
|
||||
enum ResponseBodyType {
|
||||
CODE = 'code',
|
||||
FORM = 'form',
|
||||
}
|
||||
|
||||
// TO DO: Look into whether or not code editor reports errors, in order to prevent form submission on an error
|
||||
export const RequestBodyField = ({ onChange, onBlur, type, value }: Props) => {
|
||||
const [values, setValues] = useState<Record<ResponseBodyType, string>>({
|
||||
[ResponseBodyType.FORM]: type === Mode.FORM ? value : '',
|
||||
[ResponseBodyType.CODE]: type !== Mode.FORM ? value : '',
|
||||
});
|
||||
useEffect(() => {
|
||||
onChange({
|
||||
type,
|
||||
value: type === Mode.FORM ? values[ResponseBodyType.FORM] : values[ResponseBodyType.CODE],
|
||||
});
|
||||
}, [onChange, type, values]);
|
||||
|
||||
const handleSetMode = useCallback(
|
||||
(currentMode: Mode) => {
|
||||
onChange({
|
||||
type: currentMode,
|
||||
value:
|
||||
currentMode === Mode.FORM ? values[ResponseBodyType.FORM] : values[ResponseBodyType.CODE],
|
||||
});
|
||||
},
|
||||
[onChange, values]
|
||||
);
|
||||
|
||||
const onChangeFormFields = useCallback(
|
||||
(pairs: Pair[]) => {
|
||||
const formattedPairs = pairs.reduce((acc: Record<string, string>, header) => {
|
||||
const [key, pairValue] = header;
|
||||
if (key) {
|
||||
return {
|
||||
...acc,
|
||||
[key]: pairValue,
|
||||
};
|
||||
}
|
||||
return acc;
|
||||
}, {});
|
||||
return setValues((prevValues) => ({
|
||||
...prevValues,
|
||||
[Mode.FORM]: stringify(formattedPairs),
|
||||
}));
|
||||
},
|
||||
[setValues]
|
||||
);
|
||||
|
||||
const defaultFormPairs: Pair[] = useMemo(() => {
|
||||
const pairs = parse(values[Mode.FORM]);
|
||||
const keys = Object.keys(pairs);
|
||||
const formattedPairs: Pair[] = keys.map((key: string) => {
|
||||
// key, value, checked;
|
||||
return [key, `${pairs[key]}`];
|
||||
});
|
||||
return formattedPairs;
|
||||
}, [values]);
|
||||
|
||||
const tabs = [
|
||||
{
|
||||
id: Mode.PLAINTEXT,
|
||||
name: modeLabels[Mode.PLAINTEXT],
|
||||
'data-test-subj': `syntheticsRequestBodyTab__${Mode.PLAINTEXT}`,
|
||||
content: (
|
||||
<CodeEditor
|
||||
ariaLabel={i18n.translate(
|
||||
'xpack.synthetics.createPackagePolicy.stepConfigure.requestBody.codeEditor.text.ariaLabel',
|
||||
{
|
||||
defaultMessage: 'Text code editor',
|
||||
}
|
||||
)}
|
||||
id={Mode.PLAINTEXT}
|
||||
languageId={MonacoEditorLangId.PLAINTEXT}
|
||||
onChange={(code) => {
|
||||
setValues((prevValues) => ({ ...prevValues, [ResponseBodyType.CODE]: code }));
|
||||
onBlur?.();
|
||||
}}
|
||||
value={values[ResponseBodyType.CODE]}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: Mode.JSON,
|
||||
name: modeLabels[Mode.JSON],
|
||||
'data-test-subj': `syntheticsRequestBodyTab__${Mode.JSON}`,
|
||||
content: (
|
||||
<CodeEditor
|
||||
ariaLabel={i18n.translate(
|
||||
'xpack.synthetics.createPackagePolicy.stepConfigure.requestBody.codeEditor.json.ariaLabel',
|
||||
{
|
||||
defaultMessage: 'JSON code editor',
|
||||
}
|
||||
)}
|
||||
id={Mode.JSON}
|
||||
languageId={MonacoEditorLangId.JSON}
|
||||
onChange={(code) => {
|
||||
setValues((prevValues) => ({ ...prevValues, [ResponseBodyType.CODE]: code }));
|
||||
onBlur?.();
|
||||
}}
|
||||
value={values[ResponseBodyType.CODE]}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: Mode.XML,
|
||||
name: modeLabels[Mode.XML],
|
||||
'data-test-subj': `syntheticsRequestBodyTab__${Mode.XML}`,
|
||||
content: (
|
||||
<CodeEditor
|
||||
ariaLabel={i18n.translate(
|
||||
'xpack.synthetics.createPackagePolicy.stepConfigure.requestBody.codeEditor.xml.ariaLabel',
|
||||
{
|
||||
defaultMessage: 'XML code editor',
|
||||
}
|
||||
)}
|
||||
id={Mode.XML}
|
||||
languageId={MonacoEditorLangId.XML}
|
||||
onChange={(code) => {
|
||||
setValues((prevValues) => ({ ...prevValues, [ResponseBodyType.CODE]: code }));
|
||||
onBlur?.();
|
||||
}}
|
||||
value={values[ResponseBodyType.CODE]}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: Mode.FORM,
|
||||
name: modeLabels[Mode.FORM],
|
||||
'data-test-subj': `syntheticsRequestBodyTab__${Mode.FORM}`,
|
||||
content: (
|
||||
<KeyValuePairsField
|
||||
addPairControlLabel={
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.createPackagePolicy.stepConfigure.requestBody.formField.addFormField.label"
|
||||
defaultMessage="Add form field"
|
||||
/>
|
||||
}
|
||||
defaultPairs={defaultFormPairs}
|
||||
onChange={onChangeFormFields}
|
||||
onBlur={() => onBlur?.()}
|
||||
/>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<EuiTabbedContent
|
||||
tabs={tabs}
|
||||
initialSelectedTab={tabs.find((tab) => tab.id === type)}
|
||||
autoFocus="selected"
|
||||
onTabClick={(tab) => {
|
||||
handleSetMode(tab.id as Mode);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const modeLabels = {
|
||||
[Mode.FORM]: i18n.translate(
|
||||
'xpack.synthetics.createPackagePolicy.stepConfigure.requestBodyType.form',
|
||||
{
|
||||
defaultMessage: 'Form',
|
||||
}
|
||||
),
|
||||
[Mode.PLAINTEXT]: i18n.translate(
|
||||
'xpack.synthetics.createPackagePolicy.stepConfigure.requestBodyType.text',
|
||||
{
|
||||
defaultMessage: 'Text',
|
||||
}
|
||||
),
|
||||
[Mode.JSON]: i18n.translate(
|
||||
'xpack.synthetics.createPackagePolicy.stepConfigure.requestBodyType.JSON',
|
||||
{
|
||||
defaultMessage: 'JSON',
|
||||
}
|
||||
),
|
||||
[Mode.XML]: i18n.translate(
|
||||
'xpack.synthetics.createPackagePolicy.stepConfigure.requestBodyType.XML',
|
||||
{
|
||||
defaultMessage: 'XML',
|
||||
}
|
||||
),
|
||||
};
|
|
@ -1,136 +0,0 @@
|
|||
/*
|
||||
* 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 { waitFor } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import React, { useState } from 'react';
|
||||
import { render } from '../../lib/helper/rtl_helpers';
|
||||
import { PolicyConfigContextProvider } from './contexts';
|
||||
import { IPolicyConfigContextProvider } from './contexts/policy_config_context';
|
||||
import { ScheduleField } from './schedule_field';
|
||||
import { ScheduleUnit } from './types';
|
||||
|
||||
describe('<ScheduleField/>', () => {
|
||||
const number = '1';
|
||||
const unit = ScheduleUnit.MINUTES;
|
||||
const onBlur = jest.fn();
|
||||
const WrappedComponent = ({
|
||||
allowedScheduleUnits,
|
||||
}: Omit<IPolicyConfigContextProvider, 'children'>) => {
|
||||
const [config, setConfig] = useState({
|
||||
number,
|
||||
unit,
|
||||
});
|
||||
|
||||
return (
|
||||
<PolicyConfigContextProvider allowedScheduleUnits={allowedScheduleUnits}>
|
||||
<ScheduleField
|
||||
number={config.number}
|
||||
unit={config.unit}
|
||||
onChange={(value) => setConfig(value)}
|
||||
onBlur={onBlur}
|
||||
/>
|
||||
</PolicyConfigContextProvider>
|
||||
);
|
||||
};
|
||||
|
||||
afterEach(() => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
it('shows all options by default (allowedScheduleUnits is not provided)', () => {
|
||||
const { getByText } = render(<WrappedComponent />);
|
||||
expect(getByText('Minutes')).toBeInTheDocument();
|
||||
expect(getByText('Seconds')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('shows only Minutes when allowedScheduleUnits = [ScheduleUnit.Minutes])', () => {
|
||||
const { queryByText } = render(
|
||||
<WrappedComponent allowedScheduleUnits={[ScheduleUnit.MINUTES]} />
|
||||
);
|
||||
expect(queryByText('Minutes')).toBeInTheDocument();
|
||||
expect(queryByText('Seconds')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('shows only Seconds when allowedScheduleUnits = [ScheduleUnit.Seconds])', () => {
|
||||
const { queryByText } = render(
|
||||
<WrappedComponent allowedScheduleUnits={[ScheduleUnit.SECONDS]} />
|
||||
);
|
||||
expect(queryByText('Minutes')).not.toBeInTheDocument();
|
||||
expect(queryByText('Seconds')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('only accepts whole number when allowedScheduleUnits = [ScheduleUnit.Minutes])', async () => {
|
||||
const { getByTestId } = render(
|
||||
<WrappedComponent allowedScheduleUnits={[ScheduleUnit.MINUTES]} />
|
||||
);
|
||||
const input = getByTestId('scheduleFieldInput') as HTMLInputElement;
|
||||
const select = getByTestId('scheduleFieldSelect') as HTMLInputElement;
|
||||
expect(input.value).toBe(number);
|
||||
expect(select.value).toBe(ScheduleUnit.MINUTES);
|
||||
|
||||
userEvent.clear(input);
|
||||
userEvent.type(input, '1.5');
|
||||
|
||||
// Click away to cause blur on input
|
||||
userEvent.click(select);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(input.value).toBe('2');
|
||||
});
|
||||
});
|
||||
|
||||
it('handles schedule', () => {
|
||||
const { getByText, getByTestId } = render(<WrappedComponent />);
|
||||
const input = getByTestId('scheduleFieldInput') as HTMLInputElement;
|
||||
const select = getByTestId('scheduleFieldSelect') as HTMLInputElement;
|
||||
expect(input.value).toBe(number);
|
||||
expect(select.value).toBe(unit);
|
||||
expect(getByText('Minutes')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('handles on change', async () => {
|
||||
const { getByText, getByTestId } = render(<WrappedComponent />);
|
||||
const input = getByTestId('scheduleFieldInput') as HTMLInputElement;
|
||||
const select = getByTestId('scheduleFieldSelect') as HTMLInputElement;
|
||||
const newNumber = '2';
|
||||
const newUnit = ScheduleUnit.SECONDS;
|
||||
expect(input.value).toBe(number);
|
||||
expect(select.value).toBe(unit);
|
||||
|
||||
userEvent.clear(input);
|
||||
userEvent.type(input, newNumber);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(input.value).toBe(newNumber);
|
||||
});
|
||||
|
||||
userEvent.selectOptions(select, newUnit);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(select.value).toBe(newUnit);
|
||||
expect(getByText('Seconds')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('calls onBlur when changed', () => {
|
||||
const { getByTestId } = render(
|
||||
<WrappedComponent allowedScheduleUnits={[ScheduleUnit.SECONDS, ScheduleUnit.MINUTES]} />
|
||||
);
|
||||
const input = getByTestId('scheduleFieldInput') as HTMLInputElement;
|
||||
const select = getByTestId('scheduleFieldSelect') as HTMLInputElement;
|
||||
|
||||
userEvent.clear(input);
|
||||
userEvent.type(input, '2');
|
||||
|
||||
userEvent.selectOptions(select, ScheduleUnit.MINUTES);
|
||||
|
||||
userEvent.click(input);
|
||||
|
||||
expect(onBlur).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
});
|
|
@ -1,105 +0,0 @@
|
|||
/*
|
||||
* 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 { EuiFieldNumber, EuiFlexGroup, EuiFlexItem, EuiSelect } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import React from 'react';
|
||||
import { usePolicyConfigContext } from './contexts';
|
||||
import { ConfigKey, MonitorFields, ScheduleUnit } from './types';
|
||||
|
||||
interface Props {
|
||||
number: string;
|
||||
onChange: (schedule: MonitorFields[ConfigKey.SCHEDULE]) => void;
|
||||
onBlur: () => void;
|
||||
unit: ScheduleUnit;
|
||||
readOnly?: boolean;
|
||||
}
|
||||
|
||||
export const ScheduleField = ({ number, onChange, onBlur, unit, readOnly = false }: Props) => {
|
||||
const { allowedScheduleUnits } = usePolicyConfigContext();
|
||||
const options = !allowedScheduleUnits?.length
|
||||
? allOptions
|
||||
: allOptions.filter((opt) => allowedScheduleUnits.includes(opt.value));
|
||||
|
||||
// When only minutes are allowed, don't allow user to input fractional value
|
||||
const allowedStep = options.length === 1 && options[0].value === ScheduleUnit.MINUTES ? 1 : 'any';
|
||||
|
||||
return (
|
||||
<EuiFlexGroup gutterSize="s">
|
||||
<EuiFlexItem>
|
||||
<EuiFieldNumber
|
||||
aria-label={i18n.translate(
|
||||
'xpack.synthetics.createPackagePolicy.stepConfigure.scheduleField.number',
|
||||
{
|
||||
defaultMessage: 'Number',
|
||||
}
|
||||
)}
|
||||
id="syntheticsFleetScheduleField--number"
|
||||
data-test-subj="scheduleFieldInput"
|
||||
step={allowedStep}
|
||||
min={1}
|
||||
value={number}
|
||||
onChange={(event) => {
|
||||
const updatedNumber = event.target.value;
|
||||
onChange({ number: updatedNumber, unit });
|
||||
}}
|
||||
onBlur={(event) => {
|
||||
// Enforce whole number
|
||||
if (allowedStep === 1) {
|
||||
const updatedNumber = `${Math.ceil(+event.target.value)}`;
|
||||
onChange({ number: updatedNumber, unit });
|
||||
}
|
||||
|
||||
onBlur();
|
||||
}}
|
||||
readOnly={readOnly}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiSelect
|
||||
aria-label={i18n.translate(
|
||||
'xpack.synthetics.createPackagePolicy.stepConfigure.scheduleField.unit',
|
||||
{
|
||||
defaultMessage: 'Unit',
|
||||
}
|
||||
)}
|
||||
id="syntheticsFleetScheduleField--unit"
|
||||
data-test-subj="scheduleFieldSelect"
|
||||
options={options}
|
||||
value={unit}
|
||||
onChange={(event) => {
|
||||
const updatedUnit = event.target.value;
|
||||
onChange({ number, unit: updatedUnit as ScheduleUnit });
|
||||
}}
|
||||
onBlur={() => onBlur()}
|
||||
disabled={readOnly}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
};
|
||||
|
||||
const allOptions = [
|
||||
{
|
||||
text: i18n.translate(
|
||||
'xpack.synthetics.createPackagePolicy.stepConfigure.scheduleField.seconds',
|
||||
{
|
||||
defaultMessage: 'Seconds',
|
||||
}
|
||||
),
|
||||
value: ScheduleUnit.SECONDS,
|
||||
},
|
||||
{
|
||||
text: i18n.translate(
|
||||
'xpack.synthetics.createPackagePolicy.stepConfigure.scheduleField.minutes',
|
||||
{
|
||||
defaultMessage: 'Minutes',
|
||||
}
|
||||
),
|
||||
value: ScheduleUnit.MINUTES,
|
||||
},
|
||||
];
|
|
@ -10,10 +10,6 @@ import { PackagePolicyCreateExtensionComponentProps } from '@kbn/fleet-plugin/pu
|
|||
import { useTrackPageview } from '@kbn/observability-plugin/public';
|
||||
import { useKibana } from '@kbn/kibana-react-plugin/public';
|
||||
import { DeprecateNoticeModal } from './deprecate_notice_modal';
|
||||
import { PolicyConfig } from './types';
|
||||
import { DEFAULT_FIELDS } from '../../../../common/constants/monitor_defaults';
|
||||
|
||||
export const defaultConfig: PolicyConfig = DEFAULT_FIELDS;
|
||||
|
||||
/**
|
||||
* Exports Synthetics-specific package policy instructions
|
||||
|
|
|
@ -11,8 +11,8 @@ import type { FleetStartServices } from '@kbn/fleet-plugin/public';
|
|||
import { EuiButton, EuiCallOut } from '@elastic/eui';
|
||||
import type { PackagePolicyEditExtensionComponentProps } from '@kbn/fleet-plugin/public';
|
||||
import { useKibana } from '@kbn/kibana-react-plugin/public';
|
||||
import { ConfigKey, DataStream } from '../../../../common/runtime_types';
|
||||
import { DeprecateNoticeModal } from './deprecate_notice_modal';
|
||||
import { ConfigKey, DataStream } from './types';
|
||||
import { useEditMonitorLocator } from '../../../apps/synthetics/hooks';
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,94 +0,0 @@
|
|||
/*
|
||||
* 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 from 'react';
|
||||
import { fireEvent } from '@testing-library/react';
|
||||
import { render } from '../../../lib/helper/rtl_helpers';
|
||||
import { TCPAdvancedFields } from './advanced_fields';
|
||||
import {
|
||||
TCPAdvancedFieldsContextProvider,
|
||||
defaultTCPAdvancedFields as defaultConfig,
|
||||
} from '../contexts';
|
||||
import { ConfigKey, TCPAdvancedFields as TCPAdvancedFieldsType } from '../types';
|
||||
|
||||
// ensures fields and labels map appropriately
|
||||
jest.mock('@elastic/eui/lib/services/accessibility/html_id_generator', () => ({
|
||||
htmlIdGenerator: () => () => `id-${Math.random()}`,
|
||||
}));
|
||||
|
||||
describe('<TCPAdvancedFields />', () => {
|
||||
const WrappedComponent = ({
|
||||
defaultValues = defaultConfig,
|
||||
children,
|
||||
onFieldBlur,
|
||||
}: {
|
||||
defaultValues?: TCPAdvancedFieldsType;
|
||||
children?: React.ReactNode;
|
||||
onFieldBlur?: (field: ConfigKey) => void;
|
||||
}) => {
|
||||
return (
|
||||
<TCPAdvancedFieldsContextProvider defaultValues={defaultValues}>
|
||||
<TCPAdvancedFields onFieldBlur={onFieldBlur}>{children}</TCPAdvancedFields>
|
||||
</TCPAdvancedFieldsContextProvider>
|
||||
);
|
||||
};
|
||||
|
||||
it('renders TCPAdvancedFields', () => {
|
||||
const { getByLabelText } = render(<WrappedComponent />);
|
||||
|
||||
const requestPayload = getByLabelText('Request payload') as HTMLInputElement;
|
||||
const proxyURL = getByLabelText('Proxy URL') as HTMLInputElement;
|
||||
// ComboBox has an issue with associating labels with the field
|
||||
const responseContains = getByLabelText('Check response contains') as HTMLInputElement;
|
||||
expect(requestPayload).toBeInTheDocument();
|
||||
expect(requestPayload.value).toEqual(defaultConfig[ConfigKey.REQUEST_SEND_CHECK]);
|
||||
expect(proxyURL).toBeInTheDocument();
|
||||
expect(proxyURL.value).toEqual(defaultConfig[ConfigKey.PROXY_URL]);
|
||||
expect(responseContains).toBeInTheDocument();
|
||||
expect(responseContains.value).toEqual(defaultConfig[ConfigKey.RESPONSE_RECEIVE_CHECK]);
|
||||
});
|
||||
|
||||
it('handles changing fields', () => {
|
||||
const { getByLabelText } = render(<WrappedComponent />);
|
||||
|
||||
const requestPayload = getByLabelText('Request payload') as HTMLInputElement;
|
||||
|
||||
fireEvent.change(requestPayload, { target: { value: 'success' } });
|
||||
expect(requestPayload.value).toEqual('success');
|
||||
});
|
||||
|
||||
it('calls onBlur on fields', () => {
|
||||
const onFieldBlur = jest.fn();
|
||||
const { getByLabelText } = render(<WrappedComponent onFieldBlur={onFieldBlur} />);
|
||||
|
||||
const requestPayload = getByLabelText('Request payload') as HTMLInputElement;
|
||||
|
||||
fireEvent.change(requestPayload, { target: { value: 'success' } });
|
||||
fireEvent.blur(requestPayload);
|
||||
expect(onFieldBlur).toHaveBeenCalledWith(ConfigKey.REQUEST_SEND_CHECK);
|
||||
});
|
||||
|
||||
it('shows resolve hostnames locally field when proxy url is filled for tcp monitors', () => {
|
||||
const { getByLabelText, queryByLabelText } = render(<WrappedComponent />);
|
||||
|
||||
expect(queryByLabelText('Resolve hostnames locally')).not.toBeInTheDocument();
|
||||
|
||||
const proxyUrl = getByLabelText('Proxy URL') as HTMLInputElement;
|
||||
|
||||
fireEvent.change(proxyUrl, { target: { value: 'sampleProxyUrl' } });
|
||||
|
||||
expect(getByLabelText('Resolve hostnames locally')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders upstream fields', () => {
|
||||
const upstreamFieldsText = 'Monitor Advanced field section';
|
||||
const { getByText } = render(<WrappedComponent>{upstreamFieldsText}</WrappedComponent>);
|
||||
|
||||
const upstream = getByText(upstreamFieldsText) as HTMLInputElement;
|
||||
expect(upstream).toBeInTheDocument();
|
||||
});
|
||||
});
|
|
@ -1,187 +0,0 @@
|
|||
/*
|
||||
* 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, { memo, useCallback } from 'react';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { EuiAccordion, EuiCheckbox, EuiFormRow, EuiFieldText, EuiSpacer } from '@elastic/eui';
|
||||
import { DescribedFormGroupWithWrap } from '../common/described_form_group_with_wrap';
|
||||
|
||||
import { useTCPAdvancedFieldsContext } from '../contexts';
|
||||
|
||||
import { ConfigKey } from '../types';
|
||||
|
||||
import { OptionalLabel } from '../optional_label';
|
||||
|
||||
interface Props {
|
||||
children?: React.ReactNode;
|
||||
minColumnWidth?: string;
|
||||
onFieldBlur?: (field: ConfigKey) => void;
|
||||
}
|
||||
|
||||
export const TCPAdvancedFields = memo<Props>(({ children, minColumnWidth, onFieldBlur }) => {
|
||||
const { fields, setFields } = useTCPAdvancedFieldsContext();
|
||||
|
||||
const handleInputChange = useCallback(
|
||||
({ value, configKey }: { value: unknown; configKey: ConfigKey }) => {
|
||||
setFields((prevFields) => ({ ...prevFields, [configKey]: value }));
|
||||
},
|
||||
[setFields]
|
||||
);
|
||||
|
||||
return (
|
||||
<EuiAccordion
|
||||
id="uptimeFleetTCPAdvancedOptions"
|
||||
buttonContent="Advanced TCP options"
|
||||
data-test-subj="syntheticsTCPAdvancedFieldsAccordion"
|
||||
>
|
||||
<EuiSpacer size="m" />
|
||||
<DescribedFormGroupWithWrap
|
||||
minColumnWidth={minColumnWidth}
|
||||
title={
|
||||
<h4>
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.createPackagePolicy.stepConfigure.tcpAdvacnedSettings.requestConfiguration.title"
|
||||
defaultMessage="Request configuration"
|
||||
/>
|
||||
</h4>
|
||||
}
|
||||
description={
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.createPackagePolicy.stepConfigure.tcpAdvacnedSettings.requestConfiguration.description"
|
||||
defaultMessage="Configure the payload sent to the remote host."
|
||||
/>
|
||||
}
|
||||
>
|
||||
<EuiSpacer size="s" />
|
||||
<EuiFormRow
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.proxyURL.label"
|
||||
defaultMessage="Proxy URL"
|
||||
/>
|
||||
}
|
||||
labelAppend={<OptionalLabel />}
|
||||
helpText={
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.proxyUrl.tcp.helpText"
|
||||
defaultMessage="The URL of the SOCKS5 proxy to use when connecting to the server. The value must be a URL with a scheme of socks5://."
|
||||
/>
|
||||
}
|
||||
>
|
||||
<EuiFieldText
|
||||
value={fields[ConfigKey.PROXY_URL]}
|
||||
onChange={(event) =>
|
||||
handleInputChange({
|
||||
value: event.target.value,
|
||||
configKey: ConfigKey.PROXY_URL,
|
||||
})
|
||||
}
|
||||
onBlur={() => onFieldBlur?.(ConfigKey.PROXY_URL)}
|
||||
data-test-subj="syntheticsProxyUrl"
|
||||
/>
|
||||
</EuiFormRow>
|
||||
{!!fields[ConfigKey.PROXY_URL] && (
|
||||
<EuiFormRow data-test-subj="syntheticsUseLocalResolver">
|
||||
<EuiCheckbox
|
||||
id={'uptimeFleetUseLocalResolverCheckbox'}
|
||||
checked={fields[ConfigKey.PROXY_USE_LOCAL_RESOLVER]}
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.resolveHostnamesLocally"
|
||||
defaultMessage="Resolve hostnames locally"
|
||||
/>
|
||||
}
|
||||
onChange={(event) =>
|
||||
handleInputChange({
|
||||
value: event.target.checked,
|
||||
configKey: ConfigKey.PROXY_USE_LOCAL_RESOLVER,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
)}
|
||||
<EuiFormRow
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.createPackagePolicy.stepConfigure.tcpAdvacnedSettings.requestConfiguration.requestPayload.label"
|
||||
defaultMessage="Request payload"
|
||||
/>
|
||||
}
|
||||
labelAppend={<OptionalLabel />}
|
||||
helpText={
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.createPackagePolicy.stepConfigure.tcpAdvacnedSettings.requestConfiguration.requestPayload.helpText"
|
||||
defaultMessage="A payload string to send to the remote host."
|
||||
/>
|
||||
}
|
||||
>
|
||||
<EuiFieldText
|
||||
value={fields[ConfigKey.REQUEST_SEND_CHECK]}
|
||||
onChange={useCallback(
|
||||
(event) =>
|
||||
handleInputChange({
|
||||
value: event.target.value,
|
||||
configKey: ConfigKey.REQUEST_SEND_CHECK,
|
||||
}),
|
||||
[handleInputChange]
|
||||
)}
|
||||
onBlur={() => onFieldBlur?.(ConfigKey.REQUEST_SEND_CHECK)}
|
||||
data-test-subj="syntheticsTCPRequestSendCheck"
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</DescribedFormGroupWithWrap>
|
||||
<DescribedFormGroupWithWrap
|
||||
minColumnWidth={minColumnWidth}
|
||||
title={
|
||||
<h4>
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.createPackagePolicy.stepConfigure.tcpAdvancedOptions.responseConfiguration.title"
|
||||
defaultMessage="Response checks"
|
||||
/>
|
||||
</h4>
|
||||
}
|
||||
description={
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.createPackagePolicy.stepConfigure.tcpAdvancedOptions.responseConfiguration.description"
|
||||
defaultMessage="Configure the expected response from the remote host."
|
||||
/>
|
||||
}
|
||||
>
|
||||
<EuiFormRow
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.createPackagePolicy.stepConfigure.tcpAdvacnedSettings.responseConfiguration.responseContains.label"
|
||||
defaultMessage="Check response contains"
|
||||
/>
|
||||
}
|
||||
labelAppend={<OptionalLabel />}
|
||||
helpText={
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.createPackagePolicy.stepConfigure.tcpAdvacnedSettings.responseConfiguration.responseContains.helpText"
|
||||
defaultMessage="The expected remote host response."
|
||||
/>
|
||||
}
|
||||
>
|
||||
<EuiFieldText
|
||||
value={fields[ConfigKey.RESPONSE_RECEIVE_CHECK]}
|
||||
onChange={useCallback(
|
||||
(event) =>
|
||||
handleInputChange({
|
||||
value: event.target.value,
|
||||
configKey: ConfigKey.RESPONSE_RECEIVE_CHECK,
|
||||
}),
|
||||
[handleInputChange]
|
||||
)}
|
||||
onBlur={() => onFieldBlur?.(ConfigKey.RESPONSE_RECEIVE_CHECK)}
|
||||
data-test-subj="syntheticsTCPResponseReceiveCheck"
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</DescribedFormGroupWithWrap>
|
||||
{children}
|
||||
</EuiAccordion>
|
||||
);
|
||||
});
|
|
@ -1,95 +0,0 @@
|
|||
/*
|
||||
* 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, { memo, useCallback } from 'react';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { EuiFormRow, EuiFieldText } from '@elastic/eui';
|
||||
import { ConfigKey, Validation } from '../types';
|
||||
import { useTCPSimpleFieldsContext } from '../contexts';
|
||||
import { ScheduleField } from '../schedule_field';
|
||||
import { SimpleFieldsWrapper } from '../common/simple_fields_wrapper';
|
||||
|
||||
interface Props {
|
||||
validate: Validation;
|
||||
onFieldBlur: (field: ConfigKey) => void; // To propagate blurred state up to parents
|
||||
}
|
||||
|
||||
export const TCPSimpleFields = memo<Props>(({ validate, onFieldBlur }) => {
|
||||
const { fields, setFields } = useTCPSimpleFieldsContext();
|
||||
const handleInputChange = useCallback(
|
||||
({ value, configKey }: { value: unknown; configKey: ConfigKey }) => {
|
||||
setFields((prevFields) => ({ ...prevFields, [configKey]: value }));
|
||||
},
|
||||
[setFields]
|
||||
);
|
||||
|
||||
return (
|
||||
<SimpleFieldsWrapper
|
||||
fields={fields}
|
||||
validate={validate}
|
||||
onInputChange={handleInputChange}
|
||||
onFieldBlur={onFieldBlur}
|
||||
>
|
||||
<EuiFormRow
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.tcp.hosts"
|
||||
defaultMessage="Host:Port"
|
||||
/>
|
||||
}
|
||||
isInvalid={!!validate[ConfigKey.HOSTS]?.(fields)}
|
||||
error={
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.tcp.hosts.error"
|
||||
defaultMessage="Host and port are required"
|
||||
/>
|
||||
}
|
||||
>
|
||||
<EuiFieldText
|
||||
value={fields[ConfigKey.HOSTS]}
|
||||
onChange={(event) =>
|
||||
handleInputChange({
|
||||
value: event.target.value,
|
||||
configKey: ConfigKey.HOSTS,
|
||||
})
|
||||
}
|
||||
onBlur={() => onFieldBlur(ConfigKey.HOSTS)}
|
||||
data-test-subj="syntheticsTCPHostField"
|
||||
/>
|
||||
</EuiFormRow>
|
||||
|
||||
<EuiFormRow
|
||||
id="syntheticsFleetScheduleField--number syntheticsFleetScheduleField--unit"
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.monitorInterval"
|
||||
defaultMessage="Frequency"
|
||||
/>
|
||||
}
|
||||
isInvalid={!!validate[ConfigKey.SCHEDULE]?.(fields)}
|
||||
error={
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.monitorInterval.error"
|
||||
defaultMessage="Monitor frequency is required"
|
||||
/>
|
||||
}
|
||||
>
|
||||
<ScheduleField
|
||||
onChange={(schedule) =>
|
||||
handleInputChange({
|
||||
value: schedule,
|
||||
configKey: ConfigKey.SCHEDULE,
|
||||
})
|
||||
}
|
||||
onBlur={() => onFieldBlur(ConfigKey.SCHEDULE)}
|
||||
number={fields[ConfigKey.SCHEDULE].number}
|
||||
unit={fields[ConfigKey.SCHEDULE].unit}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</SimpleFieldsWrapper>
|
||||
);
|
||||
});
|
|
@ -1,45 +0,0 @@
|
|||
/*
|
||||
* 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 { parseJsonIfString } from '../helpers/parsers';
|
||||
import { TLSFields, ConfigKey } from '../types';
|
||||
import { Normalizer } from '../common/normalizers';
|
||||
import { defaultTLSFields } from '../contexts';
|
||||
|
||||
type TLSNormalizerMap = Record<keyof TLSFields, Normalizer>;
|
||||
|
||||
export const tlsNormalizers: TLSNormalizerMap = {
|
||||
[ConfigKey.TLS_CERTIFICATE_AUTHORITIES]: (fields) =>
|
||||
tlsJsonToObjectNormalizer(
|
||||
fields?.[ConfigKey.TLS_CERTIFICATE_AUTHORITIES]?.value,
|
||||
ConfigKey.TLS_CERTIFICATE_AUTHORITIES
|
||||
),
|
||||
[ConfigKey.TLS_CERTIFICATE]: (fields) =>
|
||||
tlsJsonToObjectNormalizer(
|
||||
fields?.[ConfigKey.TLS_CERTIFICATE]?.value,
|
||||
ConfigKey.TLS_CERTIFICATE
|
||||
),
|
||||
[ConfigKey.TLS_KEY]: (fields) =>
|
||||
tlsJsonToObjectNormalizer(fields?.[ConfigKey.TLS_KEY]?.value, ConfigKey.TLS_KEY),
|
||||
[ConfigKey.TLS_KEY_PASSPHRASE]: (fields) =>
|
||||
tlsStringToObjectNormalizer(
|
||||
fields?.[ConfigKey.TLS_KEY_PASSPHRASE]?.value,
|
||||
ConfigKey.TLS_KEY_PASSPHRASE
|
||||
),
|
||||
[ConfigKey.TLS_VERIFICATION_MODE]: (fields) =>
|
||||
tlsStringToObjectNormalizer(
|
||||
fields?.[ConfigKey.TLS_VERIFICATION_MODE]?.value,
|
||||
ConfigKey.TLS_VERIFICATION_MODE
|
||||
),
|
||||
[ConfigKey.TLS_VERSION]: (fields) =>
|
||||
tlsJsonToObjectNormalizer(fields?.[ConfigKey.TLS_VERSION]?.value, ConfigKey.TLS_VERSION),
|
||||
};
|
||||
|
||||
export const tlsStringToObjectNormalizer = (value: string = '', key: keyof TLSFields) =>
|
||||
value ?? defaultTLSFields[key];
|
||||
export const tlsJsonToObjectNormalizer = (value: string = '', key: keyof TLSFields) =>
|
||||
value ? parseJsonIfString(value) : defaultTLSFields[key];
|
|
@ -1,103 +0,0 @@
|
|||
/*
|
||||
* 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 from 'react';
|
||||
import { fireEvent } from '@testing-library/react';
|
||||
import { render } from '../../lib/helper/rtl_helpers';
|
||||
import { TLSFields } from './tls_fields';
|
||||
import { ConfigKey, VerificationMode } from './types';
|
||||
import {
|
||||
TLSFieldsContextProvider,
|
||||
PolicyConfigContextProvider,
|
||||
defaultTLSFields as defaultValues,
|
||||
} from './contexts';
|
||||
|
||||
// ensures that fields appropriately match to their label
|
||||
jest.mock('@elastic/eui/lib/services/accessibility/html_id_generator', () => ({
|
||||
htmlIdGenerator: () => () => `id-${Math.random()}`,
|
||||
}));
|
||||
|
||||
describe('<TLSFields />', () => {
|
||||
const WrappedComponent = ({ isEnabled = true }: { isEnabled?: boolean }) => {
|
||||
return (
|
||||
<PolicyConfigContextProvider defaultIsTLSEnabled={isEnabled}>
|
||||
<TLSFieldsContextProvider defaultValues={defaultValues}>
|
||||
<TLSFields />
|
||||
</TLSFieldsContextProvider>
|
||||
</PolicyConfigContextProvider>
|
||||
);
|
||||
};
|
||||
it('renders TLSFields', () => {
|
||||
const { getByLabelText, getByText } = render(<WrappedComponent />);
|
||||
|
||||
expect(getByText('Certificate settings')).toBeInTheDocument();
|
||||
expect(getByText('Supported TLS protocols')).toBeInTheDocument();
|
||||
expect(getByLabelText('Client certificate')).toBeInTheDocument();
|
||||
expect(getByLabelText('Client key')).toBeInTheDocument();
|
||||
expect(getByLabelText('Certificate authorities')).toBeInTheDocument();
|
||||
expect(getByLabelText('Verification mode')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('updates fields and calls onChange', async () => {
|
||||
const { getByLabelText } = render(<WrappedComponent />);
|
||||
|
||||
const clientCertificate = getByLabelText('Client certificate') as HTMLInputElement;
|
||||
const clientKey = getByLabelText('Client key') as HTMLInputElement;
|
||||
const clientKeyPassphrase = getByLabelText('Client key passphrase') as HTMLInputElement;
|
||||
const certificateAuthorities = getByLabelText('Certificate authorities') as HTMLInputElement;
|
||||
const verificationMode = getByLabelText('Verification mode') as HTMLInputElement;
|
||||
|
||||
const newValues = {
|
||||
[ConfigKey.TLS_CERTIFICATE]: 'sampleClientCertificate',
|
||||
[ConfigKey.TLS_KEY]: 'sampleClientKey',
|
||||
[ConfigKey.TLS_KEY_PASSPHRASE]: 'sampleClientKeyPassphrase',
|
||||
[ConfigKey.TLS_CERTIFICATE_AUTHORITIES]: 'sampleCertificateAuthorities',
|
||||
[ConfigKey.TLS_VERIFICATION_MODE]: VerificationMode.NONE,
|
||||
};
|
||||
|
||||
fireEvent.change(clientCertificate, {
|
||||
target: { value: newValues[ConfigKey.TLS_CERTIFICATE] },
|
||||
});
|
||||
fireEvent.change(clientKey, { target: { value: newValues[ConfigKey.TLS_KEY] } });
|
||||
fireEvent.change(clientKeyPassphrase, {
|
||||
target: { value: newValues[ConfigKey.TLS_KEY_PASSPHRASE] },
|
||||
});
|
||||
fireEvent.change(certificateAuthorities, {
|
||||
target: { value: newValues[ConfigKey.TLS_CERTIFICATE_AUTHORITIES] },
|
||||
});
|
||||
fireEvent.change(verificationMode, {
|
||||
target: { value: newValues[ConfigKey.TLS_VERIFICATION_MODE] },
|
||||
});
|
||||
|
||||
expect(clientCertificate.value).toEqual(newValues[ConfigKey.TLS_CERTIFICATE]);
|
||||
expect(clientKey.value).toEqual(newValues[ConfigKey.TLS_KEY]);
|
||||
expect(certificateAuthorities.value).toEqual(newValues[ConfigKey.TLS_CERTIFICATE_AUTHORITIES]);
|
||||
expect(verificationMode.value).toEqual(newValues[ConfigKey.TLS_VERIFICATION_MODE]);
|
||||
});
|
||||
|
||||
it('shows warning when verification mode is set to none', () => {
|
||||
const { getByLabelText, getByText } = render(<WrappedComponent />);
|
||||
|
||||
const verificationMode = getByLabelText('Verification mode') as HTMLInputElement;
|
||||
|
||||
fireEvent.change(verificationMode, {
|
||||
target: { value: VerificationMode.NONE },
|
||||
});
|
||||
|
||||
expect(getByText('Disabling TLS')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('does not show fields when isEnabled is false', async () => {
|
||||
const { queryByLabelText } = render(<WrappedComponent isEnabled={false} />);
|
||||
|
||||
expect(queryByLabelText('Client certificate')).not.toBeInTheDocument();
|
||||
expect(queryByLabelText('Client key')).not.toBeInTheDocument();
|
||||
expect(queryByLabelText('Client key passphrase')).not.toBeInTheDocument();
|
||||
expect(queryByLabelText('Certificate authorities')).not.toBeInTheDocument();
|
||||
expect(queryByLabelText('verification mode')).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
|
@ -1,60 +0,0 @@
|
|||
/*
|
||||
* 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, { useCallback, useEffect } from 'react';
|
||||
|
||||
import { TLSOptions, TLSConfig } from './common/tls_options';
|
||||
import { useTLSFieldsContext, usePolicyConfigContext } from './contexts';
|
||||
|
||||
import { ConfigKey } from './types';
|
||||
|
||||
export const TLSFields = () => {
|
||||
const { defaultValues, setFields } = useTLSFieldsContext();
|
||||
const { isTLSEnabled } = usePolicyConfigContext();
|
||||
|
||||
const handleOnChange = useCallback(
|
||||
(tlsConfig: TLSConfig) => {
|
||||
setFields({
|
||||
[ConfigKey.TLS_CERTIFICATE_AUTHORITIES]: tlsConfig.certificateAuthorities,
|
||||
[ConfigKey.TLS_CERTIFICATE]: tlsConfig.certificate,
|
||||
[ConfigKey.TLS_KEY]: tlsConfig.key,
|
||||
[ConfigKey.TLS_KEY_PASSPHRASE]: tlsConfig.keyPassphrase,
|
||||
[ConfigKey.TLS_VERIFICATION_MODE]: tlsConfig.verificationMode,
|
||||
[ConfigKey.TLS_VERSION]: tlsConfig.version,
|
||||
});
|
||||
},
|
||||
[setFields]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isTLSEnabled) {
|
||||
setFields({
|
||||
[ConfigKey.TLS_CERTIFICATE_AUTHORITIES]: undefined,
|
||||
[ConfigKey.TLS_CERTIFICATE]: undefined,
|
||||
[ConfigKey.TLS_KEY]: undefined,
|
||||
[ConfigKey.TLS_KEY_PASSPHRASE]: undefined,
|
||||
[ConfigKey.TLS_VERIFICATION_MODE]: undefined,
|
||||
[ConfigKey.TLS_VERSION]: undefined,
|
||||
});
|
||||
}
|
||||
}, [setFields, isTLSEnabled]);
|
||||
|
||||
return isTLSEnabled ? (
|
||||
<TLSOptions
|
||||
defaultValues={{
|
||||
certificateAuthorities: defaultValues[ConfigKey.TLS_CERTIFICATE_AUTHORITIES],
|
||||
certificate: defaultValues[ConfigKey.TLS_CERTIFICATE],
|
||||
key: defaultValues[ConfigKey.TLS_KEY],
|
||||
keyPassphrase: defaultValues[ConfigKey.TLS_KEY_PASSPHRASE],
|
||||
verificationMode: defaultValues[ConfigKey.TLS_VERIFICATION_MODE],
|
||||
version: defaultValues[ConfigKey.TLS_VERSION],
|
||||
}}
|
||||
onChange={handleOnChange}
|
||||
tlsRole="client"
|
||||
/>
|
||||
) : null;
|
||||
};
|
|
@ -1,41 +0,0 @@
|
|||
/*
|
||||
* 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 {
|
||||
HTTPFields,
|
||||
TCPFields,
|
||||
ICMPFields,
|
||||
BrowserFields,
|
||||
ConfigKey,
|
||||
ContentType,
|
||||
DataStream,
|
||||
Mode,
|
||||
ThrottlingConfigKey,
|
||||
ThrottlingSuffix,
|
||||
ThrottlingSuffixType,
|
||||
} from '../../../../common/runtime_types';
|
||||
export * from '../../../../common/runtime_types/monitor_management';
|
||||
export * from '../../../../common/types/monitor_validation';
|
||||
|
||||
export interface PolicyConfig {
|
||||
[DataStream.HTTP]: HTTPFields;
|
||||
[DataStream.TCP]: TCPFields;
|
||||
[DataStream.ICMP]: ICMPFields;
|
||||
[DataStream.BROWSER]: BrowserFields;
|
||||
}
|
||||
|
||||
export const contentTypesToMode = {
|
||||
[ContentType.FORM]: Mode.FORM,
|
||||
[ContentType.JSON]: Mode.JSON,
|
||||
[ContentType.TEXT]: Mode.PLAINTEXT,
|
||||
[ContentType.XML]: Mode.XML,
|
||||
};
|
||||
|
||||
export const configKeyToThrottlingSuffix: Record<ThrottlingConfigKey, ThrottlingSuffixType> = {
|
||||
[ConfigKey.DOWNLOAD_SPEED]: ThrottlingSuffix.DOWNLOAD,
|
||||
[ConfigKey.UPLOAD_SPEED]: ThrottlingSuffix.UPLOAD,
|
||||
[ConfigKey.LATENCY]: ThrottlingSuffix.LATENCY,
|
||||
};
|
|
@ -1,103 +0,0 @@
|
|||
/*
|
||||
* 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 from 'react';
|
||||
import { screen, waitFor, act } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { render } from '../../../lib/helper/rtl_helpers';
|
||||
import * as fetchers from '../../../state/api/monitor_management';
|
||||
import {
|
||||
DataStream,
|
||||
HTTPFields,
|
||||
ScheduleUnit,
|
||||
SyntheticsMonitor,
|
||||
} from '../../../../../common/runtime_types';
|
||||
import { ActionBar } from './action_bar';
|
||||
|
||||
describe('<ActionBar />', () => {
|
||||
const setMonitor = jest.spyOn(fetchers, 'setMonitor');
|
||||
const monitor: SyntheticsMonitor = {
|
||||
name: 'test-monitor',
|
||||
schedule: {
|
||||
unit: ScheduleUnit.MINUTES,
|
||||
number: '2',
|
||||
},
|
||||
urls: 'https://elastic.co',
|
||||
type: DataStream.HTTP,
|
||||
} as unknown as HTTPFields;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('only calls setMonitor when valid and after submission', () => {
|
||||
render(<ActionBar monitor={monitor} isTestRunInProgress={false} isValid={true} />);
|
||||
|
||||
act(() => {
|
||||
userEvent.click(screen.getByText('Save monitor'));
|
||||
});
|
||||
|
||||
expect(setMonitor).toBeCalledWith({ monitor, id: undefined });
|
||||
});
|
||||
|
||||
it('does not call setMonitor until submission', () => {
|
||||
render(<ActionBar monitor={monitor} isTestRunInProgress={false} isValid={true} />);
|
||||
|
||||
expect(setMonitor).not.toBeCalled();
|
||||
|
||||
act(() => {
|
||||
userEvent.click(screen.getByText('Save monitor'));
|
||||
});
|
||||
|
||||
expect(setMonitor).toBeCalledWith({ monitor, id: undefined });
|
||||
});
|
||||
|
||||
it('does not call setMonitor if invalid', () => {
|
||||
render(<ActionBar monitor={monitor} isTestRunInProgress={false} isValid={false} />);
|
||||
|
||||
expect(setMonitor).not.toBeCalled();
|
||||
|
||||
act(() => {
|
||||
userEvent.click(screen.getByText('Save monitor'));
|
||||
});
|
||||
|
||||
expect(setMonitor).not.toBeCalled();
|
||||
});
|
||||
|
||||
it('disables button and displays help text when form is invalid after first submission', async () => {
|
||||
render(<ActionBar monitor={monitor} isTestRunInProgress={false} isValid={false} />);
|
||||
|
||||
expect(
|
||||
screen.queryByText('Your monitor has errors. Please fix them before saving.')
|
||||
).not.toBeInTheDocument();
|
||||
expect(screen.getByText('Save monitor')).not.toBeDisabled();
|
||||
|
||||
act(() => {
|
||||
userEvent.click(screen.getByText('Save monitor'));
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
screen.getByText('Your monitor has errors. Please fix them before saving.')
|
||||
).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: 'Save monitor' })).toBeDisabled();
|
||||
});
|
||||
});
|
||||
|
||||
it('calls option onSave when saving monitor', () => {
|
||||
const onSave = jest.fn();
|
||||
render(
|
||||
<ActionBar monitor={monitor} isTestRunInProgress={false} isValid={false} onSave={onSave} />
|
||||
);
|
||||
|
||||
act(() => {
|
||||
userEvent.click(screen.getByText('Save monitor'));
|
||||
});
|
||||
|
||||
expect(onSave).toBeCalled();
|
||||
});
|
||||
});
|
|
@ -1,294 +0,0 @@
|
|||
/*
|
||||
* 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, { useCallback, useContext, useState, useEffect, useRef } from 'react';
|
||||
import { useParams, Redirect } from 'react-router-dom';
|
||||
import {
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiButton,
|
||||
EuiButtonEmpty,
|
||||
EuiText,
|
||||
EuiPopover,
|
||||
EuiOutsideClickDetector,
|
||||
} from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { useSelector } from 'react-redux';
|
||||
import { FETCH_STATUS, useFetcher } from '@kbn/observability-plugin/public';
|
||||
|
||||
import { euiStyled } from '@kbn/kibana-react-plugin/common';
|
||||
import { showSyncErrors } from '../../../../apps/synthetics/components/monitors_page/management/show_sync_errors';
|
||||
import { MONITOR_MANAGEMENT_ROUTE } from '../../../../../common/constants';
|
||||
import { UptimeSettingsContext } from '../../../contexts';
|
||||
import { setMonitor } from '../../../state/api';
|
||||
|
||||
import {
|
||||
ConfigKey,
|
||||
SyntheticsMonitor,
|
||||
SourceType,
|
||||
ServiceLocationErrors,
|
||||
} from '../../../../../common/runtime_types';
|
||||
import { TestRun } from '../test_now_mode/test_now_mode';
|
||||
|
||||
import { monitorManagementListSelector } from '../../../state/selectors';
|
||||
|
||||
import { kibanaService } from '../../../state/kibana_service';
|
||||
|
||||
import {
|
||||
PRIVATE_AVAILABLE_LABEL,
|
||||
TEST_SCHEDULED_LABEL,
|
||||
} from '../../overview/monitor_list/translations';
|
||||
|
||||
export interface ActionBarProps {
|
||||
monitor: SyntheticsMonitor;
|
||||
isValid: boolean;
|
||||
testRun?: TestRun;
|
||||
isTestRunInProgress: boolean;
|
||||
onSave?: () => void;
|
||||
onTestNow?: () => void;
|
||||
}
|
||||
|
||||
export const ActionBar = ({
|
||||
monitor,
|
||||
isValid,
|
||||
onSave,
|
||||
onTestNow,
|
||||
testRun,
|
||||
isTestRunInProgress,
|
||||
}: ActionBarProps) => {
|
||||
const { monitorId } = useParams<{ monitorId: string }>();
|
||||
const { basePath } = useContext(UptimeSettingsContext);
|
||||
const { locations } = useSelector(monitorManagementListSelector);
|
||||
|
||||
const [hasBeenSubmitted, setHasBeenSubmitted] = useState(false);
|
||||
const [isSaving, setIsSaving] = useState(false);
|
||||
const [isSuccessful, setIsSuccessful] = useState(false);
|
||||
const [isPopoverOpen, setIsPopoverOpen] = useState<boolean | undefined>(undefined);
|
||||
const mouseMoveTimeoutIds = useRef<[number, number]>([0, 0]);
|
||||
const isReadOnly = monitor[ConfigKey.MONITOR_SOURCE_TYPE] === SourceType.PROJECT;
|
||||
|
||||
const isAnyPublicLocationSelected = monitor.locations?.some((loc) => loc.isServiceManaged);
|
||||
const isOnlyPrivateLocations =
|
||||
!locations.some((loc) => loc.isServiceManaged) ||
|
||||
((monitor.locations?.length ?? 0) > 0 && !isAnyPublicLocationSelected);
|
||||
|
||||
const { data, status } = useFetcher(() => {
|
||||
if (!isSaving || !isValid) {
|
||||
return;
|
||||
}
|
||||
return setMonitor({
|
||||
monitor,
|
||||
id: monitorId,
|
||||
});
|
||||
}, [monitor, monitorId, isValid, isSaving]);
|
||||
|
||||
const hasErrors = data && 'attributes' in data && data.attributes.errors?.length > 0;
|
||||
const loading = status === FETCH_STATUS.LOADING;
|
||||
|
||||
const handleOnSave = useCallback(() => {
|
||||
if (onSave) {
|
||||
onSave();
|
||||
}
|
||||
setIsSaving(true);
|
||||
setHasBeenSubmitted(true);
|
||||
}, [onSave]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isSaving) {
|
||||
return;
|
||||
}
|
||||
if (!isValid) {
|
||||
setIsSaving(false);
|
||||
return;
|
||||
}
|
||||
if (status === FETCH_STATUS.FAILURE || status === FETCH_STATUS.SUCCESS) {
|
||||
setIsSaving(false);
|
||||
}
|
||||
if (status === FETCH_STATUS.FAILURE) {
|
||||
kibanaService.toasts.addDanger({
|
||||
title: MONITOR_FAILURE_LABEL,
|
||||
toastLifeTimeMs: 3000,
|
||||
});
|
||||
} else if (status === FETCH_STATUS.SUCCESS && !hasErrors && !loading) {
|
||||
kibanaService.toasts.addSuccess({
|
||||
title: monitorId ? MONITOR_UPDATED_SUCCESS_LABEL : MONITOR_SUCCESS_LABEL,
|
||||
toastLifeTimeMs: 3000,
|
||||
});
|
||||
setIsSuccessful(true);
|
||||
} else if (hasErrors && !loading) {
|
||||
showSyncErrors(
|
||||
(data as { attributes: { errors: ServiceLocationErrors } })?.attributes.errors ?? [],
|
||||
locations,
|
||||
kibanaService.toasts
|
||||
);
|
||||
setIsSuccessful(true);
|
||||
}
|
||||
}, [data, status, isSaving, isValid, monitorId, hasErrors, locations, loading]);
|
||||
|
||||
return isSuccessful ? (
|
||||
<Redirect to={MONITOR_MANAGEMENT_ROUTE + '/all'} />
|
||||
) : (
|
||||
<EuiFlexGroup gutterSize="s" justifyContent="spaceBetween">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonEmpty
|
||||
data-test-subj="syntheticsActionBarButton"
|
||||
color="ghost"
|
||||
size="s"
|
||||
href={`${basePath}/app/uptime/${MONITOR_MANAGEMENT_ROUTE}/all`}
|
||||
>
|
||||
{DISCARD_LABEL}
|
||||
</EuiButtonEmpty>
|
||||
</EuiFlexItem>
|
||||
{!isReadOnly ? (
|
||||
<EuiFlexItem>
|
||||
<EuiFlexGroup justifyContent="flexEnd" alignItems="center">
|
||||
<EuiFlexItem grow={false}>
|
||||
<WarningText>{!isValid && hasBeenSubmitted && VALIDATION_ERROR_LABEL}</WarningText>
|
||||
</EuiFlexItem>
|
||||
|
||||
{onTestNow && (
|
||||
<EuiFlexItem grow={false}>
|
||||
{/* Popover is used instead of EuiTooltip until the resolution of https://github.com/elastic/eui/issues/5604 */}
|
||||
<EuiOutsideClickDetector
|
||||
onOutsideClick={() => {
|
||||
setIsPopoverOpen(false);
|
||||
}}
|
||||
>
|
||||
<EuiPopover
|
||||
repositionOnScroll={true}
|
||||
ownFocus={false}
|
||||
initialFocus={''}
|
||||
button={
|
||||
<EuiButton
|
||||
css={{ width: '100%' }}
|
||||
fill
|
||||
size="s"
|
||||
color="success"
|
||||
iconType="play"
|
||||
disabled={!isValid || isTestRunInProgress || !isAnyPublicLocationSelected}
|
||||
data-test-subj={'monitorTestNowRunBtn'}
|
||||
onClick={() => onTestNow()}
|
||||
onMouseOver={() => {
|
||||
// We need this custom logic to display a popover even when button is disabled.
|
||||
clearTimeout(mouseMoveTimeoutIds.current[1]);
|
||||
if (mouseMoveTimeoutIds.current[0] === 0) {
|
||||
mouseMoveTimeoutIds.current[0] = setTimeout(() => {
|
||||
clearTimeout(mouseMoveTimeoutIds.current[1]);
|
||||
setIsPopoverOpen(true);
|
||||
}, 250) as unknown as number;
|
||||
}
|
||||
}}
|
||||
onMouseOut={() => {
|
||||
// We need this custom logic to display a popover even when button is disabled.
|
||||
clearTimeout(mouseMoveTimeoutIds.current[1]);
|
||||
mouseMoveTimeoutIds.current[1] = setTimeout(() => {
|
||||
clearTimeout(mouseMoveTimeoutIds.current[0]);
|
||||
setIsPopoverOpen(false);
|
||||
mouseMoveTimeoutIds.current = [0, 0];
|
||||
}, 100) as unknown as number;
|
||||
}}
|
||||
>
|
||||
{testRun ? RE_RUN_TEST_LABEL : RUN_TEST_LABEL}
|
||||
</EuiButton>
|
||||
}
|
||||
isOpen={isPopoverOpen}
|
||||
>
|
||||
<EuiText style={{ width: 260, outline: 'none' }}>
|
||||
<p>
|
||||
{isTestRunInProgress
|
||||
? TEST_SCHEDULED_LABEL
|
||||
: isOnlyPrivateLocations || (isValid && !isAnyPublicLocationSelected)
|
||||
? PRIVATE_AVAILABLE_LABEL
|
||||
: TEST_NOW_DESCRIPTION}
|
||||
</p>
|
||||
</EuiText>
|
||||
</EuiPopover>
|
||||
</EuiOutsideClickDetector>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton
|
||||
data-test-subj="syntheticsActionBarButton"
|
||||
color="primary"
|
||||
fill
|
||||
size="s"
|
||||
iconType="check"
|
||||
onClick={handleOnSave}
|
||||
isLoading={isSaving}
|
||||
disabled={hasBeenSubmitted && !isValid}
|
||||
>
|
||||
{monitorId ? UPDATE_MONITOR_LABEL : SAVE_MONITOR_LABEL}
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
) : null}
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
};
|
||||
|
||||
const WarningText = euiStyled(EuiText)`
|
||||
box-shadow: -4px 0 ${(props) => props.theme.eui.euiColorWarning};
|
||||
padding-left: 8px;
|
||||
`;
|
||||
|
||||
const DISCARD_LABEL = i18n.translate('xpack.synthetics.monitorManagement.discardLabel', {
|
||||
defaultMessage: 'Cancel',
|
||||
});
|
||||
|
||||
const SAVE_MONITOR_LABEL = i18n.translate('xpack.synthetics.monitorManagement.saveMonitorLabel', {
|
||||
defaultMessage: 'Save monitor',
|
||||
});
|
||||
|
||||
const UPDATE_MONITOR_LABEL = i18n.translate(
|
||||
'xpack.synthetics.monitorManagement.updateMonitorLabel',
|
||||
{
|
||||
defaultMessage: 'Update monitor',
|
||||
}
|
||||
);
|
||||
|
||||
const RUN_TEST_LABEL = i18n.translate('xpack.synthetics.monitorManagement.runTest', {
|
||||
defaultMessage: 'Run test',
|
||||
});
|
||||
|
||||
const RE_RUN_TEST_LABEL = i18n.translate('xpack.synthetics.monitorManagement.reRunTest', {
|
||||
defaultMessage: 'Re-run test',
|
||||
});
|
||||
|
||||
const VALIDATION_ERROR_LABEL = i18n.translate(
|
||||
'xpack.synthetics.monitorManagement.validationError',
|
||||
{
|
||||
defaultMessage: 'Your monitor has errors. Please fix them before saving.',
|
||||
}
|
||||
);
|
||||
|
||||
const MONITOR_SUCCESS_LABEL = i18n.translate(
|
||||
'xpack.synthetics.monitorManagement.monitorAddedSuccessMessage',
|
||||
{
|
||||
defaultMessage: 'Monitor added successfully.',
|
||||
}
|
||||
);
|
||||
|
||||
const MONITOR_UPDATED_SUCCESS_LABEL = i18n.translate(
|
||||
'xpack.synthetics.monitorManagement.monitorEditedSuccessMessage',
|
||||
{
|
||||
defaultMessage: 'Monitor updated successfully.',
|
||||
}
|
||||
);
|
||||
|
||||
const MONITOR_FAILURE_LABEL = i18n.translate(
|
||||
'xpack.synthetics.monitorManagement.monitorFailureMessage',
|
||||
{
|
||||
defaultMessage: 'Monitor was unable to be saved. Please try again later.',
|
||||
}
|
||||
);
|
||||
|
||||
const TEST_NOW_DESCRIPTION = i18n.translate('xpack.synthetics.testRun.description', {
|
||||
defaultMessage: 'Test your monitor and verify the results before saving',
|
||||
});
|
|
@ -1,81 +0,0 @@
|
|||
/*
|
||||
* 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 from 'react';
|
||||
import { screen, waitFor } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { render } from '../../../lib/helper/rtl_helpers';
|
||||
import { FETCH_STATUS } from '@kbn/observability-plugin/public';
|
||||
import {
|
||||
DataStream,
|
||||
HTTPFields,
|
||||
ScheduleUnit,
|
||||
SyntheticsMonitor,
|
||||
} from '../../../../../common/runtime_types';
|
||||
import { spyOnUseFetcher } from '../../../lib/helper/spy_use_fetcher';
|
||||
import * as kibana from '../../../state/kibana_service';
|
||||
import { ActionBar } from './action_bar';
|
||||
import { mockLocationsState } from '../mocks';
|
||||
|
||||
jest.mock('../../../state/kibana_service', () => ({
|
||||
...jest.requireActual('../../../state/kibana_service'),
|
||||
kibanaService: {
|
||||
toasts: {
|
||||
addWarning: jest.fn(),
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
const monitor: SyntheticsMonitor = {
|
||||
name: 'test-monitor',
|
||||
schedule: {
|
||||
unit: ScheduleUnit.MINUTES,
|
||||
number: '2',
|
||||
},
|
||||
urls: 'https://elastic.co',
|
||||
type: DataStream.HTTP,
|
||||
} as unknown as HTTPFields;
|
||||
|
||||
describe('<ActionBar /> Service Errors', () => {
|
||||
let useFetcher: jest.SpyInstance;
|
||||
const toast = jest.fn();
|
||||
|
||||
beforeEach(() => {
|
||||
useFetcher?.mockClear();
|
||||
useFetcher = spyOnUseFetcher({});
|
||||
});
|
||||
|
||||
it('Handles service errors', async () => {
|
||||
jest.spyOn(kibana.kibanaService.toasts, 'addWarning').mockImplementation(toast);
|
||||
useFetcher.mockReturnValue({
|
||||
data: {
|
||||
attributes: {
|
||||
errors: [
|
||||
{ locationId: 'us_central', error: { reason: 'Invalid config', status: 400 } },
|
||||
{ locationId: 'us_central', error: { reason: 'Cannot schedule', status: 500 } },
|
||||
],
|
||||
},
|
||||
},
|
||||
status: FETCH_STATUS.SUCCESS,
|
||||
refetch: () => {},
|
||||
});
|
||||
render(<ActionBar monitor={monitor} isTestRunInProgress={false} isValid={true} />, {
|
||||
state: mockLocationsState,
|
||||
});
|
||||
userEvent.click(screen.getByText('Save monitor'));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(toast).toBeCalledTimes(2);
|
||||
expect(toast).toBeCalledWith(
|
||||
expect.objectContaining({
|
||||
title: 'Unable to sync monitor config',
|
||||
toastLifeTimeMs: 30000,
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,20 +0,0 @@
|
|||
/*
|
||||
* 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 from 'react';
|
||||
import { InPortal } from 'react-reverse-portal';
|
||||
import { ActionBarPortalNode } from '../../../pages/monitor_management/portals';
|
||||
|
||||
import { ActionBar, ActionBarProps } from './action_bar';
|
||||
|
||||
export const ActionBarPortal = (props: ActionBarProps) => {
|
||||
return (
|
||||
<InPortal node={ActionBarPortalNode}>
|
||||
<ActionBar {...props} />
|
||||
</InPortal>
|
||||
);
|
||||
};
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue