[8.6] [Synthetics] Deprecate adding synthetics integrations from fleet (#146369) (#146629)

# Backport

This will backport the following commits from `main` to `8.6`:
- [[Synthetics] Deprecate adding synthetics integrations from fleet
(#146369)](https://github.com/elastic/kibana/pull/146369)

<!--- Backport version: 8.9.7 -->

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

<!--BACKPORT
[{"author":{"name":"Shahzad","email":"shahzad.muhammad@elastic.co"},"sourceCommit":{"committedDate":"2022-11-29T20:29:26Z","message":"[Synthetics]
Deprecate adding synthetics integrations from fleet
(#146369)\n\nCo-authored-by: florent-leborgne
<florent.leborgne@elastic.co>\r\nFixes
https://github.com/elastic/kibana/issues/137501","sha":"d13666885469c354bdb55c7bb84f127d23b1b83f","branchLabelMapping":{"^v8.7.0$":"main","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["Team:uptime","release_note:skip","Team:Fleet","v8.6.0","v8.7.0"],"number":146369,"url":"https://github.com/elastic/kibana/pull/146369","mergeCommit":{"message":"[Synthetics]
Deprecate adding synthetics integrations from fleet
(#146369)\n\nCo-authored-by: florent-leborgne
<florent.leborgne@elastic.co>\r\nFixes
https://github.com/elastic/kibana/issues/137501","sha":"d13666885469c354bdb55c7bb84f127d23b1b83f"}},"sourceBranch":"main","suggestedTargetBranches":["8.6"],"targetPullRequestStates":[{"branch":"8.6","label":"v8.6.0","labelRegex":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"},{"branch":"main","label":"v8.7.0","labelRegex":"^v8.7.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/146369","number":146369,"mergeCommit":{"message":"[Synthetics]
Deprecate adding synthetics integrations from fleet
(#146369)\n\nCo-authored-by: florent-leborgne
<florent.leborgne@elastic.co>\r\nFixes
https://github.com/elastic/kibana/issues/137501","sha":"d13666885469c354bdb55c7bb84f127d23b1b83f"}}]}]
BACKPORT-->

Co-authored-by: Shahzad <shahzad.muhammad@elastic.co>
This commit is contained in:
Kibana Machine 2022-11-30 06:14:01 -05:00 committed by GitHub
parent 42bf33f146
commit c9fe5ac74b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
24 changed files with 138 additions and 2559 deletions

View file

@ -155,7 +155,6 @@ enabled:
- x-pack/test/functional_embedded/config.ts
- x-pack/test/functional_enterprise_search/without_host_configured.config.ts
- x-pack/test/functional_execution_context/config.ts
- x-pack/test/functional_synthetics/config.js
- x-pack/test/functional_with_es_ssl/config.ts
- x-pack/test/functional/apps/advanced_settings/config.ts
- x-pack/test/functional/apps/aiops/config.ts

View file

@ -181,7 +181,6 @@ As part of the bundled package update process, we'll likely also need to update
- `x-pack/test/fleet_api_integration/config.ts`
- `x-pack/plugins/fleet/server/integration_tests/helpers/docker_registry_helper.ts`
- `x-pack/test/functional/config.base.js`
- `x-pack/test/functional_synthetics/config.js`
To update this registry image, pull the digest SHA from the package storage Jenkins pipeline at https://beats-ci.elastic.co/blue/organizations/jenkins/Ingest-manager%2Fpackage-storage/activity and update the files above. The digest value should appear in the "publish Docker image" step as part of the `docker push` command in the logs.

View file

@ -20,11 +20,6 @@ export const ManageMonitorsBtn = () => {
const history = useHistory();
const handleOnClick = () => {
setIsOpen(false);
history.push(MONITOR_MANAGEMENT_ROUTE + '/all');
};
return (
<EuiTourStep
content={
@ -33,7 +28,13 @@ export const ManageMonitorsBtn = () => {
<p>{PUBLIC_BETA_DESCRIPTION}</p>
</EuiText>
<EuiSpacer />
<EuiButton color="primary" fill onClick={handleOnClick}>
<EuiButton
color="primary"
fill
href={history.createHref({
pathname: MONITOR_MANAGEMENT_ROUTE,
})}
>
{MONITOR_MANAGEMENT_LABEL}
</EuiButton>
</>
@ -56,7 +57,9 @@ export const ManageMonitorsBtn = () => {
aria-label={NAVIGATE_LABEL}
color="text"
data-test-subj="syntheticsManagementPageLink"
onClick={handleOnClick}
href={history.createHref({
pathname: MONITOR_MANAGEMENT_ROUTE,
})}
>
<FormattedMessage
id="xpack.synthetics.page_header.manageMonitors"

View file

@ -0,0 +1,101 @@
/*
* 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 { EuiConfirmModal, EuiIcon, EuiLink } from '@elastic/eui';
import { useKibana } from '@kbn/kibana-react-plugin/public';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n-react';
export const DeprecateNoticeModal = ({ onCancel }: { onCancel: () => void }) => {
const { application } = useKibana().services;
return (
<EuiConfirmModal
title={
<>
{HEADER_TEXT} <EuiIcon type="cheer" />
</>
}
onCancel={onCancel}
onConfirm={() => {
application?.navigateToApp('uptime', { path: '/manage-monitors' });
}}
confirmButtonText={GO_MONITOR_MANAGEMENT_TEXT}
cancelButtonText={GO_BACK_TEXT}
>
<p>
<FormattedMessage
id="xpack.synthetics.deprecateNoticeModal.description"
defaultMessage="The Elastic Synthetics integration is deprecated. Instead, you can now monitor endpoints,
pages, and user journeys directly from Uptime much more efficiently:"
/>
</p>
<p>
<li>
<FormattedMessage
id="xpack.synthetics.deprecateNoticeModal.addPrivateLocations"
defaultMessage="Add private locations against your fleet policies"
/>
</li>
<li>
<FormattedMessage
id="xpack.synthetics.deprecateNoticeModal.manageMonitors"
defaultMessage="Manage lightweight and browser monitors from a single place"
/>
</li>
<li>
<FormattedMessage
id="xpack.synthetics.deprecateNoticeModal.elasticManagedLocations"
defaultMessage="Run monitors in multiple locations managed by Elastic, or from your own private locations"
/>
</li>
<li>
<FormattedMessage
id="xpack.synthetics.deprecateNoticeModal.automateMonitors"
defaultMessage="Automate the creation of your monitors using project monitors"
/>
</li>
</p>
<p>
<FormattedMessage
id="xpack.synthetics.deprecateNoticeModal.forMoreInformation"
defaultMessage="For more information, {docsLink}"
values={{
docsLink: (
<EuiLink
target="_blank"
href="https://www.elastic.co/guide/en/observability/current/monitor-uptime-synthetics.html"
>
{READ_DOCS_TEXT}
</EuiLink>
),
}}
/>
</p>
</EuiConfirmModal>
);
};
const HEADER_TEXT = i18n.translate('xpack.synthetics.deprecateNoticeModal.headerText', {
defaultMessage: 'Synthetic Monitoring is now available out of the box in Uptime',
});
const GO_BACK_TEXT = i18n.translate('xpack.synthetics.deprecateNoticeModal.goBack', {
defaultMessage: 'Go back',
});
const READ_DOCS_TEXT = i18n.translate('xpack.synthetics.deprecateNoticeModal.readDocs', {
defaultMessage: 'read docs.',
});
const GO_MONITOR_MANAGEMENT_TEXT = i18n.translate(
'xpack.synthetics.deprecateNoticeModal.goToMonitorManagement',
{
defaultMessage: 'Go to Monitor Management',
}
);

View file

@ -5,16 +5,13 @@
* 2.0.
*/
import React, { memo, useEffect, useMemo } from 'react';
import React, { memo, useCallback } from 'react';
import { PackagePolicyCreateExtensionComponentProps } from '@kbn/fleet-plugin/public';
import { useTrackPageview } from '@kbn/observability-plugin/public';
import { DataStream, PolicyConfig, MonitorFields } from './types';
import { usePolicyConfigContext } from './contexts';
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';
import { CustomFields } from './custom_fields';
import { useUpdatePolicy } from './hooks/use_update_policy';
import { usePolicy } from './hooks/use_policy';
import { validate } from './validation';
export const defaultConfig: PolicyConfig = DEFAULT_FIELDS;
@ -27,39 +24,16 @@ export const SyntheticsPolicyCreateExtension = memo<PackagePolicyCreateExtension
useTrackPageview({ app: 'fleet', path: 'syntheticsCreate' });
useTrackPageview({ app: 'fleet', path: 'syntheticsCreate', delay: 15000 });
const { monitorType } = usePolicyConfigContext();
const policyConfig: PolicyConfig = usePolicy(newPolicy.name);
const { application } = useKibana().services;
const dataStreams: DataStream[] = useMemo(() => {
return newPolicy.inputs.map((input) => {
return input.type.replace(/synthetics\//g, '') as DataStream;
const { package: pkg } = newPolicy;
const onCancel = useCallback(() => {
application?.navigateToApp('integrations', {
path: `/detail/${pkg?.name}-${pkg?.version}/overview`,
});
}, [newPolicy]);
useUpdatePolicy({
monitorType,
defaultConfig: defaultConfig[monitorType] as Partial<MonitorFields>,
config: policyConfig[monitorType] as Partial<MonitorFields>,
newPolicy,
onChange,
validate,
});
// Fleet will initialize the create form with a default name for the integratin policy, however,
// for synthetics, we want the user to explicitely type in a name to use as the monitor name,
// so we blank it out only during 1st component render (thus why the eslint disabled rule below).
useEffect(() => {
onChange({
isValid: false,
updatedPolicy: {
...newPolicy,
name: '',
},
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return <CustomFields validate={validate[monitorType]} dataStreams={dataStreams} />;
}, [application, pkg?.name, pkg?.version]);
return <DeprecateNoticeModal onCancel={onCancel} />;
}
);
SyntheticsPolicyCreateExtension.displayName = 'SyntheticsPolicyCreateExtension';

View file

@ -1,837 +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, waitFor } from '@testing-library/react';
import { render } from '../../lib/helper/rtl_helpers';
import { NewPackagePolicy } from '@kbn/fleet-plugin/public';
import { SyntheticsPolicyCreateExtensionWrapper } from './synthetics_policy_create_extension_wrapper';
import { defaultConfig } from './synthetics_policy_create_extension';
import { ConfigKey, DataStream, ScheduleUnit, VerificationMode } from './types';
// 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 defaultNewPolicy: NewPackagePolicy = {
name: 'samplePolicyName',
description: '',
namespace: 'default',
policy_id: 'ae774160-8e49-11eb-aba5-99269d21ba6e',
enabled: true,
inputs: [
{
type: 'synthetics/http',
enabled: true,
streams: [
{
enabled: true,
data_stream: {
type: 'synthetics',
dataset: 'http',
},
vars: {
__ui: {
value: JSON.stringify({ is_tls_enabled: true }),
type: 'yaml',
},
type: {
value: 'http',
type: 'text',
},
name: {
value: 'Sample name',
type: 'text',
},
schedule: {
value: '"@every 5s"',
type: 'text',
},
urls: {
value: '',
type: 'text',
},
'service.name': {
value: '',
type: 'text',
},
timeout: {
value: '16s',
type: 'text',
},
max_redirects: {
value: 0,
type: 'integer',
},
proxy_url: {
value: '',
type: 'text',
},
tags: {
value: '[]',
type: 'yaml',
},
'response.include_headers': {
value: true,
type: 'bool',
},
'response.include_body': {
value: 'on_error',
type: 'text',
},
'check.request.method': {
value: 'GET',
type: 'text',
},
'check.request.headers': {
value: '{}',
type: 'yaml',
},
'check.request.body': {
value: '""',
type: 'yaml',
},
'check.response.status': {
value: '[]',
type: 'yaml',
},
'check.response.headers': {
value: '{}',
type: 'yaml',
},
'check.response.body.positive': {
value: '[]',
type: 'yaml',
},
'check.response.body.negative': {
value: '[]',
type: 'yaml',
},
'ssl.certificate_authorities': {
value: '',
type: 'yaml',
},
'ssl.certificate': {
value: '',
type: 'yaml',
},
'ssl.key': {
value: '',
type: 'yaml',
},
'ssl.key_passphrase': {
type: 'text',
},
'ssl.verification_mode': {
value: 'full',
type: 'text',
},
},
},
],
},
{
type: 'synthetics/tcp',
enabled: false,
streams: [
{
enabled: false,
data_stream: {
type: 'synthetics',
dataset: 'tcp',
},
vars: {
type: {
value: 'tcp',
type: 'text',
},
name: {
value: 'Sample name',
type: 'text',
},
schedule: {
value: '10s',
type: 'text',
},
hosts: {
type: 'text',
},
'service.name': {
type: 'text',
},
timeout: {
type: 'text',
},
max_redirects: {
type: 'integer',
},
proxy_url: {
type: 'text',
},
proxy_use_local_resolver: {
value: false,
type: 'bool',
},
tags: {
type: 'yaml',
},
'check.send': {
type: 'text',
},
'check.receive': {
type: 'yaml',
},
'ssl.certificate_authorities': {
type: 'yaml',
},
'ssl.certificate': {
type: 'yaml',
},
'ssl.key': {
type: 'yaml',
},
'ssl.key_passphrase': {
type: 'text',
},
'ssl.verification_mode': {
type: 'text',
},
},
},
],
},
{
type: 'synthetics/icmp',
enabled: false,
streams: [
{
enabled: false,
data_stream: {
type: 'synthetics',
dataset: 'icmp',
},
vars: {
type: {
value: 'icmp',
type: 'text',
},
name: {
value: 'Sample name',
type: 'text',
},
schedule: {
value: '10s',
type: 'text',
},
wait: {
value: '1s',
type: 'text',
},
hosts: {
type: 'text',
},
'service.name': {
type: 'text',
},
timeout: {
type: 'text',
},
tags: {
type: 'yaml',
},
},
},
],
},
{
type: 'synthetics/browser',
enabled: false,
streams: [
{
enabled: false,
data_stream: {
type: 'synthetics',
dataset: 'browser',
},
vars: {
type: {
value: 'browser',
type: 'text',
},
name: {
value: 'Sample name',
type: 'text',
},
schedule: {
value: '10s',
type: 'text',
},
'source.zip_url.url': {
type: 'text',
},
'source.zip_url.username': {
type: 'text',
},
'source.zip_url.password': {
type: 'password',
},
'source.zip_url.folder': {
type: 'text',
},
'source.inline.script': {
type: 'yaml',
},
timeout: {
type: 'text',
},
tags: {
type: 'yaml',
},
},
},
],
},
],
package: {
name: 'synthetics',
title: 'Elastic Synthetics',
version: '0.66.0',
},
};
const defaultHTTPConfig = defaultConfig[DataStream.HTTP];
const defaultTCPConfig = defaultConfig[DataStream.TCP];
describe('<SyntheticsPolicyCreateExtension />', () => {
const onChange = jest.fn();
const WrappedComponent = ({ newPolicy = defaultNewPolicy }) => {
return <SyntheticsPolicyCreateExtensionWrapper newPolicy={newPolicy} onChange={onChange} />;
};
beforeEach(() => {
onChange.mockClear();
});
it('renders SyntheticsPolicyCreateExtension', async () => {
const { getByText, getByLabelText, queryByLabelText } = render(<WrappedComponent />);
const monitorType = queryByLabelText('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(monitorType.value).toEqual(DataStream.HTTP);
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(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('handles updating fields', 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');
await waitFor(() => {
expect(onChange).toBeCalledWith({
isValid: true,
updatedPolicy: {
...defaultNewPolicy,
inputs: [
{
...defaultNewPolicy.inputs[0],
streams: [
{
...defaultNewPolicy.inputs[0].streams[0],
vars: {
...defaultNewPolicy.inputs[0].streams[0].vars,
urls: {
value: 'http://elastic.co',
type: 'text',
},
proxy_url: {
value: 'http://proxy.co',
type: 'text',
},
schedule: {
value: '"@every 1m"',
type: 'text',
},
'service.name': {
value: 'APM Service',
type: 'text',
},
max_redirects: {
value: '2',
type: 'integer',
},
timeout: {
value: '3s',
type: 'text',
},
},
},
],
},
defaultNewPolicy.inputs[1],
defaultNewPolicy.inputs[2],
defaultNewPolicy.inputs[3],
],
},
});
});
});
it('handles calling onChange', async () => {
const { getByLabelText } = render(<WrappedComponent />);
const url = getByLabelText('URL') as HTMLInputElement;
fireEvent.change(url, { target: { value: 'http://elastic.co' } });
await waitFor(() => {
expect(onChange).toBeCalledWith({
isValid: true,
updatedPolicy: {
...defaultNewPolicy,
inputs: [
{
...defaultNewPolicy.inputs[0],
streams: [
{
...defaultNewPolicy.inputs[0].streams[0],
vars: {
...defaultNewPolicy.inputs[0].streams[0].vars,
urls: {
value: 'http://elastic.co',
type: 'text',
},
},
},
],
},
defaultNewPolicy.inputs[1],
defaultNewPolicy.inputs[2],
defaultNewPolicy.inputs[3],
],
},
});
});
});
it('handles switching monitor type', async () => {
const { getByText, getByLabelText, queryByLabelText } = render(<WrappedComponent />);
const monitorType = getByLabelText('Monitor Type') as HTMLInputElement;
expect(monitorType).toBeInTheDocument();
expect(monitorType.value).toEqual(DataStream.HTTP);
fireEvent.change(monitorType, { target: { value: DataStream.TCP } });
await waitFor(() => {
expect(onChange).toBeCalledWith({
isValid: false,
updatedPolicy: {
...defaultNewPolicy,
inputs: [
{
...defaultNewPolicy.inputs[0],
enabled: false,
},
{
...defaultNewPolicy.inputs[1],
enabled: true,
},
defaultNewPolicy.inputs[2],
defaultNewPolicy.inputs[3],
],
},
});
});
// 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();
// ensure at least one tcp advanced option is present
const 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 TCP fields not to be in the DOM
expect(queryByLabelText('Proxy URL')).not.toBeInTheDocument();
});
it('handles http validation', async () => {
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();
// expect onChange to be called with isValid false
await waitFor(() => {
expect(onChange).toBeCalledWith(
expect.objectContaining({
isValid: false,
})
);
});
// 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 onChange to be called with isValid true
await waitFor(() => {
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();
expect(onChange).toBeCalledWith(
expect.objectContaining({
isValid: true,
})
);
});
});
it('handles tcp validation', async () => {
const { getByText, getByLabelText, queryByText } = render(<WrappedComponent />);
const monitorType = getByLabelText('Monitor Type') as HTMLInputElement;
fireEvent.change(monitorType, { target: { value: DataStream.TCP } });
const host = getByLabelText('Host:Port') as HTMLInputElement;
const monitorIntervalNumber = getByLabelText('Number') as HTMLInputElement;
const timeout = getByLabelText('Timeout in seconds') as HTMLInputElement;
// create errors
fireEvent.change(host, { target: { value: 'localhost' } }); // host without port
fireEvent.change(monitorIntervalNumber, { target: { value: '-1' } });
fireEvent.change(timeout, { target: { value: '-1' } });
await waitFor(() => {
const hostError = getByText('Host and port are required');
const monitorIntervalError = getByText('Monitor frequency is required');
const timeoutError = getByText('Timeout must be greater than or equal to 0');
expect(hostError).toBeInTheDocument();
expect(monitorIntervalError).toBeInTheDocument();
expect(timeoutError).toBeInTheDocument();
expect(onChange).toBeCalledWith(
expect.objectContaining({
isValid: false,
})
);
});
// resolve errors
fireEvent.change(host, { target: { value: 'smtp.gmail.com:587' } });
fireEvent.change(monitorIntervalNumber, { target: { value: '1' } });
fireEvent.change(timeout, { target: { value: '1' } });
await waitFor(() => {
expect(queryByText('Host and port are 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();
expect(onChange).toBeCalledWith(
expect.objectContaining({
isValid: true,
})
);
});
});
it('handles icmp validation', async () => {
const { getByText, getByLabelText, queryByText } = render(<WrappedComponent />);
const monitorType = getByLabelText('Monitor Type') as HTMLInputElement;
fireEvent.change(monitorType, { target: { value: DataStream.ICMP } });
const host = getByLabelText('Host') as HTMLInputElement;
const monitorIntervalNumber = getByLabelText('Number') as HTMLInputElement;
const timeout = getByLabelText('Timeout in seconds') as HTMLInputElement;
const wait = getByLabelText('Wait in seconds') as HTMLInputElement;
// create errors
fireEvent.change(host, { target: { value: '' } });
fireEvent.change(monitorIntervalNumber, { target: { value: '-1' } });
fireEvent.change(timeout, { target: { value: '-1' } });
fireEvent.change(wait, { target: { value: '-1' } });
await waitFor(() => {
const hostError = getByText('Host is required');
const monitorIntervalError = getByText('Monitor frequency is required');
const timeoutError = getByText('Timeout must be greater than or equal to 0');
const waitError = getByText('Wait must be 0 or greater');
expect(hostError).toBeInTheDocument();
expect(monitorIntervalError).toBeInTheDocument();
expect(timeoutError).toBeInTheDocument();
expect(waitError).toBeInTheDocument();
expect(onChange).toBeCalledWith(
expect.objectContaining({
isValid: false,
})
);
});
// resolve errors
fireEvent.change(host, { target: { value: '1.1.1.1' } });
fireEvent.change(monitorIntervalNumber, { target: { value: '1' } });
fireEvent.change(timeout, { target: { value: '1' } });
fireEvent.change(wait, { target: { value: '1' } });
await waitFor(() => {
expect(queryByText('Host is required')).not.toBeInTheDocument();
expect(queryByText('Monitor frequency is required')).not.toBeInTheDocument();
expect(queryByText('Timeout must be greater than or equal to 0')).not.toBeInTheDocument();
expect(queryByText('Wait must be 0 or greater')).not.toBeInTheDocument();
expect(onChange).toBeCalledWith(
expect.objectContaining({
isValid: true,
})
);
});
});
it('handles browser validation', async () => {
const { getByText, getByLabelText, queryByText, getByRole, getByTestId } = render(
<WrappedComponent />
);
const monitorType = getByLabelText('Monitor Type') as HTMLInputElement;
fireEvent.change(monitorType, { target: { value: DataStream.BROWSER } });
const zip = getByTestId('syntheticsSourceTab__zipUrl');
fireEvent.click(zip);
const zipUrl = getByRole('textbox', { name: 'Zip URL' }) as HTMLInputElement;
const monitorIntervalNumber = getByLabelText('Number') as HTMLInputElement;
// create errors
fireEvent.change(zipUrl, { target: { value: '' } });
fireEvent.change(monitorIntervalNumber, { target: { value: '-1' } });
await waitFor(() => {
const hostError = getByText('Zip URL is required');
const monitorIntervalError = getByText('Monitor frequency is required');
expect(hostError).toBeInTheDocument();
expect(monitorIntervalError).toBeInTheDocument();
expect(onChange).toBeCalledWith(
expect.objectContaining({
isValid: false,
})
);
});
// resolve errors
fireEvent.change(zipUrl, { target: { value: 'http://github.com/tests.zip' } });
fireEvent.change(monitorIntervalNumber, { target: { value: '1' } });
await waitFor(() => {
expect(queryByText('Zip URL is required')).not.toBeInTheDocument();
expect(queryByText('Monitor frequency is required')).not.toBeInTheDocument();
expect(onChange).toBeCalledWith(
expect.objectContaining({
isValid: true,
})
);
});
// test inline script validation
fireEvent.click(getByText('Inline script'));
await waitFor(() => {
expect(getByText('Script is required')).toBeInTheDocument();
});
});
it('handles changing TLS fields', async () => {
const { findByLabelText, queryByLabelText } = render(<WrappedComponent />);
const enableSSL = queryByLabelText('Enable TLS configuration') as HTMLInputElement;
// 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;
await waitFor(() => {
fireEvent.change(ca, { target: { value: 'certificateAuthorities' } });
expect(ca.value).toEqual(defaultHTTPConfig[ConfigKey.TLS_CERTIFICATE_AUTHORITIES]);
});
await waitFor(() => {
fireEvent.change(clientCertificate, { target: { value: 'clientCertificate' } });
expect(clientCertificate.value).toEqual(defaultHTTPConfig[ConfigKey.TLS_KEY]);
});
await waitFor(() => {
fireEvent.change(clientKey, { target: { value: 'clientKey' } });
expect(clientKey.value).toEqual(defaultHTTPConfig[ConfigKey.TLS_KEY]);
});
await waitFor(() => {
fireEvent.change(clientKeyPassphrase, { target: { value: 'clientKeyPassphrase' } });
expect(clientKeyPassphrase.value).toEqual(defaultHTTPConfig[ConfigKey.TLS_KEY_PASSPHRASE]);
});
await waitFor(() => {
fireEvent.change(verificationMode, { target: { value: VerificationMode.NONE } });
expect(verificationMode.value).toEqual(defaultHTTPConfig[ConfigKey.TLS_VERIFICATION_MODE]);
});
await waitFor(() => {
expect(onChange).toBeCalledWith({
isValid: false,
updatedPolicy: {
...defaultNewPolicy,
inputs: [
{
...defaultNewPolicy.inputs[0],
streams: [
{
...defaultNewPolicy.inputs[0].streams[0],
vars: {
...defaultNewPolicy.inputs[0].streams[0].vars,
[ConfigKey.TLS_CERTIFICATE_AUTHORITIES]: {
value: '"certificateAuthorities"',
type: 'yaml',
},
[ConfigKey.TLS_CERTIFICATE]: {
value: '"clientCertificate"',
type: 'yaml',
},
[ConfigKey.TLS_KEY]: {
value: '"clientKey"',
type: 'yaml',
},
[ConfigKey.TLS_KEY_PASSPHRASE]: {
value: 'clientKeyPassphrase',
type: 'text',
},
[ConfigKey.TLS_VERIFICATION_MODE]: {
value: VerificationMode.NONE,
type: 'text',
},
},
},
],
},
defaultNewPolicy.inputs[1],
defaultNewPolicy.inputs[2],
defaultNewPolicy.inputs[3],
],
},
});
});
});
});

View file

@ -27,7 +27,7 @@ export function useMonitorList() {
const { pageIndex, pageSize, sortField, sortOrder } = pageState as MonitorManagementListPageState;
const { type: viewType } = useParams<{ type: 'all' | 'invalid' }>();
const { type: viewType = 'all' } = useParams<{ type: 'all' | 'invalid' }>();
useEffect(() => {
if (viewType === 'all') {

View file

@ -48,19 +48,22 @@ export const EmptyLocations = ({
}
footer={
<EuiText size="s">
{LEARN_MORE}{' '}
<EuiLink
href="https://www.elastic.co/guide/en/observability/current/uptime-set-up-choose-agent.html#private-locations"
target="_blank"
>
{READ_DOCS}
</EuiLink>
{LEARN_MORE} <PrivateLocationDocsLink />
</EuiText>
}
/>
);
};
export const PrivateLocationDocsLink = ({ label }: { label?: string }) => (
<EuiLink
href="https://www.elastic.co/guide/en/observability/current/uptime-set-up-choose-agent.html#private-locations"
target="_blank"
>
{label ?? READ_DOCS}
</EuiLink>
);
const FIRST_MONITOR = i18n.translate('xpack.synthetics.monitorManagement.firstLocationMonitor', {
defaultMessage: 'In order to create a monitor, you will need to add a location first.',
});

View file

@ -35,7 +35,7 @@ export const MonitorListTabs = ({
const history = useHistory();
const { type: viewType } = useParams<{ type: 'all' | 'invalid' }>();
const { type: viewType = 'all' } = useParams<{ type: 'all' | 'invalid' }>();
useEffect(() => {
setSelectedTabId(viewType);
@ -52,7 +52,7 @@ export const MonitorListTabs = ({
id: 'all',
name: ALL_MONITORS_LABEL,
content: <Fragment />,
href: history.createHref({ pathname: '/manage-monitors/all' }),
href: history.createHref({ pathname: '/manage-monitors' }),
disabled: false,
},
{

View file

@ -46,7 +46,7 @@ export const MonitorListContainer = ({
const monitorList = useSelector(monitorManagementListSelector);
const { type: viewType } = useParams<{ type: 'all' | 'invalid' }>();
const { type: viewType = 'all' } = useParams<{ type: 'all' | 'invalid' }>();
const { errorSummaries, loading, count } = useInlineErrors({
onlyInvalidMonitors: viewType === 'invalid',
sortField: pageState.sortField,

View file

@ -240,7 +240,7 @@ const getRoutes = (): RouteProps[] => {
defaultMessage: 'Monitor Management | {baseTitle}',
values: { baseTitle },
}),
path: MONITOR_MANAGEMENT_ROUTE + '/:type',
path: MONITOR_MANAGEMENT_ROUTE + '/:type?',
component: () => (
<ServiceAllowedWrapper>
<MonitorManagementPage />

View file

@ -1,3 +0,0 @@
# Kibana Functional Testing
See our [Functional Testing Guide](https://www.elastic.co/guide/en/kibana/current/development-tests.html#development-functional-tests)

View file

@ -1,16 +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 { FtrProviderContext } from '../../ftr_provider_context';
export default ({ loadTestFile, getService }: FtrProviderContext) => {
describe('Uptime app', function () {
describe('with generated data', () => {
loadTestFile(require.resolve('./synthetics_integration'));
});
});
};

View file

@ -1,772 +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 from '@kbn/expect';
import { FullAgentPolicy } from '@kbn/fleet-plugin/common';
import { FtrProviderContext } from '../../ftr_provider_context';
import { skipIfNoDockerRegistry } from '../../helpers';
export default function (providerContext: FtrProviderContext) {
const { getPageObjects, getService } = providerContext;
const monitorName = 'Sample Synthetics integration';
const uptimePage = getPageObjects(['syntheticsIntegration']);
const testSubjects = getService('testSubjects');
const uptimeService = getService('uptime');
const getSyntheticsPolicy = (agentFullPolicy: FullAgentPolicy) =>
agentFullPolicy.inputs.find((input) => input.meta?.package?.name === 'synthetics');
const generatePolicy = ({
agentFullPolicy,
version,
monitorType,
name,
config,
}: {
agentFullPolicy: FullAgentPolicy;
version: string;
monitorType: string;
name: string;
config: Record<string, any>;
}) => ({
data_stream: {
namespace: 'default',
},
id: getSyntheticsPolicy(agentFullPolicy)?.id,
meta: {
package: {
name: 'synthetics',
version,
},
},
name,
package_policy_id: getSyntheticsPolicy(agentFullPolicy)?.package_policy_id,
revision: 1,
streams: [
{
data_stream: {
dataset: monitorType,
elasticsearch: {
privileges: {
indices: ['auto_configure', 'create_doc', 'read'],
},
},
type: 'synthetics',
},
id: `${getSyntheticsPolicy(agentFullPolicy)?.streams?.[0]?.id}`,
name,
type: monitorType,
enabled: true,
processors: [
{
add_observer_metadata: {
geo: {
name: 'Fleet managed',
},
},
},
{
add_fields: {
fields: {
'monitor.fleet_managed': true,
},
target: '',
},
},
],
...config,
},
...(monitorType === 'browser'
? [
{
data_stream: {
dataset: 'browser.network',
elasticsearch: {
privileges: {
indices: ['auto_configure', 'create_doc', 'read'],
},
},
type: 'synthetics',
},
id: `${getSyntheticsPolicy(agentFullPolicy)?.streams?.[1]?.id}`,
processors: [
{
add_observer_metadata: {
geo: {
name: 'Fleet managed',
},
},
},
{
add_fields: {
fields: {
'monitor.fleet_managed': true,
},
target: '',
},
},
],
},
{
data_stream: {
dataset: 'browser.screenshot',
elasticsearch: {
privileges: {
indices: ['auto_configure', 'create_doc', 'read'],
},
},
type: 'synthetics',
},
id: `${getSyntheticsPolicy(agentFullPolicy)?.streams?.[2]?.id}`,
processors: [
{
add_observer_metadata: {
geo: {
name: 'Fleet managed',
},
},
},
{
add_fields: {
fields: {
'monitor.fleet_managed': true,
},
target: '',
},
},
],
},
]
: []),
],
type: `synthetics/${monitorType}`,
use_output: 'default',
});
describe('When on the Synthetics Integration Policy Create Page', function () {
skipIfNoDockerRegistry(providerContext);
const basicConfig = {
name: monitorName,
apmServiceName: 'Sample APM Service',
tags: 'sample tag',
};
const generateHTTPConfig = (url: string) => ({
...basicConfig,
url,
});
const generateTCPorICMPConfig = (host: string) => ({
...basicConfig,
host,
});
const generateBrowserConfig = (config: Record<string, string>): Record<string, string> => ({
...basicConfig,
...config,
});
describe('displays custom UI', () => {
before(async () => {
const version = await uptimeService.syntheticsPackage.getSyntheticsPackageVersion();
await uptimePage.syntheticsIntegration.navigateToPackagePage(version!);
});
it('should display policy view', async () => {
await uptimePage.syntheticsIntegration.ensureIsOnPackagePage();
});
it('prevent saving when integration name, url/host, or schedule is missing', async () => {
const saveButton = await uptimePage.syntheticsIntegration.findSaveButton();
await saveButton.click();
await testSubjects.missingOrFail('postInstallAddAgentModal');
});
});
describe('create new policy', () => {
let version: string;
beforeEach(async () => {
version = (await uptimeService.syntheticsPackage.getSyntheticsPackageVersion())!;
await uptimePage.syntheticsIntegration.navigateToPackagePage(version!);
await uptimeService.syntheticsPackage.deletePolicyByName(monitorName);
});
afterEach(async () => {
await uptimeService.syntheticsPackage.deletePolicyByName(monitorName);
});
it('allows saving when user enters a valid integration name and url/host', async () => {
// This test ensures that updates made to the Synthetics Policy are carried all the way through
// to the generated Agent Policy that is dispatch down to the Elastic Agent.
const config = generateHTTPConfig('http://elastic.co');
await uptimePage.syntheticsIntegration.createBasicHTTPMonitorDetails(config);
await uptimePage.syntheticsIntegration.confirmAndSave();
await uptimePage.syntheticsIntegration.isPolicyCreatedSuccessfully();
const [agentPolicy] = await uptimeService.syntheticsPackage.getAgentPolicyList();
const agentPolicyId = agentPolicy.id;
const agentFullPolicy = await uptimeService.syntheticsPackage.getFullAgentPolicy(
agentPolicyId
);
expect(getSyntheticsPolicy(agentFullPolicy)).to.eql(
generatePolicy({
agentFullPolicy,
version,
name: monitorName,
monitorType: 'http',
config: {
max_redirects: 0,
'response.include_body': 'on_error',
'response.include_headers': true,
schedule: '@every 3m',
timeout: '16s',
urls: config.url,
'service.name': config.apmServiceName,
tags: [config.tags],
'check.request.method': 'GET',
__ui: {
is_tls_enabled: false,
is_zip_url_tls_enabled: false,
},
},
})
);
});
it('allows enabling tls with defaults', async () => {
// This test ensures that updates made to the Synthetics Policy are carried all the way through
// to the generated Agent Policy that is dispatch down to the Elastic Agent.
const config = generateHTTPConfig('http://elastic.co');
await uptimePage.syntheticsIntegration.createBasicHTTPMonitorDetails(config);
await uptimePage.syntheticsIntegration.enableTLS();
await uptimePage.syntheticsIntegration.confirmAndSave();
await uptimePage.syntheticsIntegration.isPolicyCreatedSuccessfully();
const [agentPolicy] = await uptimeService.syntheticsPackage.getAgentPolicyList();
const agentPolicyId = agentPolicy.id;
const agentFullPolicy = await uptimeService.syntheticsPackage.getFullAgentPolicy(
agentPolicyId
);
expect(
agentFullPolicy.inputs.find((input) => input.meta?.package?.name === 'synthetics')
).to.eql(
generatePolicy({
agentFullPolicy,
version,
name: monitorName,
monitorType: 'http',
config: {
max_redirects: 0,
'check.request.method': 'GET',
'response.include_body': 'on_error',
'response.include_headers': true,
schedule: '@every 3m',
'ssl.supported_protocols': ['TLSv1.1', 'TLSv1.2', 'TLSv1.3'],
'ssl.verification_mode': 'full',
timeout: '16s',
urls: config.url,
'service.name': config.apmServiceName,
tags: [config.tags],
__ui: {
is_tls_enabled: true,
is_zip_url_tls_enabled: false,
},
},
})
);
});
it('allows configuring tls', async () => {
// This test ensures that updates made to the Synthetics Policy are carried all the way through
// to the generated Agent Policy that is dispatch down to the Elastic Agent.
const config = generateHTTPConfig('http://elastic.co');
const tlsConfig = {
verificationMode: 'strict',
ca: 'ca',
cert: 'cert',
certKey: 'certKey',
certKeyPassphrase: 'certKeyPassphrase',
};
await uptimePage.syntheticsIntegration.createBasicHTTPMonitorDetails(config);
await uptimePage.syntheticsIntegration.configureTLSOptions(tlsConfig);
await uptimePage.syntheticsIntegration.confirmAndSave();
await uptimePage.syntheticsIntegration.isPolicyCreatedSuccessfully();
const [agentPolicy] = await uptimeService.syntheticsPackage.getAgentPolicyList();
const agentPolicyId = agentPolicy.id;
const agentFullPolicy = await uptimeService.syntheticsPackage.getFullAgentPolicy(
agentPolicyId
);
expect(getSyntheticsPolicy(agentFullPolicy)).to.eql(
generatePolicy({
agentFullPolicy,
version,
name: monitorName,
monitorType: 'http',
config: {
max_redirects: 0,
'check.request.method': 'GET',
'response.include_body': 'on_error',
'response.include_headers': true,
schedule: '@every 3m',
'ssl.supported_protocols': ['TLSv1.1', 'TLSv1.2', 'TLSv1.3'],
'ssl.verification_mode': tlsConfig.verificationMode,
'ssl.certificate': tlsConfig.cert,
'ssl.certificate_authorities': tlsConfig.ca,
'ssl.key': tlsConfig.certKey,
'ssl.key_passphrase': tlsConfig.certKeyPassphrase,
timeout: '16s',
urls: config.url,
'service.name': config.apmServiceName,
tags: [config.tags],
__ui: {
is_tls_enabled: true,
is_zip_url_tls_enabled: false,
},
},
})
);
});
it('allows configuring http advanced options', async () => {
// This test ensures that updates made to the Synthetics Policy are carried all the way through
// to the generated Agent Policy that is dispatch down to the Elastic Agent.
const config = generateHTTPConfig('http://elastic.co');
await uptimePage.syntheticsIntegration.createBasicHTTPMonitorDetails(config);
const advancedConfig = {
username: 'username',
password: 'password',
proxyUrl: 'proxyUrl',
requestMethod: 'POST',
responseStatusCheck: '204',
responseBodyCheckPositive: 'success',
responseBodyCheckNegative: 'failure',
requestHeaders: {
sampleRequestHeader1: 'sampleRequestKey1',
sampleRequestHeader2: 'sampleRequestKey2',
},
responseHeaders: {
sampleResponseHeader1: 'sampleResponseKey1',
sampleResponseHeader2: 'sampleResponseKey2',
},
requestBody: {
type: 'xml',
value: '<samplexml>samplexml<samplexml>',
},
indexResponseBody: false,
indexResponseHeaders: false,
};
await uptimePage.syntheticsIntegration.configureHTTPAdvancedOptions(advancedConfig);
await uptimePage.syntheticsIntegration.confirmAndSave();
await uptimePage.syntheticsIntegration.isPolicyCreatedSuccessfully();
const [agentPolicy] = await uptimeService.syntheticsPackage.getAgentPolicyList();
const agentPolicyId = agentPolicy.id;
const agentFullPolicy = await uptimeService.syntheticsPackage.getFullAgentPolicy(
agentPolicyId
);
expect(getSyntheticsPolicy(agentFullPolicy)).to.eql(
generatePolicy({
agentFullPolicy,
version,
name: monitorName,
monitorType: 'http',
config: {
max_redirects: 0,
'check.request.method': advancedConfig.requestMethod,
'check.request.headers': {
'Content-Type': 'application/xml',
...advancedConfig.requestHeaders,
},
'check.response.headers': advancedConfig.responseHeaders,
'check.response.status': [advancedConfig.responseStatusCheck],
'check.request.body': advancedConfig.requestBody.value,
'check.response.body.positive': [advancedConfig.responseBodyCheckPositive],
'check.response.body.negative': [advancedConfig.responseBodyCheckNegative],
'response.include_body': advancedConfig.indexResponseBody ? 'on_error' : 'never',
'response.include_headers': advancedConfig.indexResponseHeaders,
schedule: '@every 3m',
timeout: '16s',
urls: config.url,
proxy_url: advancedConfig.proxyUrl,
username: advancedConfig.username,
password: advancedConfig.password,
'service.name': config.apmServiceName,
tags: [config.tags],
__ui: {
is_tls_enabled: false,
is_zip_url_tls_enabled: false,
},
},
})
);
});
it('allows saving tcp monitor when user enters a valid integration name and host+port', async () => {
// This test ensures that updates made to the Synthetics Policy are carried all the way through
// to the generated Agent Policy that is dispatch down to the Elastic Agent.
const config = generateTCPorICMPConfig('smtp.gmail.com:587');
await uptimePage.syntheticsIntegration.createBasicTCPMonitorDetails(config);
await uptimePage.syntheticsIntegration.confirmAndSave();
await uptimePage.syntheticsIntegration.isPolicyCreatedSuccessfully();
const [agentPolicy] = await uptimeService.syntheticsPackage.getAgentPolicyList();
const agentPolicyId = agentPolicy.id;
const agentFullPolicy = await uptimeService.syntheticsPackage.getFullAgentPolicy(
agentPolicyId
);
expect(getSyntheticsPolicy(agentFullPolicy)).to.eql(
generatePolicy({
agentFullPolicy,
version,
name: monitorName,
monitorType: 'tcp',
config: {
proxy_use_local_resolver: false,
schedule: '@every 3m',
timeout: '16s',
hosts: config.host,
tags: [config.tags],
'service.name': config.apmServiceName,
__ui: {
is_tls_enabled: false,
is_zip_url_tls_enabled: false,
},
},
})
);
});
it('allows configuring tcp advanced options', async () => {
// This test ensures that updates made to the Synthetics Policy are carried all the way through
// to the generated Agent Policy that is dispatch down to the Elastic Agent.
const config = generateTCPorICMPConfig('smtp.gmail.com:587');
await uptimePage.syntheticsIntegration.createBasicTCPMonitorDetails(config);
const advancedConfig = {
proxyUrl: 'proxyUrl',
requestSendCheck: 'body',
responseReceiveCheck: 'success',
proxyUseLocalResolver: true,
};
await uptimePage.syntheticsIntegration.configureTCPAdvancedOptions(advancedConfig);
await uptimePage.syntheticsIntegration.confirmAndSave();
await uptimePage.syntheticsIntegration.isPolicyCreatedSuccessfully();
const [agentPolicy] = await uptimeService.syntheticsPackage.getAgentPolicyList();
const agentPolicyId = agentPolicy.id;
const agentFullPolicy = await uptimeService.syntheticsPackage.getFullAgentPolicy(
agentPolicyId
);
expect(getSyntheticsPolicy(agentFullPolicy)).to.eql(
generatePolicy({
agentFullPolicy,
version,
name: monitorName,
monitorType: 'tcp',
config: {
schedule: '@every 3m',
timeout: '16s',
hosts: config.host,
proxy_url: advancedConfig.proxyUrl,
proxy_use_local_resolver: advancedConfig.proxyUseLocalResolver,
'check.receive': advancedConfig.responseReceiveCheck,
'check.send': advancedConfig.requestSendCheck,
'service.name': config.apmServiceName,
tags: [config.tags],
__ui: {
is_tls_enabled: false,
is_zip_url_tls_enabled: false,
},
},
})
);
});
it('allows saving icmp monitor when user enters a valid integration name and host', async () => {
// This test ensures that updates made to the Synthetics Policy are carried all the way through
// to the generated Agent Policy that is dispatch down to the Elastic Agent.
const config = generateTCPorICMPConfig('1.1.1.1');
await uptimePage.syntheticsIntegration.createBasicICMPMonitorDetails(config);
await uptimePage.syntheticsIntegration.confirmAndSave();
await uptimePage.syntheticsIntegration.isPolicyCreatedSuccessfully();
const [agentPolicy] = await uptimeService.syntheticsPackage.getAgentPolicyList();
const agentPolicyId = agentPolicy.id;
const agentFullPolicy = await uptimeService.syntheticsPackage.getFullAgentPolicy(
agentPolicyId
);
expect(getSyntheticsPolicy(agentFullPolicy)).to.eql(
generatePolicy({
agentFullPolicy,
version,
name: monitorName,
monitorType: 'icmp',
config: {
schedule: '@every 3m',
timeout: '16s',
wait: '1s',
hosts: config.host,
'service.name': config.apmServiceName,
tags: [config.tags],
__ui: null,
},
})
);
});
it('allows saving browser monitor', async () => {
// This test ensures that updates made to the Synthetics Policy are carried all the way through
// to the generated Agent Policy that is dispatch down to the Elastic Agent.
const config = generateBrowserConfig({
zipUrl: 'http://test.zip',
params: JSON.stringify({ url: 'http://localhost:8080' }),
folder: 'folder',
username: 'username',
password: 'password',
});
await uptimePage.syntheticsIntegration.createBasicBrowserMonitorDetails(config);
await uptimePage.syntheticsIntegration.confirmAndSave();
await uptimePage.syntheticsIntegration.isPolicyCreatedSuccessfully();
const [agentPolicy] = await uptimeService.syntheticsPackage.getAgentPolicyList();
const agentPolicyId = agentPolicy.id;
const agentFullPolicy = await uptimeService.syntheticsPackage.getFullAgentPolicy(
agentPolicyId
);
expect(getSyntheticsPolicy(agentFullPolicy)).to.eql(
generatePolicy({
agentFullPolicy,
version,
name: monitorName,
monitorType: 'browser',
config: {
screenshots: 'on',
schedule: '@every 10m',
timeout: null,
tags: [config.tags],
throttling: '5d/3u/20l',
'service.name': config.apmServiceName,
'source.zip_url.url': config.zipUrl,
'source.zip_url.folder': config.folder,
'source.zip_url.username': config.username,
'source.zip_url.password': config.password,
params: JSON.parse(config.params),
__ui: {
is_tls_enabled: false,
is_zip_url_tls_enabled: false,
script_source: {
file_name: '',
is_generated_script: false,
},
},
},
})
);
});
it('allows saving browser monitor with inline script', async () => {
// This test ensures that updates made to the Synthetics Policy are carried all the way through
// to the generated Agent Policy that is dispatch down to the Elastic Agent.
const config = generateBrowserConfig({
inlineScript:
'step("load homepage", async () => { await page.goto(\'https://www.elastic.co\'); });',
});
await uptimePage.syntheticsIntegration.createBasicBrowserMonitorDetails(config, true);
await uptimePage.syntheticsIntegration.confirmAndSave();
await uptimePage.syntheticsIntegration.isPolicyCreatedSuccessfully();
const [agentPolicy] = await uptimeService.syntheticsPackage.getAgentPolicyList();
const agentPolicyId = agentPolicy.id;
const agentFullPolicy = await uptimeService.syntheticsPackage.getFullAgentPolicy(
agentPolicyId
);
expect(getSyntheticsPolicy(agentFullPolicy)).to.eql(
generatePolicy({
agentFullPolicy,
version,
name: monitorName,
monitorType: 'browser',
config: {
screenshots: 'on',
schedule: '@every 10m',
timeout: null,
tags: [config.tags],
throttling: '5d/3u/20l',
'service.name': config.apmServiceName,
'source.inline.script': config.inlineScript,
__ui: {
is_tls_enabled: false,
is_zip_url_tls_enabled: false,
script_source: {
file_name: '',
is_generated_script: false,
},
},
},
})
);
});
it('allows saving browser monitor advanced options', async () => {
// This test ensures that updates made to the Synthetics Policy are carried all the way through
// to the generated Agent Policy that is dispatch down to the Elastic Agent.
const config = generateBrowserConfig({
zipUrl: 'http://test.zip',
params: JSON.stringify({ url: 'http://localhost:8080' }),
folder: 'folder',
username: 'username',
password: 'password',
});
const advancedConfig = {
screenshots: 'off',
syntheticsArgs: '-ssBlocks',
isThrottlingEnabled: true,
downloadSpeed: '1337',
uploadSpeed: '1338',
latency: '1339',
};
await uptimePage.syntheticsIntegration.createBasicBrowserMonitorDetails(config);
await uptimePage.syntheticsIntegration.configureBrowserAdvancedOptions(advancedConfig);
await uptimePage.syntheticsIntegration.confirmAndSave();
await uptimePage.syntheticsIntegration.isPolicyCreatedSuccessfully();
const [agentPolicy] = await uptimeService.syntheticsPackage.getAgentPolicyList();
const agentPolicyId = agentPolicy.id;
const agentFullPolicy = await uptimeService.syntheticsPackage.getFullAgentPolicy(
agentPolicyId
);
expect(getSyntheticsPolicy(agentFullPolicy)).to.eql(
generatePolicy({
agentFullPolicy,
version,
name: monitorName,
monitorType: 'browser',
config: {
screenshots: advancedConfig.screenshots,
schedule: '@every 10m',
timeout: null,
tags: [config.tags],
throttling: '1337d/1338u/1339l',
'service.name': config.apmServiceName,
'source.zip_url.url': config.zipUrl,
'source.zip_url.folder': config.folder,
'source.zip_url.username': config.username,
'source.zip_url.password': config.password,
params: JSON.parse(config.params),
synthetics_args: [advancedConfig.syntheticsArgs],
__ui: {
is_tls_enabled: false,
is_zip_url_tls_enabled: false,
script_source: {
file_name: '',
is_generated_script: false,
},
},
},
})
);
});
it('allows saving disabling throttling', async () => {
// This test ensures that updates made to the Synthetics Policy are carried all the way through
// to the generated Agent Policy that is dispatch down to the Elastic Agent.
const config = generateBrowserConfig({
zipUrl: 'http://test.zip',
params: JSON.stringify({ url: 'http://localhost:8080' }),
folder: 'folder',
username: 'username',
password: 'password',
});
const advancedConfig = {
screenshots: 'off',
syntheticsArgs: '-ssBlocks',
isThrottlingEnabled: false,
downloadSpeed: '1337',
uploadSpeed: '1338',
latency: '1339',
};
await uptimePage.syntheticsIntegration.createBasicBrowserMonitorDetails(config);
await uptimePage.syntheticsIntegration.configureBrowserAdvancedOptions(advancedConfig);
await uptimePage.syntheticsIntegration.confirmAndSave();
await uptimePage.syntheticsIntegration.isPolicyCreatedSuccessfully();
const [agentPolicy] = await uptimeService.syntheticsPackage.getAgentPolicyList();
const agentPolicyId = agentPolicy.id;
const agentFullPolicy = await uptimeService.syntheticsPackage.getFullAgentPolicy(
agentPolicyId
);
expect(getSyntheticsPolicy(agentFullPolicy)).to.eql(
generatePolicy({
agentFullPolicy,
version,
name: monitorName,
monitorType: 'browser',
config: {
screenshots: advancedConfig.screenshots,
schedule: '@every 10m',
timeout: null,
tags: [config.tags],
'service.name': config.apmServiceName,
'source.zip_url.url': config.zipUrl,
'source.zip_url.folder': config.folder,
'source.zip_url.username': config.username,
'source.zip_url.password': config.password,
params: JSON.parse(config.params),
synthetics_args: [advancedConfig.syntheticsArgs],
throttling: false,
__ui: {
is_tls_enabled: false,
is_zip_url_tls_enabled: false,
script_source: {
file_name: '',
is_generated_script: false,
},
},
},
})
);
});
});
});
}

View file

@ -1,104 +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 path, { resolve } from 'path';
import { defineDockerServersConfig } from '@kbn/test';
import { dockerImage as fleetDockerImage } from '../fleet_api_integration/config';
import { services } from './services';
import { pageObjects } from './page_objects';
// the default export of config files must be a config provider
// that returns an object with the projects config values
export default async function ({ readConfigFile }) {
const registryPort = process.env.FLEET_PACKAGE_REGISTRY_PORT;
const kibanaCommonConfig = await readConfigFile(
require.resolve('../../../test/common/config.js')
);
const kibanaFunctionalConfig = await readConfigFile(
require.resolve('../../../test/functional/config.base.js')
);
// mount the config file for the package registry as well as
// the directory containing additional packages into the container
const dockerArgs = [
'-v',
`${path.join(
path.dirname(__filename),
'./fixtures/package_registry_config.yml'
)}:/package-registry/config.yml`,
];
return {
// list paths to the files that contain your plugins tests
testFiles: [resolve(__dirname, './apps/uptime')],
services,
pageObjects,
servers: kibanaFunctionalConfig.get('servers'),
esTestCluster: {
license: 'trial',
from: 'snapshot',
serverArgs: ['path.repo=/tmp/', 'xpack.security.authc.api_key.enabled=true'],
},
kbnTestServer: {
...kibanaCommonConfig.get('kbnTestServer'),
serverArgs: [
...kibanaCommonConfig.get('kbnTestServer.serverArgs'),
'--status.allowAnonymous=true',
'--server.uuid=5b2de169-2785-441b-ae8c-186a1936b17d',
'--xpack.maps.showMapsInspectorAdapter=true',
'--xpack.maps.preserveDrawingBuffer=true',
'--xpack.security.encryptionKey="wuGNaIhoMpk5sO4UBxgr3NyW1sFcLgIf"', // server restarts should not invalidate active sessions
'--xpack.encryptedSavedObjects.encryptionKey="DkdXazszSCYexXqz4YktBGHCRkV6hyNK"',
'--xpack.discoverEnhanced.actions.exploreDataInContextMenu.enabled=true',
'--savedObjects.maxImportPayloadBytes=10485760', // for OSS test management/_import_objects,
...(registryPort ? [`--xpack.fleet.registryUrl=http://localhost:${registryPort}`] : []),
],
},
uiSettings: {
defaults: {
'accessibility:disableAnimations': true,
'dateFormat:tz': 'UTC',
},
},
// the apps section defines the urls that
// `PageObjects.common.navigateTo(appKey)` will use.
// Merge urls for your plugin with the urls defined in
// Kibana's config in order to use this helper
apps: {
...kibanaFunctionalConfig.get('apps'),
fleet: {
pathname: '/app/fleet',
},
},
// choose where screenshots should be saved
screenshots: {
directory: resolve(__dirname, 'screenshots'),
},
junit: {
reportName: 'Chrome Elastic Synthetics Integration UI Functional Tests',
},
dockerServers: defineDockerServersConfig({
registry: {
enabled: !!registryPort,
image: fleetDockerImage,
portInContainer: 8080,
port: registryPort,
args: dockerArgs,
waitForLogLine: 'package manifests loaded',
},
}),
};
}

View file

@ -1,2 +0,0 @@
package_paths:
- /packages/package-storage

View file

@ -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.
*/
import { GenericFtrProviderContext, GenericFtrService } from '@kbn/test';
import { pageObjects } from './page_objects';
import { services } from './services';
export type FtrProviderContext = GenericFtrProviderContext<typeof services, typeof pageObjects>;
export class FtrService extends GenericFtrService<FtrProviderContext> {}

View file

@ -1,31 +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 { Context } from 'mocha';
import { ToolingLog } from '@kbn/tooling-log';
import { FtrProviderContext } from './ftr_provider_context';
export function warnAndSkipTest(mochaContext: Context, log: ToolingLog) {
log.warning(
'disabling tests because DockerServers service is not enabled, set FLEET_PACKAGE_REGISTRY_PORT to run them'
);
mochaContext.skip();
}
export function skipIfNoDockerRegistry(providerContext: FtrProviderContext) {
const { getService } = providerContext;
const dockerServers = getService('dockerServers');
const server = dockerServers.get('registry');
const log = getService('log');
beforeEach(function beforeSetupWithDockerRegistry() {
if (!server.enabled) {
warnAndSkipTest(this, log);
}
});
}

View file

@ -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.
*/
import { pageObjects as kibanaFunctionalPageObjects } from '../../../../test/functional/page_objects';
import { SyntheticsIntegrationPageProvider } from './synthetics_integration_page';
// just like services, PageObjects are defined as a map of
// names to Providers. Merge in Kibana's or pick specific ones
export const pageObjects = {
...kibanaFunctionalPageObjects,
syntheticsIntegration: SyntheticsIntegrationPageProvider,
};

View file

@ -1,483 +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 { WebElementWrapper } from '../../../../test/functional/services/lib/web_element_wrapper';
import { FtrProviderContext } from '../ftr_provider_context';
export function SyntheticsIntegrationPageProvider({
getService,
getPageObjects,
}: FtrProviderContext) {
const pageObjects = getPageObjects(['common', 'header']);
const testSubjects = getService('testSubjects');
const comboBox = getService('comboBox');
const fixedFooterHeight = 72; // Size of EuiBottomBar more or less
return {
/**
* Navigates to the Synthetics Integration page
*
*/
async navigateToPackagePage(packageVersion: string) {
await pageObjects.common.navigateToUrlWithBrowserHistory(
'fleet',
`/integrations/synthetics-${packageVersion}/add-integration`
);
await pageObjects.header.waitUntilLoadingHasFinished();
},
async navigateToPackageEditPage(packageId: string, agentId: string) {
await pageObjects.common.navigateToUrlWithBrowserHistory(
'fleet',
`/policies/${agentId}/edit-integration/${packageId}`
);
await pageObjects.header.waitUntilLoadingHasFinished();
},
/**
* Finds and returns the Policy Details Page Save button
*/
async findSaveButton(isEditPage?: boolean) {
await this.ensureIsOnPackagePage();
return await testSubjects.find(
isEditPage ? 'saveIntegration' : 'createPackagePolicySaveButton'
);
},
/**
* Finds and returns the Policy Details Page Cancel Button
*/
async findCancelButton() {
await this.ensureIsOnPackagePage();
return await testSubjects.find('policyDetailsCancelButton');
},
/**
* Determines if the policy was created successfully by looking for the creation success toast
*/
async isPolicyCreatedSuccessfully() {
await testSubjects.existOrFail('postInstallAddAgentModal');
},
/**
* Selects the monitor type
* @params {monitorType} the type of monitor, tcp, http, or icmp
*/
async selectMonitorType(monitorType: string) {
await testSubjects.selectValue('syntheticsMonitorTypeField', monitorType);
},
/**
* Fills a text input
* @params {testSubj} the testSubj of the input to fill
* @params {value} the value of the input
*/
async fillTextInputByTestSubj(testSubj: string, value: string) {
const field = await testSubjects.find(testSubj);
await field.scrollIntoViewIfNecessary({ bottomOffset: fixedFooterHeight });
await field.click();
await field.clearValue();
await field.type(value);
},
/**
* Fills a text input
* @params {testSubj} the testSubj of the input to fill
* @params {value} the value of the input
*/
async fillTextInput(field: WebElementWrapper, value: string) {
await field.scrollIntoViewIfNecessary({ bottomOffset: fixedFooterHeight });
await field.click();
await field.clearValue();
await field.type(value);
},
/**
* Fills a text input
* @params {testSubj} the testSubj of the comboBox
*/
async setComboBox(testSubj: string, value: string) {
await comboBox.setCustom(`${testSubj} > comboBoxInput`, value);
},
/**
* Finds and returns the HTTP advanced options accordion trigger
*/
async findHTTPAdvancedOptionsAccordion() {
await this.ensureIsOnPackagePage();
const accordion = await testSubjects.find('syntheticsHTTPAdvancedFieldsAccordion');
return accordion;
},
/**
* Finds and returns the enable throttling checkbox
*/
async findThrottleSwitch() {
await this.ensureIsOnPackagePage();
return await testSubjects.find('syntheticsBrowserIsThrottlingEnabled');
},
/**
* Finds and returns the enable TLS checkbox
*/
async findEnableTLSSwitch() {
await this.ensureIsOnPackagePage();
return await testSubjects.find('syntheticsIsTLSEnabled');
},
/**
* ensures that the package page is the currently display view
*/
async ensureIsOnPackagePage() {
await testSubjects.existOrFail('monitorSettingsSection');
},
/**
* Clicks save button and confirms update on the Policy Details page
*/
async confirmAndSave(isEditPage?: boolean) {
await this.ensureIsOnPackagePage();
const saveButton = await this.findSaveButton(isEditPage);
await saveButton.click();
await this.maybeForceInstall();
},
/**
* If the force install modal opens, click force install
*/
async maybeForceInstall() {
const confirmForceInstallModalOpen = await testSubjects.exists('confirmForceInstallModal');
if (confirmForceInstallModalOpen) {
const forceInstallBtn = await testSubjects.find('confirmModalConfirmButton');
return forceInstallBtn.click();
}
},
/**
* Fills in the username and password field
* @params username {string} the value of the username
* @params password {string} the value of the password
*/
async configureUsernameAndPassword({ username, password }: Record<string, string>) {
await this.fillTextInputByTestSubj('syntheticsUsername', username);
await this.fillTextInputByTestSubj('syntheticsPassword', password);
},
/**
*
* Configures request headers
* @params headers {string} an object containing desired headers
*
*/
async configureRequestHeaders(headers: Record<string, string>) {
await this.configureHeaders('syntheticsRequestHeaders', headers);
},
/**
*
* Configures response headers
* @params headers {string} an object containing desired headers
*
*/
async configureResponseHeaders(headers: Record<string, string>) {
await this.configureHeaders('syntheticsResponseHeaders', headers);
},
/**
*
* Configures headers
* @params testSubj {string} test subj
* @params headers {string} an object containing desired headers
*
*/
async configureHeaders(testSubj: string, headers: Record<string, string>) {
const headersContainer = await testSubjects.find(testSubj);
const addHeaderButton = await testSubjects.find(`${testSubj}__button`);
const keys = Object.keys(headers);
await Promise.all(
keys.map(async (key, index) => {
await addHeaderButton.click();
const keyField = await headersContainer.findByCssSelector(
`[data-test-subj="keyValuePairsKey${index}"]`
);
const valueField = await headersContainer.findByCssSelector(
`[data-test-subj="keyValuePairsValue${index}"]`
);
await this.fillTextInput(keyField, key);
await this.fillTextInput(valueField, headers[key]);
})
);
},
/**
*
* Configures request body
* @params contentType {string} contentType of the request body
* @params value {string} value of the request body
*
*/
async configureRequestBody(testSubj: string, value: string) {
await testSubjects.click(`syntheticsRequestBodyTab__${testSubj}`);
await this.fillCodeEditor(value);
},
/**
*
* Fills the monaco code editor
* @params value {string} value of code input
*
*/
async fillCodeEditor(value: string) {
const codeEditorContainer = await testSubjects.find('codeEditorContainer');
const textArea = await codeEditorContainer.findByCssSelector('textarea');
await textArea.clearValue();
await textArea.type(value);
},
/**
* Creates basic common monitor details
* @params name {string} the name of the monitor
* @params url {string} the url of the monitor
*
*/
async createBasicMonitorDetails({ name, apmServiceName, tags }: Record<string, string>) {
await this.fillTextInputByTestSubj('packagePolicyNameInput', name);
await this.fillTextInputByTestSubj('syntheticsAPMServiceName', apmServiceName);
await this.setComboBox('syntheticsTags', tags);
},
/**
* Fills in the fields to create a basic HTTP monitor
* @params name {string} the name of the monitor
* @params url {string} the url of the monitor
*
*/
async createBasicHTTPMonitorDetails({
name,
url,
apmServiceName,
tags,
}: Record<string, string>) {
await this.createBasicMonitorDetails({ name, apmServiceName, tags });
await this.fillTextInputByTestSubj('syntheticsUrlField', url);
},
/**
* Fills in the fields to create a basic TCP monitor
* @params name {string} the name of the monitor
* @params host {string} the host (and port) of the monitor
*
*/
async createBasicTCPMonitorDetails({
name,
host,
apmServiceName,
tags,
}: Record<string, string>) {
await this.selectMonitorType('tcp');
await this.createBasicMonitorDetails({ name, apmServiceName, tags });
await this.fillTextInputByTestSubj('syntheticsTCPHostField', host);
},
/**
* Creates a basic ICMP monitor
* @params name {string} the name of the monitor
* @params host {string} the host of the monitor
*/
async createBasicICMPMonitorDetails({
name,
host,
apmServiceName,
tags,
}: Record<string, string>) {
await this.selectMonitorType('icmp');
await this.fillTextInputByTestSubj('packagePolicyNameInput', name);
await this.createBasicMonitorDetails({ name, apmServiceName, tags });
await this.fillTextInputByTestSubj('syntheticsICMPHostField', host);
},
/**
* Creates a basic browser monitor
* @params name {string} the name of the monitor
* @params zipUrl {string} the zip url of the synthetics suites
*/
async createBasicBrowserMonitorDetails(
{
name,
inlineScript,
zipUrl,
folder,
params,
username,
password,
apmServiceName,
tags,
}: Record<string, string>,
isInline: boolean = false
) {
await this.selectMonitorType('browser');
await this.fillTextInputByTestSubj('packagePolicyNameInput', name);
await this.createBasicMonitorDetails({ name, apmServiceName, tags });
if (isInline) {
await testSubjects.click('syntheticsSourceTab__inline');
await this.fillCodeEditor(inlineScript);
return;
} else {
await testSubjects.click('syntheticsSourceTab__zipUrl');
}
await this.fillTextInputByTestSubj('syntheticsBrowserZipUrl', zipUrl);
await this.fillTextInputByTestSubj('syntheticsBrowserZipUrlFolder', folder);
await this.fillTextInputByTestSubj('syntheticsBrowserZipUrlUsername', username);
await this.fillTextInputByTestSubj('syntheticsBrowserZipUrlPassword', password);
await this.fillCodeEditor(params);
},
/**
* Enables TLS
*/
async enableTLS() {
const tlsSwitch = await this.findEnableTLSSwitch();
await tlsSwitch.click();
},
/**
* Configures TLS settings
* @params verificationMode {string} the name of the monitor
*/
async configureTLSOptions({
verificationMode,
ca,
cert,
certKey,
certKeyPassphrase,
}: Record<string, string>) {
await this.enableTLS();
await testSubjects.selectValue('syntheticsTLSVerificationMode', verificationMode);
await this.fillTextInputByTestSubj('syntheticsTLSCA', ca);
await this.fillTextInputByTestSubj('syntheticsTLSCert', cert);
await this.fillTextInputByTestSubj('syntheticsTLSCertKey', certKey);
await this.fillTextInputByTestSubj('syntheticsTLSCertKeyPassphrase', certKeyPassphrase);
},
/**
* Configure http advanced settings
*/
async configureHTTPAdvancedOptions({
username,
password,
proxyUrl,
requestMethod,
requestHeaders,
responseStatusCheck,
responseBodyCheckPositive,
responseBodyCheckNegative,
requestBody,
responseHeaders,
indexResponseBody,
indexResponseHeaders,
}: {
username: string;
password: string;
proxyUrl: string;
requestMethod: string;
responseStatusCheck: string;
responseBodyCheckPositive: string;
responseBodyCheckNegative: string;
requestBody: { value: string; type: string };
requestHeaders: Record<string, string>;
responseHeaders: Record<string, string>;
indexResponseBody: boolean;
indexResponseHeaders: boolean;
}) {
await testSubjects.click('syntheticsHTTPAdvancedFieldsAccordion');
await this.configureResponseHeaders(responseHeaders);
await this.configureRequestHeaders(requestHeaders);
await this.configureRequestBody(requestBody.type, requestBody.value);
await this.configureUsernameAndPassword({ username, password });
await this.setComboBox('syntheticsResponseStatusCheck', responseStatusCheck);
await this.setComboBox('syntheticsResponseBodyCheckPositive', responseBodyCheckPositive);
await this.setComboBox('syntheticsResponseBodyCheckNegative', responseBodyCheckNegative);
await this.fillTextInputByTestSubj('syntheticsProxyUrl', proxyUrl);
await testSubjects.selectValue('syntheticsRequestMethod', requestMethod);
if (!indexResponseBody) {
const field = await testSubjects.find('syntheticsIndexResponseBody');
const label = await field.findByCssSelector('label');
await label.click();
}
if (!indexResponseHeaders) {
const field = await testSubjects.find('syntheticsIndexResponseHeaders');
const label = await field.findByCssSelector('label');
await label.click();
}
},
/**
* Configure tcp advanced settings
*/
async configureTCPAdvancedOptions({
proxyUrl,
requestSendCheck,
responseReceiveCheck,
proxyUseLocalResolver,
}: {
proxyUrl: string;
requestSendCheck: string;
responseReceiveCheck: string;
proxyUseLocalResolver: boolean;
}) {
await testSubjects.click('syntheticsTCPAdvancedFieldsAccordion');
await this.fillTextInputByTestSubj('syntheticsProxyUrl', proxyUrl);
await this.fillTextInputByTestSubj('syntheticsTCPRequestSendCheck', requestSendCheck);
await this.fillTextInputByTestSubj('syntheticsTCPResponseReceiveCheck', responseReceiveCheck);
if (proxyUseLocalResolver) {
const field = await testSubjects.find('syntheticsUseLocalResolver');
const label = await field.findByCssSelector('label');
await label.click();
}
},
/**
* Configure browser advanced settings
* @params name {string} the name of the monitor
* @params zipUrl {string} the zip url of the synthetics suites
*/
async configureBrowserAdvancedOptions({
screenshots,
syntheticsArgs,
isThrottlingEnabled,
downloadSpeed,
uploadSpeed,
latency,
}: {
screenshots: string;
syntheticsArgs: string;
isThrottlingEnabled: boolean;
downloadSpeed: string;
uploadSpeed: string;
latency: string;
}) {
await testSubjects.click('syntheticsBrowserAdvancedFieldsAccordion');
const throttleSwitch = await this.findThrottleSwitch();
if (!isThrottlingEnabled) {
await throttleSwitch.click();
}
await testSubjects.selectValue('syntheticsBrowserScreenshots', screenshots);
await this.setComboBox('syntheticsBrowserSyntheticsArgs', syntheticsArgs);
if (isThrottlingEnabled) {
await this.fillTextInputByTestSubj('syntheticsBrowserDownloadSpeed', downloadSpeed);
await this.fillTextInputByTestSubj('syntheticsBrowserUploadSpeed', uploadSpeed);
await this.fillTextInputByTestSubj('syntheticsBrowserLatency', latency);
}
},
};
}

View file

@ -1,19 +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 { services as kibanaFunctionalServices } from '../../../../test/functional/services';
import { services as commonServices } from '../../common/services';
import { UptimeProvider } from './uptime';
// define the name and providers for services that should be
// available to your tests. If you don't specify anything here
// only the built-in services will be available
export const services = {
...kibanaFunctionalServices,
...commonServices,
uptime: UptimeProvider,
};

View file

@ -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 { UptimeProvider } from './uptime';

View file

@ -1,176 +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 {
PACKAGE_POLICY_SAVED_OBJECT_TYPE,
DeletePackagePoliciesRequest,
GetPackagePoliciesResponse,
GetFullAgentPolicyResponse,
GetPackagesResponse,
GetAgentPoliciesResponse,
} from '@kbn/fleet-plugin/common';
import { FtrProviderContext } from '../../ftr_provider_context';
const INGEST_API_ROOT = '/api/fleet';
const INGEST_API_AGENT_POLICIES = `${INGEST_API_ROOT}/agent_policies`;
const INGEST_API_PACKAGE_POLICIES = `${INGEST_API_ROOT}/package_policies`;
const INGEST_API_PACKAGE_POLICIES_DELETE = `${INGEST_API_PACKAGE_POLICIES}/delete`;
const INGEST_API_EPM_PACKAGES = `${INGEST_API_ROOT}/epm/packages`;
export function SyntheticsPackageProvider({ getService }: FtrProviderContext) {
const supertest = getService('supertest');
const log = getService('log');
const retry = getService('retry');
const logSupertestApiErrorAndThrow = (message: string, error: any): never => {
const responseBody = error?.response?.body;
const responseText = error?.response?.text;
log.error(`Error occurred at ${Date.now()} | ${new Date().toISOString()}`);
log.error(JSON.stringify(responseBody || responseText, null, 2));
log.error(error);
throw new Error(message);
};
const retrieveSyntheticsPackageInfo = (() => {
// Retrieve information about the Synthetics package
// EPM does not currently have an API to get the "lastest" information for a page given its name,
// so we'll retrieve a list of packages and then find the package info in the list.
let apiRequest: Promise<GetPackagesResponse['items'][0] | undefined>;
return () => {
if (!apiRequest) {
log.info(`Setting up call to retrieve Synthetics package`);
// Currently (as of 2020-june) the package registry used in CI is the public one and
// at times it encounters network connection issues. We use `retry.try` below to see if
// subsequent requests get through.
apiRequest = retry.try(() => {
return supertest
.get(INGEST_API_EPM_PACKAGES)
.query({ prerelease: true })
.set('kbn-xsrf', 'xxx')
.expect(200)
.catch((error) => {
return logSupertestApiErrorAndThrow(`Unable to retrieve packages via Ingest!`, error);
})
.then((response: { body: GetPackagesResponse }) => {
const { body } = response;
const syntheticsPackageInfo = body.items.find(
(epmPackage) => epmPackage.name === 'synthetics'
);
if (!syntheticsPackageInfo) {
throw new Error(
`Synthetics package was not in response from ${INGEST_API_EPM_PACKAGES}`
);
}
return Promise.resolve(syntheticsPackageInfo);
});
});
} else {
log.info('Using cached retrieval of synthetics package');
}
return apiRequest;
};
})();
return {
/**
* Returns the synthetics package version for the currently installed package. This version can then
* be used to build URLs for Fleet pages or APIs
*/
async getSyntheticsPackageVersion() {
const syntheticsPackage = await retrieveSyntheticsPackageInfo()!;
return syntheticsPackage?.version;
},
/**
* Retrieves the full Agent policy by id, which mirrors what the Elastic Agent would get
* once they checkin.
*/
async getFullAgentPolicy(agentPolicyId: string): Promise<GetFullAgentPolicyResponse['item']> {
let fullAgentPolicy: GetFullAgentPolicyResponse['item'];
try {
const apiResponse: { body: GetFullAgentPolicyResponse } = await supertest
.get(`${INGEST_API_AGENT_POLICIES}/${agentPolicyId}/full`)
.expect(200);
fullAgentPolicy = apiResponse.body.item;
} catch (error) {
return logSupertestApiErrorAndThrow('Unable to get full Agent policy', error);
}
return fullAgentPolicy!;
},
/**
* Retrieves all the agent policies.
*/
async getAgentPolicyList(): Promise<GetAgentPoliciesResponse['items']> {
let agentPolicyList: GetAgentPoliciesResponse['items'];
try {
const apiResponse: { body: GetAgentPoliciesResponse } = await supertest
.get(INGEST_API_AGENT_POLICIES)
.expect(200);
agentPolicyList = apiResponse.body.items;
} catch (error) {
return logSupertestApiErrorAndThrow('Unable to get full Agent policy list', error);
}
return agentPolicyList!;
},
/**
* Deletes a policy (Package Policy) by using the policy name
* @param name
*/
async deletePolicyByName(name: string) {
const id = await this.getPackagePolicyIdByName(name);
if (id) {
try {
const deletePackagePolicyData: DeletePackagePoliciesRequest['body'] = {
packagePolicyIds: [id],
};
await supertest
.post(INGEST_API_PACKAGE_POLICIES_DELETE)
.set('kbn-xsrf', 'xxx')
.send(deletePackagePolicyData)
.expect(200);
} catch (error) {
logSupertestApiErrorAndThrow(
`Unable to delete Package Policy via Ingest! ${name}`,
error
);
}
}
},
/**
* Gets the policy id (Package Policy) by using the policy name
* @param name
*/
async getPackagePolicyIdByName(name: string) {
const { body: packagePoliciesResponse }: { body: GetPackagePoliciesResponse } =
await supertest
.get(INGEST_API_PACKAGE_POLICIES)
.set('kbn-xsrf', 'xxx')
.query({ kuery: `${PACKAGE_POLICY_SAVED_OBJECT_TYPE}.name: ${name}` })
.send()
.expect(200);
const packagePolicyList: GetPackagePoliciesResponse['items'] = packagePoliciesResponse.items;
if (packagePolicyList.length > 1) {
throw new Error(`Found ${packagePolicyList.length} Policies - was expecting only one!`);
}
if (packagePolicyList.length) {
return packagePolicyList[0].id;
}
},
};
}

View file

@ -1,18 +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 { FtrProviderContext } from '../../ftr_provider_context';
import { SyntheticsPackageProvider } from './synthetics_package';
export function UptimeProvider(context: FtrProviderContext) {
const syntheticsPackage = SyntheticsPackageProvider(context);
return {
syntheticsPackage,
};
}