diff --git a/.buildkite/scripts/steps/artifacts/docker_image.sh b/.buildkite/scripts/steps/artifacts/docker_image.sh index 1744a02fe4243..671704b6c50e8 100755 --- a/.buildkite/scripts/steps/artifacts/docker_image.sh +++ b/.buildkite/scripts/steps/artifacts/docker_image.sh @@ -76,19 +76,19 @@ buildkite-agent artifact upload "kibana-$BASE_VERSION-docker-image-aarch64.tar.g buildkite-agent artifact upload "dependencies-$GIT_ABBREV_COMMIT.csv" cd - +# This part is related with updating the configuration of kibana-controller, +# so that new stack instances contain the latest and greatest image of kibana, +# and the respective stack components of course. echo "--- Trigger image tag update" if [[ "$BUILDKITE_BRANCH" == "$KIBANA_BASE_BRANCH" ]]; then - cat << EOF | buildkite-agent pipeline upload steps: - - trigger: k8s-gitops-update-image-tag + - trigger: serverless-gitops-update-stack-image-tag async: true label: ":argo: Update image tag for Kibana" branches: main build: env: - MODE: sed - TARGET_FILE: kibana-controller.yaml IMAGE_TAG: "git-$GIT_ABBREV_COMMIT" SERVICE: kibana-controller NAMESPACE: kibana-ci diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index c212a02c5391b..a197ebedfd30a 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -459,6 +459,7 @@ x-pack/packages/ml/local_storage @elastic/ml-ui x-pack/packages/ml/nested_property @elastic/ml-ui x-pack/plugins/ml @elastic/ml-ui x-pack/packages/ml/query_utils @elastic/ml-ui +x-pack/packages/ml/random_sampler_utils @elastic/ml-ui x-pack/packages/ml/route_utils @elastic/ml-ui x-pack/packages/ml/string_hash @elastic/ml-ui x-pack/packages/ml/trained_models_utils @elastic/ml-ui diff --git a/docs/settings/apm-settings.asciidoc b/docs/settings/apm-settings.asciidoc index c503b4cdcbf9c..50d0fa2032ac3 100644 --- a/docs/settings/apm-settings.asciidoc +++ b/docs/settings/apm-settings.asciidoc @@ -101,4 +101,7 @@ Matcher for all source map indices. Defaults to `apm-*`. `xpack.apm.autoCreateApmDataView` {ess-icon}:: Set to `false` to disable the automatic creation of the APM data view when the APM app is opened. Defaults to `true`. +`xpack.apm.latestAgentVersionsUrl` {ess-icon}:: +Specifies the URL of a self hosted file that contains latest agent versions. Defaults to `https://apm-agent-versions.elastic.co/versions.json`. Set to `''` to disable requesting latest agent versions. + // end::general-apm-settings[] diff --git a/package.json b/package.json index 93f2848a94c69..9423330117567 100644 --- a/package.json +++ b/package.json @@ -474,6 +474,7 @@ "@kbn/ml-nested-property": "link:x-pack/packages/ml/nested_property", "@kbn/ml-plugin": "link:x-pack/plugins/ml", "@kbn/ml-query-utils": "link:x-pack/packages/ml/query_utils", + "@kbn/ml-random-sampler-utils": "link:x-pack/packages/ml/random_sampler_utils", "@kbn/ml-route-utils": "link:x-pack/packages/ml/route_utils", "@kbn/ml-string-hash": "link:x-pack/packages/ml/string_hash", "@kbn/ml-trained-models-utils": "link:x-pack/packages/ml/trained_models_utils", diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/kibana_migrator.test.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/kibana_migrator.test.ts index c86001c2d202d..cd34f5848ccbb 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/kibana_migrator.test.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/kibana_migrator.test.ts @@ -20,17 +20,13 @@ import { docLinksServiceMock } from '@kbn/core-doc-links-server-mocks'; import { lastValueFrom } from 'rxjs'; import { runResilientMigrator } from './run_resilient_migrator'; -jest.mock('./run_resilient_migrator', () => ({ - runResilientMigrator: jest.fn((options) => { - return { - status: 'migrated', - // TODO check the actual value of the MigrationResult for these fields - destIndex: `${options.indexPrefix}_8.2.3_001`, - sourceIndex: `${options.indexPrefix}_8.2.2_001`, - elapsedMs: 28, - }; - }), -})); +jest.mock('./run_resilient_migrator', () => { + const actual = jest.requireActual('./run_resilient_migrator'); + + return { + runResilientMigrator: jest.fn(actual.runResilientMigrator), + }; +}); jest.mock('./document_migrator', () => { return { @@ -89,7 +85,7 @@ describe('KibanaMigrator', () => { }, { name: 'bmap', - indexPattern: 'other-index', + indexPattern: '.other-index', mappings: { properties: { field: { type: 'text' } }, }, @@ -134,8 +130,22 @@ describe('KibanaMigrator', () => { ); }); - // eslint-disable-next-line jest/no-focused-tests - fit('only runs migrations once if called multiple times', async () => { + it('only runs migrations once if called multiple times', async () => { + const successfulRun: typeof runResilientMigrator = ({ indexPrefix }) => + Promise.resolve({ + sourceIndex: indexPrefix, + destIndex: indexPrefix, + elapsedMs: 28, + status: 'migrated', + }); + const mockRunResilientMigrator = runResilientMigrator as jest.MockedFunction< + typeof runResilientMigrator + >; + + mockRunResilientMigrator.mockImplementationOnce(successfulRun); + mockRunResilientMigrator.mockImplementationOnce(successfulRun); + mockRunResilientMigrator.mockImplementationOnce(successfulRun); + mockRunResilientMigrator.mockImplementationOnce(successfulRun); const options = mockOptions(); options.client.indices.get.mockResponse({}, { statusCode: 200 }); options.client.indices.getMapping.mockResponse(mappingsResponseWithoutIndexTypesMap, { @@ -157,7 +167,35 @@ describe('KibanaMigrator', () => { await migrator.runMigrations(); // indices.get is called twice during a single migration - expect(options.client.indices.get).toHaveBeenCalledTimes(2); + expect(runResilientMigrator).toHaveBeenCalledTimes(4); + expect(runResilientMigrator).toHaveBeenNthCalledWith( + 1, + expect.objectContaining({ + indexPrefix: '.my-index', + mustRelocateDocuments: true, + }) + ); + expect(runResilientMigrator).toHaveBeenNthCalledWith( + 2, + expect.objectContaining({ + indexPrefix: '.other-index', + mustRelocateDocuments: true, + }) + ); + expect(runResilientMigrator).toHaveBeenNthCalledWith( + 3, + expect.objectContaining({ + indexPrefix: '.my-task-index', + mustRelocateDocuments: false, + }) + ); + expect(runResilientMigrator).toHaveBeenNthCalledWith( + 4, + expect.objectContaining({ + indexPrefix: '.my-complementary-index', + mustRelocateDocuments: true, + }) + ); }); it('emits results on getMigratorResult$()', async () => { @@ -179,12 +217,12 @@ describe('KibanaMigrator', () => { status: 'migrated', }); expect(result![1]).toMatchObject({ - destIndex: 'other-index_8.2.3_001', + destIndex: '.other-index_8.2.3_001', elapsedMs: expect.any(Number), status: 'patched', }); }); - it('rejects when the migration state machine terminates in a FATAL state', () => { + it('rejects when the migration state machine terminates in a FATAL state', async () => { const options = mockV2MigrationOptions(); options.client.indices.get.mockResponse( { @@ -244,34 +282,37 @@ describe('KibanaMigrator', () => { const migrator = new KibanaMigrator(options); migrator.prepareMigrations(); const results = await migrator.runMigrations(); - expect(results).toMatchInlineSnapshot(` - Array [ - Object { - "destIndex": ".my-index_8.2.3_001", - "elapsedMs": 28, - "sourceIndex": ".my-index_8.2.2_001", - "status": "migrated", - }, - Object { - "destIndex": ".other-index_8.2.3_001", - "elapsedMs": 28, - "sourceIndex": ".other-index_8.2.2_001", - "status": "migrated", - }, - Object { - "destIndex": ".my-task-index_8.2.3_001", - "elapsedMs": 28, - "sourceIndex": ".my-task-index_8.2.2_001", - "status": "migrated", - }, - Object { - "destIndex": ".my-complementary-index_8.2.3_001", - "elapsedMs": 28, - "sourceIndex": ".my-complementary-index_8.2.2_001", - "status": "migrated", - }, - ] - `); + + expect(results.length).toEqual(4); + expect(results[0]).toEqual( + expect.objectContaining({ + sourceIndex: '.my-index_pre8.2.3_001', + destIndex: '.my-index_8.2.3_001', + elapsedMs: expect.any(Number), + status: 'migrated', + }) + ); + expect(results[1]).toEqual( + expect.objectContaining({ + destIndex: '.other-index_8.2.3_001', + elapsedMs: expect.any(Number), + status: 'patched', + }) + ); + expect(results[2]).toEqual( + expect.objectContaining({ + destIndex: '.my-task-index_8.2.3_001', + elapsedMs: expect.any(Number), + status: 'patched', + }) + ); + expect(results[3]).toEqual( + expect.objectContaining({ + destIndex: '.my-complementary-index_8.2.3_001', + elapsedMs: expect.any(Number), + status: 'patched', + }) + ); expect(runResilientMigrator).toHaveBeenCalledTimes(4); expect(runResilientMigrator).toHaveBeenNthCalledWith( @@ -472,7 +513,7 @@ const mockOptions = () => { name: 'testtype2', hidden: false, namespaceType: 'single', - // We are moving 'testtype2' from '.my-index' to '.my-other-index' + // We are moving 'testtype2' from '.my-index' to '.other-index' indexPattern: '.other-index', mappings: { properties: { diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/kibana_migrator.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/kibana_migrator.ts index 579133d721306..d9b9f19b785e4 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/kibana_migrator.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/kibana_migrator.ts @@ -142,7 +142,7 @@ export class KibanaMigrator implements IKibanaMigrator { private async runMigrationsInternal(): Promise { const migrationAlgorithm = this.soMigrationsConfig.algorithm; if (migrationAlgorithm === 'zdt') { - return this.runMigrationZdt(); + return await this.runMigrationZdt(); } else { return await this.runMigrationV2(); } diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/helpers.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/helpers.ts index 93748800fae04..c8d8884c980cd 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/helpers.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/helpers.ts @@ -65,6 +65,7 @@ export function mergeMigrationMappingPropertyHashes( return { ...targetMappings, _meta: { + ...targetMappings._meta, migrationMappingPropertyHashes: { ...indexMappings._meta?.migrationMappingPropertyHashes, ...targetMappings._meta?.migrationMappingPropertyHashes, diff --git a/packages/kbn-apm-synthtrace-client/src/lib/apm/apm_fields.ts b/packages/kbn-apm-synthtrace-client/src/lib/apm/apm_fields.ts index df780671d35ce..808894dab55ef 100644 --- a/packages/kbn-apm-synthtrace-client/src/lib/apm/apm_fields.ts +++ b/packages/kbn-apm-synthtrace-client/src/lib/apm/apm_fields.ts @@ -141,6 +141,8 @@ export type ApmFields = Fields<{ values: number[]; counts: number[]; }; + 'transaction.result': string; + 'transaction.sampled': boolean; 'service.environment': string; 'service.framework.name': string; 'service.framework.version': string; @@ -164,8 +166,6 @@ export type ApmFields = Fields<{ 'span.self_time.sum.us': number; 'span.subtype': string; 'span.type': string; - 'transaction.result': string; - 'transaction.sampled': true; 'span.links': Array<{ trace: { id: string }; span: { id: string }; diff --git a/packages/kbn-apm-synthtrace-client/src/lib/apm/transaction.ts b/packages/kbn-apm-synthtrace-client/src/lib/apm/transaction.ts index 13b4cf44fb5b6..55883957c6990 100644 --- a/packages/kbn-apm-synthtrace-client/src/lib/apm/transaction.ts +++ b/packages/kbn-apm-synthtrace-client/src/lib/apm/transaction.ts @@ -32,6 +32,7 @@ export class Transaction extends BaseSpan { error.fields['trace.id'] = this.fields['trace.id']; error.fields['transaction.id'] = this.fields['transaction.id']; error.fields['transaction.type'] = this.fields['transaction.type']; + error.fields['transaction.sampled'] = this.fields['transaction.sampled']; }); return this; @@ -43,6 +44,7 @@ export class Transaction extends BaseSpan { error.fields['transaction.id'] = this.fields['transaction.id']; error.fields['transaction.name'] = this.fields['transaction.name']; error.fields['transaction.type'] = this.fields['transaction.type']; + error.fields['transaction.sampled'] = this.fields['transaction.sampled']; }); this._errors.push(...errors); @@ -56,7 +58,10 @@ export class Transaction extends BaseSpan { } sample(sampled: boolean = true) { - this._sampled = sampled; + this._sampled = this.fields['transaction.sampled'] = sampled; + this._errors.forEach((error) => { + error.fields['transaction.sampled'] = sampled; + }); return this; } diff --git a/packages/kbn-doc-links/src/get_doc_links.ts b/packages/kbn-doc-links/src/get_doc_links.ts index 4b2e6d29b219e..4a9a8cb6af371 100644 --- a/packages/kbn-doc-links/src/get_doc_links.ts +++ b/packages/kbn-doc-links/src/get_doc_links.ts @@ -137,6 +137,7 @@ export const getDocLinks = ({ kibanaBranch }: GetDocLinkOptions): DocLinks => { crawlerOverview: `${ENTERPRISE_SEARCH_DOCS}crawler.html`, deployTrainedModels: `${MACHINE_LEARNING_DOCS}ml-nlp-deploy-models.html`, documentLevelSecurity: `${ELASTICSEARCH_DOCS}document-level-security.html`, + elser: `${MACHINE_LEARNING_DOCS}ml-nlp-elser.html`, engines: `${ENTERPRISE_SEARCH_DOCS}engines.html`, ingestPipelines: `${ENTERPRISE_SEARCH_DOCS}ingest-pipelines.html`, languageAnalyzers: `${ELASTICSEARCH_DOCS}analysis-lang-analyzer.html`, diff --git a/packages/kbn-doc-links/src/types.ts b/packages/kbn-doc-links/src/types.ts index 32ca0e8d6f2c1..86e28d1adacae 100644 --- a/packages/kbn-doc-links/src/types.ts +++ b/packages/kbn-doc-links/src/types.ts @@ -122,6 +122,7 @@ export interface DocLinks { readonly crawlerOverview: string; readonly deployTrainedModels: string; readonly documentLevelSecurity: string; + readonly elser: string; readonly engines: string; readonly ingestPipelines: string; readonly languageAnalyzers: string; diff --git a/packages/kbn-slo-schema/src/rest_specs/slo.ts b/packages/kbn-slo-schema/src/rest_specs/slo.ts index 6d1ef3692f5ad..0668af1e69576 100644 --- a/packages/kbn-slo-schema/src/rest_specs/slo.ts +++ b/packages/kbn-slo-schema/src/rest_specs/slo.ts @@ -54,7 +54,7 @@ const getSLOParamsSchema = t.type({ }); const sortDirectionSchema = t.union([t.literal('asc'), t.literal('desc')]); -const sortBySchema = t.union([t.literal('name'), t.literal('indicatorType')]); +const sortBySchema = t.union([t.literal('creationTime'), t.literal('indicatorType')]); const findSLOParamsSchema = t.partial({ query: t.partial({ diff --git a/packages/kbn-slo-schema/src/schema/indicators.ts b/packages/kbn-slo-schema/src/schema/indicators.ts index 95003b00bb3e0..4581faab99ef3 100644 --- a/packages/kbn-slo-schema/src/schema/indicators.ts +++ b/packages/kbn-slo-schema/src/schema/indicators.ts @@ -50,17 +50,13 @@ const apmTransactionErrorRateIndicatorSchema = t.type({ const kqlCustomIndicatorTypeSchema = t.literal('sli.kql.custom'); const kqlCustomIndicatorSchema = t.type({ type: kqlCustomIndicatorTypeSchema, - params: t.intersection([ - t.type({ - index: t.string, - filter: t.string, - good: t.string, - total: t.string, - }), - t.partial({ - timestampField: t.string, - }), - ]), + params: t.type({ + index: t.string, + filter: t.string, + good: t.string, + total: t.string, + timestampField: t.string, + }), }); const indicatorDataSchema = t.type({ diff --git a/src/core/server/integration_tests/saved_objects/migrations/group2/check_registered_types.test.ts b/src/core/server/integration_tests/saved_objects/migrations/group2/check_registered_types.test.ts index 2d1e44557b4f7..f193e1a88b97f 100644 --- a/src/core/server/integration_tests/saved_objects/migrations/group2/check_registered_types.test.ts +++ b/src/core/server/integration_tests/saved_objects/migrations/group2/check_registered_types.test.ts @@ -57,7 +57,7 @@ describe('checking migration metadata changes on all registered SO types', () => Object { "action": "6cfc277ed3211639e37546ac625f4a68f2494215", "action_task_params": "5f419caba96dd8c77d0f94013e71d43890e3d5d6", - "alert": "1e4cd6941f1eb39c729c646e91fbfb9700de84b9", + "alert": "7bec97d7775a025ecf36a33baf17386b9e7b4c3c", "api_key_pending_invalidation": "16e7bcf8e78764102d7f525542d5b616809a21ee", "apm-indices": "d19dd7fb51f2d2cbc1f8769481721e0953f9a6d2", "apm-server-schema": "1d42f17eff9ec6c16d3a9324d9539e2d123d0a9a", diff --git a/src/core/server/integration_tests/saved_objects/migrations/group3/split_kibana_index.test.ts b/src/core/server/integration_tests/saved_objects/migrations/group3/split_kibana_index.test.ts index 213b07af255e2..a75dcb30f1dda 100644 --- a/src/core/server/integration_tests/saved_objects/migrations/group3/split_kibana_index.test.ts +++ b/src/core/server/integration_tests/saved_objects/migrations/group3/split_kibana_index.test.ts @@ -9,7 +9,6 @@ import Path from 'path'; import type { TestElasticsearchUtils } from '@kbn/core-test-helpers-kbn-server'; import type { ISavedObjectTypeRegistry, SavedObjectsType } from '@kbn/core-saved-objects-server'; -import type { MigrationResult } from '@kbn/core-saved-objects-base-server-internal'; import { readLog, startElasticsearch, @@ -19,6 +18,7 @@ import { clearLog, getAggregatedTypesCount, currentVersion, + type KibanaMigratorTestKit, } from '../kibana_migrator_test_kit'; import { delay } from '../test_utils'; @@ -32,8 +32,8 @@ const RELOCATE_TYPES: Record = { describe('split .kibana index into multiple system indices', () => { let esServer: TestElasticsearchUtils['es']; - let runMigrations: (rerun?: boolean | undefined) => Promise; let typeRegistry: ISavedObjectTypeRegistry; + let migratorTestKitFactory: () => Promise; beforeAll(async () => { typeRegistry = await getCurrentVersionTypeRegistry({ oss: false }); @@ -59,11 +59,13 @@ describe('split .kibana index into multiple system indices', () => { } ); - const { client, runMigrations: runner } = await getKibanaMigratorTestKit({ - types: updatedTypeRegistry.getAllTypes(), - kibanaIndex: '.kibana', - }); - runMigrations = runner; + migratorTestKitFactory = () => + getKibanaMigratorTestKit({ + types: updatedTypeRegistry.getAllTypes(), + kibanaIndex: '.kibana', + }); + + const { runMigrations, client } = await migratorTestKitFactory(); // count of types in the legacy index expect(await getAggregatedTypesCount(client, '.kibana_1')).toEqual({ @@ -82,7 +84,9 @@ describe('split .kibana index into multiple system indices', () => { await runMigrations(); - await client.indices.refresh({ index: ['.kibana', '.kibana_so_search', '.kibana_so_ui'] }); + await client.indices.refresh({ + index: ['.kibana', '.kibana_so_search', '.kibana_so_ui'], + }); expect(await getAggregatedTypesCount(client, '.kibana')).toEqual({ 'index-pattern': 3, @@ -359,8 +363,9 @@ describe('split .kibana index into multiple system indices', () => { afterEach(async () => { // we run the migrator again to ensure that the next time state is loaded everything still works as expected + const { runMigrations } = await migratorTestKitFactory(); await clearLog(); - await runMigrations(true); + await runMigrations(); const logs = await readLog(); expect(logs).not.toMatch('REINDEX'); diff --git a/src/core/server/integration_tests/saved_objects/migrations/kibana_migrator_test_kit.ts b/src/core/server/integration_tests/saved_objects/migrations/kibana_migrator_test_kit.ts index 2f57c3b9dc950..22e3fe218a495 100644 --- a/src/core/server/integration_tests/saved_objects/migrations/kibana_migrator_test_kit.ts +++ b/src/core/server/integration_tests/saved_objects/migrations/kibana_migrator_test_kit.ts @@ -72,7 +72,7 @@ export interface KibanaMigratorTestKitParams { export interface KibanaMigratorTestKit { client: ElasticsearchClient; migrator: IKibanaMigrator; - runMigrations: (rerun?: boolean) => Promise; + runMigrations: () => Promise; typeRegistry: ISavedObjectTypeRegistry; savedObjectsRepository: ISavedObjectsRepository; } diff --git a/src/plugins/dashboard/public/dashboard_app/top_nav/controls_toolbar_button/controls_toolbar_button.tsx b/src/plugins/dashboard/public/dashboard_app/top_nav/controls_toolbar_button/controls_toolbar_button.tsx index 1262ece75654a..22fd451e48a8d 100644 --- a/src/plugins/dashboard/public/dashboard_app/top_nav/controls_toolbar_button/controls_toolbar_button.tsx +++ b/src/plugins/dashboard/public/dashboard_app/top_nav/controls_toolbar_button/controls_toolbar_button.tsx @@ -8,7 +8,7 @@ import React from 'react'; -import { EuiContextMenuPanel } from '@elastic/eui'; +import { EuiContextMenuPanel, useEuiTheme } from '@elastic/eui'; import { ToolbarPopover } from '@kbn/shared-ux-button-toolbar'; import type { ControlGroupContainer } from '@kbn/controls-plugin/public'; @@ -18,11 +18,15 @@ import { AddTimeSliderControlButton } from './add_time_slider_control_button'; import { EditControlGroupButton } from './edit_control_group_button'; export function ControlsToolbarButton({ controlGroup }: { controlGroup: ControlGroupContainer }) { + const { euiTheme } = useEuiTheme(); + return ( {({ closePopover }: { closePopover: () => void }) => ( diff --git a/src/plugins/dashboard/public/dashboard_app/top_nav/editor_menu.tsx b/src/plugins/dashboard/public/dashboard_app/top_nav/editor_menu.tsx index 46ab6214e7ffd..56fbf8e3bba39 100644 --- a/src/plugins/dashboard/public/dashboard_app/top_nav/editor_menu.tsx +++ b/src/plugins/dashboard/public/dashboard_app/top_nav/editor_menu.tsx @@ -14,6 +14,7 @@ import { EuiContextMenuPanelItemDescriptor, EuiFlexGroup, EuiFlexItem, + useEuiTheme, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { ToolbarPopover } from '@kbn/shared-ux-button-toolbar'; @@ -53,6 +54,8 @@ export const EditorMenu = ({ createNewVisType, createNewEmbeddable }: Props) => }, } = pluginServices.getServices(); + const { euiTheme } = useEuiTheme(); + const embeddableFactories = useMemo( () => Array.from(embeddable.getEmbeddableFactories()), [embeddable] @@ -262,6 +265,8 @@ export const EditorMenu = ({ createNewVisType, createNewEmbeddable }: Props) => }; return ( + wrapSearchBarInContext({ + filtersForSuggestions: [ + buildExistsFilter({ type: 'keyword', name: 'geo.src' }, { + id: undefined, + } as unknown as DataViewBase), + ], + } as unknown as SearchBarProps) + ) .add('with only the filter bar on', () => wrapSearchBarInContext({ showDatePicker: false, diff --git a/src/plugins/unified_search/public/filter_bar/filter_bar.tsx b/src/plugins/unified_search/public/filter_bar/filter_bar.tsx index 86630283443e8..312ca40496628 100644 --- a/src/plugins/unified_search/public/filter_bar/filter_bar.tsx +++ b/src/plugins/unified_search/public/filter_bar/filter_bar.tsx @@ -22,6 +22,7 @@ export interface Props { indexPatterns: DataView[]; intl: InjectedIntl; timeRangeForSuggestionsOverride?: boolean; + filtersForSuggestions?: Filter[]; hiddenPanelOptions?: FilterItemsProps['hiddenPanelOptions']; /** * Applies extra styles necessary when coupled with the query bar @@ -54,6 +55,7 @@ const FilterBarUI = React.memo(function FilterBarUI(props: Props) { onFiltersUpdated={props.onFiltersUpdated} indexPatterns={props.indexPatterns!} timeRangeForSuggestionsOverride={props.timeRangeForSuggestionsOverride} + filtersForSuggestions={props.filtersForSuggestions} hiddenPanelOptions={props.hiddenPanelOptions} readOnly={props.isDisabled} /> diff --git a/src/plugins/unified_search/public/filter_bar/filter_editor/filter_editor.tsx b/src/plugins/unified_search/public/filter_bar/filter_editor/filter_editor.tsx index 70586bdd39857..cac83eccf0503 100644 --- a/src/plugins/unified_search/public/filter_bar/filter_editor/filter_editor.tsx +++ b/src/plugins/unified_search/public/filter_bar/filter_editor/filter_editor.tsx @@ -126,6 +126,7 @@ export interface FilterEditorComponentProps { onLocalFilterCreate?: (initialState: { filter: Filter; queryDslFilter: QueryDslFilter }) => void; onLocalFilterUpdate?: (filter: Filter | QueryDslFilter) => void; timeRangeForSuggestionsOverride?: boolean; + filtersForSuggestions?: Filter[]; mode?: 'edit' | 'add'; } @@ -334,6 +335,7 @@ class FilterEditorComponent extends Component { extends React.Com protected updateSuggestions = debounce(async (query: string = '') => { if (this.abortController) this.abortController.abort(); this.abortController = new AbortController(); - const { indexPattern, field, timeRangeForSuggestionsOverride } = this + const { indexPattern, field, timeRangeForSuggestionsOverride, filtersForSuggestions } = this .props as PhraseSuggestorProps; if (!field || !this.isSuggestingValues()) { return; @@ -85,6 +87,8 @@ export class PhraseSuggestorUI extends React.Com query, signal: this.abortController.signal, useTimeRange: timeRangeForSuggestionsOverride, + boolFilter: buildQueryFromFilters(filtersForSuggestions, undefined).filter, + method: filtersForSuggestions?.length ? 'terms_agg' : 'terms_enum', }); this.setState({ suggestions, isLoading: false }); diff --git a/src/plugins/unified_search/public/filter_bar/filter_item/filter_item.tsx b/src/plugins/unified_search/public/filter_bar/filter_item/filter_item.tsx index d994f35c1ff37..0c446af0a0f9a 100644 --- a/src/plugins/unified_search/public/filter_bar/filter_item/filter_item.tsx +++ b/src/plugins/unified_search/public/filter_bar/filter_item/filter_item.tsx @@ -56,6 +56,7 @@ export interface FilterItemProps extends WithCloseFilterEditorConfirmModalProps uiSettings: IUiSettingsClient; hiddenPanelOptions?: FilterPanelOption[]; timeRangeForSuggestionsOverride?: boolean; + filtersForSuggestions?: Filter[]; readOnly?: boolean; } @@ -391,6 +392,7 @@ function FilterItemComponent(props: FilterItemProps) { onLocalFilterCreate={onLocalFilterCreate} onCancel={() => setIsPopoverOpen(false)} timeRangeForSuggestionsOverride={props.timeRangeForSuggestionsOverride} + filtersForSuggestions={props.filtersForSuggestions} /> , ]} diff --git a/src/plugins/unified_search/public/filter_bar/filter_item/filter_items.tsx b/src/plugins/unified_search/public/filter_bar/filter_item/filter_items.tsx index c46a4973e2457..0f8195e342821 100644 --- a/src/plugins/unified_search/public/filter_bar/filter_item/filter_items.tsx +++ b/src/plugins/unified_search/public/filter_bar/filter_item/filter_items.tsx @@ -35,6 +35,8 @@ export interface FilterItemsProps { intl: InjectedIntl; /** Controls whether or not filter suggestions are influenced by the global time */ timeRangeForSuggestionsOverride?: boolean; + /** adds additional filters to be used for suggestions */ + filtersForSuggestions?: Filter[]; /** Array of panel options that controls the styling of each filter pill */ hiddenPanelOptions?: FilterItemProps['hiddenPanelOptions']; } @@ -74,6 +76,7 @@ const FilterItemsUI = React.memo(function FilterItemsUI(props: FilterItemsProps) uiSettings={uiSettings!} hiddenPanelOptions={props.hiddenPanelOptions} timeRangeForSuggestionsOverride={props.timeRangeForSuggestionsOverride} + filtersForSuggestions={props.filtersForSuggestions} readOnly={readOnly} /> diff --git a/src/plugins/unified_search/public/filters_builder/context.ts b/src/plugins/unified_search/public/filters_builder/context.ts index 1acf317e1acad..c79339e553caf 100644 --- a/src/plugins/unified_search/public/filters_builder/context.ts +++ b/src/plugins/unified_search/public/filters_builder/context.ts @@ -8,6 +8,7 @@ import React, { Dispatch } from 'react'; import type { DataView } from '@kbn/data-views-plugin/common'; +import { Filter } from '@kbn/es-query'; import type { FiltersBuilderActions } from './reducer'; interface FiltersBuilderContextType { @@ -19,6 +20,7 @@ interface FiltersBuilderContextType { }; dropTarget: string; timeRangeForSuggestionsOverride?: boolean; + filtersForSuggestions?: Filter[]; disabled: boolean; } diff --git a/src/plugins/unified_search/public/filters_builder/filter_item/filter_item.tsx b/src/plugins/unified_search/public/filters_builder/filter_item/filter_item.tsx index b1d54a3fe6f5f..8fb8b888b4958 100644 --- a/src/plugins/unified_search/public/filters_builder/filter_item/filter_item.tsx +++ b/src/plugins/unified_search/public/filters_builder/filter_item/filter_item.tsx @@ -96,6 +96,7 @@ export function FilterItem({ dropTarget, globalParams: { hideOr }, timeRangeForSuggestionsOverride, + filtersForSuggestions, disabled, } = useContext(FiltersBuilderContextType); const conditionalOperationType = getBooleanRelationType(filter); @@ -309,6 +310,7 @@ export function FilterItem({ onHandleParamsChange={onHandleParamsChange} onHandleParamsUpdate={onHandleParamsUpdate} timeRangeForSuggestionsOverride={timeRangeForSuggestionsOverride} + filtersForSuggestions={filtersForSuggestions} /> diff --git a/src/plugins/unified_search/public/filters_builder/filter_item/params_editor.tsx b/src/plugins/unified_search/public/filters_builder/filter_item/params_editor.tsx index d2ac16e17ebdc..2b5a6a909d274 100644 --- a/src/plugins/unified_search/public/filters_builder/filter_item/params_editor.tsx +++ b/src/plugins/unified_search/public/filters_builder/filter_item/params_editor.tsx @@ -21,6 +21,7 @@ interface ParamsEditorProps { onHandleParamsChange: (params: Filter['meta']['params']) => void; onHandleParamsUpdate: (value: string) => void; timeRangeForSuggestionsOverride?: boolean; + filtersForSuggestions?: Filter[]; field?: DataViewField; operator?: Operator; } @@ -33,6 +34,7 @@ export function ParamsEditor({ onHandleParamsChange, onHandleParamsUpdate, timeRangeForSuggestionsOverride, + filtersForSuggestions, }: ParamsEditorProps) { const { disabled } = useContext(FiltersBuilderContextType); const onParamsChange = useCallback( @@ -67,6 +69,7 @@ export function ParamsEditor({ onParamsChange={onParamsChange} onParamsUpdate={onParamsUpdate} timeRangeForSuggestionsOverride={timeRangeForSuggestionsOverride} + filtersForSuggestions={filtersForSuggestions} /> diff --git a/src/plugins/unified_search/public/filters_builder/filter_item/params_editor_input.tsx b/src/plugins/unified_search/public/filters_builder/filter_item/params_editor_input.tsx index d8230e92f9ab1..4a4e7c1093198 100644 --- a/src/plugins/unified_search/public/filters_builder/filter_item/params_editor_input.tsx +++ b/src/plugins/unified_search/public/filters_builder/filter_item/params_editor_input.tsx @@ -10,6 +10,7 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; import { DataView, DataViewField } from '@kbn/data-views-plugin/common'; import { EuiFieldText } from '@elastic/eui'; +import { Filter } from '@kbn/es-query'; import { PhraseValueInput, PhrasesValuesInput, @@ -35,6 +36,7 @@ interface ParamsEditorInputProps { onParamsChange: (params: unknown) => void; onParamsUpdate: (value: unknown) => void; timeRangeForSuggestionsOverride?: boolean; + filtersForSuggestions?: Filter[]; field?: DataViewField; operator?: Operator; invalid: boolean; @@ -63,6 +65,7 @@ export function ParamsEditorInput({ onParamsChange, onParamsUpdate, timeRangeForSuggestionsOverride, + filtersForSuggestions, }: ParamsEditorInputProps) { switch (operator?.type) { case 'exists': @@ -76,6 +79,7 @@ export function ParamsEditorInput({ value={params !== undefined ? `${params}` : undefined} onChange={onParamsChange} timeRangeForSuggestionsOverride={timeRangeForSuggestionsOverride} + filtersForSuggestions={filtersForSuggestions} fullWidth invalid={invalid} disabled={disabled} @@ -91,6 +95,7 @@ export function ParamsEditorInput({ onChange={onParamsChange} onParamsUpdate={onParamsUpdate} timeRangeForSuggestionsOverride={timeRangeForSuggestionsOverride} + filtersForSuggestions={filtersForSuggestions} fullWidth disabled={disabled} /> diff --git a/src/plugins/unified_search/public/filters_builder/filters_builder.tsx b/src/plugins/unified_search/public/filters_builder/filters_builder.tsx index c601cf7df0f33..18296b36f8e03 100644 --- a/src/plugins/unified_search/public/filters_builder/filters_builder.tsx +++ b/src/plugins/unified_search/public/filters_builder/filters_builder.tsx @@ -22,6 +22,7 @@ export interface FiltersBuilderProps { dataView: DataView; onChange: (filters: Filter[]) => void; timeRangeForSuggestionsOverride?: boolean; + filtersForSuggestions?: Filter[]; maxDepth?: number; hideOr?: boolean; disabled?: boolean; @@ -35,6 +36,7 @@ function FiltersBuilder({ dataView, filters, timeRangeForSuggestionsOverride, + filtersForSuggestions, maxDepth = DEFAULT_MAX_DEPTH, hideOr = false, disabled = false, @@ -124,6 +126,7 @@ function FiltersBuilder({ dispatch, dropTarget, timeRangeForSuggestionsOverride, + filtersForSuggestions, disabled, }} > diff --git a/src/plugins/unified_search/public/query_string_input/add_filter_popover.tsx b/src/plugins/unified_search/public/query_string_input/add_filter_popover.tsx index 2f65274c99c13..3217be012dfd7 100644 --- a/src/plugins/unified_search/public/query_string_input/add_filter_popover.tsx +++ b/src/plugins/unified_search/public/query_string_input/add_filter_popover.tsx @@ -36,6 +36,7 @@ interface AddFilterPopoverProps extends WithCloseFilterEditorConfirmModalProps { indexPatterns?: Array; filters: Filter[]; timeRangeForSuggestionsOverride?: boolean; + filtersForSuggestions?: Filter[]; onFiltersUpdated?: (filters: Filter[]) => void; isDisabled?: boolean; buttonProps?: Partial; @@ -45,6 +46,7 @@ const AddFilterPopoverComponent = React.memo(function AddFilterPopover({ indexPatterns, filters, timeRangeForSuggestionsOverride, + filtersForSuggestions, onFiltersUpdated, buttonProps, isDisabled, @@ -96,6 +98,7 @@ const AddFilterPopoverComponent = React.memo(function AddFilterPopover({ indexPatterns={indexPatterns} filters={filters} timeRangeForSuggestionsOverride={timeRangeForSuggestionsOverride} + filtersForSuggestions={filtersForSuggestions} onFiltersUpdated={onFiltersUpdated} onLocalFilterUpdate={onLocalFilterUpdate} onLocalFilterCreate={onLocalFilterCreate} diff --git a/src/plugins/unified_search/public/query_string_input/filter_editor_wrapper.tsx b/src/plugins/unified_search/public/query_string_input/filter_editor_wrapper.tsx index 86933bc2a57ad..6944b0c52dcd1 100644 --- a/src/plugins/unified_search/public/query_string_input/filter_editor_wrapper.tsx +++ b/src/plugins/unified_search/public/query_string_input/filter_editor_wrapper.tsx @@ -26,6 +26,7 @@ interface FilterEditorWrapperProps { indexPatterns?: Array; filters: Filter[]; timeRangeForSuggestionsOverride?: boolean; + filtersForSuggestions?: Filter[]; closePopoverOnAdd?: () => void; closePopoverOnCancel?: () => void; onFiltersUpdated?: (filters: Filter[]) => void; @@ -37,6 +38,7 @@ export const FilterEditorWrapper = React.memo(function FilterEditorWrapper({ indexPatterns, filters, timeRangeForSuggestionsOverride, + filtersForSuggestions, closePopoverOnAdd, closePopoverOnCancel, onFiltersUpdated, @@ -111,6 +113,7 @@ export const FilterEditorWrapper = React.memo(function FilterEditorWrapper({ onLocalFilterUpdate={onLocalFilterUpdate} onLocalFilterCreate={onLocalFilterCreate} timeRangeForSuggestionsOverride={timeRangeForSuggestionsOverride} + filtersForSuggestions={filtersForSuggestions} /> )} diff --git a/src/plugins/unified_search/public/query_string_input/query_bar_menu.tsx b/src/plugins/unified_search/public/query_string_input/query_bar_menu.tsx index 0c742daebd836..a9342a65ea74f 100644 --- a/src/plugins/unified_search/public/query_string_input/query_bar_menu.tsx +++ b/src/plugins/unified_search/public/query_string_input/query_bar_menu.tsx @@ -59,6 +59,7 @@ export interface QueryBarMenuProps extends WithCloseFilterEditorConfirmModalProp showFilterBar?: boolean; showSaveQuery?: boolean; timeRangeForSuggestionsOverride?: boolean; + filtersForSuggestions?: Filter[]; indexPatterns?: Array; buttonProps?: Partial; isDisabled?: boolean; @@ -88,6 +89,7 @@ function QueryBarMenuComponent({ showSaveQuery, indexPatterns, timeRangeForSuggestionsOverride, + filtersForSuggestions, buttonProps, isDisabled, onCloseFilterPopover, @@ -186,6 +188,7 @@ function QueryBarMenuComponent({ indexPatterns={indexPatterns} filters={filters!} timeRangeForSuggestionsOverride={timeRangeForSuggestionsOverride} + filtersForSuggestions={filtersForSuggestions} onFiltersUpdated={onFiltersUpdated} onLocalFilterUpdate={onLocalFilterUpdate} onLocalFilterCreate={onLocalFilterCreate} diff --git a/src/plugins/unified_search/public/query_string_input/query_bar_top_row.test.tsx b/src/plugins/unified_search/public/query_string_input/query_bar_top_row.test.tsx index 7be320d5d7aeb..afe2022529914 100644 --- a/src/plugins/unified_search/public/query_string_input/query_bar_top_row.test.tsx +++ b/src/plugins/unified_search/public/query_string_input/query_bar_top_row.test.tsx @@ -13,7 +13,7 @@ import { mount, shallow } from 'enzyme'; import { render } from '@testing-library/react'; import { EMPTY } from 'rxjs'; -import QueryBarTopRow from './query_bar_top_row'; +import QueryBarTopRow, { SharingMetaFields } from './query_bar_top_row'; import { coreMock } from '@kbn/core/public/mocks'; import { dataPluginMock } from '@kbn/data-plugin/public/mocks'; import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; @@ -309,3 +309,30 @@ describe('QueryBarTopRowTopRow', () => { expect(component.find(QUERY_INPUT_SELECTOR).length).toBe(0); }); }); + +describe('SharingMetaFields', () => { + it('Should render the component with data-shared-timefilter-duration if time is set correctly', () => { + const from = '2023-04-07'; + const to = '2023-04-08'; + const component = ; + + expect(shallow(component)).toMatchInlineSnapshot(` +
+ `); + }); + + it('Should render the component without data-shared-timefilter-duration if time is not set correctly', () => { + const component = ( + + ); + + expect(shallow(component)).toMatchInlineSnapshot(` +
+ `); + }); +}); diff --git a/src/plugins/unified_search/public/query_string_input/query_bar_top_row.tsx b/src/plugins/unified_search/public/query_string_input/query_bar_top_row.tsx index 8d1e733cc0593..8ca32108a2555 100644 --- a/src/plugins/unified_search/public/query_string_input/query_bar_top_row.tsx +++ b/src/plugins/unified_search/public/query_string_input/query_bar_top_row.tsx @@ -100,6 +100,7 @@ export interface QueryBarTopRowProps showAutoRefreshOnly?: boolean; timeHistory?: TimeHistoryContract; timeRangeForSuggestionsOverride?: boolean; + filtersForSuggestions?: Filter[]; filters: Filter[]; onFiltersUpdated?: (filters: Filter[]) => void; dataViewPickerComponentProps?: DataViewPickerProps; @@ -121,7 +122,7 @@ export interface QueryBarTopRowProps onTextLangQueryChange: (query: AggregateQuery) => void; } -const SharingMetaFields = React.memo(function SharingMetaFields({ +export const SharingMetaFields = React.memo(function SharingMetaFields({ from, to, dateFormat, @@ -138,19 +139,22 @@ const SharingMetaFields = React.memo(function SharingMetaFields({ return valueAsMoment.toISOString(); } - const dateRangePretty = usePrettyDuration({ - timeFrom: toAbsoluteString(from), - timeTo: toAbsoluteString(to), - quickRanges: [], - dateFormat, - }); - - return ( -
- ); + try { + const dateRangePretty = usePrettyDuration({ + timeFrom: toAbsoluteString(from), + timeTo: toAbsoluteString(to), + quickRanges: [], + dateFormat, + }); + return ( +
+ ); + } catch (e) { + return
; + } }); type GenericQueryBarTopRow = ( @@ -501,6 +505,7 @@ export const QueryBarTopRow = React.memo( indexPatterns={props.indexPatterns} filters={props.filters} timeRangeForSuggestionsOverride={props.timeRangeForSuggestionsOverride} + filtersForSuggestions={props.filtersForSuggestions} onFiltersUpdated={props.onFiltersUpdated} buttonProps={{ size: shouldShowDatePickerAsBadge() ? 's' : 'm', display: 'empty' }} isDisabled={props.isDisabled} @@ -545,6 +550,7 @@ export const QueryBarTopRow = React.memo( iconType={props.iconType} nonKqlMode={props.nonKqlMode} timeRangeForSuggestionsOverride={props.timeRangeForSuggestionsOverride} + filtersForSuggestions={props.filtersForSuggestions} disableLanguageSwitcher={true} prepend={renderFilterMenuOnly() && renderFilterButtonGroup()} size={props.suggestionsSize} diff --git a/src/plugins/unified_search/public/query_string_input/query_string_input.tsx b/src/plugins/unified_search/public/query_string_input/query_string_input.tsx index 24768e222af8b..25740f092dd68 100644 --- a/src/plugins/unified_search/public/query_string_input/query_string_input.tsx +++ b/src/plugins/unified_search/public/query_string_input/query_string_input.tsx @@ -36,6 +36,7 @@ import { getFieldSubtypeNested, KIBANA_USER_QUERY_LANGUAGE_KEY } from '@kbn/data import { toMountPoint } from '@kbn/kibana-react-plugin/public'; import type { IStorageWrapper } from '@kbn/kibana-utils-plugin/public'; import type { UsageCollectionStart } from '@kbn/usage-collection-plugin/public'; +import { buildQueryFromFilters, Filter } from '@kbn/es-query'; import { matchPairs } from './match_pairs'; import { toUser } from './to_user'; import { fromUser } from './from_user'; @@ -137,6 +138,11 @@ export interface QueryStringInputProps { * Override whether autocomplete suggestions are restricted by time range. */ timeRangeForSuggestionsOverride?: boolean; + + /** + * Add additional filters used for suggestions + */ + filtersForSuggestions?: Filter[]; } interface State { @@ -279,6 +285,8 @@ export default class QueryStringInputUI extends PureComponent diff --git a/src/plugins/unified_search/public/search_bar/search_bar.tsx b/src/plugins/unified_search/public/search_bar/search_bar.tsx index 3b158fd20b9f3..4d96e1d605ab3 100644 --- a/src/plugins/unified_search/public/search_bar/search_bar.tsx +++ b/src/plugins/unified_search/public/search_bar/search_bar.tsx @@ -54,6 +54,7 @@ export interface SearchBarOwnProps { showDatePicker?: boolean; showAutoRefreshOnly?: boolean; filters?: Filter[]; + filtersForSuggestions?: Filter[]; hiddenFilterPanelOptions?: QueryBarMenuProps['hiddenPanelOptions']; // Date picker isRefreshPaused?: boolean; @@ -127,6 +128,7 @@ class SearchBarUI extends C showDatePicker: true, showSubmitButton: true, showAutoRefreshOnly: false, + filtersForSuggestions: [], }; private services = this.props.kibana.services; @@ -480,6 +482,7 @@ class SearchBarUI extends C buttonProps={{ size: this.shouldShowDatePickerAsBadge() ? 's' : 'm' }} indexPatterns={this.props.indexPatterns} timeRangeForSuggestionsOverride={timeRangeForSuggestionsOverride} + filtersForSuggestions={this.props.filtersForSuggestions} manageFilterSetComponent={ this.props.showFilterBar && this.state.query ? this.renderSavedQueryManagement( @@ -500,6 +503,7 @@ class SearchBarUI extends C onFiltersUpdated={this.props.onFiltersUpdated} indexPatterns={this.props.indexPatterns!} timeRangeForSuggestionsOverride={timeRangeForSuggestionsOverride} + filtersForSuggestions={this.props.filtersForSuggestions} hiddenPanelOptions={this.props.hiddenFilterPanelOptions} readOnly={this.props.isDisabled} /> @@ -510,6 +514,7 @@ class SearchBarUI extends C onFiltersUpdated={this.props.onFiltersUpdated} indexPatterns={this.props.indexPatterns!} timeRangeForSuggestionsOverride={timeRangeForSuggestionsOverride} + filtersForSuggestions={this.props.filtersForSuggestions} hiddenPanelOptions={this.props.hiddenFilterPanelOptions} isDisabled={this.props.isDisabled} data-test-subj="unifiedFilterBar" @@ -553,6 +558,7 @@ class SearchBarUI extends C iconType={this.props.iconType} nonKqlMode={this.props.nonKqlMode} timeRangeForSuggestionsOverride={timeRangeForSuggestionsOverride} + filtersForSuggestions={this.props.filtersForSuggestions} filters={this.props.filters!} onFiltersUpdated={this.props.onFiltersUpdated} dataViewPickerComponentProps={this.props.dataViewPickerComponentProps} diff --git a/src/plugins/vis_types/timeseries/common/empty_label.test.ts b/src/plugins/vis_types/timeseries/common/empty_label.test.ts new file mode 100644 index 0000000000000..1eb83759ff23b --- /dev/null +++ b/src/plugins/vis_types/timeseries/common/empty_label.test.ts @@ -0,0 +1,27 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { getValueOrEmpty } from './empty_label'; + +describe('getValueOrEmpty', () => { + test('returns the value if not empty or slash value given', () => { + expect(getValueOrEmpty('/test/blog')).toEqual('/test/blog'); + }); + + test('returns (empty) if value is slash', () => { + expect(getValueOrEmpty('/')).toEqual('(empty)'); + }); + + test('returns (empty) if value is empty', () => { + expect(getValueOrEmpty('')).toEqual('(empty)'); + }); + + test('returns (empty) if value is null', () => { + expect(getValueOrEmpty(null)).toEqual('(empty)'); + }); +}); diff --git a/src/plugins/vis_types/timeseries/common/empty_label.ts b/src/plugins/vis_types/timeseries/common/empty_label.ts index d95e8fe3f7f16..8f200419855e0 100644 --- a/src/plugins/vis_types/timeseries/common/empty_label.ts +++ b/src/plugins/vis_types/timeseries/common/empty_label.ts @@ -13,7 +13,7 @@ export const emptyLabel = i18n.translate('visTypeTimeseries.emptyTextValue', { }); export const getValueOrEmpty = (value: unknown) => { - if (value === '' || value === null || value === undefined) { + if (value === '' || value === '/' || value === null || value === undefined) { return emptyLabel; } return `${value}`; diff --git a/test/plugin_functional/test_suites/core_plugins/rendering.ts b/test/plugin_functional/test_suites/core_plugins/rendering.ts index 2e462dd66bb1b..6106f430dbafd 100644 --- a/test/plugin_functional/test_suites/core_plugins/rendering.ts +++ b/test/plugin_functional/test_suites/core_plugins/rendering.ts @@ -165,6 +165,7 @@ export default function ({ getService }: PluginFunctionalProviderContext) { 'xpack.apm.serviceMapEnabled (boolean)', 'xpack.apm.ui.enabled (boolean)', 'xpack.apm.ui.maxTraceItems (number)', + 'xpack.apm.latestAgentVersionsUrl (string)', 'xpack.cases.files.allowedMimeTypes (array)', 'xpack.cases.files.maxSize (number)', 'xpack.cases.markdownPlugins.lens (boolean)', diff --git a/tsconfig.base.json b/tsconfig.base.json index 313f478e28b46..a141998b3b610 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -912,6 +912,8 @@ "@kbn/ml-plugin/*": ["x-pack/plugins/ml/*"], "@kbn/ml-query-utils": ["x-pack/packages/ml/query_utils"], "@kbn/ml-query-utils/*": ["x-pack/packages/ml/query_utils/*"], + "@kbn/ml-random-sampler-utils": ["x-pack/packages/ml/random_sampler_utils"], + "@kbn/ml-random-sampler-utils/*": ["x-pack/packages/ml/random_sampler_utils/*"], "@kbn/ml-route-utils": ["x-pack/packages/ml/route_utils"], "@kbn/ml-route-utils/*": ["x-pack/packages/ml/route_utils/*"], "@kbn/ml-string-hash": ["x-pack/packages/ml/string_hash"], diff --git a/x-pack/packages/ml/agg_utils/index.ts b/x-pack/packages/ml/agg_utils/index.ts index fd92a7dbd2cc5..3d0b41280cea6 100644 --- a/x-pack/packages/ml/agg_utils/index.ts +++ b/x-pack/packages/ml/agg_utils/index.ts @@ -5,11 +5,9 @@ * 2.0. */ -export { RANDOM_SAMPLER_SEED } from './src/constants'; export { buildSamplerAggregation } from './src/build_sampler_aggregation'; export { fetchAggIntervals } from './src/fetch_agg_intervals'; export { fetchHistogramsForFields } from './src/fetch_histograms_for_fields'; -export { getSampleProbability } from './src/get_sample_probability'; export { getSamplerAggregationsResponsePath } from './src/get_sampler_aggregations_response_path'; export { numberValidator } from './src/validate_number'; diff --git a/x-pack/packages/ml/agg_utils/src/build_random_sampler_aggregation.test.ts b/x-pack/packages/ml/agg_utils/src/build_random_sampler_aggregation.test.ts deleted file mode 100644 index cdc242cd4248e..0000000000000 --- a/x-pack/packages/ml/agg_utils/src/build_random_sampler_aggregation.test.ts +++ /dev/null @@ -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 { buildRandomSamplerAggregation } from './build_random_sampler_aggregation'; - -describe('buildRandomSamplerAggregation', () => { - const testAggs = { - bytes_stats: { - stats: { field: 'bytes' }, - }, - }; - - test('returns wrapped random sampler aggregation for probability of 0.01', () => { - expect(buildRandomSamplerAggregation(testAggs, 0.01)).toEqual({ - sample: { - random_sampler: { - probability: 0.01, - seed: 3867412, - }, - aggs: testAggs, - }, - }); - }); - - test('returns un-sampled aggregation as-is for probability of 1', () => { - expect(buildRandomSamplerAggregation(testAggs, 1)).toEqual(testAggs); - }); -}); diff --git a/x-pack/packages/ml/agg_utils/src/build_random_sampler_aggregation.ts b/x-pack/packages/ml/agg_utils/src/build_random_sampler_aggregation.ts deleted file mode 100644 index c663ec8db5c54..0000000000000 --- a/x-pack/packages/ml/agg_utils/src/build_random_sampler_aggregation.ts +++ /dev/null @@ -1,34 +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 * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; - -import { RANDOM_SAMPLER_SEED } from './constants'; - -/** - * Wraps the supplied aggregations in a random sampler aggregation. - * A supplied sample probability of 1 indicates no sampling, and the aggs are returned as-is. - */ -export function buildRandomSamplerAggregation( - aggs: any, - sampleProbability: number -): Record { - if (sampleProbability === 1) { - return aggs; - } - - return { - sample: { - // @ts-expect-error `random_sampler` is not yet part of `AggregationsAggregationContainer` - random_sampler: { - probability: sampleProbability, - seed: RANDOM_SAMPLER_SEED, - }, - aggs, - }, - }; -} diff --git a/x-pack/packages/ml/agg_utils/src/build_sampler_aggregation.ts b/x-pack/packages/ml/agg_utils/src/build_sampler_aggregation.ts index 30345b00caf2f..b430590245f64 100644 --- a/x-pack/packages/ml/agg_utils/src/build_sampler_aggregation.ts +++ b/x-pack/packages/ml/agg_utils/src/build_sampler_aggregation.ts @@ -14,16 +14,16 @@ import * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; */ export function buildSamplerAggregation( aggs: any, - samplerShardSize: number + shardSize: number ): Record { - if (samplerShardSize < 1) { + if (shardSize <= 0) { return aggs; } return { sample: { sampler: { - shard_size: samplerShardSize, + shard_size: shardSize, }, aggs, }, diff --git a/x-pack/packages/ml/agg_utils/src/constants.ts b/x-pack/packages/ml/agg_utils/src/constants.ts deleted file mode 100644 index 6631438a47c93..0000000000000 --- a/x-pack/packages/ml/agg_utils/src/constants.ts +++ /dev/null @@ -1,10 +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. - */ - -// For the technical preview of Explain Log Rate Spikes we use a hard coded seed. -// In future versions we might use a user specific seed or let the user costumise it. -export const RANDOM_SAMPLER_SEED = 3867412; diff --git a/x-pack/packages/ml/agg_utils/src/fetch_agg_intervals.ts b/x-pack/packages/ml/agg_utils/src/fetch_agg_intervals.ts index 74b162835b242..671e68d58ec31 100644 --- a/x-pack/packages/ml/agg_utils/src/fetch_agg_intervals.ts +++ b/x-pack/packages/ml/agg_utils/src/fetch_agg_intervals.ts @@ -13,10 +13,9 @@ import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; import { KBN_FIELD_TYPES } from '@kbn/field-types'; import { isPopulatedObject } from '@kbn/ml-is-populated-object'; import { stringHash } from '@kbn/ml-string-hash'; +import { createRandomSamplerWrapper } from '@kbn/ml-random-sampler-utils'; -import { buildRandomSamplerAggregation } from './build_random_sampler_aggregation'; import { buildSamplerAggregation } from './build_sampler_aggregation'; -import { getRandomSamplerAggregationsResponsePath } from './get_random_sampler_aggregations_response_path'; import { getSamplerAggregationsResponsePath } from './get_sampler_aggregations_response_path'; import type { HistogramField, NumericColumnStatsMap } from './types'; @@ -33,7 +32,8 @@ export const fetchAggIntervals = async ( samplerShardSize: number, runtimeMappings?: estypes.MappingRuntimeFields, abortSignal?: AbortSignal, - randomSamplerProbability?: number + randomSamplerProbability?: number, + randomSamplerSeed?: number ): Promise => { if ( samplerShardSize >= 1 && @@ -61,6 +61,11 @@ export const fetchAggIntervals = async ( return aggs; }, {} as Record); + const { wrap, unwrap } = createRandomSamplerWrapper({ + probability: randomSamplerProbability ?? 1, + seed: randomSamplerSeed, + }); + const body = await client.search( { index: indexPattern, @@ -70,7 +75,7 @@ export const fetchAggIntervals = async ( aggs: randomSamplerProbability === undefined ? buildSamplerAggregation(minMaxAggs, samplerShardSize) - : buildRandomSamplerAggregation(minMaxAggs, randomSamplerProbability), + : wrap(minMaxAggs), size: 0, ...(isPopulatedObject(runtimeMappings) ? { runtime_mappings: runtimeMappings } : {}), }, @@ -81,10 +86,19 @@ export const fetchAggIntervals = async ( const aggsPath = randomSamplerProbability === undefined ? getSamplerAggregationsResponsePath(samplerShardSize) - : getRandomSamplerAggregationsResponsePath(randomSamplerProbability); - const aggregations = aggsPath.length > 0 ? get(body.aggregations, aggsPath) : body.aggregations; + : []; + const aggregations = + aggsPath.length > 0 + ? get(body.aggregations, aggsPath) + : randomSamplerProbability !== undefined && body.aggregations !== undefined + ? unwrap(body.aggregations) + : body.aggregations; return Object.keys(aggregations).reduce((p, aggName) => { + if (aggregations === undefined) { + return p; + } + const stats = [aggregations[aggName].min, aggregations[aggName].max]; if (!stats.includes(null)) { const delta = aggregations[aggName].max - aggregations[aggName].min; diff --git a/x-pack/packages/ml/agg_utils/src/fetch_histograms_for_fields.ts b/x-pack/packages/ml/agg_utils/src/fetch_histograms_for_fields.ts index 5729c9cc2f4ed..1baa98317008a 100644 --- a/x-pack/packages/ml/agg_utils/src/fetch_histograms_for_fields.ts +++ b/x-pack/packages/ml/agg_utils/src/fetch_histograms_for_fields.ts @@ -13,11 +13,10 @@ import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; import { KBN_FIELD_TYPES } from '@kbn/field-types'; import { isPopulatedObject } from '@kbn/ml-is-populated-object'; import { stringHash } from '@kbn/ml-string-hash'; +import { createRandomSamplerWrapper } from '@kbn/ml-random-sampler-utils'; -import { buildRandomSamplerAggregation } from './build_random_sampler_aggregation'; import { buildSamplerAggregation } from './build_sampler_aggregation'; import { fetchAggIntervals } from './fetch_agg_intervals'; -import { getRandomSamplerAggregationsResponsePath } from './get_random_sampler_aggregations_response_path'; import { getSamplerAggregationsResponsePath } from './get_sampler_aggregations_response_path'; import type { AggCardinality, @@ -141,6 +140,7 @@ export type FieldsForHistograms = Array< * @param samplerShardSize shard_size parameter of the sampler aggregation * @param runtimeMappings optional runtime mappings * @param randomSamplerProbability optional random sampler probability + * @param randomSamplerSeed optional random sampler seed * @returns an array of histogram data for each supplied field */ export const fetchHistogramsForFields = async ( @@ -151,7 +151,8 @@ export const fetchHistogramsForFields = async ( samplerShardSize: number, runtimeMappings?: estypes.MappingRuntimeFields, abortSignal?: AbortSignal, - randomSamplerProbability?: number + randomSamplerProbability?: number, + randomSamplerSeed?: number ) => { if ( samplerShardSize >= 1 && @@ -170,7 +171,8 @@ export const fetchHistogramsForFields = async ( samplerShardSize, runtimeMappings, abortSignal, - randomSamplerProbability + randomSamplerProbability, + randomSamplerSeed )), ...fields.filter(isNumericHistogramFieldWithColumnStats).reduce((p, field) => { const { interval, min, max, fieldName } = field; @@ -213,6 +215,11 @@ export const fetchHistogramsForFields = async ( return []; } + const { wrap, unwrap } = createRandomSamplerWrapper({ + probability: randomSamplerProbability ?? 1, + seed: randomSamplerSeed, + }); + const body = await client.search( { index: indexPattern, @@ -222,7 +229,7 @@ export const fetchHistogramsForFields = async ( aggs: randomSamplerProbability === undefined ? buildSamplerAggregation(chartDataAggs, samplerShardSize) - : buildRandomSamplerAggregation(chartDataAggs, randomSamplerProbability), + : wrap(chartDataAggs), size: 0, ...(isPopulatedObject(runtimeMappings) ? { runtime_mappings: runtimeMappings } : {}), }, @@ -233,8 +240,13 @@ export const fetchHistogramsForFields = async ( const aggsPath = randomSamplerProbability === undefined ? getSamplerAggregationsResponsePath(samplerShardSize) - : getRandomSamplerAggregationsResponsePath(randomSamplerProbability); - const aggregations = aggsPath.length > 0 ? get(body.aggregations, aggsPath) : body.aggregations; + : []; + const aggregations = + aggsPath.length > 0 + ? get(body.aggregations, aggsPath) + : randomSamplerProbability !== undefined && body.aggregations !== undefined + ? unwrap(body.aggregations) + : body.aggregations; return fields.map((field) => { const id = stringHash(field.fieldName); diff --git a/x-pack/packages/ml/agg_utils/src/get_random_sampler_aggregations_response_path.test.ts b/x-pack/packages/ml/agg_utils/src/get_random_sampler_aggregations_response_path.test.ts deleted file mode 100644 index 4a40a7f2fbbfe..0000000000000 --- a/x-pack/packages/ml/agg_utils/src/get_random_sampler_aggregations_response_path.test.ts +++ /dev/null @@ -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 { getRandomSamplerAggregationsResponsePath } from './get_random_sampler_aggregations_response_path'; - -describe('getRandomSamplerAggregationsResponsePath', () => { - test('returns correct path for random sampler probability of 0.01', () => { - expect(getRandomSamplerAggregationsResponsePath(0.01)).toEqual(['sample']); - }); - - test('returns correct path for random sampler probability of 1', () => { - expect(getRandomSamplerAggregationsResponsePath(1)).toEqual([]); - }); -}); diff --git a/x-pack/packages/ml/agg_utils/src/get_random_sampler_aggregations_response_path.ts b/x-pack/packages/ml/agg_utils/src/get_random_sampler_aggregations_response_path.ts deleted file mode 100644 index 891eae7faaec6..0000000000000 --- a/x-pack/packages/ml/agg_utils/src/get_random_sampler_aggregations_response_path.ts +++ /dev/null @@ -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. - */ - -// Returns the path of aggregations in the elasticsearch response, as an array, -// depending on whether random sampling is being used. -// A supplied randomSamplerProbability -// (the probability parameter of the random sampler aggregation) -// of 1 indicates no random sampling, and an empty array is returned. -export function getRandomSamplerAggregationsResponsePath( - randomSamplerProbability: number -): string[] { - return randomSamplerProbability < 1 ? ['sample'] : []; -} diff --git a/x-pack/packages/ml/agg_utils/tsconfig.json b/x-pack/packages/ml/agg_utils/tsconfig.json index 967848f2d3ddf..53328adab80f2 100644 --- a/x-pack/packages/ml/agg_utils/tsconfig.json +++ b/x-pack/packages/ml/agg_utils/tsconfig.json @@ -16,7 +16,8 @@ "@kbn/field-types", "@kbn/ml-is-populated-object", "@kbn/ml-string-hash", - "@kbn/data-views-plugin" + "@kbn/data-views-plugin", + "@kbn/ml-random-sampler-utils" ], "exclude": [ "target/**/*", diff --git a/x-pack/packages/ml/random_sampler_utils/README.md b/x-pack/packages/ml/random_sampler_utils/README.md new file mode 100644 index 0000000000000..0a5ffb3361169 --- /dev/null +++ b/x-pack/packages/ml/random_sampler_utils/README.md @@ -0,0 +1,5 @@ +# @kbn/ml-random-sampler-utils + +This package includes utility functions related to creating elasticsearch aggregations with random sampling. + +https://docs.elastic.dev/kibana-dev-docs/api/kbn-ml-random-sampling-utils diff --git a/x-pack/packages/ml/random_sampler_utils/index.ts b/x-pack/packages/ml/random_sampler_utils/index.ts new file mode 100644 index 0000000000000..941df2408ae82 --- /dev/null +++ b/x-pack/packages/ml/random_sampler_utils/index.ts @@ -0,0 +1,12 @@ +/* + * 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 { getSampleProbability } from './src/get_sample_probability'; +export { + createRandomSamplerWrapper, + type RandomSamplerWrapper, +} from './src/random_sampler_wrapper'; diff --git a/x-pack/plugins/synthetics/e2e/journeys/uptime/read_only_user/index.ts b/x-pack/packages/ml/random_sampler_utils/jest.config.js similarity index 65% rename from x-pack/plugins/synthetics/e2e/journeys/uptime/read_only_user/index.ts rename to x-pack/packages/ml/random_sampler_utils/jest.config.js index 861c345b6da33..72446f7039aa7 100644 --- a/x-pack/plugins/synthetics/e2e/journeys/uptime/read_only_user/index.ts +++ b/x-pack/packages/ml/random_sampler_utils/jest.config.js @@ -5,4 +5,8 @@ * 2.0. */ -export * from './monitor_management'; +module.exports = { + preset: '@kbn/test', + rootDir: '../../../..', + roots: ['/x-pack/packages/ml/random_sampler_utils'], +}; diff --git a/x-pack/packages/ml/random_sampler_utils/kibana.jsonc b/x-pack/packages/ml/random_sampler_utils/kibana.jsonc new file mode 100644 index 0000000000000..87b8e2ec0ca01 --- /dev/null +++ b/x-pack/packages/ml/random_sampler_utils/kibana.jsonc @@ -0,0 +1,5 @@ +{ + "type": "shared-common", + "id": "@kbn/ml-random-sampler-utils", + "owner": "@elastic/ml-ui" +} diff --git a/x-pack/packages/ml/random_sampler_utils/package.json b/x-pack/packages/ml/random_sampler_utils/package.json new file mode 100644 index 0000000000000..14818a32aabd0 --- /dev/null +++ b/x-pack/packages/ml/random_sampler_utils/package.json @@ -0,0 +1,6 @@ +{ + "name": "@kbn/ml-random-sampler-utils", + "private": true, + "version": "1.0.0", + "license": "Elastic License 2.0" +} \ No newline at end of file diff --git a/x-pack/packages/ml/agg_utils/src/get_sample_probability.test.ts b/x-pack/packages/ml/random_sampler_utils/src/get_sample_probability.test.ts similarity index 88% rename from x-pack/packages/ml/agg_utils/src/get_sample_probability.test.ts rename to x-pack/packages/ml/random_sampler_utils/src/get_sample_probability.test.ts index ce6ea9e083cbb..254f4a3c1866f 100644 --- a/x-pack/packages/ml/agg_utils/src/get_sample_probability.test.ts +++ b/x-pack/packages/ml/random_sampler_utils/src/get_sample_probability.test.ts @@ -22,10 +22,10 @@ describe('getSampleProbability', () => { expect(getSampleProbability(100000)).toEqual(0.5); }); test('returns sample probability based on total docs ratio', () => { - expect(getSampleProbability(100001)).toEqual(0.4999950000499995); + expect(getSampleProbability(100001)).toEqual(0.5); expect(getSampleProbability(1000000)).toEqual(0.05); - expect(getSampleProbability(1000001)).toEqual(0.04999995000005); - expect(getSampleProbability(2000000)).toEqual(0.025); + expect(getSampleProbability(1000001)).toEqual(0.05); + expect(getSampleProbability(2000000)).toEqual(0.03); expect(getSampleProbability(5000000)).toEqual(0.01); expect(getSampleProbability(10000000)).toEqual(0.005); expect(getSampleProbability(100000000)).toEqual(0.0005); diff --git a/x-pack/packages/ml/agg_utils/src/get_sample_probability.ts b/x-pack/packages/ml/random_sampler_utils/src/get_sample_probability.ts similarity index 54% rename from x-pack/packages/ml/agg_utils/src/get_sample_probability.ts rename to x-pack/packages/ml/random_sampler_utils/src/get_sample_probability.ts index 4f1e1be948a02..09750428ab571 100644 --- a/x-pack/packages/ml/agg_utils/src/get_sample_probability.ts +++ b/x-pack/packages/ml/random_sampler_utils/src/get_sample_probability.ts @@ -7,6 +7,16 @@ const SAMPLE_PROBABILITY_MIN_DOC_COUNT = 50000; +// Trims the sample probability to the first non-zero digit. +function trimSampleProbability(d: number): number { + return +d.toFixed(Math.max(-Math.log10(d) + 1, 1)); +} + +/** + * Returns a dynamic sample probability to be used with the `random_sampler` aggregation. + * @param {number} totalDocCount The total document count to derive the sample probability from. + * @returns {number} sample probability + */ export function getSampleProbability(totalDocCount: number) { let sampleProbability = 1; @@ -14,5 +24,5 @@ export function getSampleProbability(totalDocCount: number) { sampleProbability = Math.min(0.5, SAMPLE_PROBABILITY_MIN_DOC_COUNT / totalDocCount); } - return sampleProbability; + return trimSampleProbability(sampleProbability); } diff --git a/x-pack/packages/ml/random_sampler_utils/src/random_sampler_wrapper.test.ts b/x-pack/packages/ml/random_sampler_utils/src/random_sampler_wrapper.test.ts new file mode 100644 index 0000000000000..58ab666b967ee --- /dev/null +++ b/x-pack/packages/ml/random_sampler_utils/src/random_sampler_wrapper.test.ts @@ -0,0 +1,47 @@ +/* + * 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 { createRandomSamplerWrapper } from './random_sampler_wrapper'; + +describe('createRandomSamplerWrapper', () => { + const testAggs = { + bytes_stats: { + stats: { field: 'bytes' }, + }, + }; + + const wrappedTestAggs = { + sample: { + random_sampler: { + probability: 0.01, + }, + aggs: testAggs, + }, + }; + + it('returns the un-sampled aggregation as-is for a probability of 1', () => { + expect(createRandomSamplerWrapper({ probability: 1 }).wrap(testAggs)).toEqual(testAggs); + }); + + it('returns wrapped random sampler aggregation for probability of 0.01', () => { + expect(createRandomSamplerWrapper({ probability: 0.01 }).wrap(testAggs)).toEqual( + wrappedTestAggs + ); + }); + + it('returns probability of 1 and does not wrap when used for 10 docs', () => { + const randomSamplerWrapper = createRandomSamplerWrapper({ totalNumDocs: 10 }); + expect(randomSamplerWrapper.probability).toBe(1); + expect(randomSamplerWrapper.wrap(testAggs)).toEqual(testAggs); + }); + + it('returns probability of 0.01 and does not wrap when used for 5000000 docs', () => { + const randomSamplerWrapper = createRandomSamplerWrapper({ totalNumDocs: 5000000 }); + expect(randomSamplerWrapper.probability).toBe(0.01); + expect(randomSamplerWrapper.wrap(testAggs)).toEqual(wrappedTestAggs); + }); +}); diff --git a/x-pack/packages/ml/random_sampler_utils/src/random_sampler_wrapper.ts b/x-pack/packages/ml/random_sampler_utils/src/random_sampler_wrapper.ts new file mode 100644 index 0000000000000..b6f51a901ea36 --- /dev/null +++ b/x-pack/packages/ml/random_sampler_utils/src/random_sampler_wrapper.ts @@ -0,0 +1,87 @@ +/* + * 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 { get } from 'lodash'; +import * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; +import { isPopulatedObject } from '@kbn/ml-is-populated-object'; +import { getSampleProbability } from './get_sample_probability'; + +const DEFAULT_AGG_NAME = 'sample'; + +interface RandomSamplerOptionsBase { + aggName?: string; + seed?: number; +} + +interface RandomSamplerOptionProbability extends RandomSamplerOptionsBase { + probability: number; +} + +/** + * Type guard for RandomSamplerOptionProbability + */ +function isRandomSamplerOptionProbability(arg: unknown): arg is RandomSamplerOptionProbability { + return isPopulatedObject(arg, ['probability']); +} + +interface RandomSamplerOptionTotalNumDocs extends RandomSamplerOptionsBase { + totalNumDocs: number; +} + +type RandomSamplerOptions = RandomSamplerOptionProbability | RandomSamplerOptionTotalNumDocs; + +/** + * Check if a given probability is suitable for the `random_sampler` aggregation. + * @param {unknown} p The probability to be tested. + * @returns {boolean} + */ +export function isValidProbability(p: unknown): p is number { + return typeof p === 'number' && p > 0 && p < 0.5; +} + +/** + * The return type of the `createRandomSamplerWrapper` factory. + */ +export type RandomSamplerWrapper = ReturnType; + +/** + * Factory to create the random sampler wrapper utility. + * @param {RandomSamplerOptions} options RandomSamplerOptions + * @returns {RandomSamplerWrapper} random sampler wrapper utility + */ +export const createRandomSamplerWrapper = (options: RandomSamplerOptions) => { + const probability = isRandomSamplerOptionProbability(options) + ? options.probability + : getSampleProbability(options.totalNumDocs); + + const aggName = options.aggName ?? DEFAULT_AGG_NAME; + + const wrap = >( + aggs: T + ): T | Record => { + if (!isValidProbability(probability)) { + return aggs; + } + + return { + [aggName]: { + // @ts-expect-error `random_sampler` is not yet part of `AggregationsAggregationContainer` + random_sampler: { + probability, + ...(options.seed ? { seed: options.seed } : {}), + }, + aggs, + }, + } as Record; + }; + + const unwrap = >( + responseAggs: T + ) => (!isValidProbability(probability) ? responseAggs : get(responseAggs, [aggName])); + + return { wrap, unwrap, probability }; +}; diff --git a/x-pack/packages/ml/random_sampler_utils/tsconfig.json b/x-pack/packages/ml/random_sampler_utils/tsconfig.json new file mode 100644 index 0000000000000..1db807def57b8 --- /dev/null +++ b/x-pack/packages/ml/random_sampler_utils/tsconfig.json @@ -0,0 +1,21 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "outDir": "target/types", + "types": [ + "jest", + "node", + "react" + ] + }, + "include": [ + "**/*.ts", + "**/*.tsx", + ], + "exclude": [ + "target/**/*" + ], + "kbn_references": [ + "@kbn/ml-is-populated-object", + ] +} diff --git a/x-pack/plugins/aiops/common/api/explain_log_rate_spikes/schema.ts b/x-pack/plugins/aiops/common/api/explain_log_rate_spikes/schema.ts index 5114070711929..c79619170d48e 100644 --- a/x-pack/plugins/aiops/common/api/explain_log_rate_spikes/schema.ts +++ b/x-pack/plugins/aiops/common/api/explain_log_rate_spikes/schema.ts @@ -33,6 +33,8 @@ export const aiopsExplainLogRateSpikesSchema = schema.object({ significantTerms: schema.maybe(schema.arrayOf(schema.any())), }) ), + /** Probability used for the random sampler aggregations */ + sampleProbability: schema.maybe(schema.number()), }); export type AiopsExplainLogRateSpikesSchema = TypeOf; diff --git a/x-pack/plugins/aiops/common/constants.ts b/x-pack/plugins/aiops/common/constants.ts index 4f274004099b8..823ef05b1ed3f 100644 --- a/x-pack/plugins/aiops/common/constants.ts +++ b/x-pack/plugins/aiops/common/constants.ts @@ -6,3 +6,7 @@ */ export const SPIKE_ANALYSIS_THRESHOLD = 0.02; + +// For the technical preview of Explain Log Rate Spikes we use a hard coded seed. +// In future versions we might use a user specific seed or let the user costumise it. +export const RANDOM_SAMPLER_SEED = 3867412; diff --git a/x-pack/plugins/aiops/public/components/change_point_detection/chart_component.tsx b/x-pack/plugins/aiops/public/components/change_point_detection/chart_component.tsx index 6cc620a804691..e22249b9fa8ac 100644 --- a/x-pack/plugins/aiops/public/components/change_point_detection/chart_component.tsx +++ b/x-pack/plugins/aiops/public/components/change_point_detection/chart_component.tsx @@ -31,35 +31,39 @@ export const ChartComponent: FC = React.memo(({ annotation const timeRange = useTimeRangeUpdates(); const { dataView } = useDataSource(); - const { requestParams, bucketInterval } = useChangePointDetectionContext(); + const { requestParams, bucketInterval, resultQuery, resultFilters } = + useChangePointDetectionContext(); const filters = useMemo(() => { - return annotation.group - ? [ - { - meta: { - index: dataView.id!, - alias: null, - negate: false, - disabled: false, - type: 'phrase', - key: annotation.group.name, - params: { - query: annotation.group.value, + return [ + ...resultFilters, + ...(annotation.group + ? [ + { + meta: { + index: dataView.id!, + alias: null, + negate: false, + disabled: false, + type: 'phrase', + key: annotation.group.name, + params: { + query: annotation.group.value, + }, }, - }, - query: { - match_phrase: { - [annotation.group.name]: annotation.group.value, + query: { + match_phrase: { + [annotation.group.name]: annotation.group.value, + }, + }, + $state: { + store: FilterStateStore.APP_STATE, }, }, - $state: { - store: FilterStateStore.APP_STATE, - }, - }, - ] - : []; - }, [dataView.id, annotation.group]); + ] + : []), + ]; + }, [dataView.id, annotation.group, resultFilters]); // @ts-ignore incorrect types for attributes const attributes = useMemo(() => { @@ -151,10 +155,7 @@ export const ChartComponent: FC = React.memo(({ annotation : []), ], }, - query: { - query: '', - language: 'kuery', - }, + query: resultQuery, filters, datasourceStates: { formBased: { @@ -202,13 +203,26 @@ export const ChartComponent: FC = React.memo(({ annotation adHocDataViews: {}, }, }; - }, [dataView.id, dataView.timeFieldName, annotation, requestParams, filters, bucketInterval]); + }, [ + annotation.group?.value, + annotation.timestamp, + annotation.label, + dataView.id, + dataView.timeFieldName, + resultQuery, + filters, + bucketInterval.expression, + requestParams.fn, + requestParams.metricField, + ]); return ( = ({ documentCountStatsSplit, documentCountStatsSplitLabel = '', totalCount, + sampleProbability, windowParameters, }) => { const [isBrushCleared, setIsBrushCleared] = useState(true); @@ -62,7 +64,9 @@ export const DocumentCountContent: FC = ({ timeRangeEarliest === undefined || timeRangeLatest === undefined ) { - return totalCount !== undefined ? : null; + return totalCount !== undefined ? ( + + ) : null; } const chartPoints: DocumentCountChartPoint[] = Object.entries(documentCountStats.buckets).map( @@ -98,7 +102,7 @@ export const DocumentCountContent: FC = ({ <> - + {!isBrushCleared && ( diff --git a/x-pack/plugins/aiops/public/components/document_count_content/total_count_header/total_count_header.tsx b/x-pack/plugins/aiops/public/components/document_count_content/total_count_header/total_count_header.tsx index 043ec8275a82f..8f7173c31b177 100644 --- a/x-pack/plugins/aiops/public/components/document_count_content/total_count_header/total_count_header.tsx +++ b/x-pack/plugins/aiops/public/components/document_count_content/total_count_header/total_count_header.tsx @@ -12,7 +12,13 @@ import { EuiFlexItem, EuiText } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; import React from 'react'; -export const TotalCountHeader = ({ totalCount }: { totalCount: number }) => ( +export const TotalCountHeader = ({ + sampleProbability, + totalCount, +}: { + sampleProbability?: number; + totalCount: number; +}) => ( ( ), }} /> + {sampleProbability !== undefined && sampleProbability < 1 && ( + <> + {' '} + + + + ), + }} + /> + + )} ); diff --git a/x-pack/plugins/aiops/public/components/explain_log_rate_spikes/explain_log_rate_spikes_analysis.tsx b/x-pack/plugins/aiops/public/components/explain_log_rate_spikes/explain_log_rate_spikes_analysis.tsx index 6006365e631bb..3b0220d2331f4 100644 --- a/x-pack/plugins/aiops/public/components/explain_log_rate_spikes/explain_log_rate_spikes_analysis.tsx +++ b/x-pack/plugins/aiops/public/components/explain_log_rate_spikes/explain_log_rate_spikes_analysis.tsx @@ -65,6 +65,8 @@ interface ExplainLogRateSpikesAnalysisProps { windowParameters: WindowParameters; /** The search query to be applied to the analysis as a filter */ searchQuery: Query['query']; + /** Sample probability to be applied to random sampler aggregations */ + sampleProbability: number; } export const ExplainLogRateSpikesAnalysis: FC = ({ @@ -73,6 +75,7 @@ export const ExplainLogRateSpikesAnalysis: FC latest, windowParameters, searchQuery, + sampleProbability, }) => { const { http } = useAiopsAppContext(); const basePath = http.basePath.get() ?? ''; @@ -114,6 +117,7 @@ export const ExplainLogRateSpikesAnalysis: FC flushFix: true, ...windowParameters, overrides, + sampleProbability, }, { reducer: streamReducer, initialState } ); diff --git a/x-pack/plugins/aiops/public/components/explain_log_rate_spikes/explain_log_rate_spikes_page.tsx b/x-pack/plugins/aiops/public/components/explain_log_rate_spikes/explain_log_rate_spikes_page.tsx index 6d3528b500f57..86082d64e0e27 100644 --- a/x-pack/plugins/aiops/public/components/explain_log_rate_spikes/explain_log_rate_spikes_page.tsx +++ b/x-pack/plugins/aiops/public/components/explain_log_rate_spikes/explain_log_rate_spikes_page.tsx @@ -118,7 +118,8 @@ export const ExplainLogRateSpikesPage: FC = () => { currentSelectedGroup ); - const { totalCount, documentCountStats, documentCountStatsCompare } = documentStats; + const { sampleProbability, totalCount, documentCountStats, documentCountStatsCompare } = + documentStats; useEffect( // TODO: Consolidate this hook/function with with Data visualizer's @@ -198,6 +199,7 @@ export const ExplainLogRateSpikesPage: FC = () => { currentSelectedGroup )} totalCount={totalCount} + sampleProbability={sampleProbability} windowParameters={windowParameters} /> @@ -212,6 +214,7 @@ export const ExplainLogRateSpikesPage: FC = () => { latest={latest} windowParameters={windowParameters} searchQuery={searchQuery} + sampleProbability={sampleProbability} /> )} {windowParameters === undefined && ( diff --git a/x-pack/plugins/aiops/public/get_document_stats.ts b/x-pack/plugins/aiops/public/get_document_stats.ts index cbfefea4d4370..f2523ea2b942e 100644 --- a/x-pack/plugins/aiops/public/get_document_stats.ts +++ b/x-pack/plugins/aiops/public/get_document_stats.ts @@ -12,6 +12,7 @@ import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { isPopulatedObject } from '@kbn/ml-is-populated-object'; import type { SignificantTerm } from '@kbn/ml-agg-utils'; import type { Query } from '@kbn/es-query'; +import type { RandomSamplerWrapper } from '@kbn/ml-random-sampler-utils'; import { buildExtendedBaseFilterCriteria } from './application/utils/build_extended_base_filter_criteria'; import { GroupTableItem } from './components/spike_analysis_table/types'; @@ -36,9 +37,14 @@ export interface DocumentStatsSearchStrategyParams { selectedSignificantTerm?: SignificantTerm; includeSelectedSignificantTerm?: boolean; selectedGroup?: GroupTableItem | null; + trackTotalHits?: boolean; } -export const getDocumentCountStatsRequest = (params: DocumentStatsSearchStrategyParams) => { +export const getDocumentCountStatsRequest = ( + params: DocumentStatsSearchStrategyParams, + randomSamplerWrapper?: RandomSamplerWrapper, + skipAggs = false +) => { const { index, timeFieldName, @@ -51,6 +57,7 @@ export const getDocumentCountStatsRequest = (params: DocumentStatsSearchStrategy selectedSignificantTerm, includeSelectedSignificantTerm, selectedGroup, + trackTotalHits, } = params; const size = 0; @@ -64,33 +71,41 @@ export const getDocumentCountStatsRequest = (params: DocumentStatsSearchStrategy selectedGroup ); - // Don't use the sampler aggregation as this can lead to some potentially - // confusing date histogram results depending on the date range of data amongst shards. - const aggs = { + const rawAggs: Record = { eventRate: { date_histogram: { field: timeFieldName, fixed_interval: `${intervalMs}ms`, min_doc_count: 0, - extended_bounds: { - min: earliestMs, - max: latestMs, - }, + ...(earliestMs !== undefined && latestMs !== undefined + ? { + extended_bounds: { + min: earliestMs, + max: latestMs, + }, + } + : {}), }, }, }; + const aggs = randomSamplerWrapper ? randomSamplerWrapper.wrap(rawAggs) : rawAggs; + const searchBody = { query: { bool: { filter: filterCriteria, }, }, - ...(!fieldsToFetch && timeFieldName !== undefined && intervalMs !== undefined && intervalMs > 0 + ...(!fieldsToFetch && + !skipAggs && + timeFieldName !== undefined && + intervalMs !== undefined && + intervalMs > 0 ? { aggs } : {}), ...(isPopulatedObject(runtimeFieldMap) ? { runtime_mappings: runtimeFieldMap } : {}), - track_total_hits: true, + track_total_hits: trackTotalHits === true, size, }; return { @@ -101,7 +116,8 @@ export const getDocumentCountStatsRequest = (params: DocumentStatsSearchStrategy export const processDocumentCountStats = ( body: estypes.SearchResponse | undefined, - params: DocumentStatsSearchStrategyParams + params: DocumentStatsSearchStrategyParams, + randomSamplerWrapper?: RandomSamplerWrapper ): DocumentCountStats | undefined => { if (!body) return undefined; @@ -118,8 +134,10 @@ export const processDocumentCountStats = ( } const buckets: { [key: string]: number } = {}; const dataByTimeBucket: Array<{ key: string; doc_count: number }> = get( - body, - ['aggregations', 'eventRate', 'buckets'], + randomSamplerWrapper && body.aggregations !== undefined + ? randomSamplerWrapper.unwrap(body.aggregations) + : body.aggregations, + ['eventRate', 'buckets'], [] ); each(dataByTimeBucket, (dataForTime) => { diff --git a/x-pack/plugins/aiops/public/hooks/use_document_count_stats.ts b/x-pack/plugins/aiops/public/hooks/use_document_count_stats.ts index 1d443d98491b8..9e4f50368a0b5 100644 --- a/x-pack/plugins/aiops/public/hooks/use_document_count_stats.ts +++ b/x-pack/plugins/aiops/public/hooks/use_document_count_stats.ts @@ -11,6 +11,9 @@ import { lastValueFrom } from 'rxjs'; import { i18n } from '@kbn/i18n'; import type { ToastsStart } from '@kbn/core/public'; import { stringHash } from '@kbn/ml-string-hash'; +import { createRandomSamplerWrapper } from '@kbn/ml-random-sampler-utils'; + +import { RANDOM_SAMPLER_SEED } from '../../common/constants'; import { extractErrorProperties } from '../application/utils/error_utils'; import { @@ -23,6 +26,7 @@ import { import { useAiopsAppContext } from './use_aiops_app_context'; export interface DocumentStats { + sampleProbability: number; totalCount: number; documentCountStats?: DocumentCountStats; documentCountStatsCompare?: DocumentCountStats; @@ -67,6 +71,7 @@ export function useDocumentCountStats({ + sampleProbability: 1, totalCount: 0, }); @@ -87,19 +92,48 @@ export function useDocumentCountStats @@ -117,21 +110,14 @@ export async function fetchFrequentItemSets( }, }; - // frequent items can be slow, so sample and use 10% min_support - const randomSamplerAgg: Record = { - sample: { - // @ts-expect-error `random_sampler` is not yet part of `AggregationsAggregationContainer` - random_sampler: { - probability: sampleProbability, - seed: RANDOM_SAMPLER_SEED, - }, - aggs: frequentItemSetsAgg, - }, - }; + const { wrap, unwrap } = createRandomSamplerWrapper({ + probability: sampleProbability, + seed: RANDOM_SAMPLER_SEED, + }); const esBody = { query, - aggs: sampleProbability < 1 ? randomSamplerAgg : frequentItemSetsAgg, + aggs: wrap(frequentItemSetsAgg), size: 0, track_total_hits: true, }; @@ -160,17 +146,17 @@ export async function fetchFrequentItemSets( const totalDocCountFi = (body.hits.total as estypes.SearchTotalHits).value; - const frequentItemSets = isRandomSamplerAggregation(body.aggregations) - ? body.aggregations.sample.fi - : body.aggregations.fi; + const frequentItemSets = unwrap( + body.aggregations as Record + ) as FrequentItemSetsAggregation; - const shape = frequentItemSets.buckets.length; + const shape = frequentItemSets.fi.buckets.length; let maximum = shape; if (maximum > 50000) { maximum = 50000; } - const fiss = frequentItemSets.buckets; + const fiss = frequentItemSets.fi.buckets; fiss.length = maximum; const results: ItemsetResult[] = []; diff --git a/x-pack/plugins/aiops/server/routes/queries/fetch_index_info.test.ts b/x-pack/plugins/aiops/server/routes/queries/fetch_index_info.test.ts index f38feaee8a58b..5d272c18226b0 100644 --- a/x-pack/plugins/aiops/server/routes/queries/fetch_index_info.test.ts +++ b/x-pack/plugins/aiops/server/routes/queries/fetch_index_info.test.ts @@ -105,13 +105,9 @@ describe('fetch_index_info', () => { search: esClientSearchMock, } as unknown as ElasticsearchClient; - const { totalDocCount, sampleProbability, fieldCandidates } = await fetchIndexInfo( - esClientMock, - params - ); + const { totalDocCount, fieldCandidates } = await fetchIndexInfo(esClientMock, params); expect(fieldCandidates).toEqual(['myIpFieldName', 'myKeywordFieldName']); - expect(sampleProbability).toEqual(0.01); expect(totalDocCount).toEqual(5000000); expect(esClientFieldCapsMock).toHaveBeenCalledTimes(1); expect(esClientSearchMock).toHaveBeenCalledTimes(1); diff --git a/x-pack/plugins/aiops/server/routes/queries/fetch_index_info.ts b/x-pack/plugins/aiops/server/routes/queries/fetch_index_info.ts index 33a298af8e98f..fae9c7fc062b8 100644 --- a/x-pack/plugins/aiops/server/routes/queries/fetch_index_info.ts +++ b/x-pack/plugins/aiops/server/routes/queries/fetch_index_info.ts @@ -9,7 +9,6 @@ import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { ES_FIELD_TYPES } from '@kbn/field-types'; import type { ElasticsearchClient } from '@kbn/core/server'; -import { getSampleProbability } from '@kbn/ml-agg-utils'; import type { AiopsExplainLogRateSpikesSchema } from '../../../common/api/explain_log_rate_spikes'; @@ -51,7 +50,7 @@ export const fetchIndexInfo = async ( esClient: ElasticsearchClient, params: AiopsExplainLogRateSpikesSchema, abortSignal?: AbortSignal -): Promise<{ fieldCandidates: string[]; sampleProbability: number; totalDocCount: number }> => { +): Promise<{ fieldCandidates: string[]; totalDocCount: number }> => { const { index } = params; // Get all supported fields const respMapping = await esClient.fieldCaps( @@ -96,7 +95,6 @@ export const fetchIndexInfo = async ( }); const totalDocCount = (resp.hits.total as estypes.SearchTotalHits).value; - const sampleProbability = getSampleProbability(totalDocCount); - return { fieldCandidates: [...finalFieldCandidates], sampleProbability, totalDocCount }; + return { fieldCandidates: [...finalFieldCandidates], totalDocCount }; }; diff --git a/x-pack/plugins/aiops/server/routes/queries/fetch_significant_term_p_values.ts b/x-pack/plugins/aiops/server/routes/queries/fetch_significant_term_p_values.ts index 60761837c3f6b..58da371d7b5a8 100644 --- a/x-pack/plugins/aiops/server/routes/queries/fetch_significant_term_p_values.ts +++ b/x-pack/plugins/aiops/server/routes/queries/fetch_significant_term_p_values.ts @@ -9,9 +9,13 @@ import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { ElasticsearchClient } from '@kbn/core/server'; import type { Logger } from '@kbn/logging'; -import { type SignificantTerm, RANDOM_SAMPLER_SEED } from '@kbn/ml-agg-utils'; -import { isPopulatedObject } from '@kbn/ml-is-populated-object'; -import { SPIKE_ANALYSIS_THRESHOLD } from '../../../common/constants'; +import { type SignificantTerm } from '@kbn/ml-agg-utils'; +import { + createRandomSamplerWrapper, + type RandomSamplerWrapper, +} from '@kbn/ml-random-sampler-utils'; + +import { SPIKE_ANALYSIS_THRESHOLD, RANDOM_SAMPLER_SEED } from '../../../common/constants'; import type { AiopsExplainLogRateSpikesSchema } from '../../../common/api/explain_log_rate_spikes'; import { isRequestAbortedError } from '../../lib/is_request_aborted_error'; @@ -25,8 +29,7 @@ import { getRequestBase } from './get_request_base'; export const getSignificantTermRequest = ( params: AiopsExplainLogRateSpikesSchema, fieldName: string, - // The default value of 1 means no sampling will be used - sampleProbability: number = 1 + { wrap }: RandomSamplerWrapper ): estypes.SearchRequest => { const query = getQueryWithParams({ params, @@ -53,7 +56,7 @@ export const getSignificantTermRequest = ( ]; } - const pValueAgg: Record = { + const pValueAgg: Record<'change_point_p_value', estypes.AggregationsAggregationContainer> = { change_point_p_value: { significant_terms: { field: fieldName, @@ -80,21 +83,10 @@ export const getSignificantTermRequest = ( }, }; - const randomSamplerAgg: Record = { - sample: { - // @ts-expect-error `random_sampler` is not yet part of `AggregationsAggregationContainer` - random_sampler: { - probability: sampleProbability, - seed: RANDOM_SAMPLER_SEED, - }, - aggs: pValueAgg, - }, - }; - const body = { query, size: 0, - aggs: sampleProbability < 1 ? randomSamplerAgg : pValueAgg, + aggs: wrap(pValueAgg), }; return { @@ -109,18 +101,6 @@ interface Aggs extends estypes.AggregationsSignificantLongTermsAggregate { buckets: estypes.AggregationsSignificantLongTermsBucket[]; } -interface PValuesAggregation extends estypes.AggregationsSamplerAggregation { - change_point_p_value: Aggs; -} - -interface RandomSamplerAggregation { - sample: PValuesAggregation; -} - -function isRandomSamplerAggregation(arg: unknown): arg is RandomSamplerAggregation { - return isPopulatedObject(arg, ['sample']); -} - export const fetchSignificantTermPValues = async ( esClient: ElasticsearchClient, params: AiopsExplainLogRateSpikesSchema, @@ -131,14 +111,19 @@ export const fetchSignificantTermPValues = async ( emitError: (m: string) => void, abortSignal?: AbortSignal ): Promise => { + const randomSamplerWrapper = createRandomSamplerWrapper({ + probability: sampleProbability, + seed: RANDOM_SAMPLER_SEED, + }); + const result: SignificantTerm[] = []; const settledPromises = await Promise.allSettled( fieldNames.map((fieldName) => - esClient.search( - getSignificantTermRequest(params, fieldName, sampleProbability), - { signal: abortSignal, maxRetries: 0 } - ) + esClient.search(getSignificantTermRequest(params, fieldName, randomSamplerWrapper), { + signal: abortSignal, + maxRetries: 0, + }) ) ); @@ -172,9 +157,9 @@ export const fetchSignificantTermPValues = async ( continue; } - const overallResult = isRandomSamplerAggregation(resp.aggregations) - ? resp.aggregations.sample.change_point_p_value - : resp.aggregations.change_point_p_value; + const overallResult = ( + randomSamplerWrapper.unwrap(resp.aggregations) as Record<'change_point_p_value', Aggs> + ).change_point_p_value; for (const bucket of overallResult.buckets) { const pValue = Math.exp(-bucket.score); diff --git a/x-pack/plugins/aiops/tsconfig.json b/x-pack/plugins/aiops/tsconfig.json index 7db9e716506ea..9398c0c189e04 100644 --- a/x-pack/plugins/aiops/tsconfig.json +++ b/x-pack/plugins/aiops/tsconfig.json @@ -49,6 +49,7 @@ "@kbn/ml-query-utils", "@kbn/ml-is-defined", "@kbn/ml-route-utils", + "@kbn/ml-random-sampler-utils", ], "exclude": [ "target/**/*", diff --git a/x-pack/plugins/alerting/common/rule.ts b/x-pack/plugins/alerting/common/rule.ts index e0616dbc142f8..e2b42e8ccf292 100644 --- a/x-pack/plugins/alerting/common/rule.ts +++ b/x-pack/plugins/alerting/common/rule.ts @@ -168,6 +168,7 @@ export interface Rule { updatedAt: Date; apiKey: string | null; apiKeyOwner: string | null; + apiKeyCreatedByUser?: boolean | null; throttle?: string | null; muteAll: boolean; notifyWhen?: RuleNotifyWhenType | null; diff --git a/x-pack/plugins/alerting/public/lib/common_transformations.ts b/x-pack/plugins/alerting/public/lib/common_transformations.ts index b123962bb4ea7..e79bed27ec3d8 100644 --- a/x-pack/plugins/alerting/public/lib/common_transformations.ts +++ b/x-pack/plugins/alerting/public/lib/common_transformations.ts @@ -109,6 +109,7 @@ export function transformRule(input: ApiRule): Rule { updated_at: updatedAt, api_key: apiKey, api_key_owner: apiKeyOwner, + api_key_created_by_user: apiKeyCreatedByUser, notify_when: notifyWhen, mute_all: muteAll, muted_alert_ids: mutedInstanceIds, @@ -140,6 +141,8 @@ export function transformRule(input: ApiRule): Rule { ...(nextRun ? { nextRun: new Date(nextRun) } : {}), ...(monitoring ? { monitoring: transformMonitoring(monitoring) } : {}), ...(lastRun ? { lastRun: transformLastRun(lastRun) } : {}), + ...(apiKeyCreatedByUser !== undefined ? { apiKeyCreatedByUser } : {}), + ...rest, }; } diff --git a/x-pack/plugins/alerting/server/routes/clone_rule.ts b/x-pack/plugins/alerting/server/routes/clone_rule.ts index 11819953cf9d6..fa775e626d903 100644 --- a/x-pack/plugins/alerting/server/routes/clone_rule.ts +++ b/x-pack/plugins/alerting/server/routes/clone_rule.ts @@ -36,6 +36,7 @@ const rewriteBodyRes: RewriteResponseCase> = ({ createdAt, updatedAt, apiKeyOwner, + apiKeyCreatedByUser, notifyWhen, muteAll, mutedInstanceIds, @@ -75,6 +76,7 @@ const rewriteBodyRes: RewriteResponseCase> = ({ : {}), ...(lastRun ? { last_run: rewriteRuleLastRun(lastRun) } : {}), ...(nextRun ? { next_run: nextRun } : {}), + ...(apiKeyCreatedByUser !== undefined ? { api_key_created_by_user: apiKeyCreatedByUser } : {}), }); export const cloneRuleRoute = ( diff --git a/x-pack/plugins/alerting/server/routes/create_rule.ts b/x-pack/plugins/alerting/server/routes/create_rule.ts index 6ada08e161a50..01a0864013415 100644 --- a/x-pack/plugins/alerting/server/routes/create_rule.ts +++ b/x-pack/plugins/alerting/server/routes/create_rule.ts @@ -74,6 +74,7 @@ const rewriteBodyRes: RewriteResponseCase> = ({ createdAt, updatedAt, apiKeyOwner, + apiKeyCreatedByUser, notifyWhen, muteAll, mutedInstanceIds, @@ -103,6 +104,7 @@ const rewriteBodyRes: RewriteResponseCase> = ({ actions: rewriteActionsRes(actions), ...(lastRun ? { last_run: rewriteRuleLastRun(lastRun) } : {}), ...(nextRun ? { next_run: nextRun } : {}), + ...(apiKeyCreatedByUser !== undefined ? { api_key_created_by_user: apiKeyCreatedByUser } : {}), }); export const createRuleRoute = ({ router, licenseState, usageCounter }: RouteOptions) => { diff --git a/x-pack/plugins/alerting/server/routes/get_rule.ts b/x-pack/plugins/alerting/server/routes/get_rule.ts index 0a7a08847399c..ab84f0c16a5e2 100644 --- a/x-pack/plugins/alerting/server/routes/get_rule.ts +++ b/x-pack/plugins/alerting/server/routes/get_rule.ts @@ -29,6 +29,7 @@ const rewriteBodyRes: RewriteResponseCase> = ({ createdAt, updatedAt, apiKeyOwner, + apiKeyCreatedByUser, notifyWhen, muteAll, mutedInstanceIds, @@ -78,6 +79,7 @@ const rewriteBodyRes: RewriteResponseCase> = ({ ...(lastRun ? { last_run: rewriteRuleLastRun(lastRun) } : {}), ...(nextRun ? { next_run: nextRun } : {}), ...(viewInAppRelativeUrl ? { view_in_app_relative_url: viewInAppRelativeUrl } : {}), + ...(apiKeyCreatedByUser !== undefined ? { api_key_created_by_user: apiKeyCreatedByUser } : {}), }); interface BuildGetRulesRouteParams { diff --git a/x-pack/plugins/alerting/server/routes/lib/rewrite_rule.ts b/x-pack/plugins/alerting/server/routes/lib/rewrite_rule.ts index b829df52cc417..953211a5ef4f7 100644 --- a/x-pack/plugins/alerting/server/routes/lib/rewrite_rule.ts +++ b/x-pack/plugins/alerting/server/routes/lib/rewrite_rule.ts @@ -25,6 +25,7 @@ export const rewriteRule = ({ createdAt, updatedAt, apiKeyOwner, + apiKeyCreatedByUser, notifyWhen, muteAll, mutedInstanceIds, @@ -76,4 +77,5 @@ export const rewriteRule = ({ })), ...(lastRun ? { last_run: rewriteRuleLastRun(lastRun) } : {}), ...(nextRun ? { next_run: nextRun } : {}), + ...(apiKeyCreatedByUser !== undefined ? { api_key_created_by_user: apiKeyCreatedByUser } : {}), }); diff --git a/x-pack/plugins/alerting/server/routes/resolve_rule.ts b/x-pack/plugins/alerting/server/routes/resolve_rule.ts index 27128850d9e82..e42dd6795f14a 100644 --- a/x-pack/plugins/alerting/server/routes/resolve_rule.ts +++ b/x-pack/plugins/alerting/server/routes/resolve_rule.ts @@ -33,6 +33,7 @@ const rewriteBodyRes: RewriteResponseCase> createdAt, updatedAt, apiKeyOwner, + apiKeyCreatedByUser, notifyWhen, muteAll, mutedInstanceIds, @@ -62,6 +63,7 @@ const rewriteBodyRes: RewriteResponseCase> actions: rewriteActionsRes(actions), ...(lastRun ? { last_run: rewriteRuleLastRun(lastRun) } : {}), ...(nextRun ? { next_run: nextRun } : {}), + ...(apiKeyCreatedByUser !== undefined ? { api_key_created_by_user: apiKeyCreatedByUser } : {}), }); export const resolveRuleRoute = ( diff --git a/x-pack/plugins/alerting/server/routes/update_rule.ts b/x-pack/plugins/alerting/server/routes/update_rule.ts index 475fc210dd2d5..d24af256de613 100644 --- a/x-pack/plugins/alerting/server/routes/update_rule.ts +++ b/x-pack/plugins/alerting/server/routes/update_rule.ts @@ -74,6 +74,7 @@ const rewriteBodyRes: RewriteResponseCase> = ({ createdAt, updatedAt, apiKeyOwner, + apiKeyCreatedByUser, notifyWhen, muteAll, mutedInstanceIds, @@ -113,6 +114,7 @@ const rewriteBodyRes: RewriteResponseCase> = ({ : {}), ...(lastRun ? { last_run: rewriteRuleLastRun(lastRun) } : {}), ...(nextRun ? { next_run: nextRun } : {}), + ...(apiKeyCreatedByUser !== undefined ? { api_key_created_by_user: apiKeyCreatedByUser } : {}), }); export const updateRuleRoute = ( diff --git a/x-pack/plugins/alerting/server/rules_client/common/api_key_as_alert_attributes.test.ts b/x-pack/plugins/alerting/server/rules_client/common/api_key_as_alert_attributes.test.ts new file mode 100644 index 0000000000000..495dab4f81487 --- /dev/null +++ b/x-pack/plugins/alerting/server/rules_client/common/api_key_as_alert_attributes.test.ts @@ -0,0 +1,68 @@ +/* + * 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 { apiKeyAsAlertAttributes } from './api_key_as_alert_attributes'; + +describe('apiKeyAsAlertAttributes', () => { + test('return attributes', () => { + expect( + apiKeyAsAlertAttributes( + { + apiKeysEnabled: true, + result: { + id: '123', + name: '123', + api_key: 'abc', + }, + }, + 'test', + false + ) + ).toEqual({ + apiKey: 'MTIzOmFiYw==', + apiKeyOwner: 'test', + apiKeyCreatedByUser: false, + }); + }); + + test('returns null attributes when api keys are not enabled', () => { + expect( + apiKeyAsAlertAttributes( + { + apiKeysEnabled: false, + }, + 'test', + false + ) + ).toEqual({ + apiKey: null, + apiKeyOwner: null, + apiKeyCreatedByUser: null, + }); + }); + + test('returns apiKeyCreatedByUser as true when createdByUser is passed in', () => { + expect( + apiKeyAsAlertAttributes( + { + apiKeysEnabled: true, + result: { + id: '123', + name: '123', + api_key: 'abc', + }, + }, + 'test', + true + ) + ).toEqual({ + apiKey: 'MTIzOmFiYw==', + apiKeyOwner: 'test', + apiKeyCreatedByUser: true, + }); + }); +}); diff --git a/x-pack/plugins/alerting/server/rules_client/common/api_key_as_alert_attributes.ts b/x-pack/plugins/alerting/server/rules_client/common/api_key_as_alert_attributes.ts index e98fb875b74a7..841ffdd1703a7 100644 --- a/x-pack/plugins/alerting/server/rules_client/common/api_key_as_alert_attributes.ts +++ b/x-pack/plugins/alerting/server/rules_client/common/api_key_as_alert_attributes.ts @@ -10,15 +10,18 @@ import { CreateAPIKeyResult } from '../types'; export function apiKeyAsAlertAttributes( apiKey: CreateAPIKeyResult | null, - username: string | null -): Pick { + username: string | null, + createdByUser: boolean +): Pick { return apiKey && apiKey.apiKeysEnabled ? { apiKeyOwner: username, apiKey: Buffer.from(`${apiKey.result.id}:${apiKey.result.api_key}`).toString('base64'), + apiKeyCreatedByUser: createdByUser, } : { apiKeyOwner: null, apiKey: null, + apiKeyCreatedByUser: null, }; } diff --git a/x-pack/plugins/alerting/server/rules_client/lib/create_new_api_key_set.test.ts b/x-pack/plugins/alerting/server/rules_client/lib/create_new_api_key_set.test.ts new file mode 100644 index 0000000000000..6f3e595a1ab46 --- /dev/null +++ b/x-pack/plugins/alerting/server/rules_client/lib/create_new_api_key_set.test.ts @@ -0,0 +1,169 @@ +/* + * 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 { savedObjectsClientMock, loggingSystemMock } from '@kbn/core/server/mocks'; +import { taskManagerMock } from '@kbn/task-manager-plugin/server/mocks'; +import { ruleTypeRegistryMock } from '../../rule_type_registry.mock'; +import { alertingAuthorizationMock } from '../../authorization/alerting_authorization.mock'; +import { encryptedSavedObjectsMock } from '@kbn/encrypted-saved-objects-plugin/server/mocks'; +import { actionsAuthorizationMock } from '@kbn/actions-plugin/server/mocks'; +import { AlertingAuthorization } from '../../authorization/alerting_authorization'; +import { ActionsAuthorization } from '@kbn/actions-plugin/server'; +import { RawRule } from '../../types'; +import { getBeforeSetup, mockedDateString } from '../tests/lib'; +import { createNewAPIKeySet } from './create_new_api_key_set'; +import { RulesClientContext } from '../types'; + +const taskManager = taskManagerMock.createStart(); +const ruleTypeRegistry = ruleTypeRegistryMock.create(); +const unsecuredSavedObjectsClient = savedObjectsClientMock.create(); +const encryptedSavedObjects = encryptedSavedObjectsMock.createClient(); +const authorization = alertingAuthorizationMock.create(); +const actionsAuthorization = actionsAuthorizationMock.create(); + +const kibanaVersion = 'v8.0.0'; +const rulesClientParams: jest.Mocked = { + taskManager, + ruleTypeRegistry, + unsecuredSavedObjectsClient, + authorization: authorization as unknown as AlertingAuthorization, + actionsAuthorization: actionsAuthorization as unknown as ActionsAuthorization, + spaceId: 'default', + getUserName: jest.fn(), + createAPIKey: jest.fn(), + logger: loggingSystemMock.create().get(), + encryptedSavedObjectsClient: encryptedSavedObjects, + getActionsClient: jest.fn(), + getEventLogClient: jest.fn(), + kibanaVersion, + minimumScheduleInterval: { value: '1m', enforce: false }, + minimumScheduleIntervalInMs: 1, + fieldsToExcludeFromPublicApi: [], + isAuthenticationTypeAPIKey: jest.fn(), + getAuthenticationAPIKey: jest.fn(), +}; + +const username = 'test'; +const attributes: RawRule = { + enabled: true, + name: 'rule-name', + tags: ['tag-1', 'tag-2'], + alertTypeId: '123', + consumer: 'rule-consumer', + legacyId: null, + schedule: { interval: '1s' }, + actions: [], + params: {}, + createdBy: null, + updatedBy: null, + createdAt: mockedDateString, + updatedAt: mockedDateString, + apiKey: null, + apiKeyOwner: null, + throttle: null, + notifyWhen: null, + muteAll: false, + mutedInstanceIds: [], + executionStatus: { + status: 'unknown', + lastExecutionDate: '2020-08-20T19:23:38Z', + error: null, + warning: null, + }, + revision: 0, +}; + +describe('createNewAPIKeySet', () => { + beforeEach(() => { + getBeforeSetup(rulesClientParams, taskManager, ruleTypeRegistry); + }); + + test('create new api keys', async () => { + rulesClientParams.createAPIKey.mockResolvedValueOnce({ + apiKeysEnabled: true, + result: { id: '123', name: '123', api_key: 'abc' }, + }); + const apiKey = await createNewAPIKeySet(rulesClientParams, { + id: attributes.alertTypeId, + ruleName: attributes.name, + username, + shouldUpdateApiKey: true, + }); + expect(apiKey).toEqual({ + apiKey: 'MTIzOmFiYw==', + apiKeyCreatedByUser: undefined, + apiKeyOwner: 'test', + }); + expect(rulesClientParams.createAPIKey).toHaveBeenCalledTimes(1); + }); + + test('should get api key from the request if the user is authenticated using api keys', async () => { + rulesClientParams.getAuthenticationAPIKey.mockReturnValueOnce({ + apiKeysEnabled: true, + result: { id: '123', name: '123', api_key: 'abc' }, + }); + rulesClientParams.isAuthenticationTypeAPIKey.mockReturnValueOnce(true); + const apiKey = await createNewAPIKeySet(rulesClientParams, { + id: attributes.alertTypeId, + ruleName: attributes.name, + username, + shouldUpdateApiKey: true, + }); + expect(apiKey).toEqual({ + apiKey: 'MTIzOmFiYw==', + apiKeyCreatedByUser: true, + apiKeyOwner: 'test', + }); + expect(rulesClientParams.getAuthenticationAPIKey).toHaveBeenCalledTimes(1); + expect(rulesClientParams.isAuthenticationTypeAPIKey).toHaveBeenCalledTimes(1); + }); + + test('should throw an error if getting the api key fails', async () => { + rulesClientParams.createAPIKey.mockRejectedValueOnce(new Error('Test failure')); + await expect( + async () => + await createNewAPIKeySet(rulesClientParams, { + id: attributes.alertTypeId, + ruleName: attributes.name, + username, + shouldUpdateApiKey: true, + }) + ).rejects.toThrowErrorMatchingInlineSnapshot( + `"Error creating API key for rule - Test failure"` + ); + }); + + test('should throw an error if getting the api key fails and an error message is passed in', async () => { + rulesClientParams.createAPIKey.mockRejectedValueOnce(new Error('Test failure')); + await expect( + async () => + await createNewAPIKeySet(rulesClientParams, { + id: attributes.alertTypeId, + ruleName: attributes.name, + username, + shouldUpdateApiKey: true, + errorMessage: 'Error updating rule: could not create API key', + }) + ).rejects.toThrowErrorMatchingInlineSnapshot( + `"Error updating rule: could not create API key - Test failure"` + ); + }); + + test('should return null if shouldUpdateApiKey: false', async () => { + const apiKey = await createNewAPIKeySet(rulesClientParams, { + id: attributes.alertTypeId, + ruleName: attributes.name, + username, + shouldUpdateApiKey: false, + }); + expect(apiKey).toEqual({ + apiKey: null, + apiKeyCreatedByUser: null, + apiKeyOwner: null, + }); + }); +}); diff --git a/x-pack/plugins/alerting/server/rules_client/lib/create_new_api_key_set.ts b/x-pack/plugins/alerting/server/rules_client/lib/create_new_api_key_set.ts index 202ab4d23972d..a9d0a7ba9674a 100644 --- a/x-pack/plugins/alerting/server/rules_client/lib/create_new_api_key_set.ts +++ b/x-pack/plugins/alerting/server/rules_client/lib/create_new_api_key_set.ts @@ -13,21 +13,33 @@ import { RulesClientContext } from '../types'; export async function createNewAPIKeySet( context: RulesClientContext, { - attributes, + id, + ruleName, username, + shouldUpdateApiKey, + errorMessage, }: { - attributes: RawRule; + id: string; + ruleName: string; username: string | null; + shouldUpdateApiKey: boolean; + errorMessage?: string; } -): Promise> { +): Promise> { let createdAPIKey = null; + let isAuthTypeApiKey = false; try { - createdAPIKey = await context.createAPIKey( - generateAPIKeyName(attributes.alertTypeId, attributes.name) - ); + isAuthTypeApiKey = context.isAuthenticationTypeAPIKey(); + const name = generateAPIKeyName(id, ruleName); + createdAPIKey = shouldUpdateApiKey + ? isAuthTypeApiKey + ? context.getAuthenticationAPIKey(`${name}-user-created`) + : await context.createAPIKey(name) + : null; } catch (error) { - throw Boom.badRequest(`Error creating API key for rule: ${error.message}`); + const message = errorMessage ? errorMessage : 'Error creating API key for rule'; + throw Boom.badRequest(`${message} - ${error.message}`); } - return apiKeyAsAlertAttributes(createdAPIKey, username); + return apiKeyAsAlertAttributes(createdAPIKey, username, isAuthTypeApiKey); } diff --git a/x-pack/plugins/alerting/server/rules_client/lib/create_rule_saved_object.ts b/x-pack/plugins/alerting/server/rules_client/lib/create_rule_saved_object.ts index 87bc26b31e7fa..fbd812618aad6 100644 --- a/x-pack/plugins/alerting/server/rules_client/lib/create_rule_saved_object.ts +++ b/x-pack/plugins/alerting/server/rules_client/lib/create_rule_saved_object.ts @@ -54,7 +54,7 @@ export async function createRuleSavedObject { for await (const response of rulesFinder.find()) { for (const rule of response.saved_objects) { - if (rule.attributes.apiKey) { + if (rule.attributes.apiKey && !rule.attributes.apiKeyCreatedByUser) { apiKeyToRuleIdMapping[rule.id] = rule.attributes.apiKey; } if (rule.attributes.name) { diff --git a/x-pack/plugins/alerting/server/rules_client/methods/bulk_edit.ts b/x-pack/plugins/alerting/server/rules_client/methods/bulk_edit.ts index f0f2891a954c5..a3d845af2c0af 100644 --- a/x-pack/plugins/alerting/server/rules_client/methods/bulk_edit.ts +++ b/x-pack/plugins/alerting/server/rules_client/methods/bulk_edit.ts @@ -41,8 +41,6 @@ import { applyBulkEditOperation, buildKueryNodeFilter, injectReferencesIntoActions, - generateAPIKeyName, - apiKeyAsAlertAttributes, getBulkSnoozeAttributes, getBulkUnsnoozeAttributes, verifySnoozeScheduleLimit, @@ -61,13 +59,13 @@ import { validateActions, updateMeta, addGeneratedActionValues, + createNewAPIKeySet, } from '../lib'; import { NormalizedAlertAction, BulkOperationError, RuleBulkOperationAggregation, RulesClientContext, - CreateAPIKeyResult, NormalizedAlertActionWithGeneratedValues, } from '../types'; @@ -121,9 +119,17 @@ export type BulkEditOperation = value?: undefined; }; -type ApiKeysMap = Map; +type ApiKeysMap = Map< + string, + { + oldApiKey?: string; + newApiKey?: string; + oldApiKeyCreatedByUser?: boolean | null; + newApiKeyCreatedByUser?: boolean | null; + } +>; -type ApiKeyAttributes = Pick; +type ApiKeyAttributes = Pick; type RuleType = ReturnType; @@ -433,7 +439,10 @@ async function updateRuleAttributesAndParamsInMemory { try { if (rule.attributes.apiKey) { - apiKeysMap.set(rule.id, { oldApiKey: rule.attributes.apiKey }); + apiKeysMap.set(rule.id, { + oldApiKey: rule.attributes.apiKey, + oldApiKeyCreatedByUser: rule.attributes.apiKeyCreatedByUser, + }); } const ruleType = context.ruleTypeRegistry.get(rule.attributes.alertTypeId); @@ -734,23 +743,20 @@ async function prepareApiKeys( hasUpdateApiKeyOperation: boolean, username: string | null ): Promise<{ apiKeyAttributes: ApiKeyAttributes }> { - const shouldUpdateApiKey = attributes.enabled || hasUpdateApiKeyOperation; - - let createdAPIKey: CreateAPIKeyResult | null = null; - try { - createdAPIKey = shouldUpdateApiKey - ? await context.createAPIKey(generateAPIKeyName(ruleType.id, attributes.name)) - : null; - } catch (error) { - throw Error(`Error updating rule: could not create API key - ${error.message}`); - } + const apiKeyAttributes = await createNewAPIKeySet(context, { + id: ruleType.id, + ruleName: attributes.name, + username, + shouldUpdateApiKey: attributes.enabled || hasUpdateApiKeyOperation, + errorMessage: 'Error updating rule: could not create API key', + }); - const apiKeyAttributes = apiKeyAsAlertAttributes(createdAPIKey, username); // collect generated API keys if (apiKeyAttributes.apiKey) { apiKeysMap.set(rule.id, { ...apiKeysMap.get(rule.id), newApiKey: apiKeyAttributes.apiKey, + newApiKeyCreatedByUser: apiKeyAttributes.apiKeyCreatedByUser, }); } @@ -812,7 +818,7 @@ async function saveBulkUpdatedRules( await bulkMarkApiKeysForInvalidation( { apiKeys: Array.from(apiKeysMap.values()) - .filter((value) => value.newApiKey) + .filter((value) => value.newApiKey && !value.newApiKeyCreatedByUser) .map((value) => value.newApiKey as string), }, context.logger, @@ -824,13 +830,15 @@ async function saveBulkUpdatedRules( result.saved_objects.map(({ id, error }) => { const oldApiKey = apiKeysMap.get(id)?.oldApiKey; + const oldApiKeyCreatedByUser = apiKeysMap.get(id)?.oldApiKeyCreatedByUser; const newApiKey = apiKeysMap.get(id)?.newApiKey; + const newApiKeyCreatedByUser = apiKeysMap.get(id)?.newApiKeyCreatedByUser; // if SO wasn't saved and has new API key it will be invalidated - if (error && newApiKey) { + if (error && newApiKey && !newApiKeyCreatedByUser) { apiKeysToInvalidate.push(newApiKey); // if SO saved and has old Api Key it will be invalidate - } else if (!error && oldApiKey) { + } else if (!error && oldApiKey && !oldApiKeyCreatedByUser) { apiKeysToInvalidate.push(oldApiKey); } }); diff --git a/x-pack/plugins/alerting/server/rules_client/methods/bulk_enable.ts b/x-pack/plugins/alerting/server/rules_client/methods/bulk_enable.ts index 03643ccc02ac2..7273855379a1c 100644 --- a/x-pack/plugins/alerting/server/rules_client/methods/bulk_enable.ts +++ b/x-pack/plugins/alerting/server/rules_client/methods/bulk_enable.ts @@ -146,7 +146,12 @@ const bulkEnableRulesWithOCC = async ( const updatedAttributes = updateMeta(context, { ...rule.attributes, ...(!rule.attributes.apiKey && - (await createNewAPIKeySet(context, { attributes: rule.attributes, username }))), + (await createNewAPIKeySet(context, { + id: rule.attributes.alertTypeId, + ruleName: rule.attributes.name, + username, + shouldUpdateApiKey: true, + }))), enabled: true, updatedBy: username, updatedAt: new Date().toISOString(), diff --git a/x-pack/plugins/alerting/server/rules_client/methods/clone.ts b/x-pack/plugins/alerting/server/rules_client/methods/clone.ts index ac58b213905be..8cb5aee60ca7f 100644 --- a/x-pack/plugins/alerting/server/rules_client/methods/clone.ts +++ b/x-pack/plugins/alerting/server/rules_client/methods/clone.ts @@ -17,8 +17,7 @@ import { parseDuration } from '../../../common/parse_duration'; import { ruleAuditEvent, RuleAuditAction } from '../common/audit_events'; import { getRuleExecutionStatusPending } from '../../lib/rule_execution_status'; import { isDetectionEngineAADRuleType } from '../../saved_objects/migrations/utils'; -import { generateAPIKeyName, apiKeyAsAlertAttributes } from '../common'; -import { createRuleSavedObject } from '../lib'; +import { createNewAPIKeySet, createRuleSavedObject } from '../lib'; import { RulesClientContext } from '../types'; export type CloneArguments = [string, { newId?: string }]; @@ -95,18 +94,18 @@ export async function clone( const createTime = Date.now(); const lastRunTimestamp = new Date(); const legacyId = Semver.lt(context.kibanaVersion, '8.0.0') ? id : null; - let createdAPIKey = null; - try { - createdAPIKey = ruleSavedObject.attributes.enabled - ? await context.createAPIKey(generateAPIKeyName(ruleType.id, ruleName)) - : null; - } catch (error) { - throw Boom.badRequest(`Error creating rule: could not create API key - ${error.message}`); - } + const apiKeyAttributes = await createNewAPIKeySet(context, { + id: ruleType.id, + ruleName, + username, + shouldUpdateApiKey: ruleSavedObject.attributes.enabled, + errorMessage: 'Error creating rule: could not create API key', + }); + const rawRule: RawRule = { ...ruleSavedObject.attributes, name: ruleName, - ...apiKeyAsAlertAttributes(createdAPIKey, username), + ...apiKeyAttributes, legacyId, createdBy: username, updatedBy: username, diff --git a/x-pack/plugins/alerting/server/rules_client/methods/create.ts b/x-pack/plugins/alerting/server/rules_client/methods/create.ts index 4381392b7b091..4d2e586dcaade 100644 --- a/x-pack/plugins/alerting/server/rules_client/methods/create.ts +++ b/x-pack/plugins/alerting/server/rules_client/methods/create.ts @@ -40,6 +40,7 @@ export interface CreateOptions { | 'updatedAt' | 'apiKey' | 'apiKeyOwner' + | 'apiKeyCreatedByUser' | 'muteAll' | 'mutedInstanceIds' | 'actions' @@ -91,11 +92,20 @@ export async function create( const username = await context.getUserName(); let createdAPIKey = null; + let isAuthTypeApiKey = false; try { + isAuthTypeApiKey = context.isAuthenticationTypeAPIKey(); + const name = generateAPIKeyName(ruleType.id, data.name); createdAPIKey = data.enabled - ? await withSpan({ name: 'createAPIKey', type: 'rules' }, () => - context.createAPIKey(generateAPIKeyName(ruleType.id, data.name)) - ) + ? isAuthTypeApiKey + ? context.getAuthenticationAPIKey(`${name}-user-created`) + : await withSpan( + { + name: 'createAPIKey', + type: 'rules', + }, + () => context.createAPIKey(name) + ) : null; } catch (error) { throw Boom.badRequest(`Error creating rule: could not create API key - ${error.message}`); @@ -144,7 +154,7 @@ export async function create( const rawRule: RawRule = { ...data, - ...apiKeyAsAlertAttributes(createdAPIKey, username), + ...apiKeyAsAlertAttributes(createdAPIKey, username, isAuthTypeApiKey), legacyId, actions, createdBy: username, diff --git a/x-pack/plugins/alerting/server/rules_client/methods/delete.ts b/x-pack/plugins/alerting/server/rules_client/methods/delete.ts index 406184e8f013e..ca6feefe3ff9b 100644 --- a/x-pack/plugins/alerting/server/rules_client/methods/delete.ts +++ b/x-pack/plugins/alerting/server/rules_client/methods/delete.ts @@ -23,6 +23,7 @@ export async function deleteRule(context: RulesClientContext, { id }: { id: stri async function deleteWithOCC(context: RulesClientContext, { id }: { id: string }) { let taskIdToRemove: string | undefined | null; let apiKeyToInvalidate: string | null = null; + let apiKeyCreatedByUser: boolean | undefined | null = false; let attributes: RawRule; try { @@ -31,6 +32,7 @@ async function deleteWithOCC(context: RulesClientContext, { id }: { id: string } namespace: context.namespace, }); apiKeyToInvalidate = decryptedAlert.attributes.apiKey; + apiKeyCreatedByUser = decryptedAlert.attributes.apiKeyCreatedByUser; taskIdToRemove = decryptedAlert.attributes.scheduledTaskId; attributes = decryptedAlert.attributes; } catch (e) { @@ -73,7 +75,7 @@ async function deleteWithOCC(context: RulesClientContext, { id }: { id: string } await Promise.all([ taskIdToRemove ? context.taskManager.removeIfExists(taskIdToRemove) : null, - apiKeyToInvalidate + apiKeyToInvalidate && !apiKeyCreatedByUser ? bulkMarkApiKeysForInvalidation( { apiKeys: [apiKeyToInvalidate] }, context.logger, diff --git a/x-pack/plugins/alerting/server/rules_client/methods/enable.ts b/x-pack/plugins/alerting/server/rules_client/methods/enable.ts index 6e93dee530665..a70b71b7b5116 100644 --- a/x-pack/plugins/alerting/server/rules_client/methods/enable.ts +++ b/x-pack/plugins/alerting/server/rules_client/methods/enable.ts @@ -83,7 +83,13 @@ async function enableWithOCC(context: RulesClientContext, { id }: { id: string } const updateAttributes = updateMeta(context, { ...attributes, - ...(!existingApiKey && (await createNewAPIKeySet(context, { attributes, username }))), + ...(!existingApiKey && + (await createNewAPIKeySet(context, { + id: attributes.alertTypeId, + ruleName: attributes.name, + username, + shouldUpdateApiKey: true, + }))), ...(attributes.monitoring && { monitoring: resetMonitoringLastRun(attributes.monitoring), }), diff --git a/x-pack/plugins/alerting/server/rules_client/methods/update.ts b/x-pack/plugins/alerting/server/rules_client/methods/update.ts index bbd99ae201501..d2b57d44fc528 100644 --- a/x-pack/plugins/alerting/server/rules_client/methods/update.ts +++ b/x-pack/plugins/alerting/server/rules_client/methods/update.ts @@ -32,8 +32,8 @@ import { getPartialRuleFromRaw, addGeneratedActionValues, incrementRevision, + createNewAPIKeySet, } from '../lib'; -import { generateAPIKeyName, apiKeyAsAlertAttributes } from '../common'; export interface UpdateOptions { id: string; @@ -122,7 +122,7 @@ async function updateWithOCC( ); await Promise.all([ - alertSavedObject.attributes.apiKey + alertSavedObject.attributes.apiKey && !alertSavedObject.attributes.apiKeyCreatedByUser ? bulkMarkApiKeysForInvalidation( { apiKeys: [alertSavedObject.attributes.apiKey] }, context.logger, @@ -206,16 +206,13 @@ async function updateAlert( const username = await context.getUserName(); - let createdAPIKey = null; - try { - createdAPIKey = attributes.enabled - ? await context.createAPIKey(generateAPIKeyName(ruleType.id, data.name)) - : null; - } catch (error) { - throw Boom.badRequest(`Error updating rule: could not create API key - ${error.message}`); - } - - const apiKeyAttributes = apiKeyAsAlertAttributes(createdAPIKey, username); + const apiKeyAttributes = await createNewAPIKeySet(context, { + id: ruleType.id, + ruleName: data.name, + username, + shouldUpdateApiKey: attributes.enabled, + errorMessage: 'Error updating rule: could not create API key', + }); const notifyWhen = getRuleNotifyWhenType(data.notifyWhen ?? null, data.throttle ?? null); // Increment revision if applicable field has changed @@ -260,7 +257,12 @@ async function updateAlert( } catch (e) { // Avoid unused API key await bulkMarkApiKeysForInvalidation( - { apiKeys: createAttributes.apiKey ? [createAttributes.apiKey] : [] }, + { + apiKeys: + createAttributes.apiKey && !createAttributes.apiKeyCreatedByUser + ? [createAttributes.apiKey] + : [], + }, context.logger, context.unsecuredSavedObjectsClient ); diff --git a/x-pack/plugins/alerting/server/rules_client/methods/update_api_key.ts b/x-pack/plugins/alerting/server/rules_client/methods/update_api_key.ts index abb7d32404d57..4fefb5a8b367e 100644 --- a/x-pack/plugins/alerting/server/rules_client/methods/update_api_key.ts +++ b/x-pack/plugins/alerting/server/rules_client/methods/update_api_key.ts @@ -5,14 +5,12 @@ * 2.0. */ -import Boom from '@hapi/boom'; import { RawRule } from '../../types'; import { WriteOperations, AlertingAuthorizationEntity } from '../../authorization'; import { retryIfConflicts } from '../../lib/retry_if_conflicts'; import { bulkMarkApiKeysForInvalidation } from '../../invalidate_pending_api_keys/bulk_mark_api_keys_for_invalidation'; import { ruleAuditEvent, RuleAuditAction } from '../common/audit_events'; -import { generateAPIKeyName, apiKeyAsAlertAttributes } from '../common'; -import { updateMeta } from '../lib'; +import { createNewAPIKeySet, updateMeta } from '../lib'; import { RulesClientContext } from '../types'; export async function updateApiKey( @@ -27,7 +25,8 @@ export async function updateApiKey( } async function updateApiKeyWithOCC(context: RulesClientContext, { id }: { id: string }) { - let apiKeyToInvalidate: string | null = null; + let oldApiKeyToInvalidate: string | null = null; + let oldApiKeyCreatedByUser: boolean | undefined | null = false; let attributes: RawRule; let version: string | undefined; @@ -36,7 +35,8 @@ async function updateApiKeyWithOCC(context: RulesClientContext, { id }: { id: st await context.encryptedSavedObjectsClient.getDecryptedAsInternalUser('alert', id, { namespace: context.namespace, }); - apiKeyToInvalidate = decryptedAlert.attributes.apiKey; + oldApiKeyToInvalidate = decryptedAlert.attributes.apiKey; + oldApiKeyCreatedByUser = decryptedAlert.attributes.apiKeyCreatedByUser; attributes = decryptedAlert.attributes; version = decryptedAlert.version; } catch (e) { @@ -73,20 +73,17 @@ async function updateApiKeyWithOCC(context: RulesClientContext, { id }: { id: st const username = await context.getUserName(); - let createdAPIKey = null; - try { - createdAPIKey = await context.createAPIKey( - generateAPIKeyName(attributes.alertTypeId, attributes.name) - ); - } catch (error) { - throw Boom.badRequest( - `Error updating API key for rule: could not create API key - ${error.message}` - ); - } + const apiKeyAttributes = await createNewAPIKeySet(context, { + id: attributes.alertTypeId, + ruleName: attributes.name, + username, + shouldUpdateApiKey: true, + errorMessage: 'Error updating API key for rule: could not create API key', + }); const updateAttributes = updateMeta(context, { ...attributes, - ...apiKeyAsAlertAttributes(createdAPIKey, username), + ...apiKeyAttributes, updatedAt: new Date().toISOString(), updatedBy: username, }); @@ -106,16 +103,21 @@ async function updateApiKeyWithOCC(context: RulesClientContext, { id }: { id: st } catch (e) { // Avoid unused API key await bulkMarkApiKeysForInvalidation( - { apiKeys: updateAttributes.apiKey ? [updateAttributes.apiKey] : [] }, + { + apiKeys: + updateAttributes.apiKey && !updateAttributes.apiKeyCreatedByUser + ? [updateAttributes.apiKey] + : [], + }, context.logger, context.unsecuredSavedObjectsClient ); throw e; } - if (apiKeyToInvalidate) { + if (oldApiKeyToInvalidate && !oldApiKeyCreatedByUser) { await bulkMarkApiKeysForInvalidation( - { apiKeys: [apiKeyToInvalidate] }, + { apiKeys: [oldApiKeyToInvalidate] }, context.logger, context.unsecuredSavedObjectsClient ); diff --git a/x-pack/plugins/alerting/server/rules_client/rules_client.ts b/x-pack/plugins/alerting/server/rules_client/rules_client.ts index 91f9910f4f274..fbf441705481d 100644 --- a/x-pack/plugins/alerting/server/rules_client/rules_client.ts +++ b/x-pack/plugins/alerting/server/rules_client/rules_client.ts @@ -71,6 +71,7 @@ export const fieldsToExcludeFromRevisionUpdates: ReadonlySet = { getActionsClient: jest.fn(), getEventLogClient: jest.fn(), kibanaVersion, + isAuthenticationTypeAPIKey: jest.fn(), + getAuthenticationAPIKey: jest.fn(), }; beforeEach(() => { diff --git a/x-pack/plugins/alerting/server/rules_client/tests/bulk_delete.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/bulk_delete.test.ts index b61ef395d4e72..8e9f1a79b8de0 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/bulk_delete.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/bulk_delete.test.ts @@ -19,7 +19,13 @@ import { auditLoggerMock } from '@kbn/security-plugin/server/audit/mocks'; import { getBeforeSetup, setGlobalDate } from './lib'; import { bulkMarkApiKeysForInvalidation } from '../../invalidate_pending_api_keys/bulk_mark_api_keys_for_invalidation'; import { loggerMock } from '@kbn/logging-mocks'; -import { enabledRule1, enabledRule2, returnedRule1, returnedRule2 } from './test_helpers'; +import { + defaultRule, + enabledRule1, + enabledRule2, + returnedRule1, + returnedRule2, +} from './test_helpers'; jest.mock('../../invalidate_pending_api_keys/bulk_mark_api_keys_for_invalidation', () => ({ bulkMarkApiKeysForInvalidation: jest.fn(), @@ -53,6 +59,8 @@ const rulesClientParams: jest.Mocked = { kibanaVersion, auditLogger, minimumScheduleInterval: { value: '1m', enforce: false }, + isAuthenticationTypeAPIKey: jest.fn(), + getAuthenticationAPIKey: jest.fn(), }; const getBulkOperationStatusErrorResponse = (statusCode: number) => ({ @@ -66,6 +74,36 @@ const getBulkOperationStatusErrorResponse = (statusCode: number) => ({ }, }); +export const enabledRule3 = { + ...defaultRule, + id: 'id3', + attributes: { + ...defaultRule.attributes, + enabled: true, + scheduledTaskId: 'id3', + apiKey: Buffer.from('789:ghi').toString('base64'), + apiKeyCreatedByUser: true, + }, +}; + +export const returnedRule3 = { + actions: [], + alertTypeId: 'fakeType', + apiKey: 'Nzg5OmdoaQ==', + apiKeyCreatedByUser: true, + consumer: 'fakeConsumer', + enabled: true, + id: 'id3', + name: 'fakeName', + notifyWhen: undefined, + params: undefined, + schedule: { + interval: '5m', + }, + scheduledTaskId: 'id3', + snoozeSchedule: [], +}; + beforeEach(() => { getBeforeSetup(rulesClientParams, taskManager, ruleTypeRegistry); jest.clearAllMocks(); @@ -77,7 +115,7 @@ describe('bulkDelete', () => { let rulesClient: RulesClient; const mockCreatePointInTimeFinderAsInternalUser = ( - response = { saved_objects: [enabledRule1, enabledRule2] } + response = { saved_objects: [enabledRule1, enabledRule2, enabledRule3] } ) => { encryptedSavedObjects.createPointInTimeFinderDecryptedAsInternalUser = jest .fn() @@ -126,11 +164,12 @@ describe('bulkDelete', () => { }); }); - test('should try to delete rules, one successful and one with 500 error', async () => { + test('should try to delete rules, two successful and one with 500 error', async () => { unsecuredSavedObjectsClient.bulkDelete.mockResolvedValue({ statuses: [ { id: 'id1', type: 'alert', success: true }, getBulkOperationStatusErrorResponse(500), + { id: 'id3', type: 'alert', success: true }, ], }); @@ -140,9 +179,10 @@ describe('bulkDelete', () => { expect(unsecuredSavedObjectsClient.bulkDelete).toHaveBeenCalledWith([ enabledRule1, enabledRule2, + enabledRule3, ]); expect(taskManager.bulkRemove).toHaveBeenCalledTimes(1); - expect(taskManager.bulkRemove).toHaveBeenCalledWith(['id1']); + expect(taskManager.bulkRemove).toHaveBeenCalledWith(['id1', 'id3']); expect(bulkMarkApiKeysForInvalidation).toHaveBeenCalledTimes(1); expect(bulkMarkApiKeysForInvalidation).toHaveBeenCalledWith( { apiKeys: ['MTIzOmFiYw=='] }, @@ -150,7 +190,7 @@ describe('bulkDelete', () => { expect.anything() ); expect(result).toStrictEqual({ - rules: [returnedRule1], + rules: [returnedRule1, returnedRule3], errors: [{ message: 'UPS', rule: { id: 'id2', name: 'fakeName' }, status: 500 }], total: 2, taskIdsFailedToBeDeleted: [], @@ -313,6 +353,25 @@ describe('bulkDelete', () => { ); }); + test('should not mark API keys for invalidation if the user is authenticated using an api key', async () => { + unsecuredSavedObjectsClient.bulkDelete.mockResolvedValue({ + statuses: [ + { id: 'id3', type: 'alert', success: true }, + { id: 'id1', type: 'alert', success: true }, + { id: 'id2', type: 'alert', success: true }, + ], + }); + + await rulesClient.bulkDeleteRules({ filter: 'fake_filter' }); + + expect(bulkMarkApiKeysForInvalidation).toHaveBeenCalledTimes(1); + expect(bulkMarkApiKeysForInvalidation).toHaveBeenCalledWith( + { apiKeys: ['MTIzOmFiYw==', 'MzIxOmFiYw=='] }, + expect.anything(), + expect.anything() + ); + }); + describe('taskManager', () => { test('should return task id if deleting task failed', async () => { unsecuredSavedObjectsClient.bulkDelete.mockResolvedValue({ diff --git a/x-pack/plugins/alerting/server/rules_client/tests/bulk_disable.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/bulk_disable.test.ts index e69dd1b0ffb44..03c637adaae00 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/bulk_disable.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/bulk_disable.test.ts @@ -73,6 +73,8 @@ const rulesClientParams: jest.Mocked = { auditLogger, eventLogger, minimumScheduleInterval: { value: '1m', enforce: false }, + isAuthenticationTypeAPIKey: jest.fn(), + getAuthenticationAPIKey: jest.fn(), }; beforeEach(() => { diff --git a/x-pack/plugins/alerting/server/rules_client/tests/bulk_edit.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/bulk_edit.test.ts index 25cbbb7471b77..b91bb1cae5e3c 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/bulk_edit.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/bulk_edit.test.ts @@ -47,6 +47,9 @@ const auditLogger = auditLoggerMock.create(); const kibanaVersion = 'v8.2.0'; const createAPIKeyMock = jest.fn(); +const isAuthenticationTypeApiKeyMock = jest.fn(); +const getAuthenticationApiKeyMock = jest.fn(); + const rulesClientParams: jest.Mocked = { taskManager, ruleTypeRegistry, @@ -64,6 +67,8 @@ const rulesClientParams: jest.Mocked = { kibanaVersion, auditLogger, minimumScheduleInterval: { value: '1m', enforce: false }, + isAuthenticationTypeAPIKey: isAuthenticationTypeApiKeyMock, + getAuthenticationAPIKey: getAuthenticationApiKeyMock, }; const paramsModifier = jest.fn(); @@ -104,6 +109,7 @@ describe('bulkEdit()', () => { attributes: { ...existingRule.attributes, apiKey: MOCK_API_KEY, + apiKeyCreatedByUser: false, }, }; @@ -580,6 +586,7 @@ describe('bulkEdit()', () => { ], apiKey: null, apiKeyOwner: null, + apiKeyCreatedByUser: null, meta: { versionApiKeyLastmodified: 'v8.2.0' }, name: 'my rule name', enabled: false, @@ -781,6 +788,7 @@ describe('bulkEdit()', () => { ], apiKey: null, apiKeyOwner: null, + apiKeyCreatedByUser: null, meta: { versionApiKeyLastmodified: 'v8.2.0' }, name: 'my rule name', enabled: false, @@ -2070,6 +2078,142 @@ describe('bulkEdit()', () => { }); expect(rulesClientParams.createAPIKey).toHaveBeenCalledWith('Alerting: myType/my rule name'); }); + + describe('set by the user when authenticated using api keys', () => { + beforeEach(() => { + isAuthenticationTypeApiKeyMock.mockReturnValue(true); + getAuthenticationApiKeyMock.mockReturnValue({ + apiKeysEnabled: true, + result: { api_key: '111' }, + }); + mockCreatePointInTimeFinderAsInternalUser({ + saved_objects: [ + { + ...existingDecryptedRule, + attributes: { + ...existingDecryptedRule.attributes, + enabled: true, + apiKeyCreatedByUser: true, + }, + }, + ], + }); + }); + + test('should not call bulkMarkApiKeysForInvalidation', async () => { + await rulesClient.bulkEdit({ + filter: 'alert.attributes.tags: "APM"', + operations: [ + { + field: 'tags', + operation: 'add', + value: ['test-1'], + }, + ], + }); + + expect(bulkMarkApiKeysForInvalidation).not.toHaveBeenCalled(); + }); + + test('should call bulkMarkApiKeysForInvalidation with empty array if bulkCreate failed', async () => { + unsecuredSavedObjectsClient.bulkCreate.mockImplementation(() => { + throw new Error('Fail'); + }); + + await expect( + rulesClient.bulkEdit({ + filter: 'alert.attributes.tags: "APM"', + operations: [ + { + field: 'tags', + operation: 'add', + value: ['test-1'], + }, + ], + }) + ).rejects.toThrow('Fail'); + + expect(bulkMarkApiKeysForInvalidation).toHaveBeenCalledTimes(1); + expect(bulkMarkApiKeysForInvalidation).toHaveBeenCalledWith( + { apiKeys: [] }, + expect.any(Object), + expect.any(Object) + ); + }); + + test('should call bulkMarkApiKeysForInvalidation with empty array if SO update failed', async () => { + unsecuredSavedObjectsClient.bulkCreate.mockResolvedValue({ + saved_objects: [ + { + id: '1', + type: 'alert', + attributes: { + enabled: true, + tags: ['foo'], + alertTypeId: 'myType', + schedule: { interval: '1m' }, + consumer: 'myApp', + scheduledTaskId: 'task-123', + params: { index: ['test-index-*'] }, + throttle: null, + notifyWhen: null, + actions: [], + }, + references: [], + version: '123', + error: { + error: 'test failure', + statusCode: 500, + message: 'test failure', + }, + }, + ], + }); + + await rulesClient.bulkEdit({ + filter: 'alert.attributes.tags: "APM"', + operations: [ + { + field: 'tags', + operation: 'add', + value: ['test-1'], + }, + ], + }); + + expect(bulkMarkApiKeysForInvalidation).not.toHaveBeenCalled(); + }); + + test('should not call get apiKey if rule is disabled', async () => { + await rulesClient.bulkEdit({ + filter: 'alert.attributes.tags: "APM"', + operations: [ + { + field: 'tags', + operation: 'add', + value: ['test-1'], + }, + ], + }); + expect(rulesClientParams.getAuthenticationAPIKey).not.toHaveBeenCalledWith(); + }); + + test('should return error in rule errors if key is not generated', async () => { + await rulesClient.bulkEdit({ + filter: 'alert.attributes.tags: "APM"', + operations: [ + { + field: 'tags', + operation: 'add', + value: ['test-1'], + }, + ], + }); + expect(rulesClientParams.getAuthenticationAPIKey).toHaveBeenCalledWith( + 'Alerting: myType/my rule name-user-created' + ); + }); + }); }); describe('params validation', () => { diff --git a/x-pack/plugins/alerting/server/rules_client/tests/bulk_enable.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/bulk_enable.test.ts index 0aeca36a2b458..b82001e83886c 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/bulk_enable.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/bulk_enable.test.ts @@ -64,6 +64,8 @@ const rulesClientParams: jest.Mocked = { kibanaVersion, auditLogger, minimumScheduleInterval: { value: '1m', enforce: false }, + isAuthenticationTypeAPIKey: jest.fn(), + getAuthenticationAPIKey: jest.fn(), }; beforeEach(() => { diff --git a/x-pack/plugins/alerting/server/rules_client/tests/clear_expired_snoozes.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/clear_expired_snoozes.test.ts index 3952ed4c9003c..04b015986579e 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/clear_expired_snoozes.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/clear_expired_snoozes.test.ts @@ -60,6 +60,8 @@ const rulesClientParams: jest.Mocked = { kibanaVersion, auditLogger, eventLogger, + isAuthenticationTypeAPIKey: jest.fn(), + getAuthenticationAPIKey: jest.fn(), }; describe('clearExpiredSnoozes()', () => { diff --git a/x-pack/plugins/alerting/server/rules_client/tests/create.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/create.test.ts index b59d1e36028e8..2be00b0fc5352 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/create.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/create.test.ts @@ -68,6 +68,8 @@ const rulesClientParams: jest.Mocked = { kibanaVersion, auditLogger, minimumScheduleInterval: { value: '1m', enforce: false }, + isAuthenticationTypeAPIKey: jest.fn(), + getAuthenticationAPIKey: jest.fn(), }; beforeEach(() => { @@ -415,6 +417,7 @@ describe('create()', () => { ], "alertTypeId": "123", "apiKey": null, + "apiKeyCreatedByUser": null, "apiKeyOwner": null, "consumer": "bar", "createdAt": "2019-02-12T21:01:22.479Z", @@ -636,6 +639,7 @@ describe('create()', () => { ], "alertTypeId": "123", "apiKey": null, + "apiKeyCreatedByUser": null, "apiKeyOwner": null, "consumer": "bar", "createdAt": "2019-02-12T21:01:22.479Z", @@ -1087,6 +1091,7 @@ describe('create()', () => { alertTypeId: '123', apiKey: null, apiKeyOwner: null, + apiKeyCreatedByUser: null, consumer: 'bar', createdAt: '2019-02-12T21:01:22.479Z', createdBy: 'elastic', @@ -1297,6 +1302,7 @@ describe('create()', () => { alertTypeId: '123', apiKey: null, apiKeyOwner: null, + apiKeyCreatedByUser: null, consumer: 'bar', createdAt: '2019-02-12T21:01:22.479Z', createdBy: 'elastic', @@ -1477,6 +1483,7 @@ describe('create()', () => { alertTypeId: '123', apiKey: null, apiKeyOwner: null, + apiKeyCreatedByUser: null, legacyId: null, consumer: 'bar', createdAt: '2019-02-12T21:01:22.479Z', @@ -1650,6 +1657,7 @@ describe('create()', () => { params: { bar: true }, apiKey: null, apiKeyOwner: null, + apiKeyCreatedByUser: null, createdBy: 'elastic', createdAt: '2019-02-12T21:01:22.479Z', updatedBy: 'elastic', @@ -1787,6 +1795,7 @@ describe('create()', () => { params: { bar: true }, apiKey: null, apiKeyOwner: null, + apiKeyCreatedByUser: null, createdBy: 'elastic', createdAt: '2019-02-12T21:01:22.479Z', updatedBy: 'elastic', @@ -1924,6 +1933,7 @@ describe('create()', () => { params: { bar: true }, apiKey: null, apiKeyOwner: null, + apiKeyCreatedByUser: null, createdBy: 'elastic', createdAt: '2019-02-12T21:01:22.479Z', updatedBy: 'elastic', @@ -2083,6 +2093,7 @@ describe('create()', () => { ], apiKeyOwner: null, apiKey: null, + apiKeyCreatedByUser: null, legacyId: null, createdBy: 'elastic', updatedBy: 'elastic', @@ -2562,6 +2573,7 @@ describe('create()', () => { params: { bar: true }, apiKey: null, apiKeyOwner: null, + apiKeyCreatedByUser: null, createdBy: 'elastic', createdAt: '2019-02-12T21:01:22.479Z', updatedBy: 'elastic', @@ -3309,4 +3321,137 @@ describe('create()', () => { expect(unsecuredSavedObjectsClient.create).not.toHaveBeenCalled(); expect(taskManager.schedule).not.toHaveBeenCalled(); }); + + test('calls the authentication API key function if the user is authenticated using an api key', async () => { + const data = getMockData(); + rulesClientParams.getAuthenticationAPIKey.mockReturnValueOnce({ + apiKeysEnabled: true, + result: { id: '123', name: '123', api_key: 'abc' }, + }); + rulesClientParams.isAuthenticationTypeAPIKey.mockReturnValueOnce(true); + unsecuredSavedObjectsClient.create.mockResolvedValueOnce({ + id: '1', + type: 'alert', + attributes: { + alertTypeId: '123', + schedule: { interval: '1m' }, + params: { + bar: true, + }, + actions: [ + { + group: 'default', + actionRef: 'action_0', + actionTypeId: 'test', + params: { + foo: true, + }, + }, + ], + }, + references: [ + { + name: 'action_0', + type: 'action', + id: '1', + }, + ], + }); + unsecuredSavedObjectsClient.create.mockResolvedValueOnce({ + id: '1', + type: 'alert', + attributes: { + actions: [], + scheduledTaskId: 'task-123', + }, + references: [ + { + id: '1', + name: 'action_0', + type: 'action', + }, + ], + }); + await rulesClient.create({ data }); + + expect(rulesClientParams.isAuthenticationTypeAPIKey).toHaveBeenCalledTimes(1); + expect(rulesClientParams.getAuthenticationAPIKey).toHaveBeenCalledTimes(1); + expect(unsecuredSavedObjectsClient.create).toHaveBeenCalledWith( + 'alert', + { + actions: [ + { + actionRef: 'action_0', + group: 'default', + actionTypeId: 'test', + params: { foo: true }, + uuid: '151', + }, + ], + alertTypeId: '123', + consumer: 'bar', + name: 'abc', + legacyId: null, + params: { bar: true }, + apiKey: Buffer.from('123:abc').toString('base64'), + apiKeyOwner: 'elastic', + apiKeyCreatedByUser: true, + createdBy: 'elastic', + createdAt: '2019-02-12T21:01:22.479Z', + updatedBy: 'elastic', + updatedAt: '2019-02-12T21:01:22.479Z', + enabled: true, + meta: { + versionApiKeyLastmodified: kibanaVersion, + }, + schedule: { interval: '1m' }, + throttle: null, + notifyWhen: null, + muteAll: false, + snoozeSchedule: [], + mutedInstanceIds: [], + tags: ['foo'], + executionStatus: { + lastExecutionDate: '2019-02-12T21:01:22.479Z', + status: 'pending', + error: null, + warning: null, + }, + monitoring: getDefaultMonitoring('2019-02-12T21:01:22.479Z'), + revision: 0, + running: false, + }, + { + id: 'mock-saved-object-id', + references: [ + { + id: '1', + name: 'action_0', + type: 'action', + }, + ], + } + ); + }); + + test('throws error and does not add API key to invalidatePendingApiKey SO when create saved object fails if the user is authenticated using an api key', async () => { + const data = getMockData(); + rulesClientParams.getAuthenticationAPIKey.mockReturnValueOnce({ + apiKeysEnabled: true, + result: { id: '123', name: '123', api_key: 'abc' }, + }); + rulesClientParams.isAuthenticationTypeAPIKey.mockReturnValueOnce(true); + unsecuredSavedObjectsClient.create.mockRejectedValueOnce(new Error('Test failure')); + await expect(rulesClient.create({ data })).rejects.toThrowErrorMatchingInlineSnapshot( + `"Test failure"` + ); + expect(taskManager.schedule).not.toHaveBeenCalled(); + expect(unsecuredSavedObjectsClient.create).toHaveBeenCalledTimes(1); + expect(bulkMarkApiKeysForInvalidation).toHaveBeenCalledTimes(1); + expect(bulkMarkApiKeysForInvalidation).toHaveBeenCalledWith( + { apiKeys: [] }, + expect.any(Object), + expect.any(Object) + ); + }); }); diff --git a/x-pack/plugins/alerting/server/rules_client/tests/delete.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/delete.test.ts index 6b45c16bcd654..edf03a2d54e42 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/delete.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/delete.test.ts @@ -48,6 +48,8 @@ const rulesClientParams: jest.Mocked = { getEventLogClient: jest.fn(), kibanaVersion, auditLogger, + isAuthenticationTypeAPIKey: jest.fn(), + getAuthenticationAPIKey: jest.fn(), }; beforeEach(() => { @@ -161,6 +163,20 @@ describe('delete()', () => { expect(unsecuredSavedObjectsClient.create).not.toHaveBeenCalled(); }); + test(`doesn't invalidate API key if set by the user when authenticated using api keys`, async () => { + encryptedSavedObjects.getDecryptedAsInternalUser.mockResolvedValue({ + ...existingAlert, + attributes: { + ...existingAlert.attributes, + apiKeyCreatedByUser: true, + }, + }); + + await rulesClient.delete({ id: '1' }); + expect(unsecuredSavedObjectsClient.create).not.toHaveBeenCalled(); + expect(bulkMarkApiKeysForInvalidation).not.toHaveBeenCalled(); + }); + test('swallows error when invalidate API key throws', async () => { unsecuredSavedObjectsClient.create.mockRejectedValueOnce(new Error('Fail')); await rulesClient.delete({ id: '1' }); diff --git a/x-pack/plugins/alerting/server/rules_client/tests/disable.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/disable.test.ts index 57906bcae3b97..09dd8f54d01fd 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/disable.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/disable.test.ts @@ -55,6 +55,8 @@ const rulesClientParams: jest.Mocked = { kibanaVersion, auditLogger, eventLogger, + isAuthenticationTypeAPIKey: jest.fn(), + getAuthenticationAPIKey: jest.fn(), }; beforeEach(() => { diff --git a/x-pack/plugins/alerting/server/rules_client/tests/enable.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/enable.test.ts index 13826b720fa76..d15e96f573a84 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/enable.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/enable.test.ts @@ -48,6 +48,8 @@ const rulesClientParams: jest.Mocked = { getEventLogClient: jest.fn(), kibanaVersion, auditLogger, + isAuthenticationTypeAPIKey: jest.fn(), + getAuthenticationAPIKey: jest.fn(), }; setGlobalDate(); @@ -375,7 +377,7 @@ describe('enable()', () => { }); await expect( async () => await rulesClient.enable({ id: '1' }) - ).rejects.toThrowErrorMatchingInlineSnapshot(`"Error creating API key for rule: no"`); + ).rejects.toThrowErrorMatchingInlineSnapshot(`"Error creating API key for rule - no"`); expect(taskManager.bulkEnable).not.toHaveBeenCalled(); }); diff --git a/x-pack/plugins/alerting/server/rules_client/tests/find.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/find.test.ts index 200b4a291baa1..b8eb4d4d0c478 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/find.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/find.test.ts @@ -45,6 +45,8 @@ const rulesClientParams: jest.Mocked = { getActionsClient: jest.fn(), getEventLogClient: jest.fn(), kibanaVersion, + isAuthenticationTypeAPIKey: jest.fn(), + getAuthenticationAPIKey: jest.fn(), }; beforeEach(() => { diff --git a/x-pack/plugins/alerting/server/rules_client/tests/get.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/get.test.ts index 4465dbffcc48a..30abf4c661d19 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/get.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/get.test.ts @@ -43,6 +43,8 @@ const rulesClientParams: jest.Mocked = { getActionsClient: jest.fn(), getEventLogClient: jest.fn(), kibanaVersion, + isAuthenticationTypeAPIKey: jest.fn(), + getAuthenticationAPIKey: jest.fn(), }; beforeEach(() => { diff --git a/x-pack/plugins/alerting/server/rules_client/tests/get_action_error_log.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/get_action_error_log.test.ts index 90d68301f97aa..fdece7b756d42 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/get_action_error_log.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/get_action_error_log.test.ts @@ -50,6 +50,8 @@ const rulesClientParams: jest.Mocked = { getEventLogClient: jest.fn(), kibanaVersion, auditLogger, + isAuthenticationTypeAPIKey: jest.fn(), + getAuthenticationAPIKey: jest.fn(), }; beforeEach(() => { diff --git a/x-pack/plugins/alerting/server/rules_client/tests/get_alert_state.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/get_alert_state.test.ts index 88f6e466be821..c35bcd6c56311 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/get_alert_state.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/get_alert_state.test.ts @@ -42,6 +42,8 @@ const rulesClientParams: jest.Mocked = { getActionsClient: jest.fn(), getEventLogClient: jest.fn(), kibanaVersion, + isAuthenticationTypeAPIKey: jest.fn(), + getAuthenticationAPIKey: jest.fn(), }; beforeEach(() => { diff --git a/x-pack/plugins/alerting/server/rules_client/tests/get_alert_summary.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/get_alert_summary.test.ts index f9dcfaeb77a50..212977a8381c2 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/get_alert_summary.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/get_alert_summary.test.ts @@ -48,6 +48,8 @@ const rulesClientParams: jest.Mocked = { getActionsClient: jest.fn(), getEventLogClient: jest.fn(), kibanaVersion, + isAuthenticationTypeAPIKey: jest.fn(), + getAuthenticationAPIKey: jest.fn(), }; beforeEach(() => { diff --git a/x-pack/plugins/alerting/server/rules_client/tests/get_execution_log.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/get_execution_log.test.ts index 3f8dfd92ee894..cebe9fa963971 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/get_execution_log.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/get_execution_log.test.ts @@ -51,6 +51,8 @@ const rulesClientParams: jest.Mocked = { getEventLogClient: jest.fn(), kibanaVersion, auditLogger, + isAuthenticationTypeAPIKey: jest.fn(), + getAuthenticationAPIKey: jest.fn(), }; beforeEach(() => { diff --git a/x-pack/plugins/alerting/server/rules_client/tests/list_alert_types.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/list_alert_types.test.ts index 0a3f5981a33a0..4f6afce8d41ec 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/list_alert_types.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/list_alert_types.test.ts @@ -46,6 +46,8 @@ const rulesClientParams: jest.Mocked = { getActionsClient: jest.fn(), getEventLogClient: jest.fn(), kibanaVersion, + isAuthenticationTypeAPIKey: jest.fn(), + getAuthenticationAPIKey: jest.fn(), }; beforeEach(() => { diff --git a/x-pack/plugins/alerting/server/rules_client/tests/mute_all.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/mute_all.test.ts index e2625be88482c..fde2534ec6e21 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/mute_all.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/mute_all.test.ts @@ -42,6 +42,8 @@ const rulesClientParams: jest.Mocked = { getActionsClient: jest.fn(), getEventLogClient: jest.fn(), kibanaVersion, + isAuthenticationTypeAPIKey: jest.fn(), + getAuthenticationAPIKey: jest.fn(), }; beforeEach(() => { diff --git a/x-pack/plugins/alerting/server/rules_client/tests/mute_instance.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/mute_instance.test.ts index d566ea8c18bd5..1582b84e59da8 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/mute_instance.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/mute_instance.test.ts @@ -42,6 +42,8 @@ const rulesClientParams: jest.Mocked = { getActionsClient: jest.fn(), getEventLogClient: jest.fn(), kibanaVersion, + isAuthenticationTypeAPIKey: jest.fn(), + getAuthenticationAPIKey: jest.fn(), }; beforeEach(() => { diff --git a/x-pack/plugins/alerting/server/rules_client/tests/resolve.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/resolve.test.ts index 2caf948ea8753..6f52da62023e9 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/resolve.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/resolve.test.ts @@ -43,6 +43,8 @@ const rulesClientParams: jest.Mocked = { getActionsClient: jest.fn(), getEventLogClient: jest.fn(), kibanaVersion, + isAuthenticationTypeAPIKey: jest.fn(), + getAuthenticationAPIKey: jest.fn(), }; beforeEach(() => { diff --git a/x-pack/plugins/alerting/server/rules_client/tests/run_soon.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/run_soon.test.ts index 138b167549c09..99579a7831b94 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/run_soon.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/run_soon.test.ts @@ -44,6 +44,8 @@ const rulesClientParams: jest.Mocked = { getEventLogClient: jest.fn(), kibanaVersion, auditLogger, + isAuthenticationTypeAPIKey: jest.fn(), + getAuthenticationAPIKey: jest.fn(), }; setGlobalDate(); diff --git a/x-pack/plugins/alerting/server/rules_client/tests/unmute_all.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/unmute_all.test.ts index f5d4cb372f867..737974eeba11e 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/unmute_all.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/unmute_all.test.ts @@ -42,6 +42,8 @@ const rulesClientParams: jest.Mocked = { getActionsClient: jest.fn(), getEventLogClient: jest.fn(), kibanaVersion, + isAuthenticationTypeAPIKey: jest.fn(), + getAuthenticationAPIKey: jest.fn(), }; beforeEach(() => { diff --git a/x-pack/plugins/alerting/server/rules_client/tests/unmute_instance.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/unmute_instance.test.ts index 6f52b6291474f..75f47210f11d1 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/unmute_instance.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/unmute_instance.test.ts @@ -42,6 +42,8 @@ const rulesClientParams: jest.Mocked = { getActionsClient: jest.fn(), getEventLogClient: jest.fn(), kibanaVersion, + isAuthenticationTypeAPIKey: jest.fn(), + getAuthenticationAPIKey: jest.fn(), }; beforeEach(() => { diff --git a/x-pack/plugins/alerting/server/rules_client/tests/update.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/update.test.ts index 074bbfa3552b1..6fcc0cb915a36 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/update.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/update.test.ts @@ -69,6 +69,8 @@ const rulesClientParams: jest.Mocked = { kibanaVersion, auditLogger, minimumScheduleInterval: { value: '1m', enforce: false }, + isAuthenticationTypeAPIKey: jest.fn(), + getAuthenticationAPIKey: jest.fn(), }; beforeEach(() => { @@ -381,6 +383,7 @@ describe('update()', () => { ], "alertTypeId": "myType", "apiKey": null, + "apiKeyCreatedByUser": null, "apiKeyOwner": null, "consumer": "myApp", "enabled": true, @@ -622,6 +625,7 @@ describe('update()', () => { alertTypeId: 'myType', apiKey: null, apiKeyOwner: null, + apiKeyCreatedByUser: null, consumer: 'myApp', enabled: true, meta: { versionApiKeyLastmodified: 'v7.10.0' }, @@ -809,6 +813,7 @@ describe('update()', () => { alertTypeId: 'myType', apiKey: null, apiKeyOwner: null, + apiKeyCreatedByUser: null, consumer: 'myApp', enabled: true, meta: { versionApiKeyLastmodified: 'v7.10.0' }, @@ -987,6 +992,7 @@ describe('update()', () => { ], "alertTypeId": "myType", "apiKey": "MTIzOmFiYw==", + "apiKeyCreatedByUser": undefined, "apiKeyOwner": "elastic", "consumer": "myApp", "enabled": true, @@ -1139,6 +1145,7 @@ describe('update()', () => { ], "alertTypeId": "myType", "apiKey": null, + "apiKeyCreatedByUser": null, "apiKeyOwner": null, "consumer": "myApp", "enabled": false, @@ -2155,6 +2162,7 @@ describe('update()', () => { alertTypeId: 'myType', apiKey: null, apiKeyOwner: null, + apiKeyCreatedByUser: null, consumer: 'myApp', enabled: true, meta: { versionApiKeyLastmodified: 'v7.10.0' }, @@ -2702,6 +2710,7 @@ describe('update()', () => { alertTypeId: 'myType', apiKey: null, apiKeyOwner: null, + apiKeyCreatedByUser: null, consumer: 'myApp', enabled: true, mapped_params: { risk_score: 40, severity: '20-low' }, @@ -2724,4 +2733,210 @@ describe('update()', () => { } ); }); + + it('calls the authentication API key function if the user is authenticated using an api key', async () => { + rulesClientParams.isAuthenticationTypeAPIKey.mockReturnValueOnce(true); + rulesClientParams.getAuthenticationAPIKey.mockReturnValueOnce({ + apiKeysEnabled: true, + result: { id: '123', name: '123', api_key: 'abc' }, + }); + unsecuredSavedObjectsClient.create.mockResolvedValueOnce({ + id: '1', + type: 'alert', + attributes: { + enabled: true, + schedule: { interval: '1m' }, + params: { + bar: true, + }, + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), + notifyWhen: 'onThrottleInterval', + actions: [ + { + group: 'default', + actionRef: 'action_0', + actionTypeId: 'test', + params: { + foo: true, + }, + }, + ], + apiKey: Buffer.from('123:abc').toString('base64'), + apiKeyCreatedByUser: true, + revision: 1, + scheduledTaskId: 'task-123', + }, + updated_at: new Date().toISOString(), + references: [ + { + name: 'action_0', + type: 'action', + id: '1', + }, + ], + }); + encryptedSavedObjects.getDecryptedAsInternalUser.mockResolvedValue({ + ...existingDecryptedAlert, + attributes: { + ...existingDecryptedAlert.attributes, + apiKeyCreatedByUser: true, + }, + }); + + const result = await rulesClient.update({ + id: '1', + data: { + schedule: { interval: '1m' }, + name: 'abc', + tags: ['foo'], + params: { + bar: true, + }, + throttle: '5m', + notifyWhen: 'onThrottleInterval', + actions: [ + { + group: 'default', + id: '1', + params: { + foo: true, + }, + }, + ], + }, + }); + expect(result).toMatchInlineSnapshot(` + Object { + "actions": Array [ + Object { + "actionTypeId": "test", + "group": "default", + "id": "1", + "params": Object { + "foo": true, + }, + }, + ], + "apiKey": "MTIzOmFiYw==", + "apiKeyCreatedByUser": true, + "createdAt": 2019-02-12T21:01:22.479Z, + "enabled": true, + "id": "1", + "notifyWhen": "onThrottleInterval", + "params": Object { + "bar": true, + }, + "revision": 1, + "schedule": Object { + "interval": "1m", + }, + "scheduledTaskId": "task-123", + "updatedAt": 2019-02-12T21:01:22.479Z, + } + `); + expect(unsecuredSavedObjectsClient.create).toHaveBeenCalledTimes(1); + expect(bulkMarkApiKeysForInvalidationMock).not.toHaveBeenCalled(); + + expect(unsecuredSavedObjectsClient.create.mock.calls[0]).toHaveLength(3); + expect(unsecuredSavedObjectsClient.create.mock.calls[0][0]).toEqual('alert'); + expect(unsecuredSavedObjectsClient.create.mock.calls[0][1]).toMatchInlineSnapshot(` + Object { + "actions": Array [ + Object { + "actionRef": "action_0", + "actionTypeId": "test", + "group": "default", + "params": Object { + "foo": true, + }, + "uuid": "152", + }, + ], + "alertTypeId": "myType", + "apiKey": "MTIzOmFiYw==", + "apiKeyCreatedByUser": true, + "apiKeyOwner": "elastic", + "consumer": "myApp", + "enabled": true, + "meta": Object { + "versionApiKeyLastmodified": "v7.10.0", + }, + "name": "abc", + "notifyWhen": "onThrottleInterval", + "params": Object { + "bar": true, + }, + "revision": 1, + "schedule": Object { + "interval": "1m", + }, + "scheduledTaskId": "task-123", + "tags": Array [ + "foo", + ], + "throttle": "5m", + "updatedAt": "2019-02-12T21:01:22.479Z", + "updatedBy": "elastic", + } + `); + expect(unsecuredSavedObjectsClient.create.mock.calls[0][2]).toMatchInlineSnapshot(` + Object { + "id": "1", + "overwrite": true, + "references": Array [ + Object { + "id": "1", + "name": "action_0", + "type": "action", + }, + ], + "version": "123", + } + `); + }); + + test('throws when unsecuredSavedObjectsClient update fails and does not invalidate newly created API key if the user is authenticated using an api key', async () => { + rulesClientParams.isAuthenticationTypeAPIKey.mockReturnValueOnce(true); + rulesClientParams.getAuthenticationAPIKey.mockReturnValueOnce({ + apiKeysEnabled: true, + result: { id: '123', name: '123', api_key: 'abc' }, + }); + unsecuredSavedObjectsClient.create.mockRejectedValue(new Error('Fail')); + await expect( + rulesClient.update({ + id: '1', + data: { + schedule: { interval: '1m' }, + name: 'abc', + tags: ['foo'], + params: { + bar: true, + }, + actions: [ + { + group: 'default', + id: '1', + params: { + foo: true, + }, + frequency: { + summary: false, + notifyWhen: 'onActionGroupChange', + throttle: null, + }, + }, + ], + }, + }) + ).rejects.toThrowErrorMatchingInlineSnapshot(`"Fail"`); + expect(bulkMarkApiKeysForInvalidationMock).toHaveBeenCalledTimes(1); + expect(bulkMarkApiKeysForInvalidationMock).toHaveBeenCalledWith( + { + apiKeys: [], + }, + expect.any(Object), + expect.any(Object) + ); + }); }); diff --git a/x-pack/plugins/alerting/server/rules_client/tests/update_api_key.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/update_api_key.test.ts index 3051a39f238c1..8689e0e7de2ca 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/update_api_key.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/update_api_key.test.ts @@ -49,6 +49,8 @@ const rulesClientParams: jest.Mocked = { getEventLogClient: jest.fn(), kibanaVersion, auditLogger, + isAuthenticationTypeAPIKey: jest.fn(), + getAuthenticationAPIKey: jest.fn(), }; beforeEach(() => { @@ -99,6 +101,7 @@ describe('updateApiKey()', () => { }); test('updates the API key for the alert', async () => { + rulesClientParams.isAuthenticationTypeAPIKey.mockReturnValueOnce(false); rulesClientParams.createAPIKey.mockResolvedValueOnce({ apiKeysEnabled: true, result: { id: '234', name: '123', api_key: 'abc' }, @@ -118,6 +121,7 @@ describe('updateApiKey()', () => { enabled: true, apiKey: Buffer.from('234:abc').toString('base64'), apiKeyOwner: 'elastic', + apiKeyCreatedByUser: false, revision: 0, updatedBy: 'elastic', updatedAt: '2019-02-12T21:01:22.479Z', @@ -146,6 +150,110 @@ describe('updateApiKey()', () => { ); }); + test('updates the API key for the alert and does not invalidate the old api key if created by a user authenticated using an api key', async () => { + encryptedSavedObjects.getDecryptedAsInternalUser.mockResolvedValue({ + ...existingEncryptedAlert, + attributes: { + ...existingEncryptedAlert.attributes, + apiKeyCreatedByUser: true, + }, + }); + rulesClientParams.isAuthenticationTypeAPIKey.mockReturnValueOnce(false); + rulesClientParams.createAPIKey.mockResolvedValueOnce({ + apiKeysEnabled: true, + result: { id: '234', name: '123', api_key: 'abc' }, + }); + await rulesClient.updateApiKey({ id: '1' }); + expect(unsecuredSavedObjectsClient.get).not.toHaveBeenCalled(); + expect(encryptedSavedObjects.getDecryptedAsInternalUser).toHaveBeenCalledWith('alert', '1', { + namespace: 'default', + }); + expect(unsecuredSavedObjectsClient.update).toHaveBeenCalledWith( + 'alert', + '1', + { + schedule: { interval: '10s' }, + alertTypeId: 'myType', + consumer: 'myApp', + enabled: true, + apiKey: Buffer.from('234:abc').toString('base64'), + apiKeyOwner: 'elastic', + apiKeyCreatedByUser: false, + revision: 0, + updatedBy: 'elastic', + updatedAt: '2019-02-12T21:01:22.479Z', + actions: [ + { + group: 'default', + id: '1', + actionTypeId: '1', + actionRef: '1', + params: { + foo: true, + }, + }, + ], + meta: { + versionApiKeyLastmodified: kibanaVersion, + }, + }, + { version: '123' } + ); + expect(bulkMarkApiKeysForInvalidation).not.toHaveBeenCalled(); + }); + + test('calls the authentication API key function if the user is authenticated using an api key', async () => { + encryptedSavedObjects.getDecryptedAsInternalUser.mockResolvedValue({ + ...existingEncryptedAlert, + attributes: { + ...existingEncryptedAlert.attributes, + apiKeyCreatedByUser: true, + }, + }); + rulesClientParams.isAuthenticationTypeAPIKey.mockReturnValueOnce(true); + rulesClientParams.getAuthenticationAPIKey.mockReturnValueOnce({ + apiKeysEnabled: true, + result: { id: '234', name: '123', api_key: 'abc' }, + }); + await rulesClient.updateApiKey({ id: '1' }); + expect(unsecuredSavedObjectsClient.get).not.toHaveBeenCalled(); + expect(encryptedSavedObjects.getDecryptedAsInternalUser).toHaveBeenCalledWith('alert', '1', { + namespace: 'default', + }); + expect(unsecuredSavedObjectsClient.update).toHaveBeenCalledWith( + 'alert', + '1', + { + schedule: { interval: '10s' }, + alertTypeId: 'myType', + consumer: 'myApp', + enabled: true, + apiKey: Buffer.from('234:abc').toString('base64'), + apiKeyOwner: 'elastic', + apiKeyCreatedByUser: true, + revision: 0, + updatedBy: 'elastic', + updatedAt: '2019-02-12T21:01:22.479Z', + actions: [ + { + group: 'default', + id: '1', + actionTypeId: '1', + actionRef: '1', + params: { + foo: true, + }, + }, + ], + meta: { + versionApiKeyLastmodified: kibanaVersion, + }, + }, + { version: '123' } + ); + expect(bulkMarkApiKeysForInvalidation).not.toHaveBeenCalled(); + }); + test('throws an error if API key creation throws', async () => { rulesClientParams.createAPIKey.mockImplementation(() => { throw new Error('no'); diff --git a/x-pack/plugins/alerting/server/rules_client/types.ts b/x-pack/plugins/alerting/server/rules_client/types.ts index 42faafa3db7dd..cb68900e49083 100644 --- a/x-pack/plugins/alerting/server/rules_client/types.ts +++ b/x-pack/plugins/alerting/server/rules_client/types.ts @@ -69,6 +69,8 @@ export interface RulesClientContext { readonly auditLogger?: AuditLogger; readonly eventLogger?: IEventLogger; readonly fieldsToExcludeFromPublicApi: Array; + readonly isAuthenticationTypeAPIKey: () => boolean; + readonly getAuthenticationAPIKey: (name: string) => CreateAPIKeyResult; } export type NormalizedAlertAction = Omit; diff --git a/x-pack/plugins/alerting/server/rules_client_conflict_retries.test.ts b/x-pack/plugins/alerting/server/rules_client_conflict_retries.test.ts index bafb7b78293be..7f1a26b0e4940 100644 --- a/x-pack/plugins/alerting/server/rules_client_conflict_retries.test.ts +++ b/x-pack/plugins/alerting/server/rules_client_conflict_retries.test.ts @@ -53,6 +53,8 @@ const rulesClientParams: jest.Mocked = { getEventLogClient: jest.fn(), kibanaVersion, minimumScheduleInterval: { value: '1m', enforce: false }, + isAuthenticationTypeAPIKey: jest.fn(), + getAuthenticationAPIKey: jest.fn(), }; // this suite consists of two suites running tests against mutable RulesClient APIs: diff --git a/x-pack/plugins/alerting/server/rules_client_factory.test.ts b/x-pack/plugins/alerting/server/rules_client_factory.test.ts index 14363fcabf440..18364256ce4a9 100644 --- a/x-pack/plugins/alerting/server/rules_client_factory.test.ts +++ b/x-pack/plugins/alerting/server/rules_client_factory.test.ts @@ -122,6 +122,8 @@ test('creates a rules client with proper constructor arguments when security is encryptedSavedObjectsClient: rulesClientFactoryParams.encryptedSavedObjectsClient, kibanaVersion: '7.10.0', minimumScheduleInterval: { value: '1m', enforce: false }, + isAuthenticationTypeAPIKey: expect.any(Function), + getAuthenticationAPIKey: expect.any(Function), }); }); @@ -160,6 +162,8 @@ test('creates a rules client with proper constructor arguments', async () => { getEventLogClient: expect.any(Function), kibanaVersion: '7.10.0', minimumScheduleInterval: { value: '1m', enforce: false }, + isAuthenticationTypeAPIKey: expect.any(Function), + getAuthenticationAPIKey: expect.any(Function), }); }); diff --git a/x-pack/plugins/alerting/server/rules_client_factory.ts b/x-pack/plugins/alerting/server/rules_client_factory.ts index e6d77b618f0b0..d99dc12b13e28 100644 --- a/x-pack/plugins/alerting/server/rules_client_factory.ts +++ b/x-pack/plugins/alerting/server/rules_client_factory.ts @@ -12,7 +12,11 @@ import { PluginInitializerContext, } from '@kbn/core/server'; import { PluginStartContract as ActionsPluginStartContract } from '@kbn/actions-plugin/server'; -import { SecurityPluginSetup, SecurityPluginStart } from '@kbn/security-plugin/server'; +import { + HTTPAuthorizationHeader, + SecurityPluginSetup, + SecurityPluginStart, +} from '@kbn/security-plugin/server'; import { EncryptedSavedObjectsClient } from '@kbn/encrypted-saved-objects-plugin/server'; import { TaskManagerStartContract } from '@kbn/task-manager-plugin/server'; import { IEventLogClientService, IEventLogger } from '@kbn/event-log-plugin/server'; @@ -133,6 +137,30 @@ export class RulesClientFactory { return eventLog.getClient(request); }, eventLogger: this.eventLogger, + isAuthenticationTypeAPIKey() { + if (!securityPluginStart) { + return false; + } + const user = securityPluginStart.authc.getCurrentUser(request); + return user && user.authentication_type ? user.authentication_type === 'api_key' : false; + }, + getAuthenticationAPIKey(name: string) { + const authorizationHeader = HTTPAuthorizationHeader.parseFromRequest(request); + if (authorizationHeader && authorizationHeader.credentials) { + const apiKey = Buffer.from(authorizationHeader.credentials, 'base64') + .toString() + .split(':'); + return { + apiKeysEnabled: true, + result: { + name, + id: apiKey[0], + api_key: apiKey[1], + }, + }; + } + return { apiKeysEnabled: false }; + }, }); } } diff --git a/x-pack/plugins/alerting/server/saved_objects/mappings.ts b/x-pack/plugins/alerting/server/saved_objects/mappings.ts index 9e3bc06e7ca39..80260733eafed 100644 --- a/x-pack/plugins/alerting/server/saved_objects/mappings.ts +++ b/x-pack/plugins/alerting/server/saved_objects/mappings.ts @@ -8,6 +8,7 @@ import { SavedObjectsTypeMappingDefinition } from '@kbn/core/server'; export const alertMappings: SavedObjectsTypeMappingDefinition = { + dynamic: false, properties: { enabled: { type: 'boolean', diff --git a/x-pack/plugins/alerting/server/saved_objects/transform_rule_for_export.test.ts b/x-pack/plugins/alerting/server/saved_objects/transform_rule_for_export.test.ts index a5befc94a340a..eea12e78bdc69 100644 --- a/x-pack/plugins/alerting/server/saved_objects/transform_rule_for_export.test.ts +++ b/x-pack/plugins/alerting/server/saved_objects/transform_rule_for_export.test.ts @@ -34,6 +34,7 @@ describe('transform rule for export', () => { updatedAt: date, apiKey: '4tndskbuhewotw4klrhgjewrt9u', apiKeyOwner: 'me', + apiKeyCreatedByUser: true, throttle: null, legacyId: '1', notifyWhen: 'onActionGroupChange', @@ -66,6 +67,7 @@ describe('transform rule for export', () => { updatedAt: date, apiKey: null, apiKeyOwner: null, + apiKeyCreatedByUser: null, throttle: null, legacyId: '2', notifyWhen: 'onActionGroupChange', @@ -91,6 +93,7 @@ describe('transform rule for export', () => { enabled: false, apiKey: null, apiKeyOwner: null, + apiKeyCreatedByUser: null, scheduledTaskId: null, legacyId: null, executionStatus: { diff --git a/x-pack/plugins/alerting/server/saved_objects/transform_rule_for_export.ts b/x-pack/plugins/alerting/server/saved_objects/transform_rule_for_export.ts index 4c1aafa9e5cb7..fe4f1023d8103 100644 --- a/x-pack/plugins/alerting/server/saved_objects/transform_rule_for_export.ts +++ b/x-pack/plugins/alerting/server/saved_objects/transform_rule_for_export.ts @@ -26,6 +26,7 @@ function transformRuleForExport( enabled: false, apiKey: null, apiKeyOwner: null, + apiKeyCreatedByUser: null, scheduledTaskId: null, executionStatus: getRuleExecutionStatusPending(exportDate), }, diff --git a/x-pack/plugins/alerting/server/types.ts b/x-pack/plugins/alerting/server/types.ts index 804c2739c74e7..d1be241612135 100644 --- a/x-pack/plugins/alerting/server/types.ts +++ b/x-pack/plugins/alerting/server/types.ts @@ -357,6 +357,7 @@ export interface RawRule extends SavedObjectAttributes { updatedAt: string; apiKey: string | null; apiKeyOwner: string | null; + apiKeyCreatedByUser?: boolean | null; throttle?: string | null; notifyWhen?: RuleNotifyWhenType | null; muteAll: boolean; diff --git a/x-pack/plugins/apm/common/agent_explorer.ts b/x-pack/plugins/apm/common/agent_explorer.ts index 07ce5ed7ab1f0..642544d4533c1 100644 --- a/x-pack/plugins/apm/common/agent_explorer.ts +++ b/x-pack/plugins/apm/common/agent_explorer.ts @@ -14,3 +14,12 @@ export enum AgentExplorerFieldName { AgentDocsPageUrl = 'agentDocsPageUrl', Instances = 'instances', } + +export interface ElasticApmAgentLatestVersion { + latest_version: string; +} + +export interface OtelAgentLatestVersion { + sdk_latest_version: string; + auto_latest_version?: string; +} diff --git a/x-pack/plugins/apm/public/components/app/settings/agent_explorer/agent_instances/agent_contextual_information/index.tsx b/x-pack/plugins/apm/public/components/app/settings/agent_explorer/agent_instances/agent_contextual_information/index.tsx index 122a58fc1acdb..268340b11e4a2 100644 --- a/x-pack/plugins/apm/public/components/app/settings/agent_explorer/agent_instances/agent_contextual_information/index.tsx +++ b/x-pack/plugins/apm/public/components/app/settings/agent_explorer/agent_instances/agent_contextual_information/index.tsx @@ -8,6 +8,7 @@ import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { TypeOf } from '@kbn/typed-react-router-config'; +import { isEmpty } from 'lodash'; import React from 'react'; import { AgentName } from '../../../../../../../typings/es_schemas/ui/fields/agent'; import { useApmPluginContext } from '../../../../../../context/apm_plugin/use_apm_plugin_context'; @@ -18,6 +19,7 @@ import { StickyProperties } from '../../../../../shared/sticky_properties'; import { getComparisonEnabled } from '../../../../../shared/time_comparison/get_comparison_enabled'; import { TruncateWithTooltip } from '../../../../../shared/truncate_with_tooltip'; import { AgentExplorerDocsLink } from '../../agent_explorer_docs_link'; +import { AgentLatestVersion } from '../../agent_latest_version'; const serviceLabel = i18n.translate( 'xpack.apm.agentInstancesDetails.serviceLabel', @@ -40,6 +42,13 @@ const instancesLabel = i18n.translate( } ); +const latestVersionLabel = i18n.translate( + 'xpack.apm.agentInstancesDetails.latestVersionLabel', + { + defaultMessage: 'Latest agent version', + } +); + const agentDocsLabel = i18n.translate( 'xpack.apm.agentInstancesDetails.agentDocsUrlLabel', { @@ -52,17 +61,25 @@ export function AgentContextualInformation({ serviceName, agentDocsPageUrl, instances, + latestVersion, query, + isLatestVersionsLoading, + latestVersionsFailed, }: { agentName: AgentName; serviceName: string; agentDocsPageUrl?: string; instances: number; + latestVersion?: string; query: TypeOf['query']; + isLatestVersionsLoading: boolean; + latestVersionsFailed: boolean; }) { - const { core } = useApmPluginContext(); + const { core, config } = useApmPluginContext(); + const latestAgentVersionEnabled = !isEmpty(config.latestAgentVersionsUrl); const comparisonEnabled = getComparisonEnabled({ core }); const { rangeFrom, rangeTo } = useDefaultTimeRange(); + const width = latestAgentVersionEnabled ? '20%' : '25%'; const stickyProperties = [ { @@ -88,7 +105,7 @@ export function AgentContextualInformation({ } /> ), - width: '25%', + width, }, { label: agentNameLabel, @@ -100,7 +117,7 @@ export function AgentContextualInformation({ ), - width: '25%', + width, }, { label: instancesLabel, @@ -112,8 +129,25 @@ export function AgentContextualInformation({ ), - width: '25%', + width, }, + ...(latestAgentVersionEnabled + ? [ + { + label: latestVersionLabel, + fieldName: latestVersionLabel, + val: ( + + ), + width, + }, + ] + : []), { label: agentDocsLabel, fieldName: agentDocsLabel, @@ -129,7 +163,7 @@ export function AgentContextualInformation({ } /> ), - width: '25%', + width, }, ]; diff --git a/x-pack/plugins/apm/public/components/app/settings/agent_explorer/agent_instances/index.tsx b/x-pack/plugins/apm/public/components/app/settings/agent_explorer/agent_instances/index.tsx index 33b2b780d4e34..a403bf0c614c8 100644 --- a/x-pack/plugins/apm/public/components/app/settings/agent_explorer/agent_instances/index.tsx +++ b/x-pack/plugins/apm/public/components/app/settings/agent_explorer/agent_instances/index.tsx @@ -58,10 +58,17 @@ function useAgentInstancesFetcher({ serviceName }: { serviceName: string }) { interface Props { agent: AgentExplorerItem; + isLatestVersionsLoading: boolean; + latestVersionsFailed: boolean; onClose: () => void; } -export function AgentInstances({ agent, onClose }: Props) { +export function AgentInstances({ + agent, + isLatestVersionsLoading, + latestVersionsFailed, + onClose, +}: Props) { const { query } = useApmParams('/settings/agent-explorer'); const instances = useAgentInstancesFetcher({ @@ -95,7 +102,10 @@ export function AgentInstances({ agent, onClose }: Props) { serviceName={agent.serviceName} agentDocsPageUrl={agent.agentDocsPageUrl} instances={agent.instances} + latestVersion={agent.latestVersion} query={query} + isLatestVersionsLoading={isLatestVersionsLoading} + latestVersionsFailed={latestVersionsFailed} /> diff --git a/x-pack/plugins/apm/public/components/app/settings/agent_explorer/agent_latest_version/index.tsx b/x-pack/plugins/apm/public/components/app/settings/agent_explorer/agent_latest_version/index.tsx new file mode 100644 index 0000000000000..21fb770269dd1 --- /dev/null +++ b/x-pack/plugins/apm/public/components/app/settings/agent_explorer/agent_latest_version/index.tsx @@ -0,0 +1,58 @@ +/* + * 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 { EuiSkeletonRectangle, EuiToolTip, useEuiTheme } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import React from 'react'; +import { NOT_AVAILABLE_LABEL } from '../../../../../../common/i18n'; +import { AgentName } from '../../../../../../typings/es_schemas/ui/fields/agent'; + +export function AgentLatestVersion({ + agentName, + isLoading, + latestVersion, + failed, +}: { + agentName: AgentName; + isLoading: boolean; + latestVersion?: string; + failed: boolean; +}) { + const { euiTheme } = useEuiTheme(); + + const latestVersionElement = latestVersion ? ( + <>{latestVersion} + ) : ( + <>{NOT_AVAILABLE_LABEL} + ); + + const failedLatestVersionsElement = ( + + <>{NOT_AVAILABLE_LABEL} + + ); + + return ( + + {!failed ? latestVersionElement : failedLatestVersionsElement} + + ); +} diff --git a/x-pack/plugins/apm/public/components/app/settings/agent_explorer/agent_list/index.tsx b/x-pack/plugins/apm/public/components/app/settings/agent_explorer/agent_list/index.tsx index 51055428a9a34..665d454c440fb 100644 --- a/x-pack/plugins/apm/public/components/app/settings/agent_explorer/agent_list/index.tsx +++ b/x-pack/plugins/apm/public/components/app/settings/agent_explorer/agent_list/index.tsx @@ -9,13 +9,16 @@ import { EuiButtonIcon, EuiFlexGroup, EuiFlexItem, + EuiIcon, EuiToolTip, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; +import { isEmpty } from 'lodash'; import React, { useMemo, useState } from 'react'; import { ValuesType } from 'utility-types'; import { AgentExplorerFieldName } from '../../../../../../common/agent_explorer'; import { AgentName } from '../../../../../../typings/es_schemas/ui/fields/agent'; +import { useApmPluginContext } from '../../../../../context/apm_plugin/use_apm_plugin_context'; import { APIReturnType } from '../../../../../services/rest/create_call_apm_api'; import { AgentIcon } from '../../../../shared/agent_icon'; import { EnvironmentBadge } from '../../../../shared/environment_badge'; @@ -24,6 +27,7 @@ import { ITableColumn, ManagedTable } from '../../../../shared/managed_table'; import { TruncateWithTooltip } from '../../../../shared/truncate_with_tooltip'; import { AgentExplorerDocsLink } from '../agent_explorer_docs_link'; import { AgentInstances } from '../agent_instances'; +import { AgentLatestVersion } from '../agent_latest_version'; export type AgentExplorerItem = ValuesType< APIReturnType<'GET /internal/apm/get_agents_per_service'>['items'] @@ -31,9 +35,15 @@ export type AgentExplorerItem = ValuesType< export function getAgentsColumns({ selectedAgent, + isLatestVersionsLoading, + latestAgentVersionEnabled, + latestVersionsFailed, onAgentSelected, }: { selectedAgent?: AgentExplorerItem; + isLatestVersionsLoading: boolean; + latestAgentVersionEnabled: boolean; + latestVersionsFailed: boolean; onAgentSelected: (agent: AgentExplorerItem) => void; }): Array> { return [ @@ -153,6 +163,51 @@ export function getAgentsColumns({ /> ), }, + ...(latestAgentVersionEnabled + ? [ + { + field: AgentExplorerFieldName.AgentLastVersion, + name: ( + + <> + {i18n.translate( + 'xpack.apm.agentExplorerTable.agentLatestVersionColumnLabel', + { defaultMessage: 'Latest Agent Version' } + )} +   + + + + ), + width: '10%', + align: 'center', + truncateText: true, + render: ( + _: any, + { agentName, latestVersion }: AgentExplorerItem + ) => ( + + ), + }, + ] + : []), { field: AgentExplorerFieldName.AgentDocsPageUrl, name: i18n.translate( @@ -177,9 +232,20 @@ interface Props { items: AgentExplorerItem[]; noItemsMessage: React.ReactNode; isLoading: boolean; + isLatestVersionsLoading: boolean; + latestVersionsFailed: boolean; } -export function AgentList({ items, noItemsMessage, isLoading }: Props) { +export function AgentList({ + items, + noItemsMessage, + isLoading, + isLatestVersionsLoading, + latestVersionsFailed, +}: Props) { + const { config } = useApmPluginContext(); + const latestAgentVersionEnabled = !isEmpty(config.latestAgentVersionsUrl); + const [selectedAgent, setSelectedAgent] = useState(); const onAgentSelected = (agent: AgentExplorerItem) => { @@ -191,14 +257,31 @@ export function AgentList({ items, noItemsMessage, isLoading }: Props) { }; const agentColumns = useMemo( - () => getAgentsColumns({ selectedAgent, onAgentSelected }), - [selectedAgent] + () => + getAgentsColumns({ + selectedAgent, + isLatestVersionsLoading, + latestAgentVersionEnabled, + latestVersionsFailed, + onAgentSelected, + }), + [ + selectedAgent, + latestAgentVersionEnabled, + isLatestVersionsLoading, + latestVersionsFailed, + ] ); return ( <> {selectedAgent && ( - + )} { + return agentTelemetryAutoVersion.length > 0 + ? otelLatestVersion?.auto_latest_version + : otelLatestVersion?.sdk_latest_version; +}; + function useAgentExplorerFetcher({ start, end, @@ -63,6 +79,17 @@ function useAgentExplorerFetcher({ ); } +function useLatestAgentVersionsFetcher(latestAgentVersionEnabled: boolean) { + return useFetcher( + (callApmApi) => { + if (latestAgentVersionEnabled) { + return callApmApi('GET /internal/apm/get_latest_agent_versions'); + } + }, + [latestAgentVersionEnabled] + ); +} + export function AgentExplorer() { const history = useHistory(); @@ -74,9 +101,30 @@ export function AgentExplorer() { const rangeTo = 'now'; const { start, end } = useTimeRange({ rangeFrom, rangeTo }); + const { config } = useApmPluginContext(); + const latestAgentVersionEnabled = !isEmpty(config.latestAgentVersionsUrl); + const agents = useAgentExplorerFetcher({ start, end }); + const { data: latestAgentVersions, status: latestAgentVersionsStatus } = + useLatestAgentVersionsFetcher(latestAgentVersionEnabled); const isLoading = agents.status === FETCH_STATUS.LOADING; + const isLatestAgentVersionsLoading = + latestAgentVersionsStatus === FETCH_STATUS.LOADING; + + const agentItems = (agents.data?.items ?? []).map((agent) => ({ + ...agent, + latestVersion: isOpenTelemetryAgentName(agent.agentName) + ? getOtelLatestAgentVersion( + agent.agentTelemetryAutoVersion, + latestAgentVersions?.data?.[agent.agentName] as OtelAgentLatestVersion + ) + : ( + latestAgentVersions?.data?.[ + agent.agentName + ] as ElasticApmAgentLatestVersion + )?.latest_version, + })); const noItemsMessage = ( diff --git a/x-pack/plugins/apm/public/context/apm_plugin/mock_apm_plugin_context.tsx b/x-pack/plugins/apm/public/context/apm_plugin/mock_apm_plugin_context.tsx index 04a4df5257916..ffca2a1b3cc4a 100644 --- a/x-pack/plugins/apm/public/context/apm_plugin/mock_apm_plugin_context.tsx +++ b/x-pack/plugins/apm/public/context/apm_plugin/mock_apm_plugin_context.tsx @@ -66,6 +66,7 @@ const mockConfig: ConfigSchema = { ui: { enabled: false, }, + latestAgentVersionsUrl: '', }; const urlService = new UrlService({ diff --git a/x-pack/plugins/apm/public/index.ts b/x-pack/plugins/apm/public/index.ts index bc50f97a5e8d7..adee80b39787a 100644 --- a/x-pack/plugins/apm/public/index.ts +++ b/x-pack/plugins/apm/public/index.ts @@ -13,6 +13,7 @@ export interface ConfigSchema { ui: { enabled: boolean; }; + latestAgentVersionsUrl: string; } export const plugin: PluginInitializer = ( diff --git a/x-pack/plugins/apm/server/index.ts b/x-pack/plugins/apm/server/index.ts index 74eea568788b5..bfd7f1c19e376 100644 --- a/x-pack/plugins/apm/server/index.ts +++ b/x-pack/plugins/apm/server/index.ts @@ -53,6 +53,9 @@ const configSchema = schema.object({ onboarding: schema.string({ defaultValue: 'apm-*' }), }), forceSyntheticSource: schema.boolean({ defaultValue: false }), + latestAgentVersionsUrl: schema.string({ + defaultValue: 'https://apm-agent-versions.elastic.co/versions.json', + }), }); // plugin config @@ -110,6 +113,7 @@ export const config: PluginConfigDescriptor = { exposeToBrowser: { serviceMapEnabled: true, ui: true, + latestAgentVersionsUrl: true, }, schema: configSchema, }; diff --git a/x-pack/plugins/apm/server/lib/helpers/create_es_client/create_apm_event_client/index.ts b/x-pack/plugins/apm/server/lib/helpers/create_es_client/create_apm_event_client/index.ts index b06ab4ecb2c71..c94444e4bbdd9 100644 --- a/x-pack/plugins/apm/server/lib/helpers/create_es_client/create_apm_event_client/index.ts +++ b/x-pack/plugins/apm/server/lib/helpers/create_es_client/create_apm_event_client/index.ts @@ -175,7 +175,8 @@ export class APMEventClient { ...params.body, query: { bool: { - filter: compact([params.body.query, ...filters]), + filter: filters, + must: compact([params.body.query]), }, }, }, diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/helpers/parsers.ts b/x-pack/plugins/apm/server/routes/agent_explorer/error_with_status_code.ts similarity index 65% rename from x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/helpers/parsers.ts rename to x-pack/plugins/apm/server/routes/agent_explorer/error_with_status_code.ts index 7dcba327386e6..92e8fbda5bafd 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/helpers/parsers.ts +++ b/x-pack/plugins/apm/server/routes/agent_explorer/error_with_status_code.ts @@ -5,10 +5,8 @@ * 2.0. */ -export function parseJsonIfString(value: T) { - if (typeof value === 'string') { - return JSON.parse(value); +export class ErrorWithStatusCode extends Error { + constructor(message: string, public readonly statusCode: string) { + super(message); } - - return value; } diff --git a/x-pack/plugins/apm/server/routes/agent_explorer/fetch_agents_last_version.test.ts b/x-pack/plugins/apm/server/routes/agent_explorer/fetch_agents_last_version.test.ts new file mode 100644 index 0000000000000..6a24106a4373f --- /dev/null +++ b/x-pack/plugins/apm/server/routes/agent_explorer/fetch_agents_last_version.test.ts @@ -0,0 +1,62 @@ +/* + * 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. + */ + +jest.mock('node-fetch'); +import Boom from '@hapi/boom'; +import { loggerMock } from '@kbn/logging-mocks'; +import { fetchAgentsLatestVersion } from './fetch_agents_latest_version'; + +// eslint-disable-next-line @typescript-eslint/no-var-requires +const fetchMock = require('node-fetch') as jest.Mock; +const logger = loggerMock.create(); + +describe('ApmFetchAgentslatestsVersion', () => { + beforeEach(() => { + jest.resetAllMocks(); + }); + + it('when url is empty should not fetch latest versions', async () => { + const boom = Boom.notImplemented( + 'To use latest agent versions you must set xpack.apm.latestAgentVersionsUrl.' + ); + + await expect(fetchAgentsLatestVersion(logger, '')).rejects.toThrow(boom); + expect(fetchMock).toBeCalledTimes(0); + }); + + describe('when url is defined', () => { + it('should handle errors gracefully', async () => { + fetchMock.mockResolvedValue({ + text: () => 'Request Timeout', + status: 408, + ok: false, + }); + + const { data, error } = await fetchAgentsLatestVersion(logger, 'my-url'); + + expect(fetchMock).toBeCalledTimes(1); + expect(data).toEqual({}); + expect(error?.statusCode).toEqual('408'); + }); + + it('should return latest agents version', async () => { + fetchMock.mockResolvedValue({ + json: () => ({ + java: '1.1.0', + }), + status: 200, + ok: true, + }); + + const { data, error } = await fetchAgentsLatestVersion(logger, 'my-url'); + + expect(fetchMock).toBeCalledTimes(1); + expect(data).toEqual({ java: '1.1.0' }); + expect(error).toBeFalsy(); + }); + }); +}); diff --git a/x-pack/plugins/apm/server/routes/agent_explorer/fetch_agents_latest_version.ts b/x-pack/plugins/apm/server/routes/agent_explorer/fetch_agents_latest_version.ts new file mode 100644 index 0000000000000..22982a740bb01 --- /dev/null +++ b/x-pack/plugins/apm/server/routes/agent_explorer/fetch_agents_latest_version.ts @@ -0,0 +1,67 @@ +/* + * 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 Boom from '@hapi/boom'; +import { Logger } from '@kbn/core/server'; +import { i18n } from '@kbn/i18n'; +import { isEmpty } from 'lodash'; +import fetch from 'node-fetch'; +import { + ElasticApmAgentLatestVersion, + OtelAgentLatestVersion, +} from '../../../common/agent_explorer'; +import { AgentName } from '../../../typings/es_schemas/ui/fields/agent'; +import { ErrorWithStatusCode } from './error_with_status_code'; + +const MISSING_CONFIGURATION = i18n.translate( + 'xpack.apm.agent_explorer.error.missing_configuration', + { + defaultMessage: + 'To use latest agent versions you must set xpack.apm.latestAgentVersionsUrl.', + } +); + +export interface AgentLatestVersionsResponse { + data: AgentLatestVersions; + error?: { message: string; type?: string; statusCode?: string }; +} + +type AgentLatestVersions = Record< + AgentName, + ElasticApmAgentLatestVersion | OtelAgentLatestVersion +>; + +export const fetchAgentsLatestVersion = async ( + logger: Logger, + latestAgentVersionsUrl: string +): Promise => { + if (isEmpty(latestAgentVersionsUrl)) { + throw Boom.notImplemented(MISSING_CONFIGURATION); + } + + try { + const response = await fetch(latestAgentVersionsUrl); + + if (response.status !== 200) { + throw new ErrorWithStatusCode( + `${response.status} - ${await response.text()}`, + `${response.status}` + ); + } + + const data = await response.json(); + + return { data }; + } catch (error) { + const message = `Failed to retrieve latest APM Agent versions due to ${error}`; + logger.warn(message); + + return { + data: {} as AgentLatestVersions, + error, + }; + } +}; diff --git a/x-pack/plugins/apm/server/routes/agent_explorer/get_agents.ts b/x-pack/plugins/apm/server/routes/agent_explorer/get_agents.ts index 077040950cb7e..b52febfe1a9a1 100644 --- a/x-pack/plugins/apm/server/routes/agent_explorer/get_agents.ts +++ b/x-pack/plugins/apm/server/routes/agent_explorer/get_agents.ts @@ -9,8 +9,8 @@ import { isOpenTelemetryAgentName } from '../../../common/agent_name'; import { AgentName } from '../../../typings/es_schemas/ui/fields/agent'; import { APMEventClient } from '../../lib/helpers/create_es_client/create_apm_event_client'; import { RandomSampler } from '../../lib/helpers/get_random_sampler'; -import { getAgentsItems } from './get_agents_items'; import { getAgentDocsPageUrl } from './get_agent_url_repository'; +import { getAgentsItems } from './get_agents_items'; const getOtelAgentVersion = (item: { agentTelemetryAutoVersion: string[]; @@ -29,7 +29,9 @@ export interface AgentExplorerAgentsResponse { environments: string[]; agentName: AgentName; agentVersion: string[]; + agentTelemetryAutoVersion: string[]; instances: number; + latestVersion?: string; }>; } @@ -65,16 +67,19 @@ export async function getAgents({ return { items: items.map((item) => { - const agentVersion = isOpenTelemetryAgentName(item.agentName) - ? getOtelAgentVersion(item) - : item.agentVersion; + const agentDocsPageUrl = getAgentDocsPageUrl(item.agentName); - const { agentTelemetryAutoVersion, ...rest } = item; + if (isOpenTelemetryAgentName(item.agentName)) { + return { + ...item, + agentVersion: getOtelAgentVersion(item), + agentDocsPageUrl, + }; + } return { - ...rest, - agentVersion, - agentDocsPageUrl: getAgentDocsPageUrl(item.agentName as AgentName), + ...item, + agentDocsPageUrl, }; }), }; diff --git a/x-pack/plugins/apm/server/routes/agent_explorer/route.ts b/x-pack/plugins/apm/server/routes/agent_explorer/route.ts index 09eed8277cae7..6be63dea80b06 100644 --- a/x-pack/plugins/apm/server/routes/agent_explorer/route.ts +++ b/x-pack/plugins/apm/server/routes/agent_explorer/route.ts @@ -20,6 +20,10 @@ import { AgentExplorerAgentInstancesResponse, getAgentInstances, } from './get_agent_instances'; +import { + AgentLatestVersionsResponse, + fetchAgentsLatestVersion, +} from './fetch_agents_latest_version'; const agentExplorerRoute = createApmServerRoute({ endpoint: 'GET /internal/apm/get_agents_per_service', @@ -71,6 +75,16 @@ const agentExplorerRoute = createApmServerRoute({ }, }); +const latestAgentVersionsRoute = createApmServerRoute({ + endpoint: 'GET /internal/apm/get_latest_agent_versions', + options: { tags: ['access:apm'] }, + async handler(resources): Promise { + const { logger, config } = resources; + + return fetchAgentsLatestVersion(logger, config.latestAgentVersionsUrl); + }, +}); + const agentExplorerInstanceRoute = createApmServerRoute({ endpoint: 'GET /internal/apm/services/{serviceName}/agent_instances', options: { tags: ['access:apm'] }, @@ -104,5 +118,6 @@ const agentExplorerInstanceRoute = createApmServerRoute({ export const agentExplorerRouteRepository = { ...agentExplorerRoute, + ...latestAgentVersionsRoute, ...agentExplorerInstanceRoute, }; diff --git a/x-pack/plugins/apm/server/routes/errors/get_error_groups/get_error_group_sample_ids.ts b/x-pack/plugins/apm/server/routes/errors/get_error_groups/get_error_group_sample_ids.ts index 1947da3832c42..2796ec590ad42 100644 --- a/x-pack/plugins/apm/server/routes/errors/get_error_groups/get_error_group_sample_ids.ts +++ b/x-pack/plugins/apm/server/routes/errors/get_error_groups/get_error_group_sample_ids.ts @@ -60,7 +60,7 @@ export async function getErrorGroupSampleIds({ should: [{ term: { [TRANSACTION_SAMPLED]: true } }], // prefer error samples with related transactions }, }, - _source: [ERROR_ID], + _source: [ERROR_ID, 'transaction'], sort: asMutableArray([ { _score: { order: 'desc' } }, // sort by _score first to ensure that errors with transaction.sampled:true ends up on top { '@timestamp': { order: 'desc' } }, // sort by timestamp to get the most recent error diff --git a/x-pack/plugins/apm/server/routes/services/get_service_instances/get_service_instances_system_metric_statistics.ts b/x-pack/plugins/apm/server/routes/services/get_service_instances/get_service_instances_system_metric_statistics.ts index bdb3f4c39d707..7462b649a6019 100644 --- a/x-pack/plugins/apm/server/routes/services/get_service_instances/get_service_instances_system_metric_statistics.ts +++ b/x-pack/plugins/apm/server/routes/services/get_service_instances/get_service_instances_system_metric_statistics.ts @@ -155,9 +155,17 @@ export async function getServiceInstancesSystemMetricStatistics< ...(isComparisonSearch && serviceNodeIds ? [{ terms: { [SERVICE_NODE_NAME]: serviceNodeIds } }] : []), + { + bool: { + should: [ + cgroupMemoryFilter, + systemMemoryFilter, + cpuUsageFilter, + ], + minimum_should_match: 1, + }, + }, ], - should: [cgroupMemoryFilter, systemMemoryFilter, cpuUsageFilter], - minimum_should_match: 1, }, }, aggs: { diff --git a/x-pack/plugins/apm/server/routes/services/get_service_overview_container_metadata.ts b/x-pack/plugins/apm/server/routes/services/get_service_overview_container_metadata.ts index 5840f143869ef..984f6cedb457b 100644 --- a/x-pack/plugins/apm/server/routes/services/get_service_overview_container_metadata.ts +++ b/x-pack/plugins/apm/server/routes/services/get_service_overview_container_metadata.ts @@ -6,18 +6,11 @@ */ import { rangeQuery } from '@kbn/observability-plugin/server'; +import { CONTAINER_ID, CONTAINER_IMAGE } from '../../../common/es_fields/apm'; import { - CONTAINER_ID, - CONTAINER_IMAGE, - KUBERNETES, - KUBERNETES_POD_NAME, - KUBERNETES_POD_UID, -} from '../../../common/es_fields/apm'; -import { - KUBERNETES_CONTAINER_NAME, + KUBERNETES_DEPLOYMENT_NAME, KUBERNETES_NAMESPACE, KUBERNETES_REPLICASET_NAME, - KUBERNETES_DEPLOYMENT_NAME, } from '../../../common/es_fields/infra_metrics'; import { InfraMetricsClient } from '../../lib/helpers/create_es_client/create_infra_metrics_client/create_infra_metrics_client'; @@ -32,17 +25,6 @@ export const getServiceOverviewContainerMetadata = async ({ start: number; end: number; }) => { - const should = [ - { exists: { field: KUBERNETES } }, - { exists: { field: CONTAINER_IMAGE } }, - { exists: { field: KUBERNETES_CONTAINER_NAME } }, - { exists: { field: KUBERNETES_NAMESPACE } }, - { exists: { field: KUBERNETES_POD_NAME } }, - { exists: { field: KUBERNETES_POD_UID } }, - { exists: { field: KUBERNETES_REPLICASET_NAME } }, - { exists: { field: KUBERNETES_DEPLOYMENT_NAME } }, - ]; - const response = await infraMetricsClient.search({ size: 0, track_total_hits: false, @@ -56,7 +38,6 @@ export const getServiceOverviewContainerMetadata = async ({ }, ...rangeQuery(start, end), ], - should, }, }, aggs: { diff --git a/x-pack/plugins/apm/server/routes/transactions/__snapshots__/queries.test.ts.snap b/x-pack/plugins/apm/server/routes/transactions/__snapshots__/queries.test.ts.snap index b25733b5bedde..e02f473f93be9 100644 --- a/x-pack/plugins/apm/server/routes/transactions/__snapshots__/queries.test.ts.snap +++ b/x-pack/plugins/apm/server/routes/transactions/__snapshots__/queries.test.ts.snap @@ -346,6 +346,18 @@ Object { }, }, "size": 500, + "sort": Array [ + Object { + "_score": Object { + "order": "desc", + }, + }, + Object { + "@timestamp": Object { + "order": "desc", + }, + }, + ], "track_total_hits": false, }, } diff --git a/x-pack/plugins/apm/server/routes/transactions/trace_samples/index.ts b/x-pack/plugins/apm/server/routes/transactions/trace_samples/index.ts index b3cf49c8c4ef6..2268f4162d113 100644 --- a/x-pack/plugins/apm/server/routes/transactions/trace_samples/index.ts +++ b/x-pack/plugins/apm/server/routes/transactions/trace_samples/index.ts @@ -4,11 +4,12 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; -import { rangeQuery, kqlQuery } from '@kbn/observability-plugin/server'; +import { + Sort, + QueryDslQueryContainer, +} from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { ProcessorEvent } from '@kbn/observability-plugin/common'; -import { orderBy } from 'lodash'; -import { withApmSpan } from '../../../utils/with_apm_span'; +import { kqlQuery, rangeQuery } from '@kbn/observability-plugin/server'; import { SERVICE_NAME, TRACE_ID, @@ -19,7 +20,7 @@ import { } from '../../../../common/es_fields/apm'; import { environmentQuery } from '../../../../common/utils/environment_query'; import { APMEventClient } from '../../../lib/helpers/create_es_client/create_apm_event_client'; - +import { withApmSpan } from '../../../utils/with_apm_span'; const TRACE_SAMPLES_SIZE = 500; export interface TransactionTraceSamplesResponse { @@ -99,19 +100,27 @@ export async function getTraceSamples({ }, }, size: TRACE_SAMPLES_SIZE, + sort: [ + { + _score: { + order: 'desc', + }, + }, + { + '@timestamp': { + order: 'desc', + }, + }, + ] as Sort, }, }); - const traceSamples = orderBy( - response.hits.hits.map((hit) => ({ - score: hit._score, - timestamp: hit._source['@timestamp'], - transactionId: hit._source.transaction.id, - traceId: hit._source.trace.id, - })), - ['score', 'timestamp'], - ['desc', 'desc'] - ); + const traceSamples = response.hits.hits.map((hit) => ({ + score: hit._score, + timestamp: hit._source['@timestamp'], + transactionId: hit._source.transaction.id, + traceId: hit._source.trace.id, + })); return { traceSamples }; }); diff --git a/x-pack/plugins/apm/tsconfig.json b/x-pack/plugins/apm/tsconfig.json index d4d263af763a9..5419244c7cb15 100644 --- a/x-pack/plugins/apm/tsconfig.json +++ b/x-pack/plugins/apm/tsconfig.json @@ -82,6 +82,7 @@ "@kbn/shared-ux-router", "@kbn/alerts-as-data-utils", "@kbn/exploratory-view-plugin", + "@kbn/logging-mocks", ], "exclude": [ "target/**/*", diff --git a/x-pack/plugins/cases/server/authorization/__snapshots__/audit_logger.test.ts.snap b/x-pack/plugins/cases/server/authorization/__snapshots__/audit_logger.test.ts.snap index 4c7c643c80fa9..b960adbc2c933 100644 --- a/x-pack/plugins/cases/server/authorization/__snapshots__/audit_logger.test.ts.snap +++ b/x-pack/plugins/cases/server/authorization/__snapshots__/audit_logger.test.ts.snap @@ -2184,90 +2184,6 @@ Object { } `; -exports[`audit_logger log function event structure creates the correct audit event for operation: "getUserActionStats" with an error and entity 1`] = ` -Object { - "error": Object { - "code": "Error", - "message": "an error", - }, - "event": Object { - "action": "case_user_action_get_stats", - "category": Array [ - "database", - ], - "outcome": "failure", - "type": Array [ - "access", - ], - }, - "kibana": Object { - "saved_object": Object { - "id": "1", - "type": "cases-user-actions", - }, - }, - "message": "Failed attempt to access cases-user-actions [id=1] as owner \\"awesome\\"", -} -`; - -exports[`audit_logger log function event structure creates the correct audit event for operation: "getUserActionStats" with an error but no entity 1`] = ` -Object { - "error": Object { - "code": "Error", - "message": "an error", - }, - "event": Object { - "action": "case_user_action_get_stats", - "category": Array [ - "database", - ], - "outcome": "failure", - "type": Array [ - "access", - ], - }, - "message": "Failed attempt to access a user actions as any owners", -} -`; - -exports[`audit_logger log function event structure creates the correct audit event for operation: "getUserActionStats" without an error but with an entity 1`] = ` -Object { - "event": Object { - "action": "case_user_action_get_stats", - "category": Array [ - "database", - ], - "outcome": "success", - "type": Array [ - "access", - ], - }, - "kibana": Object { - "saved_object": Object { - "id": "5", - "type": "cases-user-actions", - }, - }, - "message": "User has accessed cases-user-actions [id=5] as owner \\"super\\"", -} -`; - -exports[`audit_logger log function event structure creates the correct audit event for operation: "getUserActionStats" without an error or entity 1`] = ` -Object { - "event": Object { - "action": "case_user_action_get_stats", - "category": Array [ - "database", - ], - "outcome": "success", - "type": Array [ - "access", - ], - }, - "message": "User has accessed a user actions as any owners", -} -`; - exports[`audit_logger log function event structure creates the correct audit event for operation: "getUserActionUsers" with an error and entity 1`] = ` Object { "error": Object { diff --git a/x-pack/plugins/cases/server/authorization/index.ts b/x-pack/plugins/cases/server/authorization/index.ts index 83529478d1d10..7a51297b77a24 100644 --- a/x-pack/plugins/cases/server/authorization/index.ts +++ b/x-pack/plugins/cases/server/authorization/index.ts @@ -359,14 +359,6 @@ export const Operations: Record; - private readonly params: LimiterParams; - constructor(params: LimiterParams) { - this.params = params; this.limit = params.limit; this.errorMessage = makeErrorMessage(this.limit, params.attachmentNoun); - - this.limitAggregation = { - limiter: { - value_count: { - field: `${CASE_COMMENT_SAVED_OBJECT}.attributes.${params.field}`, - }, - }, - }; - } - - public async countOfItemsWithinCase( - attachmentService: AttachmentService, - caseId: string - ): Promise { - const itemsAttachedToCase = await attachmentService.executeCaseAggregations<{ - limiter: { value: number }; - }>({ - caseId, - aggregations: this.limitAggregation, - attachmentType: this.params.attachmentType, - filter: this.params.filter, - }); - - return itemsAttachedToCase?.limiter?.value ?? 0; } + public abstract countOfItemsWithinCase(caseId: string): Promise; public abstract countOfItemsInRequest(requests: CommentRequest[]): number; } diff --git a/x-pack/plugins/cases/server/common/limiter_checker/index.test.ts b/x-pack/plugins/cases/server/common/limiter_checker/index.test.ts index fce1cfe3ee781..1430d49ac2bb8 100644 --- a/x-pack/plugins/cases/server/common/limiter_checker/index.test.ts +++ b/x-pack/plugins/cases/server/common/limiter_checker/index.test.ts @@ -5,13 +5,15 @@ * 2.0. */ +import { createFileServiceMock } from '@kbn/files-plugin/server/mocks'; import { AttachmentLimitChecker } from '.'; import { createAttachmentServiceMock } from '../../services/mocks'; import { createAlertRequests, createFileRequests, createUserRequests } from './test_utils'; describe('AttachmentLimitChecker', () => { const mockAttachmentService = createAttachmentServiceMock(); - const checker = new AttachmentLimitChecker(mockAttachmentService, 'id'); + const mockFileService = createFileServiceMock(); + const checker = new AttachmentLimitChecker(mockAttachmentService, mockFileService, 'id'); beforeEach(() => { jest.clearAllMocks(); @@ -23,6 +25,13 @@ describe('AttachmentLimitChecker', () => { }, }; }); + + mockFileService.find.mockImplementation(async () => { + return { + files: [], + total: 5, + }; + }); }); describe('validate', () => { diff --git a/x-pack/plugins/cases/server/common/limiter_checker/index.ts b/x-pack/plugins/cases/server/common/limiter_checker/index.ts index b65a9da69d678..97928f2eeb356 100644 --- a/x-pack/plugins/cases/server/common/limiter_checker/index.ts +++ b/x-pack/plugins/cases/server/common/limiter_checker/index.ts @@ -7,6 +7,7 @@ import Boom from '@hapi/boom'; +import type { FileServiceStart } from '@kbn/files-plugin/server'; import type { CommentRequest } from '../../../common/api'; import type { AttachmentService } from '../../services'; import type { Limiter } from './types'; @@ -14,12 +15,15 @@ import { AlertLimiter } from './limiters/alerts'; import { FileLimiter } from './limiters/files'; export class AttachmentLimitChecker { - private readonly limiters: Limiter[] = [new AlertLimiter(), new FileLimiter()]; + private readonly limiters: Limiter[]; constructor( - private readonly attachmentService: AttachmentService, + attachmentService: AttachmentService, + fileService: FileServiceStart, private readonly caseId: string - ) {} + ) { + this.limiters = [new AlertLimiter(attachmentService), new FileLimiter(fileService)]; + } public async validate(requests: CommentRequest[]) { for (const limiter of this.limiters) { @@ -27,10 +31,7 @@ export class AttachmentLimitChecker { const hasItemsInRequests = itemsWithinRequests > 0; const totalAfterRequests = async () => { - const itemsWithinCase = await limiter.countOfItemsWithinCase( - this.attachmentService, - this.caseId - ); + const itemsWithinCase = await limiter.countOfItemsWithinCase(this.caseId); return itemsWithinRequests + itemsWithinCase; }; diff --git a/x-pack/plugins/cases/server/common/limiter_checker/limiters/alerts.test.ts b/x-pack/plugins/cases/server/common/limiter_checker/limiters/alerts.test.ts index 1cef61e02197b..1c9f0298dd570 100644 --- a/x-pack/plugins/cases/server/common/limiter_checker/limiters/alerts.test.ts +++ b/x-pack/plugins/cases/server/common/limiter_checker/limiters/alerts.test.ts @@ -10,7 +10,20 @@ import { AlertLimiter } from './alerts'; import { createAlertRequests, createUserRequests } from '../test_utils'; describe('AlertLimiter', () => { - const alert = new AlertLimiter(); + const attachmentService = createAttachmentServiceMock(); + attachmentService.executeCaseAggregations.mockImplementation(async () => { + return { + limiter: { + value: 5, + }, + }; + }); + + const alert = new AlertLimiter(attachmentService); + + beforeEach(() => { + jest.clearAllMocks(); + }); describe('public fields', () => { it('sets the errorMessage to the 1k limit', () => { @@ -62,21 +75,8 @@ describe('AlertLimiter', () => { }); describe('countOfItemsWithinCase', () => { - const attachmentService = createAttachmentServiceMock(); - attachmentService.executeCaseAggregations.mockImplementation(async () => { - return { - limiter: { - value: 5, - }, - }; - }); - - beforeEach(() => { - jest.clearAllMocks(); - }); - it('calls the aggregation function with the correct arguments', async () => { - await alert.countOfItemsWithinCase(attachmentService, 'id'); + await alert.countOfItemsWithinCase('id'); expect(attachmentService.executeCaseAggregations.mock.calls[0]).toMatchInlineSnapshot(` Array [ @@ -90,14 +90,13 @@ describe('AlertLimiter', () => { }, "attachmentType": "alert", "caseId": "id", - "filter": undefined, }, ] `); }); it('returns 5', async () => { - expect(await alert.countOfItemsWithinCase(attachmentService, 'id')).toBe(5); + expect(await alert.countOfItemsWithinCase('id')).toBe(5); }); }); }); diff --git a/x-pack/plugins/cases/server/common/limiter_checker/limiters/alerts.ts b/x-pack/plugins/cases/server/common/limiter_checker/limiters/alerts.ts index d7d90eb911011..b79ae19518771 100644 --- a/x-pack/plugins/cases/server/common/limiter_checker/limiters/alerts.ts +++ b/x-pack/plugins/cases/server/common/limiter_checker/limiters/alerts.ts @@ -5,14 +5,15 @@ * 2.0. */ +import type { AttachmentService } from '../../../services'; import { CommentType } from '../../../../common/api'; import type { CommentRequest, CommentRequestAlertType } from '../../../../common/api'; -import { MAX_ALERTS_PER_CASE } from '../../../../common/constants'; +import { CASE_COMMENT_SAVED_OBJECT, MAX_ALERTS_PER_CASE } from '../../../../common/constants'; import { isCommentRequestTypeAlert } from '../../utils'; import { BaseLimiter } from '../base_limiter'; export class AlertLimiter extends BaseLimiter { - constructor() { + constructor(private readonly attachmentService: AttachmentService) { super({ limit: MAX_ALERTS_PER_CASE, attachmentType: CommentType.alert, @@ -21,6 +22,26 @@ export class AlertLimiter extends BaseLimiter { }); } + public async countOfItemsWithinCase(caseId: string): Promise { + const limitAggregation = { + limiter: { + value_count: { + field: `${CASE_COMMENT_SAVED_OBJECT}.attributes.alertId`, + }, + }, + }; + + const itemsAttachedToCase = await this.attachmentService.executeCaseAggregations<{ + limiter: { value: number }; + }>({ + caseId, + aggregations: limitAggregation, + attachmentType: CommentType.alert, + }); + + return itemsAttachedToCase?.limiter?.value ?? 0; + } + public countOfItemsInRequest(requests: CommentRequest[]): number { const totalAlertsInReq = requests .filter(isCommentRequestTypeAlert) diff --git a/x-pack/plugins/cases/server/common/limiter_checker/limiters/files.test.ts b/x-pack/plugins/cases/server/common/limiter_checker/limiters/files.test.ts index e081369fafe17..a7408812ceb7f 100644 --- a/x-pack/plugins/cases/server/common/limiter_checker/limiters/files.test.ts +++ b/x-pack/plugins/cases/server/common/limiter_checker/limiters/files.test.ts @@ -5,12 +5,24 @@ * 2.0. */ -import { createAttachmentServiceMock } from '../../../services/mocks'; +import { createFileServiceMock } from '@kbn/files-plugin/server/mocks'; import { FileLimiter } from './files'; import { createFileRequests, createUserRequests } from '../test_utils'; describe('FileLimiter', () => { - const file = new FileLimiter(); + const mockFileService = createFileServiceMock(); + mockFileService.find.mockImplementation(async () => { + return { + files: [], + total: 5, + }; + }); + + const file = new FileLimiter(mockFileService); + + beforeEach(() => { + jest.clearAllMocks(); + }); describe('public fields', () => { it('sets the errorMessage to the 100 limit', () => { @@ -62,57 +74,25 @@ describe('FileLimiter', () => { }); describe('countOfItemsWithinCase', () => { - const attachmentService = createAttachmentServiceMock(); - attachmentService.executeCaseAggregations.mockImplementation(async () => { - return { - limiter: { - value: 5, - }, - }; - }); - - beforeEach(() => { - jest.clearAllMocks(); - }); - it('calls the aggregation function with the correct arguments', async () => { - await file.countOfItemsWithinCase(attachmentService, 'id'); + await file.countOfItemsWithinCase('id'); - expect(attachmentService.executeCaseAggregations.mock.calls[0]).toMatchInlineSnapshot(` + expect(mockFileService.find.mock.calls[0]).toMatchInlineSnapshot(` Array [ Object { - "aggregations": Object { - "limiter": Object { - "value_count": Object { - "field": "cases-comments.attributes.externalReferenceAttachmentTypeId", - }, - }, - }, - "attachmentType": "externalReference", - "caseId": "id", - "filter": Object { - "arguments": Array [ - Object { - "isQuoted": false, - "type": "literal", - "value": "cases-comments.attributes.externalReferenceAttachmentTypeId", - }, - Object { - "isQuoted": false, - "type": "literal", - "value": ".files", - }, + "meta": Object { + "caseIds": Array [ + "id", ], - "function": "is", - "type": "function", }, + "perPage": 1, }, ] `); }); it('returns 5', async () => { - expect(await file.countOfItemsWithinCase(attachmentService, 'id')).toBe(5); + expect(await file.countOfItemsWithinCase('id')).toBe(5); }); }); }); diff --git a/x-pack/plugins/cases/server/common/limiter_checker/limiters/files.ts b/x-pack/plugins/cases/server/common/limiter_checker/limiters/files.ts index dac2e124b669a..77f2a9a0fe611 100644 --- a/x-pack/plugins/cases/server/common/limiter_checker/limiters/files.ts +++ b/x-pack/plugins/cases/server/common/limiter_checker/limiters/files.ts @@ -5,24 +5,34 @@ * 2.0. */ -import { buildFilter } from '../../../client/utils'; -import { CommentType, FILE_ATTACHMENT_TYPE } from '../../../../common/api'; +import type { FileServiceStart } from '@kbn/files-plugin/server'; +import { CommentType } from '../../../../common/api'; import type { CommentRequest } from '../../../../common/api'; -import { CASE_COMMENT_SAVED_OBJECT, MAX_FILES_PER_CASE } from '../../../../common/constants'; +import { MAX_FILES_PER_CASE } from '../../../../common/constants'; import { isFileAttachmentRequest } from '../../utils'; import { BaseLimiter } from '../base_limiter'; export class FileLimiter extends BaseLimiter { - constructor() { + constructor(private readonly fileService: FileServiceStart) { super({ limit: MAX_FILES_PER_CASE, attachmentType: CommentType.externalReference, field: 'externalReferenceAttachmentTypeId', - filter: createFileFilter(), attachmentNoun: 'files', }); } + public async countOfItemsWithinCase(caseId: string): Promise { + const files = await this.fileService.find({ + perPage: 1, + meta: { + caseIds: [caseId], + }, + }); + + return files.total; + } + public countOfItemsInRequest(requests: CommentRequest[]): number { let fileRequests = 0; @@ -35,11 +45,3 @@ export class FileLimiter extends BaseLimiter { return fileRequests; } } - -const createFileFilter = () => - buildFilter({ - filters: FILE_ATTACHMENT_TYPE, - field: 'externalReferenceAttachmentTypeId', - operator: 'or', - type: CASE_COMMENT_SAVED_OBJECT, - }); diff --git a/x-pack/plugins/cases/server/common/limiter_checker/types.ts b/x-pack/plugins/cases/server/common/limiter_checker/types.ts index 7b7be0c12a5a5..87dcd33478ff7 100644 --- a/x-pack/plugins/cases/server/common/limiter_checker/types.ts +++ b/x-pack/plugins/cases/server/common/limiter_checker/types.ts @@ -6,11 +6,10 @@ */ import type { CommentRequest } from '../../../common/api'; -import type { AttachmentService } from '../../services'; export interface Limiter { readonly limit: number; readonly errorMessage: string; - countOfItemsWithinCase(attachmentService: AttachmentService, caseId: string): Promise; + countOfItemsWithinCase(caseId: string): Promise; countOfItemsInRequest: (requests: CommentRequest[]) => number; } diff --git a/x-pack/plugins/cases/server/common/models/case_with_comments.ts b/x-pack/plugins/cases/server/common/models/case_with_comments.ts index 7ffe35997ee67..04b3d31486546 100644 --- a/x-pack/plugins/cases/server/common/models/case_with_comments.ts +++ b/x-pack/plugins/cases/server/common/models/case_with_comments.ts @@ -258,6 +258,7 @@ export class CaseCommentModel { const limitChecker = new AttachmentLimitChecker( this.params.services.attachmentService, + this.params.fileService, this.caseInfo.id ); diff --git a/x-pack/plugins/cases/server/internal_attachments/index.ts b/x-pack/plugins/cases/server/internal_attachments/index.ts index 975e8fbad99f9..4ba9f8892861a 100644 --- a/x-pack/plugins/cases/server/internal_attachments/index.ts +++ b/x-pack/plugins/cases/server/internal_attachments/index.ts @@ -24,5 +24,12 @@ export const registerInternalAttachments = ( }; const schemaValidator = (data: unknown): void => { - pipe(excess(FileAttachmentMetadataRt).decode(data), fold(throwErrors(badRequest), identity)); + const fileMetadata = pipe( + excess(FileAttachmentMetadataRt).decode(data), + fold(throwErrors(badRequest), identity) + ); + + if (fileMetadata.files.length > 1) { + throw badRequest('Only a single file can be stored in an attachment'); + } }; diff --git a/x-pack/plugins/cloud_defend/public/components/control_yaml_view/hooks/policy_schema.json b/x-pack/plugins/cloud_defend/public/components/control_yaml_view/hooks/policy_schema.json index cd3f320a888bf..075d50384b09a 100644 --- a/x-pack/plugins/cloud_defend/public/components/control_yaml_view/hooks/policy_schema.json +++ b/x-pack/plugins/cloud_defend/public/components/control_yaml_view/hooks/policy_schema.json @@ -176,7 +176,8 @@ "type": "array", "minItems": 1, "items": { - "type": "string" + "type": "string", + "pattern": "^(?:\\/[^\\/\\*]+)+(?:\\/\\*|\\/\\*\\*)?$" } }, "ignoreVolumeMounts": { @@ -323,7 +324,8 @@ "type": "array", "minItems": 1, "items": { - "type": "string" + "type": "string", + "pattern": "^(?:\\/[^\\/\\*]+)+(?:\\/\\*|\\/\\*\\*)?$" } }, "processName": { diff --git a/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/policy_template_form.test.tsx b/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/policy_template_form.test.tsx index 3427c25e49c19..4fccd79b8238d 100644 --- a/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/policy_template_form.test.tsx +++ b/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/policy_template_form.test.tsx @@ -13,8 +13,23 @@ import type { NewPackagePolicy, PackageInfo, PackagePolicy } from '@kbn/fleet-pl import userEvent from '@testing-library/user-event'; import { getPosturePolicy } from './utils'; import { CLOUDBEAT_AWS, CLOUDBEAT_EKS } from '../../../common/constants'; +import { useParams } from 'react-router-dom'; + +// mock useParams +jest.mock('react-router-dom', () => ({ + ...jest.requireActual('react-router-dom'), + useParams: jest.fn().mockReturnValue({ + integration: undefined, + }), +})); describe('', () => { + beforeEach(() => { + (useParams as jest.Mock).mockReturnValue({ + integration: undefined, + }); + }); + const onChange = jest.fn(); const WrappedComponent = ({ @@ -183,13 +198,30 @@ describe('', () => { ...input, enabled: input.policy_template === 'kspm', })); + policy.name = 'cloud_security_posture-1'; + + (useParams as jest.Mock).mockReturnValue({ + integration: 'kspm', + }); render(); // 1st call happens on mount and selects the default policy template enabled input expect(onChange).toHaveBeenNthCalledWith(1, { isValid: true, - updatedPolicy: getMockPolicyK8s(), + updatedPolicy: { + ...getMockPolicyK8s(), + name: 'cloud_security_posture-1', + }, + }); + + // 2nd call happens to set integration name + expect(onChange).toHaveBeenNthCalledWith(2, { + isValid: true, + updatedPolicy: { + ...policy, + name: 'kspm-1', + }, }); }); @@ -200,13 +232,30 @@ describe('', () => { ...input, enabled: input.policy_template === 'cspm', })); + policy.name = 'cloud_security_posture-1'; + + (useParams as jest.Mock).mockReturnValue({ + integration: 'cspm', + }); render(); // 1st call happens on mount and selects the default policy template enabled input expect(onChange).toHaveBeenNthCalledWith(1, { isValid: true, - updatedPolicy: getMockPolicyAWS(), + updatedPolicy: { + ...getMockPolicyAWS(), + name: 'cloud_security_posture-1', + }, + }); + + // 2nd call happens to set integration name + expect(onChange).toHaveBeenNthCalledWith(2, { + isValid: true, + updatedPolicy: { + ...policy, + name: 'cspm-1', + }, }); }); diff --git a/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/policy_template_form.tsx b/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/policy_template_form.tsx index 66158d7dc1cbf..0c5e237bfccb1 100644 --- a/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/policy_template_form.tsx +++ b/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/policy_template_form.tsx @@ -115,7 +115,7 @@ export const CspPolicyTemplateForm = memo (validationResults?.vars || {})[key] !== null ); - const [loading, setLoading] = useState(validationResultsNonNullFields.length > 0); + const [isLoading, setIsLoading] = useState(validationResultsNonNullFields.length > 0); // delaying component rendering due to a race condition issue from Fleet // TODO: remove this workaround when the following issue is resolved: @@ -124,25 +124,27 @@ export const CspPolicyTemplateForm = memo 0) { // Forcing rerender to recover from the validation errors state - setLoading(true); + setIsLoading(true); } - setTimeout(() => setLoading(false), 200); + setTimeout(() => setIsLoading(false), 200); }, [validationResultsNonNullFields]); useEffect(() => { if (isEditPage) return; - if (loading) return; + if (isLoading) return; // Pick default input type for policy template. // Only 1 enabled input is supported when all inputs are initially enabled. // Required for mount only to ensure a single input type is selected // This will remove errors in validationResults.vars setEnabledPolicyInput(DEFAULT_INPUT_TYPE[input.policy_template]); // eslint-disable-next-line react-hooks/exhaustive-deps - }, [loading, input.policy_template, isEditPage]); + }, [isLoading, input.policy_template, isEditPage]); + + usePolicyTemplateInitialName({ isEditPage, isLoading, integration, newPolicy, updatePolicy }); useEnsureDefaultNamespace({ newPolicy, input, updatePolicy }); - if (loading) { + if (isLoading) { return ( @@ -224,6 +226,37 @@ CspPolicyTemplateForm.displayName = 'CspPolicyTemplateForm'; // eslint-disable-next-line import/no-default-export export { CspPolicyTemplateForm as default }; +const usePolicyTemplateInitialName = ({ + isEditPage, + isLoading, + integration, + newPolicy, + updatePolicy, +}: { + isEditPage: boolean; + isLoading: boolean; + integration: CloudSecurityPolicyTemplate | undefined; + newPolicy: NewPackagePolicy; + updatePolicy: (policy: NewPackagePolicy) => void; +}) => { + useEffect(() => { + if (!integration) return; + if (isEditPage) return; + if (isLoading) return; + const sequenceNumber = newPolicy.name.replace(/\D/g, ''); + const sequenceSuffix = sequenceNumber ? `-${sequenceNumber}` : ''; + const currentIntegrationName = `${integration}${sequenceSuffix}`; + if (newPolicy.name === currentIntegrationName) { + return; + } + updatePolicy({ + ...newPolicy, + name: currentIntegrationName, + }); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [isLoading, integration, isEditPage]); +}; + const useEnsureDefaultNamespace = ({ newPolicy, input, diff --git a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/search_strategy/requests/build_random_sampler_agg.ts b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/search_strategy/requests/build_random_sampler_agg.ts index 424da1b921785..2b03e9521e48a 100644 --- a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/search_strategy/requests/build_random_sampler_agg.ts +++ b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/search_strategy/requests/build_random_sampler_agg.ts @@ -59,45 +59,3 @@ export function buildAggregationWithSamplingOption( }, }; } - -/** - * Wraps the supplied aggregations in a random sampler aggregation. - */ -export function buildRandomSamplerAggregation( - aggs: Aggs, - probability: number | null, - seed: number -): Record { - if (probability === null || probability <= 0 || probability > 1) { - return aggs; - } - - return { - sample: { - aggs, - // @ts-expect-error AggregationsAggregationContainer needs to be updated with random_sampler - random_sampler: { - probability, - ...(seed ? { seed } : {}), - }, - }, - }; -} - -export function buildSamplerAggregation( - aggs: Aggs, - shardSize: number -): Record { - if (shardSize <= 0) { - return aggs; - } - - return { - sample: { - aggs, - sampler: { - shard_size: shardSize, - }, - }, - }; -} diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/configure_pipeline.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/configure_pipeline.tsx index 86581f5f2436e..67996b6da2f4c 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/configure_pipeline.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/configure_pipeline.tsx @@ -36,6 +36,7 @@ import { InferenceConfiguration } from './inference_config'; import { EMPTY_PIPELINE_CONFIGURATION, MLInferenceLogic } from './ml_inference_logic'; import { MlModelSelectOption } from './model_select_option'; import { PipelineSelectOption } from './pipeline_select_option'; +import { TextExpansionCallOut } from './text_expansion_callout'; import { MODEL_REDACTED_VALUE, MODEL_SELECT_PLACEHOLDER } from './utils'; const MODEL_SELECT_PLACEHOLDER_VALUE = 'model_placeholder$$'; @@ -315,6 +316,8 @@ export const ConfigurePipeline: React.FC = () => { + + diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/text_expansion_callout.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/text_expansion_callout.tsx new file mode 100644 index 0000000000000..a6784afecabd0 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/text_expansion_callout.tsx @@ -0,0 +1,136 @@ +/* + * 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 { useValues } from 'kea'; + +import { + EuiBadge, + EuiButtonIcon, + EuiFlexGroup, + EuiFlexItem, + EuiLink, + EuiPanel, + EuiText, + EuiTitle, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage, FormattedHTMLMessage } from '@kbn/i18n-react'; + +import { docLinks } from '../../../../../shared/doc_links'; + +import { MLInferenceLogic } from './ml_inference_logic'; + +export interface TextExpansionCallOutState { + dismiss: () => void; + dismissable: boolean; + show: boolean; +} + +export interface TextExpansionCallOutProps { + dismissable?: boolean; +} + +export const TEXT_EXPANSION_CALL_OUT_DISMISSED_KEY = + 'enterprise-search-text-expansion-callout-dismissed'; + +export const useTextExpansionCallOutData = ({ + dismissable = false, +}: TextExpansionCallOutProps): TextExpansionCallOutState => { + const { supportedMLModels } = useValues(MLInferenceLogic); + + const doesNotHaveTextExpansionModel = useMemo(() => { + return !supportedMLModels.some((m) => m.inference_config?.text_expansion); + }, [supportedMLModels]); + + const [show, setShow] = useState(() => { + if (!dismissable) return true; + + try { + return localStorage.getItem(TEXT_EXPANSION_CALL_OUT_DISMISSED_KEY) !== 'true'; + } catch { + return true; + } + }); + + useEffect(() => { + try { + localStorage.setItem(TEXT_EXPANSION_CALL_OUT_DISMISSED_KEY, JSON.stringify(!show)); + } catch { + return; + } + }, [show]); + + const dismiss = useCallback(() => { + setShow(false); + }, []); + + return { dismiss, dismissable, show: doesNotHaveTextExpansionModel && show }; +}; + +export const TextExpansionCallOut: React.FC = (props) => { + const { dismiss, dismissable, show } = useTextExpansionCallOutData(props); + + if (!show) return null; + + return ( + + + + + + + + + + +

+ + + +

+
+
+ {dismissable && ( + + + + )} +
+ + + + + + + + +
+
+ ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference_pipeline_processors_card.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference_pipeline_processors_card.tsx index 624643f564d85..4a6a6235982fd 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference_pipeline_processors_card.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference_pipeline_processors_card.tsx @@ -16,6 +16,7 @@ import { IndexNameLogic } from '../index_name_logic'; import { InferencePipelineCard } from './inference_pipeline_card'; import { AddMLInferencePipelineButton } from './ml_inference/add_ml_inference_button'; +import { TextExpansionCallOut } from './ml_inference/text_expansion_callout'; import { PipelinesLogic } from './pipelines_logic'; export const MlInferencePipelineProcessorsCard: React.FC = () => { @@ -29,6 +30,7 @@ export const MlInferencePipelineProcessorsCard: React.FC = () => { return ( + openAddMlInferencePipelineModal()} /> diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/doc_links/doc_links.ts b/x-pack/plugins/enterprise_search/public/applications/shared/doc_links/doc_links.ts index 1d71d74dd04fb..8955c2d505a43 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/doc_links/doc_links.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/doc_links/doc_links.ts @@ -72,6 +72,7 @@ class DocLinks { public elasticsearchGettingStarted: string; public elasticsearchMapping: string; public elasticsearchSecureCluster: string; + public elser: string; public enterpriseSearchConfig: string; public enterpriseSearchEngines: string; public enterpriseSearchMailService: string; @@ -190,6 +191,7 @@ class DocLinks { this.elasticsearchGettingStarted = ''; this.elasticsearchMapping = ''; this.elasticsearchSecureCluster = ''; + this.elser = ''; this.enterpriseSearchConfig = ''; this.enterpriseSearchEngines = ''; this.enterpriseSearchMailService = ''; @@ -309,6 +311,7 @@ class DocLinks { this.elasticsearchGettingStarted = docLinks.links.elasticsearch.gettingStarted; this.elasticsearchMapping = docLinks.links.elasticsearch.mapping; this.elasticsearchSecureCluster = docLinks.links.elasticsearch.secureCluster; + this.elser = docLinks.links.enterpriseSearch.elser; this.enterpriseSearchConfig = docLinks.links.enterpriseSearch.configuration; this.enterpriseSearchEngines = docLinks.links.enterpriseSearch.engines; this.enterpriseSearchMailService = docLinks.links.enterpriseSearch.mailService; diff --git a/x-pack/plugins/lens/common/expressions/time_scale/time_scale.test.ts b/x-pack/plugins/lens/common/expressions/time_scale/time_scale.test.ts index a2b7d730766c7..b55a434676669 100644 --- a/x-pack/plugins/lens/common/expressions/time_scale/time_scale.test.ts +++ b/x-pack/plugins/lens/common/expressions/time_scale/time_scale.test.ts @@ -14,6 +14,7 @@ import { functionWrapper } from '@kbn/expressions-plugin/common/expression_funct import { getTimeScale } from './time_scale'; import type { TimeScaleArgs } from './types'; +import { getTimeBounds } from './time_scale_fn'; describe('time_scale', () => { let timeScaleWrapped: ( @@ -503,4 +504,19 @@ describe('time_scale', () => { // should resolve now without another async dependency expect(timeScaleResolved).toHaveBeenCalled(); }); + + it('getTimeBounds should not alter the default moment timezone', () => { + // configuring an exotic timezone + moment.tz.setDefault('Pacific/Honolulu'); + // @ts-ignore + expect(moment.defaultZone?.name).toBe('Pacific/Honolulu'); + + getTimeBounds( + { from: '2023-04-01T00:00:00.000+02:00', to: '2023-04-02T00:00:00.000+02:00' }, + 'Europe/Lisbon', + () => new Date('2023-04-01T00:00:00.000Z') + ); + // @ts-ignore + expect(moment.defaultZone?.name).toBe('Pacific/Honolulu'); + }); }); diff --git a/x-pack/plugins/lens/common/expressions/time_scale/time_scale_fn.ts b/x-pack/plugins/lens/common/expressions/time_scale/time_scale_fn.ts index 6b2e1857a902c..a8dd7f85741a6 100644 --- a/x-pack/plugins/lens/common/expressions/time_scale/time_scale_fn.ts +++ b/x-pack/plugins/lens/common/expressions/time_scale/time_scale_fn.ts @@ -24,31 +24,31 @@ const unitInMs: Record = { d: 1000 * 60 * 60 * 24, }; -// the datemath plugin always parses dates by using the current default moment time zone. -// to use the configured time zone, we are temporary switching it just for the calculation. - -// The code between this call and the reset in the finally block is not allowed to get async, -// otherwise the timezone setting can leak out of this function. -const withChangedTimeZone = ( - timeZone: string | undefined, - action: () => TReturnedValue -): TReturnedValue => { +function safelySetTimeZone(timeZone: string) { + const zone = moment.tz.zone(timeZone); + if (zone) moment.tz.setDefault(zone.name); +} + +/** + * This function can be called both from server side and from the client side. Each of them could have + * a different configured timezone. To be sure the time bounds are computed relative to the same passed timezone, + * temporarily switch the default moment timezone to the one passed, and switch it back after the calculation is done. + */ +export function getTimeBounds(timeRange: TimeRange, timeZone?: string, getForceNow?: () => Date) { if (timeZone) { - const defaultTimezone = moment().zoneName(); - try { - moment.tz.setDefault(timeZone); - return action(); - } finally { - // reset default moment timezone - moment.tz.setDefault(defaultTimezone); - } + // the `defaultZone` property is injected by moment.timezone. + // If is not available is it fine to keep undefined because calling setDefault() will automatically reset to default + // https://github.com/moment/moment-timezone/blob/2448cdcbe15875bc22ddfbc184794d0a6b568b90/moment-timezone.js#L603 + // @ts-expect-error because is not part of the exposed types unfortunately + const currentDefaultTimeZone = moment.defaultZone?.name; + safelySetTimeZone(timeZone); + const timeBounds = calculateBounds(timeRange, { forceNow: getForceNow?.() }); + safelySetTimeZone(currentDefaultTimeZone); + return timeBounds; } else { - return action(); + return calculateBounds(timeRange, { forceNow: getForceNow?.() }); } -}; - -const getTimeBounds = (timeRange: TimeRange, timeZone?: string, getForceNow?: () => Date) => - withChangedTimeZone(timeZone, () => calculateBounds(timeRange, { forceNow: getForceNow?.() })); +} export const timeScaleFn = ( diff --git a/x-pack/plugins/ml/.gitignore b/x-pack/plugins/ml/.gitignore index 8307d0840ef73..37660af74eb93 100644 --- a/x-pack/plugins/ml/.gitignore +++ b/x-pack/plugins/ml/.gitignore @@ -1,3 +1,4 @@ scripts/apidoc_scripts/ML_API.mdx +scripts/apidoc_scripts/ml_kibana_api.mdx scripts/apidoc_scripts/header.md apidoc_config.json diff --git a/x-pack/plugins/ml/package.json b/x-pack/plugins/ml/package.json index d45cf57a0302c..df3b357a415ee 100644 --- a/x-pack/plugins/ml/package.json +++ b/x-pack/plugins/ml/package.json @@ -7,6 +7,7 @@ "scripts": { "generateHeader": "node scripts/apidoc_scripts/header_generator/index.js", "generateApidocConfig": "node scripts/apidoc_scripts/apidoc_config/index.js", - "apiDocs": "yarn generateHeader && yarn generateApidocConfig && cd ./scripts/apidoc_scripts/ && ../../../../../node_modules/.bin/apidoc-markdown -i ../../server/routes -c ./apidoc_config.json -o ./ML_API.mdx --parse-workers apischema=./schema_worker/index.js --parse-parsers apischema=./schema_parser/index.js --parse-filters apiversion=./version_filter/index.js --header ./header.md --template ./template.md" + "generateContentPage": "node scripts/apidoc_scripts/content_page/index.js", + "apiDocs": "yarn generateContentPage && yarn generateHeader && yarn generateApidocConfig && cd ./scripts/apidoc_scripts/ && ../../../../../node_modules/.bin/apidoc-markdown -i ../../server/routes -c ./apidoc_config.json -o ./ML_API.mdx --parse-workers apischema=./schema_worker/index.js --parse-parsers apischema=./schema_parser/index.js --parse-filters apiversion=./version_filter/index.js --header ./header.md --template ./template.md" } -} \ No newline at end of file +} diff --git a/x-pack/plugins/ml/scripts/apidoc_scripts/content_page/content_page.ts b/x-pack/plugins/ml/scripts/apidoc_scripts/content_page/content_page.ts new file mode 100644 index 0000000000000..402a68c07e2d5 --- /dev/null +++ b/x-pack/plugins/ml/scripts/apidoc_scripts/content_page/content_page.ts @@ -0,0 +1,68 @@ +/* + * 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 * as fs from 'fs'; +import * as path from 'path'; +// @ts-ignore can only be default-imported using the 'esModuleInterop' flag +import moment from 'moment'; +import { kibanaPackageJson } from '@kbn/repo-info'; +// eslint-disable-next-line import/no-extraneous-dependencies +import { createDoc } from 'apidoc-light'; + +interface Group { + anchor: string; + text: string; +} + +const getContent = (groups: Group[]) => { + const groupsStr = groups + .map(({ anchor, text }) => `- `) + .join('\n'); + + return `--- + id: uiMlKibanaRestApi + slug: /ml-team/docs/ui/rest-api/ml-kibana-rest-api + title: Machine Learning Kibana REST API + image: https://source.unsplash.com/400x175/?Nature + description: This page contains documentation for the ML Kibana REST API. + date: ${moment().format('YYYY-MM-DD')} + tags: ['machine learning','internal docs', 'UI'] + --- + + _Updated for ${kibanaPackageJson.version}_ + + Some of the features of the Machine Learning (ML) Kibana plugin are provided via a REST API, which is ideal for creating an integration with the ML plugin. + + Each API is experimental and can include breaking changes in any version of the ML plugin, or might have been entirely removed from the plugin. + + - + + The following APIs are available: + +${groupsStr} + `; +}; + +export const generateContentPage = () => { + const doc = createDoc({ + src: path.resolve(__dirname, '..', '..', '..', 'server', 'routes'), + config: path.resolve(__dirname, '..', 'apidoc_config', 'apidoc.json'), + // if you don't want to generate the output files: + dryRun: true, + // if you don't want to see any log output: + silent: true, + }); + + const groups = [...new Set(doc.data.map((v) => v.group))].map((group) => { + return { + anchor: `-${group.toLowerCase()}`, + text: group.replace(/([a-z])([A-Z])/g, '$1 $2'), + }; + }); + + fs.writeFileSync(path.resolve(__dirname, '..', 'ml_kibana_api.mdx'), getContent(groups)); +}; diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/mocks/index.ts b/x-pack/plugins/ml/scripts/apidoc_scripts/content_page/index.js similarity index 72% rename from x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/mocks/index.ts rename to x-pack/plugins/ml/scripts/apidoc_scripts/content_page/index.js index 1ec4437601d57..5b0aced7aed1b 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/mocks/index.ts +++ b/x-pack/plugins/ml/scripts/apidoc_scripts/content_page/index.js @@ -5,4 +5,5 @@ * 2.0. */ -export * from './locations'; +require('../../../../../../src/setup_node_env'); +require('./content_page').generateContentPage(); diff --git a/x-pack/plugins/monitoring/server/alerts/license_expiration_rule.test.ts b/x-pack/plugins/monitoring/server/alerts/license_expiration_rule.test.ts index 565bbd6100702..7943e2dfca973 100644 --- a/x-pack/plugins/monitoring/server/alerts/license_expiration_rule.test.ts +++ b/x-pack/plugins/monitoring/server/alerts/license_expiration_rule.test.ts @@ -20,13 +20,6 @@ jest.mock('../lib/alerts/fetch_licenses', () => ({ jest.mock('../lib/alerts/fetch_clusters', () => ({ fetchClusters: jest.fn(), })); -jest.mock('moment', () => { - const actual = jest.requireActual('moment'); - return { - ...actual, - duration: () => ({ humanize: () => 'HUMANIZED_DURATION' }), - }; -}); jest.mock('../static_globals', () => ({ Globals: { @@ -120,7 +113,12 @@ describe('LicenseExpirationRule', () => { getState.mockReset(); }); + afterAll(() => { + jest.useRealTimers(); + }); + it('should fire actions', async () => { + jest.useFakeTimers().setSystemTime(new Date('2023-03-30T00:00:00.000Z')); const alert = new LicenseExpirationRule(); const type = alert.getRuleType(); await type.executor({ @@ -169,7 +167,7 @@ describe('LicenseExpirationRule', () => { ], }, severity: 'danger', - triggeredMS: 1, + triggeredMS: 1680134400000, lastCheckedMS: 0, }, }, @@ -179,11 +177,11 @@ describe('LicenseExpirationRule', () => { action: '[Please update your license.](elasticsearch/nodes)', actionPlain: 'Please update your license.', internalFullMessage: - 'License expiration alert is firing for testCluster. Your license expires in HUMANIZED_DURATION. [Please update your license.](elasticsearch/nodes)', + 'License expiration alert is firing for testCluster. Your license expires in 53 years. [Please update your license.](elasticsearch/nodes)', internalShortMessage: - 'License expiration alert is firing for testCluster. Your license expires in HUMANIZED_DURATION. Please update your license.', + 'License expiration alert is firing for testCluster. Your license expires in 53 years. Please update your license.', clusterName, - expiredDate: 'HUMANIZED_DURATION', + expiredDate: '53 years', state: 'firing', }); }); diff --git a/x-pack/plugins/observability/docs/openapi/slo/bundled.yaml b/x-pack/plugins/observability/docs/openapi/slo/bundled.yaml index c92937dca8828..26d1664f6b3e3 100644 --- a/x-pack/plugins/observability/docs/openapi/slo/bundled.yaml +++ b/x-pack/plugins/observability/docs/openapi/slo/bundled.yaml @@ -98,7 +98,7 @@ paths: schema: type: string enum: - - name + - creationTime - indicatorType default: name example: name @@ -362,6 +362,7 @@ components: nullable: false required: - index + - timestampField properties: index: description: The index or index pattern to use diff --git a/x-pack/plugins/observability/docs/openapi/slo/components/schemas/indicator_properties_custom_kql.yaml b/x-pack/plugins/observability/docs/openapi/slo/components/schemas/indicator_properties_custom_kql.yaml index 73ff20a587366..ce4e152631e97 100644 --- a/x-pack/plugins/observability/docs/openapi/slo/components/schemas/indicator_properties_custom_kql.yaml +++ b/x-pack/plugins/observability/docs/openapi/slo/components/schemas/indicator_properties_custom_kql.yaml @@ -11,6 +11,7 @@ properties: nullable: false required: - index + - timestampField properties: index: description: The index or index pattern to use diff --git a/x-pack/plugins/observability/docs/openapi/slo/paths/s@{spaceid}@api@slos.yaml b/x-pack/plugins/observability/docs/openapi/slo/paths/s@{spaceid}@api@slos.yaml index 76ac5912056f1..3822e05b511bb 100644 --- a/x-pack/plugins/observability/docs/openapi/slo/paths/s@{spaceid}@api@slos.yaml +++ b/x-pack/plugins/observability/docs/openapi/slo/paths/s@{spaceid}@api@slos.yaml @@ -81,7 +81,7 @@ get: description: Sort by field schema: type: string - enum: [name, indicatorType] + enum: [creationTime, indicatorType] default: name example: name - name: sortDirection diff --git a/x-pack/plugins/observability/public/data/slo/indicator.ts b/x-pack/plugins/observability/public/data/slo/indicator.ts index bad06ce156e81..a7353965a932e 100644 --- a/x-pack/plugins/observability/public/data/slo/indicator.ts +++ b/x-pack/plugins/observability/public/data/slo/indicator.ts @@ -51,6 +51,7 @@ export const buildCustomKqlIndicator = ( good: 'latency < 300', total: 'latency > 0', filter: 'labels.eventId: event-0', + timestampField: '@timestamp', ...params, }, }; diff --git a/x-pack/plugins/observability/public/hooks/slo/use_fetch_slo_list.ts b/x-pack/plugins/observability/public/hooks/slo/use_fetch_slo_list.ts index 82e675bcc8437..fa9c2761e94d8 100644 --- a/x-pack/plugins/observability/public/hooks/slo/use_fetch_slo_list.ts +++ b/x-pack/plugins/observability/public/hooks/slo/use_fetch_slo_list.ts @@ -42,7 +42,7 @@ const LONG_REFETCH_INTERVAL = 1000 * 60; // 1 minute export function useFetchSloList({ name = '', page = 1, - sortBy = 'name', + sortBy = 'creationTime', indicatorTypes = [], shouldRefetch, }: SLOListParams | undefined = {}): UseFetchSloListResponse { diff --git a/x-pack/plugins/observability/public/pages/slo_edit/constants.ts b/x-pack/plugins/observability/public/pages/slo_edit/constants.ts index c3c1c0afb03a7..4846ea2973b8c 100644 --- a/x-pack/plugins/observability/public/pages/slo_edit/constants.ts +++ b/x-pack/plugins/observability/public/pages/slo_edit/constants.ts @@ -62,6 +62,7 @@ export const SLO_EDIT_FORM_DEFAULT_VALUES: CreateSLOInput = { filter: '', good: '', total: '', + timestampField: '', }, }, timeWindow: { diff --git a/x-pack/plugins/observability/public/pages/slos/components/slo_list.tsx b/x-pack/plugins/observability/public/pages/slos/components/slo_list.tsx index 1ca463c1ff940..e71a2cd98a0e6 100644 --- a/x-pack/plugins/observability/public/pages/slos/components/slo_list.tsx +++ b/x-pack/plugins/observability/public/pages/slos/components/slo_list.tsx @@ -26,7 +26,7 @@ export function SloList({ autoRefresh }: Props) { const [activePage, setActivePage] = useState(0); const [query, setQuery] = useState(''); - const [sort, setSort] = useState('name'); + const [sort, setSort] = useState('creationTime'); const [indicatorTypeFilter, setIndicatorTypeFilter] = useState([]); const { isLoading, isError, sloList, refetch } = useFetchSloList({ diff --git a/x-pack/plugins/observability/public/pages/slos/components/slo_list_search_filter_sort_bar.tsx b/x-pack/plugins/observability/public/pages/slos/components/slo_list_search_filter_sort_bar.tsx index 479efb65be8b1..341acf96ede0d 100644 --- a/x-pack/plugins/observability/public/pages/slos/components/slo_list_search_filter_sort_bar.tsx +++ b/x-pack/plugins/observability/public/pages/slos/components/slo_list_search_filter_sort_bar.tsx @@ -32,7 +32,7 @@ export interface SloListSearchFilterSortBarProps { onChangeIndicatorTypeFilter: (filter: FilterType[]) => void; } -export type SortType = 'name' | 'indicatorType'; +export type SortType = 'creationTime' | 'indicatorType'; export type FilterType = | 'sli.apm.transactionDuration' | 'sli.apm.transactionErrorRate' @@ -46,10 +46,10 @@ export type Item = EuiSelectableOption & { const SORT_OPTIONS: Array> = [ { - label: i18n.translate('xpack.observability.slo.list.sortBy.name', { - defaultMessage: 'Name', + label: i18n.translate('xpack.observability.slo.list.sortBy.creationTime', { + defaultMessage: 'Creation time', }), - type: 'name', + type: 'creationTime', checked: 'on', }, { @@ -108,7 +108,7 @@ export function SloListSearchFilterSortBar({ }; useEffect(() => { - if (selectedSort?.type === 'name' || selectedSort?.type === 'indicatorType') { + if (selectedSort?.type === 'creationTime' || selectedSort?.type === 'indicatorType') { onChangeSort(selectedSort.type); } }, [onChangeSort, selectedSort]); diff --git a/x-pack/plugins/observability/server/services/slo/find_slo.test.ts b/x-pack/plugins/observability/server/services/slo/find_slo.test.ts index 998d2d37a1ca4..682f83a1606ba 100644 --- a/x-pack/plugins/observability/server/services/slo/find_slo.test.ts +++ b/x-pack/plugins/observability/server/services/slo/find_slo.test.ts @@ -33,7 +33,7 @@ describe('FindSLO', () => { expect(mockRepository.find).toHaveBeenCalledWith( { name: undefined }, - { field: SortField.Name, direction: SortDirection.Asc }, + { field: SortField.CreationTime, direction: SortDirection.Asc }, { page: 1, perPage: 25 } ); @@ -98,7 +98,7 @@ describe('FindSLO', () => { expect(mockRepository.find).toHaveBeenCalledWith( { name: undefined }, - { field: SortField.Name, direction: SortDirection.Asc }, + { field: SortField.CreationTime, direction: SortDirection.Asc }, { page: 1, perPage: 25 } ); }); @@ -112,7 +112,7 @@ describe('FindSLO', () => { expect(mockRepository.find).toHaveBeenCalledWith( { name: 'Availability' }, - { field: SortField.Name, direction: SortDirection.Asc }, + { field: SortField.CreationTime, direction: SortDirection.Asc }, { page: 1, perPage: 25 } ); }); @@ -126,7 +126,7 @@ describe('FindSLO', () => { expect(mockRepository.find).toHaveBeenCalledWith( { indicatorTypes: ['sli.kql.custom'] }, - { field: SortField.Name, direction: SortDirection.Asc }, + { field: SortField.CreationTime, direction: SortDirection.Asc }, { page: 1, perPage: 25 } ); }); @@ -140,7 +140,7 @@ describe('FindSLO', () => { expect(mockRepository.find).toHaveBeenCalledWith( { name: 'My SLO*' }, - { field: SortField.Name, direction: SortDirection.Asc }, + { field: SortField.CreationTime, direction: SortDirection.Asc }, { page: 2, perPage: 100 } ); }); @@ -154,7 +154,7 @@ describe('FindSLO', () => { expect(mockRepository.find).toHaveBeenCalledWith( { name: undefined }, - { field: SortField.Name, direction: SortDirection.Asc }, + { field: SortField.CreationTime, direction: SortDirection.Asc }, { page: 1, perPage: 25 } ); }); @@ -168,7 +168,7 @@ describe('FindSLO', () => { expect(mockRepository.find).toHaveBeenCalledWith( { name: undefined }, - { field: SortField.Name, direction: SortDirection.Asc }, + { field: SortField.CreationTime, direction: SortDirection.Asc }, { page: 1, perPage: 25 } ); }); diff --git a/x-pack/plugins/observability/server/services/slo/find_slo.ts b/x-pack/plugins/observability/server/services/slo/find_slo.ts index dd56977d0cf84..1a21be1a416e8 100644 --- a/x-pack/plugins/observability/server/services/slo/find_slo.ts +++ b/x-pack/plugins/observability/server/services/slo/find_slo.ts @@ -73,7 +73,7 @@ function toCriteria(params: FindSLOParams): Criteria { function toSort(params: FindSLOParams): Sort { return { - field: params.sortBy === 'indicatorType' ? SortField.IndicatorType : SortField.Name, + field: params.sortBy === 'indicatorType' ? SortField.IndicatorType : SortField.CreationTime, direction: params.sortDirection === 'desc' ? SortDirection.Desc : SortDirection.Asc, }; } diff --git a/x-pack/plugins/observability/server/services/slo/fixtures/slo.ts b/x-pack/plugins/observability/server/services/slo/fixtures/slo.ts index 4b0529995c567..3176f4986d851 100644 --- a/x-pack/plugins/observability/server/services/slo/fixtures/slo.ts +++ b/x-pack/plugins/observability/server/services/slo/fixtures/slo.ts @@ -64,6 +64,7 @@ export const createKQLCustomIndicator = ( filter: 'labels.groupId: group-3', good: 'latency < 300', total: '', + timestampField: 'log_timestamp', ...params, }, }); diff --git a/x-pack/plugins/observability/server/services/slo/slo_repository.test.ts b/x-pack/plugins/observability/server/services/slo/slo_repository.test.ts index 238c6dbc5dac0..a3685d9712a2d 100644 --- a/x-pack/plugins/observability/server/services/slo/slo_repository.test.ts +++ b/x-pack/plugins/observability/server/services/slo/slo_repository.test.ts @@ -121,12 +121,12 @@ describe('KibanaSavedObjectsSLORepository', () => { describe('find', () => { const DEFAULT_PAGINATION: Pagination = { page: 1, perPage: 25 }; const DEFAULT_SORTING: Sort = { - field: SortField.Name, + field: SortField.CreationTime, direction: SortDirection.Asc, }; - describe('Name filter', () => { - it('includes the filter on name with wildcard when provided', async () => { + describe('Name search', () => { + it('includes the search on name with wildcard when provided', async () => { const repository = new KibanaSavedObjectsSLORepository(soClientMock); soClientMock.find.mockResolvedValueOnce(createFindResponse([SOME_SLO])); @@ -146,13 +146,15 @@ describe('KibanaSavedObjectsSLORepository', () => { type: SO_SLO_TYPE, page: 1, perPage: 25, - filter: `(slo.attributes.name: *availability*)`, - sortField: 'name', + filter: undefined, + search: '*availability*', + searchFields: ['name'], + sortField: 'created_at', sortOrder: 'asc', }); }); - it('includes the filter on name with added wildcard when not provided', async () => { + it('includes the search on name with added wildcard when not provided', async () => { const repository = new KibanaSavedObjectsSLORepository(soClientMock); soClientMock.find.mockResolvedValueOnce(createFindResponse([SOME_SLO])); @@ -172,8 +174,10 @@ describe('KibanaSavedObjectsSLORepository', () => { type: SO_SLO_TYPE, page: 1, perPage: 25, - filter: `(slo.attributes.name: *availa*)`, - sortField: 'name', + filter: undefined, + search: '*availa*', + searchFields: ['name'], + sortField: 'created_at', sortOrder: 'asc', }); }); @@ -201,7 +205,9 @@ describe('KibanaSavedObjectsSLORepository', () => { page: 1, perPage: 25, filter: `(slo.attributes.indicator.type: sli.kql.custom)`, - sortField: 'name', + search: undefined, + searchFields: undefined, + sortField: 'created_at', sortOrder: 'asc', }); }); @@ -227,13 +233,15 @@ describe('KibanaSavedObjectsSLORepository', () => { page: 1, perPage: 25, filter: `(slo.attributes.indicator.type: sli.kql.custom or slo.attributes.indicator.type: sli.apm.transactionDuration)`, - sortField: 'name', + search: undefined, + searchFields: undefined, + sortField: 'created_at', sortOrder: 'asc', }); }); }); - it('includes filter on name and indicator types', async () => { + it('includes search on name and filter on indicator types', async () => { const repository = new KibanaSavedObjectsSLORepository(soClientMock); soClientMock.find.mockResolvedValueOnce(createFindResponse([SOME_SLO])); @@ -253,13 +261,15 @@ describe('KibanaSavedObjectsSLORepository', () => { type: SO_SLO_TYPE, page: 1, perPage: 25, - filter: `(slo.attributes.name: *latency*) and (slo.attributes.indicator.type: sli.kql.custom or slo.attributes.indicator.type: sli.apm.transactionDuration)`, - sortField: 'name', + filter: `(slo.attributes.indicator.type: sli.kql.custom or slo.attributes.indicator.type: sli.apm.transactionDuration)`, + search: '*latency*', + searchFields: ['name'], + sortField: 'created_at', sortOrder: 'asc', }); }); - it('does not include the filter when no criteria provided', async () => { + it('does not include the search or filter when no criteria provided', async () => { const repository = new KibanaSavedObjectsSLORepository(soClientMock); soClientMock.find.mockResolvedValueOnce(createFindResponse([SOME_SLO])); @@ -275,12 +285,14 @@ describe('KibanaSavedObjectsSLORepository', () => { type: SO_SLO_TYPE, page: 1, perPage: 25, - sortField: 'name', + search: undefined, + searchFields: undefined, + sortField: 'created_at', sortOrder: 'asc', }); }); - it('sorts by name ascending', async () => { + it('sorts by creation time ascending', async () => { const repository = new KibanaSavedObjectsSLORepository(soClientMock); soClientMock.find.mockResolvedValueOnce(createFindResponse([SOME_SLO])); @@ -290,18 +302,20 @@ describe('KibanaSavedObjectsSLORepository', () => { type: SO_SLO_TYPE, page: 1, perPage: 25, - sortField: 'name', + search: undefined, + searchFields: undefined, + sortField: 'created_at', sortOrder: 'asc', }); }); - it('sorts by name descending', async () => { + it('sorts by creation time descending', async () => { const repository = new KibanaSavedObjectsSLORepository(soClientMock); soClientMock.find.mockResolvedValueOnce(createFindResponse([SOME_SLO])); await repository.find( {}, - { field: SortField.Name, direction: SortDirection.Desc }, + { field: SortField.CreationTime, direction: SortDirection.Desc }, DEFAULT_PAGINATION ); @@ -309,7 +323,9 @@ describe('KibanaSavedObjectsSLORepository', () => { type: SO_SLO_TYPE, page: 1, perPage: 25, - sortField: 'name', + search: undefined, + searchFields: undefined, + sortField: 'created_at', sortOrder: 'desc', }); }); @@ -328,6 +344,8 @@ describe('KibanaSavedObjectsSLORepository', () => { type: SO_SLO_TYPE, page: 1, perPage: 25, + search: undefined, + searchFields: undefined, sortField: 'indicator.type', sortOrder: 'asc', }); diff --git a/x-pack/plugins/observability/server/services/slo/slo_repository.ts b/x-pack/plugins/observability/server/services/slo/slo_repository.ts index 3bf0c3b85937b..454bc6bd94781 100644 --- a/x-pack/plugins/observability/server/services/slo/slo_repository.ts +++ b/x-pack/plugins/observability/server/services/slo/slo_repository.ts @@ -37,7 +37,7 @@ export const SortDirection = { type SortDirection = ObjectValues; export const SortField = { - Name: 'Name', + CreationTime: 'CreationTime', IndicatorType: 'IndicatorType', }; @@ -99,12 +99,15 @@ export class KibanaSavedObjectsSLORepository implements SLORepository { } async find(criteria: Criteria, sort: Sort, pagination: Pagination): Promise> { + const { search, searchFields } = buildSearch(criteria); const filterKuery = buildFilterKuery(criteria); const { sortField, sortOrder } = buildSortQuery(sort); const response = await this.soClient.find({ type: SO_SLO_TYPE, page: pagination.page, perPage: pagination.perPage, + search, + searchFields, filter: filterKuery, sortField, sortOrder, @@ -138,12 +141,19 @@ export class KibanaSavedObjectsSLORepository implements SLORepository { } } -function buildFilterKuery(criteria: Criteria): string | undefined { - const filters: string[] = []; - if (!!criteria.name) { - filters.push(`(slo.attributes.name: ${addWildcardsIfAbsent(criteria.name)})`); +function buildSearch(criteria: Criteria): { + search: string | undefined; + searchFields: string[] | undefined; +} { + if (!criteria.name) { + return { search: undefined, searchFields: undefined }; } + return { search: addWildcardsIfAbsent(criteria.name), searchFields: ['name'] }; +} + +function buildFilterKuery(criteria: Criteria): string | undefined { + const filters: string[] = []; if (!!criteria.indicatorTypes) { const indicatorTypesFilter: string[] = criteria.indicatorTypes.map( (indicatorType) => `slo.attributes.indicator.type: ${indicatorType}` @@ -160,9 +170,9 @@ function buildSortQuery(sort: Sort): { sortField: string; sortOrder: 'asc' | 'de case SortField.IndicatorType: sortField = 'indicator.type'; break; - case SortField.Name: + case SortField.CreationTime: default: - sortField = 'name'; + sortField = 'created_at'; break; } diff --git a/x-pack/plugins/observability/server/services/slo/transform_generators/__snapshots__/kql_custom.test.ts.snap b/x-pack/plugins/observability/server/services/slo/transform_generators/__snapshots__/kql_custom.test.ts.snap index afe345d2b7261..d83111944207e 100644 --- a/x-pack/plugins/observability/server/services/slo/transform_generators/__snapshots__/kql_custom.test.ts.snap +++ b/x-pack/plugins/observability/server/services/slo/transform_generators/__snapshots__/kql_custom.test.ts.snap @@ -147,7 +147,7 @@ Object { "group_by": Object { "@timestamp": Object { "date_histogram": Object { - "field": "@timestamp", + "field": "log_timestamp", "fixed_interval": "2m", }, }, @@ -198,7 +198,7 @@ Object { "sync": Object { "time": Object { "delay": "1m", - "field": "@timestamp", + "field": "log_timestamp", }, }, "transform_id": Any, @@ -243,7 +243,7 @@ Object { "group_by": Object { "@timestamp": Object { "date_histogram": Object { - "field": "@timestamp", + "field": "log_timestamp", "fixed_interval": "1m", }, }, @@ -294,7 +294,7 @@ Object { "sync": Object { "time": Object { "delay": "1m", - "field": "@timestamp", + "field": "log_timestamp", }, }, "transform_id": Any, diff --git a/x-pack/plugins/observability/server/services/slo/transform_generators/kql_custom.ts b/x-pack/plugins/observability/server/services/slo/transform_generators/kql_custom.ts index 40e1aceb71370..49151c158952a 100644 --- a/x-pack/plugins/observability/server/services/slo/transform_generators/kql_custom.ts +++ b/x-pack/plugins/observability/server/services/slo/transform_generators/kql_custom.ts @@ -29,9 +29,9 @@ export class KQLCustomTransformGenerator extends TransformGenerator { this.buildDescription(slo), this.buildSource(slo, slo.indicator), this.buildDestination(), - this.buildGroupBy(slo, this.getTimestampFieldOrDefault(slo.indicator.params.timestampField)), + this.buildGroupBy(slo, slo.indicator.params.timestampField), this.buildAggregations(slo, slo.indicator), - this.buildSettings(slo, this.getTimestampFieldOrDefault(slo.indicator.params.timestampField)) + this.buildSettings(slo, slo.indicator.params.timestampField) ); } @@ -79,8 +79,4 @@ export class KQLCustomTransformGenerator extends TransformGenerator { }), }; } - - private getTimestampFieldOrDefault(timestampField: string | undefined): string { - return !!timestampField && timestampField.length > 0 ? timestampField : '@timestamp'; - } } diff --git a/x-pack/plugins/security_solution/common/constants.ts b/x-pack/plugins/security_solution/common/constants.ts index 5c903966bd0cc..8794e81e4a441 100644 --- a/x-pack/plugins/security_solution/common/constants.ts +++ b/x-pack/plugins/security_solution/common/constants.ts @@ -61,6 +61,7 @@ export const DEFAULT_RULE_REFRESH_INTERVAL_VALUE = 60000 as const; // ms export const DEFAULT_RULE_NOTIFICATION_QUERY_SIZE = 100 as const; export const SECURITY_FEATURE_ID = 'Security' as const; export const SECURITY_TAG_NAME = 'Security Solution' as const; +export const SECURITY_TAG_DESCRIPTION = 'Security Solution auto-generated tag' as const; export const DEFAULT_SPACE_ID = 'default' as const; export const DEFAULT_RELATIVE_DATE_THRESHOLD = 24 as const; @@ -303,6 +304,10 @@ export const prebuiltSavedObjectsBulkCreateUrl = (templateName: string) => export const PREBUILT_SAVED_OBJECTS_BULK_DELETE = `${INTERNAL_RISK_SCORE_URL}/prebuilt_content/saved_objects/_bulk_delete/{template_name}`; export const prebuiltSavedObjectsBulkDeleteUrl = (templateName: string) => `${INTERNAL_RISK_SCORE_URL}/prebuilt_content/saved_objects/_bulk_delete/${templateName}` as const; + +export const INTERNAL_DASHBOARDS_URL = `/internal/dashboards` as const; +export const INTERNAL_TAGS_URL = `/internal/tags`; + export const RISK_SCORE_CREATE_INDEX = `${INTERNAL_RISK_SCORE_URL}/indices/create`; export const RISK_SCORE_DELETE_INDICES = `${INTERNAL_RISK_SCORE_URL}/indices/delete`; export const RISK_SCORE_CREATE_STORED_SCRIPT = `${INTERNAL_RISK_SCORE_URL}/stored_scripts/create`; diff --git a/x-pack/plugins/security_solution/cypress/e2e/detection_rules/bulk_edit_rules_actions.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/bulk_edit_rules_actions.cy.ts index 508df9c6ef58b..fceecae2b1d5b 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/detection_rules/bulk_edit_rules_actions.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/bulk_edit_rules_actions.cy.ts @@ -62,7 +62,8 @@ const expectedNumberOfRulesToBeEdited = expectedNumberOfCustomRulesToBeEdited + const expectedExistingSlackMessage = 'Existing slack action'; const expectedSlackMessage = 'Slack action test message'; -describe('Detection rules, bulk edit of rule actions', () => { +// TODO: Fix flakiness and unskip https://github.com/elastic/kibana/issues/154721 +describe.skip('Detection rules, bulk edit of rule actions', () => { before(() => { cleanKibana(); login(); diff --git a/x-pack/plugins/security_solution/cypress/e2e/detection_rules/rules_selection.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/rules_selection.cy.ts index e27ad7cbfc20e..9359e93f7fe47 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/detection_rules/rules_selection.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/rules_selection.cy.ts @@ -20,7 +20,8 @@ import { cleanKibana } from '../../tasks/common'; import { login, visitWithoutDateRange } from '../../tasks/login'; import { DETECTIONS_RULE_MANAGEMENT_URL } from '../../urls/navigation'; -describe('Rules selection', () => { +// TODO: See https://github.com/elastic/kibana/issues/154694 +describe.skip('Rules selection', () => { beforeEach(() => { cleanKibana(); login(); diff --git a/x-pack/plugins/security_solution/cypress/e2e/detection_rules/rules_table_auto_refresh.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/rules_table_auto_refresh.cy.ts index b11ba39323507..38728635b63cf 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/detection_rules/rules_table_auto_refresh.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/rules_table_auto_refresh.cy.ts @@ -33,7 +33,8 @@ import { setRowsPerPageTo } from '../../tasks/table_pagination'; const DEFAULT_RULE_REFRESH_INTERVAL_VALUE = 60000; -describe('Alerts detection rules table auto-refresh', () => { +// TODO: See https://github.com/elastic/kibana/issues/154694 +describe.skip('Alerts detection rules table auto-refresh', () => { before(() => { cleanKibana(); login(); diff --git a/x-pack/plugins/security_solution/public/cases/pages/index.tsx b/x-pack/plugins/security_solution/public/cases/pages/index.tsx index 26f600bf7e0cb..6485a395df95a 100644 --- a/x-pack/plugins/security_solution/public/cases/pages/index.tsx +++ b/x-pack/plugins/security_solution/public/cases/pages/index.tsx @@ -115,7 +115,7 @@ const CaseContainerComponent: React.FC = () => { owner: [APP_ID], features: { metrics: ['alerts.count', 'alerts.users', 'alerts.hosts', 'connectors', 'lifespan'], - alerts: { isExperimental: true }, + alerts: { isExperimental: false }, }, refreshRef, onComponentInitialized, diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/breadcrumbs/index.test.ts b/x-pack/plugins/security_solution/public/common/components/navigation/breadcrumbs/index.test.ts index 9b6a38cf274af..c5b0f8c2bfb06 100644 --- a/x-pack/plugins/security_solution/public/common/components/navigation/breadcrumbs/index.test.ts +++ b/x-pack/plugins/security_solution/public/common/components/navigation/breadcrumbs/index.test.ts @@ -412,6 +412,10 @@ describe('Navigation Breadcrumbs', () => { rulesBReadcrumb, { text: 'ALERT_RULE_NAME', + href: `securitySolutionUI/rules/id/${mockDetailName}`, + }, + { + text: 'Deleted rule', href: '', }, ]); @@ -764,7 +768,11 @@ describe('Navigation Breadcrumbs', () => { rulesBReadcrumb, { text: mockRuleName, - href: ``, + href: `securitySolutionUI/rules/id/${mockDetailName}`, + }, + { + text: 'Deleted rule', + href: '', }, ]); }); diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/pages/monitor_management/portals.tsx b/x-pack/plugins/security_solution/public/common/containers/dashboards/__mocks__/utils.ts similarity index 50% rename from x-pack/plugins/synthetics/public/legacy_uptime/pages/monitor_management/portals.tsx rename to x-pack/plugins/security_solution/public/common/containers/dashboards/__mocks__/utils.ts index b4574d2a5c6d3..eb8263c9f9adc 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/pages/monitor_management/portals.tsx +++ b/x-pack/plugins/security_solution/public/common/containers/dashboards/__mocks__/utils.ts @@ -5,10 +5,8 @@ * 2.0. */ -import { createHtmlPortalNode } from 'react-reverse-portal'; +import { MOCK_TAG_ID, DEFAULT_DASHBOARDS_RESPONSE } from '../api/__mocks__'; -export const ActionBarPortalNode = createHtmlPortalNode(); +export const getSecurityTagIds = jest.fn().mockResolvedValue([MOCK_TAG_ID]); -export const APIKeysPortalNode = createHtmlPortalNode(); - -export const ManageLocationsPortalNode = createHtmlPortalNode(); +export const getSecurityDashboards = jest.fn().mockResolvedValue(DEFAULT_DASHBOARDS_RESPONSE); diff --git a/x-pack/plugins/security_solution/public/common/containers/dashboards/api/__mocks__/index.ts b/x-pack/plugins/security_solution/public/common/containers/dashboards/api/__mocks__/index.ts new file mode 100644 index 0000000000000..7dd6d2c9132ef --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/containers/dashboards/api/__mocks__/index.ts @@ -0,0 +1,53 @@ +/* + * 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 { SECURITY_TAG_NAME, SECURITY_TAG_DESCRIPTION } from '../../../../../../common/constants'; + +export const MOCK_TAG_ID = 'securityTagId'; + +export const DEFAULT_TAGS_RESPONSE = [ + { + id: MOCK_TAG_ID, + name: SECURITY_TAG_NAME, + description: SECURITY_TAG_DESCRIPTION, + color: '#2c7b82', + }, +]; + +export const DEFAULT_DASHBOARDS_RESPONSE = [ + { + type: 'dashboard', + id: 'c0ac2c00-c1c0-11e7-8995-936807a28b16-ecs', + namespaces: ['default'], + attributes: { + description: 'Summary of Linux kernel audit events.', + title: '[Auditbeat Auditd] Overview ECS', + version: 1, + }, + references: [ + { + name: 'tag-ref-ba964280-d211-11ed-890b-153ddf1a08e9', + id: 'ba964280-d211-11ed-890b-153ddf1a08e9', + type: 'tag', + }, + ], + coreMigrationVersion: '8.8.0', + typeMigrationVersion: '8.7.0', + updated_at: '2023-04-03T11:38:00.902Z', + created_at: '2023-04-03T11:20:50.603Z', + version: 'WzE4NzQsMV0=', + score: 0, + }, +]; + +export const getSecuritySolutionTags = jest + .fn() + .mockImplementation(() => Promise.resolve(DEFAULT_TAGS_RESPONSE)); + +export const getSecuritySolutionDashboards = jest + .fn() + .mockImplementation(() => Promise.resolve(DEFAULT_DASHBOARDS_RESPONSE)); diff --git a/x-pack/plugins/security_solution/public/common/containers/dashboards/api/index.ts b/x-pack/plugins/security_solution/public/common/containers/dashboards/api/index.ts new file mode 100644 index 0000000000000..841f348bd24ab --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/containers/dashboards/api/index.ts @@ -0,0 +1,20 @@ +/* + * 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 type { HttpSetup } from '@kbn/core/public'; +import type { Tag } from '@kbn/saved-objects-tagging-plugin/public'; +import { INTERNAL_TAGS_URL, INTERNAL_DASHBOARDS_URL } from '../../../../../common/constants'; +import type { DashboardTableItem } from '../types'; + +export const getSecuritySolutionTags = ({ http }: { http: HttpSetup }): Promise => + http.get(INTERNAL_TAGS_URL); + +export const getSecuritySolutionDashboards = ({ + http, +}: { + http: HttpSetup; +}): Promise => http.get(INTERNAL_DASHBOARDS_URL); diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/state/actions/delete_monitor.ts b/x-pack/plugins/security_solution/public/common/containers/dashboards/types.ts similarity index 56% rename from x-pack/plugins/synthetics/public/legacy_uptime/state/actions/delete_monitor.ts rename to x-pack/plugins/security_solution/public/common/containers/dashboards/types.ts index 194b02cd16038..e82c383192218 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/state/actions/delete_monitor.ts +++ b/x-pack/plugins/security_solution/public/common/containers/dashboards/types.ts @@ -5,10 +5,12 @@ * 2.0. */ -import { createAsyncAction } from './utils'; - -export const deleteMonitorAction = createAsyncAction< - { id: string; name: string }, - string, - { id: string; error: Error } ->('DELETE_MONITOR'); +export interface DashboardTableItem { + id: string; + type: string; + attributes: { + title: string; + description: string; + }; + references: Array<{ name: string; type: string; id: string }>; +} diff --git a/x-pack/plugins/security_solution/public/common/containers/dashboards/use_create_security_dashboard_link.test.ts b/x-pack/plugins/security_solution/public/common/containers/dashboards/use_create_security_dashboard_link.test.ts index b92b303a7b77f..a41640d678262 100644 --- a/x-pack/plugins/security_solution/public/common/containers/dashboards/use_create_security_dashboard_link.test.ts +++ b/x-pack/plugins/security_solution/public/common/containers/dashboards/use_create_security_dashboard_link.test.ts @@ -9,26 +9,15 @@ import { renderHook, act } from '@testing-library/react-hooks'; import type { DashboardStart } from '@kbn/dashboard-plugin/public'; import { useKibana } from '../../lib/kibana'; import { TestProviders } from '../../mock/test_providers'; -import type { Tag } from '@kbn/saved-objects-tagging-plugin/common'; import { useCreateSecurityDashboardLink } from './use_create_security_dashboard_link'; +import { MOCK_TAG_ID } from './api/__mocks__'; +import { getSecurityTagIds as mockGetSecurityTagIds } from './utils'; jest.mock('../../lib/kibana'); -const TAG_ID = 'securityTagId'; -const CREATED_TAG: Tag = { - id: TAG_ID, - name: 'tag title', - description: 'tag description', - color: '#999999', -}; const URL = '/path'; -const mockGetSecurityTagId = jest.fn(async (): Promise => null); -const mockCreateSecurityTag = jest.fn(async () => CREATED_TAG); -jest.mock('./utils', () => ({ - getSecurityTagId: () => mockGetSecurityTagId(), - createSecurityTag: () => mockCreateSecurityTag(), -})); +jest.mock('./utils'); const renderUseCreateSecurityDashboardLink = () => renderHook(() => useCreateSecurityDashboardLink(), { @@ -57,8 +46,7 @@ describe('useCreateSecurityDashboardLink', () => { it('should request when renders', async () => { await asyncRenderUseCreateSecurityDashboard(); - expect(mockGetSecurityTagId).toHaveBeenCalledTimes(1); - expect(mockCreateSecurityTag).toHaveBeenCalledTimes(1); + expect(mockGetSecurityTagIds).toHaveBeenCalledTimes(1); }); it('should return a memoized value when rerendered', async () => { @@ -71,28 +59,18 @@ describe('useCreateSecurityDashboardLink', () => { expect(result1).toBe(result2); }); - it('should not request create tag if already exists', async () => { - mockGetSecurityTagId.mockResolvedValueOnce(TAG_ID); - await asyncRenderUseCreateSecurityDashboard(); - - expect(mockGetSecurityTagId).toHaveBeenCalledTimes(1); - expect(mockCreateSecurityTag).not.toHaveBeenCalled(); - }); - it('should generate create url with tag', async () => { await asyncRenderUseCreateSecurityDashboard(); - expect(mockGetRedirectUrl).toHaveBeenCalledWith({ tags: [TAG_ID] }); + expect(mockGetRedirectUrl).toHaveBeenCalledWith({ tags: [MOCK_TAG_ID] }); }); it('should not re-request tag id when re-rendered', async () => { const { rerender } = await asyncRenderUseCreateSecurityDashboard(); - expect(mockGetSecurityTagId).toHaveBeenCalledTimes(1); - expect(mockCreateSecurityTag).toHaveBeenCalledTimes(1); + expect(mockGetSecurityTagIds).toHaveBeenCalledTimes(1); act(() => rerender()); - expect(mockGetSecurityTagId).toHaveBeenCalledTimes(1); - expect(mockCreateSecurityTag).toHaveBeenCalledTimes(1); + expect(mockGetSecurityTagIds).toHaveBeenCalledTimes(1); }); it('should return isLoading while requesting', async () => { diff --git a/x-pack/plugins/security_solution/public/common/containers/dashboards/use_create_security_dashboard_link.ts b/x-pack/plugins/security_solution/public/common/containers/dashboards/use_create_security_dashboard_link.ts index 2c8c5d7315de8..7b5a46d9b78e6 100644 --- a/x-pack/plugins/security_solution/public/common/containers/dashboards/use_create_security_dashboard_link.ts +++ b/x-pack/plugins/security_solution/public/common/containers/dashboards/use_create_security_dashboard_link.ts @@ -7,30 +7,24 @@ import { useEffect, useMemo, useState } from 'react'; import { useKibana } from '../../lib/kibana'; -import { getSecurityTagId, createSecurityTag } from './utils'; +import { getSecurityTagIds } from './utils'; type UseCreateDashboard = () => { isLoading: boolean; url: string }; export const useCreateSecurityDashboardLink: UseCreateDashboard = () => { - const { - dashboard: { locator } = {}, - savedObjects: { client: savedObjectsClient }, - savedObjectsTagging, - } = useKibana().services; + const { dashboard: { locator } = {}, savedObjectsTagging, http } = useKibana().services; - const [securityTagId, setSecurityTagId] = useState(null); + const [securityTagId, setSecurityTagId] = useState(null); useEffect(() => { let ignore = false; const getOrCreateSecurityTag = async () => { - if (savedObjectsClient && savedObjectsTagging) { - let tagId = await getSecurityTagId(savedObjectsClient); - if (!tagId) { - const newTag = await createSecurityTag(savedObjectsTagging.client); - tagId = newTag.id; - } - if (!ignore) { - setSecurityTagId(tagId); + if (http && savedObjectsTagging) { + // getSecurityTagIds creates a tag if it coundn't find one + const tagIds = await getSecurityTagIds(http); + + if (!ignore && tagIds) { + setSecurityTagId(tagIds); } } }; @@ -40,12 +34,12 @@ export const useCreateSecurityDashboardLink: UseCreateDashboard = () => { return () => { ignore = true; }; - }, [savedObjectsClient, savedObjectsTagging]); + }, [http, savedObjectsTagging]); const result = useMemo( () => ({ isLoading: securityTagId == null, - url: securityTagId ? locator?.getRedirectUrl({ tags: [securityTagId] }) ?? '' : '', + url: securityTagId ? locator?.getRedirectUrl({ tags: [securityTagId[0]] }) ?? '' : '', }), [securityTagId, locator] ); diff --git a/x-pack/plugins/security_solution/public/common/containers/dashboards/use_security_dashboards_table.test.tsx b/x-pack/plugins/security_solution/public/common/containers/dashboards/use_security_dashboards_table.test.tsx index 3a6a12be604e9..143a1335d4de8 100644 --- a/x-pack/plugins/security_solution/public/common/containers/dashboards/use_security_dashboards_table.test.tsx +++ b/x-pack/plugins/security_solution/public/common/containers/dashboards/use_security_dashboards_table.test.tsx @@ -12,12 +12,12 @@ import type { DashboardStart } from '@kbn/dashboard-plugin/public'; import { EuiBasicTable } from '@elastic/eui'; import { useKibana } from '../../lib/kibana'; import { TestProviders } from '../../mock/test_providers'; -import type { DashboardTableItem } from './use_security_dashboards_table'; import { useSecurityDashboardsTableColumns, useSecurityDashboardsTableItems, } from './use_security_dashboards_table'; import * as telemetry from '../../lib/telemetry'; +import type { DashboardTableItem } from './types'; jest.mock('../../lib/kibana'); const spyTrack = jest.spyOn(telemetry, 'track'); diff --git a/x-pack/plugins/security_solution/public/common/containers/dashboards/use_security_dashboards_table.tsx b/x-pack/plugins/security_solution/public/common/containers/dashboards/use_security_dashboards_table.tsx index 771e68fde0b78..9380ce31ea49c 100644 --- a/x-pack/plugins/security_solution/public/common/containers/dashboards/use_security_dashboards_table.tsx +++ b/x-pack/plugins/security_solution/public/common/containers/dashboards/use_security_dashboards_table.tsx @@ -8,26 +8,18 @@ import React, { useEffect, useMemo, useCallback } from 'react'; import type { MouseEventHandler } from 'react'; import type { EuiBasicTableColumn } from '@elastic/eui'; -import type { SavedObjectAttributes } from '@kbn/securitysolution-io-ts-alerting-types'; -import type { SavedObject } from '@kbn/core/public'; import { getSecurityDashboards } from './utils'; import { LinkAnchor } from '../../components/links'; import { useKibana, useNavigateTo } from '../../lib/kibana'; import * as i18n from './translations'; import { useFetch, REQUEST_NAMES } from '../../hooks/use_fetch'; import { METRIC_TYPE, TELEMETRY_EVENT, track } from '../../lib/telemetry'; - -export interface DashboardTableItem extends SavedObject { - title?: string; - description?: string; -} +import type { DashboardTableItem } from './types'; const EMPTY_DESCRIPTION = '-' as const; export const useSecurityDashboardsTableItems = () => { - const { - savedObjects: { client: savedObjectsClient }, - } = useKibana().services; + const { http } = useKibana().services; const { fetch, data, isLoading, error } = useFetch( REQUEST_NAMES.SECURITY_DASHBOARDS, @@ -35,10 +27,10 @@ export const useSecurityDashboardsTableItems = () => { ); useEffect(() => { - if (savedObjectsClient) { - fetch(savedObjectsClient); + if (http) { + fetch(http); } - }, [fetch, savedObjectsClient]); + }, [fetch, http]); const items = useMemo(() => { if (!data) { diff --git a/x-pack/plugins/security_solution/public/common/containers/dashboards/utils.test.ts b/x-pack/plugins/security_solution/public/common/containers/dashboards/utils.test.ts index 24911a82a2e82..72c1057fb5e45 100644 --- a/x-pack/plugins/security_solution/public/common/containers/dashboards/utils.test.ts +++ b/x-pack/plugins/security_solution/public/common/containers/dashboards/utils.test.ts @@ -5,75 +5,82 @@ * 2.0. */ -import type { SavedObjectsClientContract } from '@kbn/core/public'; -import { SECURITY_TAG_NAME } from '../../../../common/constants'; -import { getSecurityTagId } from './utils'; +import type { HttpSetup } from '@kbn/core/public'; +import { + getSecuritySolutionTags as mockGetSecuritySolutionTags, + getSecuritySolutionDashboards as mockGetSecuritySolutionDashboards, +} from './api'; +import { getSecurityDashboards, getSecurityTagIds } from './utils'; -const TAG_ID = 'securityTagId'; -const DEFAULT_TAGS_RESPONSE = [ - { - id: TAG_ID, - attributes: { name: SECURITY_TAG_NAME }, - }, - { - id: `${TAG_ID}_2`, - attributes: { name: `${SECURITY_TAG_NAME}_2` }, - }, -]; - -const mockSavedObjectsFind = jest.fn(async () => ({ savedObjects: DEFAULT_TAGS_RESPONSE })); -const savedObjectsClient = { - find: mockSavedObjectsFind, -} as unknown as SavedObjectsClientContract; +jest.mock('./api'); +const mockHttp = {} as unknown as HttpSetup; describe('dashboards utils', () => { afterEach(() => { jest.clearAllMocks(); }); - describe('getSecurityTagId', () => { - it('should call saved objects find with security tag name', async () => { - await getSecurityTagId(savedObjectsClient); + describe('getSecurityTagIds', () => { + it('should call getSecuritySolutionTags with http', async () => { + await getSecurityTagIds(mockHttp); - expect(mockSavedObjectsFind).toHaveBeenCalledWith( - expect.objectContaining({ type: 'tag', search: SECURITY_TAG_NAME, searchFields: ['name'] }) + expect(mockGetSecuritySolutionTags).toHaveBeenCalledWith( + expect.objectContaining({ http: mockHttp }) ); }); - it('should find saved object with security tag name', async () => { - const result = await getSecurityTagId(savedObjectsClient); - - expect(result).toEqual(TAG_ID); - }); - - it('should not find saved object with wrong security tag name', async () => { - mockSavedObjectsFind.mockResolvedValueOnce({ savedObjects: [DEFAULT_TAGS_RESPONSE[1]] }); - const result = await getSecurityTagId(savedObjectsClient); + it('should find saved objects Ids with security tags', async () => { + const result = await getSecurityTagIds(mockHttp); - expect(result).toBeUndefined(); + expect(result).toMatchInlineSnapshot(` + Array [ + "securityTagId", + ] + `); }); }); - describe('createSecurityTag', () => { - it('should call saved objects find with security tag name', async () => { - await getSecurityTagId(savedObjectsClient); + describe('getSecurityDashboards', () => { + it('should call getSecuritySolutionDashboards with http', async () => { + await getSecurityDashboards(mockHttp); - expect(mockSavedObjectsFind).toHaveBeenCalledWith( - expect.objectContaining({ type: 'tag', search: SECURITY_TAG_NAME, searchFields: ['name'] }) + expect(mockGetSecuritySolutionDashboards).toHaveBeenCalledWith( + expect.objectContaining({ http: mockHttp }) ); }); - it('should find saved object with security tag name', async () => { - const result = await getSecurityTagId(savedObjectsClient); - - expect(result).toEqual(TAG_ID); - }); - - it('should not find saved object with wrong security tag name', async () => { - mockSavedObjectsFind.mockResolvedValueOnce({ savedObjects: [DEFAULT_TAGS_RESPONSE[1]] }); - const result = await getSecurityTagId(savedObjectsClient); - - expect(result).toBeUndefined(); + it('should find saved objects with security tags', async () => { + const result = await getSecurityDashboards(mockHttp); + + expect(result).toMatchInlineSnapshot(` + Array [ + Object { + "attributes": Object { + "description": "Summary of Linux kernel audit events.", + "title": "[Auditbeat Auditd] Overview ECS", + "version": 1, + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2023-04-03T11:20:50.603Z", + "id": "c0ac2c00-c1c0-11e7-8995-936807a28b16-ecs", + "namespaces": Array [ + "default", + ], + "references": Array [ + Object { + "id": "ba964280-d211-11ed-890b-153ddf1a08e9", + "name": "tag-ref-ba964280-d211-11ed-890b-153ddf1a08e9", + "type": "tag", + }, + ], + "score": 0, + "type": "dashboard", + "typeMigrationVersion": "8.7.0", + "updated_at": "2023-04-03T11:38:00.902Z", + "version": "WzE4NzQsMV0=", + }, + ] + `); }); }); }); diff --git a/x-pack/plugins/security_solution/public/common/containers/dashboards/utils.ts b/x-pack/plugins/security_solution/public/common/containers/dashboards/utils.ts index f5f4e56424513..97a40a7196b8a 100644 --- a/x-pack/plugins/security_solution/public/common/containers/dashboards/utils.ts +++ b/x-pack/plugins/security_solution/public/common/containers/dashboards/utils.ts @@ -5,65 +5,26 @@ * 2.0. */ -import type { SavedObjectAttributes } from '@kbn/securitysolution-io-ts-alerting-types'; -import type { SavedObjectsClientContract, SavedObject } from '@kbn/core/public'; -import type { Tag, TagAttributes } from '@kbn/saved-objects-tagging-plugin/common'; -import type { SavedObjectsTaggingApi } from '@kbn/saved-objects-tagging-oss-plugin/public'; -import { SECURITY_TAG_NAME } from '../../../../common/constants'; - -export const SECURITY_TAG_DESCRIPTION = 'Security Solution auto-generated tag' as const; - -/** - * Returns the hex representation of a random color (e.g `#F1B7E2`) - */ -const getRandomColor = (): string => { - return `#${String(Math.floor(Math.random() * 16777215).toString(16)).padStart(6, '0')}`; -}; +import type { HttpSetup } from '@kbn/core/public'; +import { getSecuritySolutionDashboards, getSecuritySolutionTags } from './api'; +import type { DashboardTableItem } from './types'; /** - * Request the security tag saved object and returns the id if exists + * Request the security tag saved object and returns the id if exists. + * It creates one if the tag doesn't exist. */ -export const getSecurityTagId = async ( - savedObjectsClient: SavedObjectsClientContract -): Promise => { - const tagResponse = await savedObjectsClient.find({ - type: 'tag', - searchFields: ['name'], - search: SECURITY_TAG_NAME, - }); - // The search query returns partial matches, we need to find the exact tag name - return tagResponse.savedObjects.find(({ attributes }) => attributes.name === SECURITY_TAG_NAME) - ?.id; -}; - -/** - * Creates the security tag saved object and returns its id - */ -export const createSecurityTag = async ( - tagsClient: SavedObjectsTaggingApi['client'] -): Promise => { - // We need to use the TaggingApi client to make sure the Dashboards app tags cache is refreshed - const tagResponse = await tagsClient.create({ - name: SECURITY_TAG_NAME, - description: SECURITY_TAG_DESCRIPTION, - color: getRandomColor(), - }); - return tagResponse; +export const getSecurityTagIds = async (http: HttpSetup): Promise => { + const tagResponse = await getSecuritySolutionTags({ http }); + return tagResponse?.map(({ id }: { id: string }) => id); }; /** * Requests the saved objects of the security tagged dashboards */ export const getSecurityDashboards = async ( - savedObjectsClient: SavedObjectsClientContract -): Promise>> => { - const tagId = await getSecurityTagId(savedObjectsClient); - if (!tagId) { - return []; - } - const dashboardsResponse = await savedObjectsClient.find({ - type: 'dashboard', - hasReference: { id: tagId, type: 'tag' }, - }); - return dashboardsResponse.savedObjects; + http: HttpSetup +): Promise => { + const dashboardsResponse = await getSecuritySolutionDashboards({ http }); + + return dashboardsResponse; }; diff --git a/x-pack/plugins/security_solution/public/common/utils/route/spy_routes.tsx b/x-pack/plugins/security_solution/public/common/utils/route/spy_routes.tsx index baebde568137a..7cf30f6726188 100644 --- a/x-pack/plugins/security_solution/public/common/utils/route/spy_routes.tsx +++ b/x-pack/plugins/security_solution/public/common/utils/route/spy_routes.tsx @@ -24,7 +24,7 @@ type SpyRouteProps = RouteComponentProps<{ flowTarget: FlowTarget | undefined; }> & { location: H.Location; - state?: Record; + state?: Record; pageName?: SecurityPageName; }; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/index.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/index.tsx index ca0c1fb9e868d..7b397afb290e5 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/index.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/index.tsx @@ -891,7 +891,10 @@ const RuleDetailsPageComponent: React.FC = ({ - + ); }; diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/utils.ts b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/utils.ts index f48c35cecb425..0a4fcbb0de3c5 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/utils.ts +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/utils.ts @@ -24,6 +24,7 @@ import { RuleDetailTabs, RULE_DETAILS_TAB_NAME, } from '../../../../detection_engine/rule_details_ui/pages/rule_details'; +import { DELETED_RULE } from '../../../../detection_engine/rule_details_ui/pages/rule_details/translations'; import { fillEmptySeverityMappings } from './helpers'; export const ruleStepsOrder: RuleStepsOrder = [ @@ -95,6 +96,10 @@ export const getTrailingBreadcrumbs = ( ]; } + if (!isRuleEditPage(params.pathName) && params.state && !params.state.isExistingRule) { + breadcrumb = [...breadcrumb, { text: DELETED_RULE, href: '' }]; + } + return breadcrumb; }; diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_execute_action/execute_action_host_response.test.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_execute_action/execute_action_host_response.test.tsx index 956118e1ac297..ea91afe44afdd 100644 --- a/x-pack/plugins/security_solution/public/management/components/endpoint_execute_action/execute_action_host_response.test.tsx +++ b/x-pack/plugins/security_solution/public/management/components/endpoint_execute_action/execute_action_host_response.test.tsx @@ -18,19 +18,22 @@ import { type ExecuteActionHostResponseProps, } from './execute_action_host_response'; import { getEmptyValue } from '@kbn/cases-plugin/public/components/empty_value'; +import { EXECUTE_OUTPUT_FILE_TRUNCATED_MESSAGE } from './execute_action_host_response_output'; describe('When using the `ExecuteActionHostResponse` component', () => { let render: () => ReturnType; let renderResult: ReturnType; let renderProps: ExecuteActionHostResponseProps; - const action = new EndpointActionGenerator('seed').generateActionDetails< - ResponseActionExecuteOutputContent, - ResponseActionsExecuteParameters - >({ command: 'execute', agents: ['agent-a'] }); + let action: ActionDetails; beforeEach(() => { const appTestContext = createAppRootMockRenderer(); + action = new EndpointActionGenerator('seed').generateActionDetails< + ResponseActionExecuteOutputContent, + ResponseActionsExecuteParameters + >({ command: 'execute', agents: ['agent-a'] }); + renderProps = { action, canAccessFileDownloadLink: true, @@ -60,7 +63,7 @@ describe('When using the `ExecuteActionHostResponse` component', () => { ); }); - it('should show current working directory', async () => { + it('should show execute from directory', async () => { render(); const { queryByTestId } = renderResult; expect(queryByTestId(`test-executeResponseOutput-context`)).toBeInTheDocument(); @@ -81,6 +84,13 @@ describe('When using the `ExecuteActionHostResponse` component', () => { ).toContain('isOpen'); }); + it('should show execute error output accordion as `open`', async () => { + render(); + expect(renderResult.getByTestId('test-executeResponseOutput-error').className).toContain( + 'isOpen' + ); + }); + it('should show `-` in output accordion when no output content', async () => { (renderProps.action as ActionDetails).outputs = { 'agent-a': { @@ -98,7 +108,7 @@ describe('When using the `ExecuteActionHostResponse` component', () => { ).toContain(`Execution output (truncated)${getEmptyValue()}`); }); - it('should show `-` in error accordion when no error content', async () => { + it('should NOT show the error accordion when no error content', async () => { (renderProps.action as ActionDetails).outputs = { 'agent-a': { type: 'json', @@ -110,9 +120,7 @@ describe('When using the `ExecuteActionHostResponse` component', () => { }; render(); - expect(renderResult.getByTestId('test-executeResponseOutput-error').textContent).toContain( - `Execution error (truncated)${getEmptyValue()}` - ); + expect(renderResult.queryByTestId('test-executeResponseOutput-error')).not.toBeInTheDocument(); }); it('should not show execute output accordions when no output in action details', () => { @@ -123,4 +131,27 @@ describe('When using the `ExecuteActionHostResponse` component', () => { expect(queryByTestId(`test-executeResponseOutput-context`)).not.toBeInTheDocument(); expect(queryByTestId(`test-executeResponseOutput-${outputSuffix}`)).not.toBeInTheDocument(); }); + + it('should show file truncate messages for STDOUT and STDERR', () => { + (renderProps.action as ActionDetails).outputs = { + 'agent-a': { + type: 'json', + content: { + ...(renderProps.action as ActionDetails).outputs?.[ + action.agents[0] + ].content!, + output_file_stdout_truncated: true, + output_file_stderr_truncated: true, + }, + }, + }; + const { getByTestId } = render(); + + expect(getByTestId('test-executeResponseOutput-output-fileTruncatedMsg')).toHaveTextContent( + EXECUTE_OUTPUT_FILE_TRUNCATED_MESSAGE + ); + expect(getByTestId('test-executeResponseOutput-error-fileTruncatedMsg')).toHaveTextContent( + EXECUTE_OUTPUT_FILE_TRUNCATED_MESSAGE + ); + }); }); diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_execute_action/execute_action_host_response.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_execute_action/execute_action_host_response.tsx index c46a2550a32f0..3c7b5818907ac 100644 --- a/x-pack/plugins/security_solution/public/management/components/endpoint_execute_action/execute_action_host_response.tsx +++ b/x-pack/plugins/security_solution/public/management/components/endpoint_execute_action/execute_action_host_response.tsx @@ -4,7 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { EuiFlexItem } from '@elastic/eui'; +import { EuiFlexItem, EuiSpacer } from '@elastic/eui'; import React, { memo, useMemo } from 'react'; import type { ActionDetails, @@ -39,14 +39,6 @@ export const ExecuteActionHostResponse = memo( [action.outputs, agentId] ); - const isTruncatedFile = useMemo( - () => - (outputContent?.output_file_stderr_truncated || - outputContent?.output_file_stdout_truncated) ?? - false, - [outputContent] - ); - return ( <> @@ -54,10 +46,10 @@ export const ExecuteActionHostResponse = memo( action={action} buttonTitle={EXECUTE_FILE_LINK_TITLE} canAccessFileDownloadLink={canAccessFileDownloadLink} - isTruncatedFile={isTruncatedFile} data-test-subj={`${dataTestSubj}-getExecuteLink`} textSize={textSize} /> + {outputContent && ( ( content = emptyValue, initialIsOpen = false, isTruncated = false, + isFileTruncated = false, textSize, type, 'data-test-subj': dataTestSubj, }) => { + const getTestId = useTestIdGenerator(dataTestSubj); const id = useGeneratedHtmlId({ prefix: 'executeActionOutputAccordions', suffix: type, @@ -145,6 +158,14 @@ const ExecutionActionOutputAccordion = memo( data-test-subj={dataTestSubj} > + {isFileTruncated && ( + <> + + {EXECUTE_OUTPUT_FILE_TRUNCATED_MESSAGE} + + + + )} {typeof content === 'string' ?

{content}

: content}
@@ -192,10 +213,10 @@ export const ExecuteActionHostResponseOutput = memo - + {outputContent.stderr.length > 0 && ( + <> + + + + )} - - ); diff --git a/x-pack/plugins/security_solution/public/management/components/response_action_file_download_link/response_action_file_download_link.tsx b/x-pack/plugins/security_solution/public/management/components/response_action_file_download_link/response_action_file_download_link.tsx index a3e99810704d9..7f86c96fcefae 100644 --- a/x-pack/plugins/security_solution/public/management/components/response_action_file_download_link/response_action_file_download_link.tsx +++ b/x-pack/plugins/security_solution/public/management/components/response_action_file_download_link/response_action_file_download_link.tsx @@ -88,7 +88,7 @@ const TruncatedTextInfo = memo( - + {FILE_TRUNCATED_MESSAGE} @@ -159,7 +159,11 @@ export const ResponseActionFileDownloadLink = memo + {FILE_NO_LONGER_AVAILABLE_MESSAGE} ); @@ -187,7 +191,7 @@ export const ResponseActionFileDownloadLink = memo {FILE_PASSCODE_INFO_MESSAGE} - + {FILE_DELETED_MESSAGE} {isTruncatedFile && ( diff --git a/x-pack/plugins/security_solution/scripts/endpoint/agent_emulator/screens/actions_responder.ts b/x-pack/plugins/security_solution/scripts/endpoint/agent_emulator/screens/actions_responder.ts index 448f8907986fc..59c05a0393081 100644 --- a/x-pack/plugins/security_solution/scripts/endpoint/agent_emulator/screens/actions_responder.ts +++ b/x-pack/plugins/security_solution/scripts/endpoint/agent_emulator/screens/actions_responder.ts @@ -40,6 +40,15 @@ export class ActionResponderScreen extends ScreenBaseClass { RESPOND.FLEET.STATE=SUCCESS Respond to Fleet Action with success RESPOND.FLEET.STATE=FAILURE Respond to Fleet Action with failure + Action specific: + In addition, the following can also be used when 'RESPOND.STATE' is 'SUCCESS' + + Token Description + --------------------------- ------------------------------------ + EXECUTE:SUCCESS Successful execute response output + EXECUTE:FAILURE Failure execute output + + ${blue(HORIZONTAL_LINE.substring(0, HORIZONTAL_LINE.length - 2))} ${actionsAndStatus.output}`; } diff --git a/x-pack/plugins/security_solution/scripts/endpoint/agent_emulator/services/endpoint_response_actions.ts b/x-pack/plugins/security_solution/scripts/endpoint/agent_emulator/services/endpoint_response_actions.ts index 995aa73835fca..2b3d73efac361 100644 --- a/x-pack/plugins/security_solution/scripts/endpoint/agent_emulator/services/endpoint_response_actions.ts +++ b/x-pack/plugins/security_solution/scripts/endpoint/agent_emulator/services/endpoint_response_actions.ts @@ -282,6 +282,8 @@ type ResponseOutput = Pick< >; const getOutputDataIfNeeded = (action: ActionDetails): ResponseOutput => { + const commentUppercase = (action?.comment ?? '').toUpperCase(); + switch (action.command) { case 'running-processes': return { @@ -319,11 +321,24 @@ const getOutputDataIfNeeded = (action: ActionDetails): ResponseOutput => { } as ResponseOutput; case 'execute': + const executeOutput: Partial = { + output_file_id: getFileDownloadId(action, action.agents[0]), + }; + + // Error? + if (commentUppercase.indexOf('EXECUTE:FAILURE') > -1) { + executeOutput.stdout = ''; + executeOutput.stdout_truncated = false; + executeOutput.output_file_stdout_truncated = false; + } else { + executeOutput.stderr = ''; + executeOutput.stderr_truncated = false; + executeOutput.output_file_stderr_truncated = false; + } + return { output: endpointActionGenerator.generateExecuteActionResponseOutput({ - content: { - output_file_id: getFileDownloadId(action, action.agents[0]), - }, + content: executeOutput, }), } as ResponseOutput; diff --git a/x-pack/plugins/security_solution/server/lib/dashboards/__mocks__/index.ts b/x-pack/plugins/security_solution/server/lib/dashboards/__mocks__/index.ts new file mode 100644 index 0000000000000..85960cd4629fa --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/dashboards/__mocks__/index.ts @@ -0,0 +1,123 @@ +/* + * 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 const mockGetDashboardsResult = [ + { + type: 'dashboard', + id: 'd698d5f0-cd58-11ed-affc-fb75e701db4b', + namespaces: ['default'], + attributes: { + kibanaSavedObjectMeta: { + searchSourceJSON: '{"query":{"query":"","language":"kuery"},"filter":[]}', + }, + description: '', + timeRestore: false, + optionsJSON: + '{"useMargins":true,"syncColors":false,"syncCursor":true,"syncTooltips":false,"hidePanelTitles":false}', + panelsJSON: + '[{"version":"8.8.0","type":"lens","gridData":{"x":0,"y":0,"w":24,"h":15,"i":"46c2105e-0edd-460c-8ecf-aaf3777b0c9b"},"panelIndex":"46c2105e-0edd-460c-8ecf-aaf3777b0c9b","embeddableConfig":{"attributes":{"title":"my alerts chart","description":"","visualizationType":"lnsXY","type":"lens","references":[{"type":"index-pattern","id":"security-solution-default","name":"indexpattern-datasource-layer-eafb5cfd-bd7e-4c1f-a675-ef11a17c616d"}],"state":{"visualization":{"title":"Empty XY chart","legend":{"isVisible":true,"position":"left","legendSize":"xlarge"},"valueLabels":"hide","preferredSeriesType":"bar_stacked","layers":[{"layerId":"eafb5cfd-bd7e-4c1f-a675-ef11a17c616d","accessors":["e09e0380-0740-4105-becc-0a4ca12e3944"],"position":"top","seriesType":"bar_stacked","showGridlines":false,"layerType":"data","xAccessor":"aac9d7d0-13a3-480a-892b-08207a787926","splitAccessor":"34919782-4546-43a5-b668-06ac934d3acd"}],"yRightExtent":{"mode":"full"},"yLeftExtent":{"mode":"full"},"axisTitlesVisibilitySettings":{"x":false,"yLeft":false,"yRight":true},"valuesInLegend":true},"query":{"query":"","language":"kuery"},"filters":[{"meta":{"alias":null,"negate":true,"disabled":false,"type":"exists","key":"kibana.alert.building_block_type"},"query":{"exists":{"field":"kibana.alert.building_block_type"}},"$state":{"store":"appState"}},{"meta":{"type":"phrases","key":"_index","params":[".alerts-security.alerts-default"],"alias":null,"negate":false,"disabled":false},"query":{"bool":{"should":[{"match_phrase":{"_index":".alerts-security.alerts-default"}}],"minimum_should_match":1}},"$state":{"store":"appState"}}],"datasourceStates":{"formBased":{"layers":{"eafb5cfd-bd7e-4c1f-a675-ef11a17c616d":{"columns":{"aac9d7d0-13a3-480a-892b-08207a787926":{"label":"@timestamp","dataType":"date","operationType":"date_histogram","sourceField":"@timestamp","isBucketed":true,"scale":"interval","params":{"interval":"auto"}},"e09e0380-0740-4105-becc-0a4ca12e3944":{"label":"Count of records","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"___records___"},"34919782-4546-43a5-b668-06ac934d3acd":{"label":"Top values of kibana.alert.rule.name","dataType":"string","operationType":"terms","scale":"ordinal","sourceField":"kibana.alert.rule.name","isBucketed":true,"params":{"size":1000,"orderBy":{"type":"column","columnId":"e09e0380-0740-4105-becc-0a4ca12e3944"},"orderDirection":"desc","otherBucket":true,"missingBucket":false,"parentFormat":{"id":"terms"},"secondaryFields":[]}}},"columnOrder":["34919782-4546-43a5-b668-06ac934d3acd","aac9d7d0-13a3-480a-892b-08207a787926","e09e0380-0740-4105-becc-0a4ca12e3944"],"incompleteColumns":{}}}}},"internalReferences":[],"adHocDataViews":{}}},"enhancements":{}}},{"version":"8.8.0","type":"lens","gridData":{"x":24,"y":0,"w":24,"h":15,"i":"8bcec072-4cf2-417d-a740-2b7f34c69473"},"panelIndex":"8bcec072-4cf2-417d-a740-2b7f34c69473","embeddableConfig":{"attributes":{"title":"events","description":"","visualizationType":"lnsXY","type":"lens","references":[{"type":"index-pattern","id":"security-solution-default","name":"indexpattern-datasource-layer-63ba54b1-ca7d-4fa3-b43b-d748723abad4"}],"state":{"visualization":{"title":"Empty XY chart","legend":{"isVisible":true,"position":"left","legendSize":"xlarge"},"valueLabels":"hide","preferredSeriesType":"bar_stacked","layers":[{"layerId":"63ba54b1-ca7d-4fa3-b43b-d748723abad4","accessors":["e09e0380-0740-4105-becc-0a4ca12e3944"],"position":"top","seriesType":"bar_stacked","showGridlines":false,"layerType":"data","xAccessor":"aac9d7d0-13a3-480a-892b-08207a787926","splitAccessor":"34919782-4546-43a5-b668-06ac934d3acd"}],"yRightExtent":{"mode":"full"},"yLeftExtent":{"mode":"full"},"axisTitlesVisibilitySettings":{"x":false,"yLeft":false,"yRight":true}},"query":{"query":"","language":"kuery"},"filters":[{"meta":{"alias":null,"disabled":false,"key":"query","negate":false,"type":"custom"},"query":{"bool":{"should":[{"exists":{"field":"host.name"}}],"minimum_should_match":1}},"$state":{"store":"appState"}},{"meta":{"type":"phrases","key":"_index","params":["auditbeat-*","packetbeat-*"],"alias":null,"negate":false,"disabled":false},"query":{"bool":{"should":[{"match_phrase":{"_index":"auditbeat-*"}},{"match_phrase":{"_index":"packetbeat-*"}}],"minimum_should_match":1}},"$state":{"store":"appState"}}],"datasourceStates":{"formBased":{"layers":{"63ba54b1-ca7d-4fa3-b43b-d748723abad4":{"columns":{"aac9d7d0-13a3-480a-892b-08207a787926":{"label":"@timestamp","dataType":"date","operationType":"date_histogram","sourceField":"@timestamp","isBucketed":true,"scale":"interval","params":{"interval":"auto"}},"e09e0380-0740-4105-becc-0a4ca12e3944":{"label":"Count of records","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"___records___"},"34919782-4546-43a5-b668-06ac934d3acd":{"label":"Top values of event.action","dataType":"string","operationType":"terms","scale":"ordinal","sourceField":"event.action","isBucketed":true,"params":{"size":10,"orderBy":{"type":"column","columnId":"e09e0380-0740-4105-becc-0a4ca12e3944"},"orderDirection":"desc","otherBucket":true,"missingBucket":false,"parentFormat":{"id":"terms"}}}},"columnOrder":["34919782-4546-43a5-b668-06ac934d3acd","aac9d7d0-13a3-480a-892b-08207a787926","e09e0380-0740-4105-becc-0a4ca12e3944"],"incompleteColumns":{}}}}},"internalReferences":[],"adHocDataViews":{}}},"enhancements":{}}}]', + title: 'my alerts dashboard', + version: 1, + }, + references: [ + { + type: 'index-pattern', + id: 'security-solution-default', + name: '46c2105e-0edd-460c-8ecf-aaf3777b0c9b:indexpattern-datasource-layer-eafb5cfd-bd7e-4c1f-a675-ef11a17c616d', + }, + { + type: 'index-pattern', + id: 'security-solution-default', + name: '8bcec072-4cf2-417d-a740-2b7f34c69473:indexpattern-datasource-layer-63ba54b1-ca7d-4fa3-b43b-d748723abad4', + }, + { + type: 'tag', + id: 'de7ad1f0-ccc8-11ed-9175-1b0d4269ff48', + name: 'tag-ref-de7ad1f0-ccc8-11ed-9175-1b0d4269ff48', + }, + ], + coreMigrationVersion: '8.8.0', + typeMigrationVersion: '8.7.0', + updated_at: '2023-03-28T11:27:28.365Z', + created_at: '2023-03-28T11:27:28.365Z', + version: 'WzE3NTIwLDFd', + score: 0, + }, + { + type: 'dashboard', + id: 'eee18bf0-cfc1-11ed-8380-f532c904188c', + namespaces: ['default'], + attributes: { + kibanaSavedObjectMeta: { + searchSourceJSON: + '{"query":{"query":"","language":"kuery"},"filter":[{"meta":{"type":"phrase","key":"event.action","params":{"query":"process_stopped"},"disabled":false,"negate":false,"alias":null,"indexRefName":"kibanaSavedObjectMeta.searchSourceJSON.filter[0].meta.index"},"query":{"match_phrase":{"event.action":"process_stopped"}},"$state":{"store":"appState"}}]}', + }, + description: '', + timeRestore: false, + optionsJSON: + '{"useMargins":true,"syncColors":false,"syncCursor":true,"syncTooltips":false,"hidePanelTitles":false}', + panelsJSON: + '[{"version":"8.8.0","type":"lens","gridData":{"x":0,"y":0,"w":24,"h":15,"i":"46c2105e-0edd-460c-8ecf-aaf3777b0c9b"},"panelIndex":"46c2105e-0edd-460c-8ecf-aaf3777b0c9b","embeddableConfig":{"attributes":{"title":"my alerts chart","description":"","visualizationType":"lnsXY","type":"lens","references":[{"type":"index-pattern","id":"security-solution-default","name":"indexpattern-datasource-layer-eafb5cfd-bd7e-4c1f-a675-ef11a17c616d"}],"state":{"visualization":{"title":"Empty XY chart","legend":{"isVisible":true,"position":"left","legendSize":"xlarge"},"valueLabels":"hide","preferredSeriesType":"bar_stacked","layers":[{"layerId":"eafb5cfd-bd7e-4c1f-a675-ef11a17c616d","accessors":["e09e0380-0740-4105-becc-0a4ca12e3944"],"position":"top","seriesType":"bar_stacked","showGridlines":false,"layerType":"data","xAccessor":"aac9d7d0-13a3-480a-892b-08207a787926","splitAccessor":"34919782-4546-43a5-b668-06ac934d3acd"}],"yRightExtent":{"mode":"full"},"yLeftExtent":{"mode":"full"},"axisTitlesVisibilitySettings":{"x":false,"yLeft":false,"yRight":true},"valuesInLegend":true},"query":{"query":"","language":"kuery"},"filters":[{"meta":{"alias":null,"negate":true,"disabled":false,"type":"exists","key":"kibana.alert.building_block_type"},"query":{"exists":{"field":"kibana.alert.building_block_type"}},"$state":{"store":"appState"}},{"meta":{"type":"phrases","key":"_index","params":[".alerts-security.alerts-default"],"alias":null,"negate":false,"disabled":false},"query":{"bool":{"should":[{"match_phrase":{"_index":".alerts-security.alerts-default"}}],"minimum_should_match":1}},"$state":{"store":"appState"}}],"datasourceStates":{"formBased":{"layers":{"eafb5cfd-bd7e-4c1f-a675-ef11a17c616d":{"columns":{"aac9d7d0-13a3-480a-892b-08207a787926":{"label":"@timestamp","dataType":"date","operationType":"date_histogram","sourceField":"@timestamp","isBucketed":true,"scale":"interval","params":{"interval":"auto"}},"e09e0380-0740-4105-becc-0a4ca12e3944":{"label":"Count of records","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"___records___"},"34919782-4546-43a5-b668-06ac934d3acd":{"label":"Top values of kibana.alert.rule.name","dataType":"string","operationType":"terms","scale":"ordinal","sourceField":"kibana.alert.rule.name","isBucketed":true,"params":{"size":1000,"orderBy":{"type":"column","columnId":"e09e0380-0740-4105-becc-0a4ca12e3944"},"orderDirection":"desc","otherBucket":true,"missingBucket":false,"parentFormat":{"id":"terms"},"secondaryFields":[]}}},"columnOrder":["34919782-4546-43a5-b668-06ac934d3acd","aac9d7d0-13a3-480a-892b-08207a787926","e09e0380-0740-4105-becc-0a4ca12e3944"],"incompleteColumns":{}}}}},"internalReferences":[],"adHocDataViews":{}}},"enhancements":{}}},{"version":"8.8.0","type":"lens","gridData":{"x":24,"y":0,"w":24,"h":15,"i":"8bcec072-4cf2-417d-a740-2b7f34c69473"},"panelIndex":"8bcec072-4cf2-417d-a740-2b7f34c69473","embeddableConfig":{"attributes":{"title":"events","description":"","visualizationType":"lnsXY","type":"lens","references":[{"type":"index-pattern","id":"security-solution-default","name":"indexpattern-datasource-layer-63ba54b1-ca7d-4fa3-b43b-d748723abad4"}],"state":{"visualization":{"title":"Empty XY chart","legend":{"isVisible":true,"position":"left","legendSize":"xlarge"},"valueLabels":"hide","preferredSeriesType":"bar_stacked","layers":[{"layerId":"63ba54b1-ca7d-4fa3-b43b-d748723abad4","accessors":["e09e0380-0740-4105-becc-0a4ca12e3944"],"position":"top","seriesType":"bar_stacked","showGridlines":false,"layerType":"data","xAccessor":"aac9d7d0-13a3-480a-892b-08207a787926","splitAccessor":"34919782-4546-43a5-b668-06ac934d3acd"}],"yRightExtent":{"mode":"full"},"yLeftExtent":{"mode":"full"},"axisTitlesVisibilitySettings":{"x":false,"yLeft":false,"yRight":true}},"query":{"query":"","language":"kuery"},"filters":[{"meta":{"alias":null,"disabled":false,"key":"query","negate":false,"type":"custom"},"query":{"bool":{"should":[{"exists":{"field":"host.name"}}],"minimum_should_match":1}},"$state":{"store":"appState"}},{"meta":{"type":"phrases","key":"_index","params":["auditbeat-*","packetbeat-*"],"alias":null,"negate":false,"disabled":false},"query":{"bool":{"should":[{"match_phrase":{"_index":"auditbeat-*"}},{"match_phrase":{"_index":"packetbeat-*"}}],"minimum_should_match":1}},"$state":{"store":"appState"}}],"datasourceStates":{"formBased":{"layers":{"63ba54b1-ca7d-4fa3-b43b-d748723abad4":{"columns":{"aac9d7d0-13a3-480a-892b-08207a787926":{"label":"@timestamp","dataType":"date","operationType":"date_histogram","sourceField":"@timestamp","isBucketed":true,"scale":"interval","params":{"interval":"auto"}},"e09e0380-0740-4105-becc-0a4ca12e3944":{"label":"Count of records","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"___records___"},"34919782-4546-43a5-b668-06ac934d3acd":{"label":"Top values of event.action","dataType":"string","operationType":"terms","scale":"ordinal","sourceField":"event.action","isBucketed":true,"params":{"size":10,"orderBy":{"type":"column","columnId":"e09e0380-0740-4105-becc-0a4ca12e3944"},"orderDirection":"desc","otherBucket":true,"missingBucket":false,"parentFormat":{"id":"terms"}}}},"columnOrder":["34919782-4546-43a5-b668-06ac934d3acd","aac9d7d0-13a3-480a-892b-08207a787926","e09e0380-0740-4105-becc-0a4ca12e3944"],"incompleteColumns":{}}}}},"internalReferences":[],"adHocDataViews":{}}},"enhancements":{}}}]', + title: 'my alerts dashboard - 2', + version: 1, + }, + references: [ + { + name: 'kibanaSavedObjectMeta.searchSourceJSON.filter[0].meta.index', + type: 'index-pattern', + id: 'security-solution-default', + }, + { + type: 'index-pattern', + id: 'security-solution-default', + name: '46c2105e-0edd-460c-8ecf-aaf3777b0c9b:indexpattern-datasource-layer-eafb5cfd-bd7e-4c1f-a675-ef11a17c616d', + }, + { + type: 'index-pattern', + id: 'security-solution-default', + name: '8bcec072-4cf2-417d-a740-2b7f34c69473:indexpattern-datasource-layer-63ba54b1-ca7d-4fa3-b43b-d748723abad4', + }, + { + type: 'tag', + id: 'de7ad1f0-ccc8-11ed-9175-1b0d4269ff48', + name: 'tag-ref-de7ad1f0-ccc8-11ed-9175-1b0d4269ff48', + }, + { + type: 'tag', + id: 'edb233b0-cfc1-11ed-8380-f532c904188c', + name: 'tag-ref-edb233b0-cfc1-11ed-8380-f532c904188c', + }, + ], + coreMigrationVersion: '8.8.0', + typeMigrationVersion: '8.7.0', + updated_at: '2023-03-31T12:45:36.175Z', + created_at: '2023-03-31T12:45:36.175Z', + version: 'WzE5MTQyLDFd', + score: 0, + }, +]; + +export const mockGetTagsResult = [ + { + type: 'tag', + id: 'de7ad1f0-ccc8-11ed-9175-1b0d4269ff48', + namespaces: ['default'], + attributes: { + name: 'Security Solution', + description: 'Security Solution auto-generated tag', + color: '#4bc922', + }, + references: [], + coreMigrationVersion: '8.8.0', + typeMigrationVersion: '8.0.0', + updated_at: '2023-03-27T17:57:41.647Z', + created_at: '2023-03-27T17:57:41.647Z', + version: 'WzE2Njc1LDFd', + score: null, + sort: [1679939861647], + }, +]; diff --git a/x-pack/plugins/security_solution/server/lib/dashboards/helpers.ts b/x-pack/plugins/security_solution/server/lib/dashboards/helpers.ts new file mode 100644 index 0000000000000..58bedbc4411d2 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/dashboards/helpers.ts @@ -0,0 +1,94 @@ +/* + * 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 type { Logger } from '@kbn/core/server'; +import type { + SavedObject, + SavedObjectsClientContract, + SavedObjectsFindResult, +} from '@kbn/core-saved-objects-api-server'; +import type { TagAttributes } from '@kbn/saved-objects-tagging-plugin/common'; +import type { DashboardAttributes } from '@kbn/dashboard-plugin/common'; +import type { OutputError } from '@kbn/securitysolution-es-utils'; +import { transformError } from '@kbn/securitysolution-es-utils'; +import { SECURITY_TAG_NAME, SECURITY_TAG_DESCRIPTION } from '../../../common/constants'; +import { createTag, findTagsByName } from './saved_objects/tags'; + +/** + * Returns the hex representation of a random color (e.g `#F1B7E2`) + */ +const getRandomColor = (): string => { + return `#${String(Math.floor(Math.random() * 16777215).toString(16)).padStart(6, '0')}`; +}; + +export const getOrCreateSecurityTag = async ({ + logger, + savedObjectsClient, +}: { + logger: Logger; + savedObjectsClient: SavedObjectsClientContract; +}): Promise<{ + response: Array> | null; + error?: OutputError; +}> => { + const { response: existingTags } = await findTagsByName({ + savedObjectsClient, + search: SECURITY_TAG_NAME, + }); + + if (existingTags && existingTags.length > 0) { + return { response: existingTags }; + } else { + const { error, response: createdTag } = await createTag({ + savedObjectsClient, + tagName: SECURITY_TAG_NAME, + description: SECURITY_TAG_DESCRIPTION, + color: getRandomColor(), + }); + + if (createdTag && !error) { + return { response: [createdTag] }; + } else { + logger.error(`Failed to create ${SECURITY_TAG_NAME} tag - ${JSON.stringify(error?.message)}`); + return { + response: null, + error: error ?? transformError(new Error(`Failed to create ${SECURITY_TAG_NAME} tag`)), + }; + } + } +}; + +export const getSecuritySolutionDashboards = async ({ + logger, + savedObjectsClient, +}: { + logger: Logger; + savedObjectsClient: SavedObjectsClientContract; +}): Promise<{ + response: Array> | null; + error?: OutputError; +}> => { + const { response: foundTags } = await findTagsByName({ + savedObjectsClient, + search: SECURITY_TAG_NAME, + }); + + if (!foundTags || foundTags?.length === 0) { + return { response: [] }; + } + + try { + const dashboardsResponse = await savedObjectsClient.find({ + type: 'dashboard', + hasReference: foundTags.map(({ id: tagId }) => ({ id: tagId, type: 'tag' })), + }); + return { response: dashboardsResponse.saved_objects }; + } catch (e) { + logger.error(`Failed to get SecuritySolution Dashboards - ${JSON.stringify(e?.message)}`); + + return { response: null, error: transformError(e) }; + } +}; diff --git a/x-pack/plugins/security_solution/server/lib/dashboards/routes/get_security_solution_dashboards.test.ts b/x-pack/plugins/security_solution/server/lib/dashboards/routes/get_security_solution_dashboards.test.ts new file mode 100644 index 0000000000000..5bca5246e2592 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/dashboards/routes/get_security_solution_dashboards.test.ts @@ -0,0 +1,70 @@ +/* + * 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 type { Logger } from '@kbn/core/server'; +import type { SecurityPluginSetup } from '@kbn/security-plugin/server'; +import { INTERNAL_DASHBOARDS_URL } from '../../../../common/constants'; +import { + serverMock, + requestContextMock, + mockGetCurrentUser, + requestMock, +} from '../../detection_engine/routes/__mocks__'; +import { getSecuritySolutionDashboards } from '../helpers'; +import { mockGetDashboardsResult } from '../__mocks__'; +import { getSecuritySolutionDashboardsRoute } from './get_security_solution_dashboards'; +jest.mock('../helpers', () => ({ getSecuritySolutionDashboards: jest.fn() })); + +describe('getSecuritySolutionDashboardsRoute', () => { + let server: ReturnType; + let securitySetup: SecurityPluginSetup; + const { context } = requestContextMock.createTools(); + const logger = { error: jest.fn() } as unknown as Logger; + const mockRequest = requestMock.create({ + method: 'get', + path: INTERNAL_DASHBOARDS_URL, + }); + beforeEach(() => { + jest.clearAllMocks(); + server = serverMock.create(); + + securitySetup = { + authc: { + getCurrentUser: jest.fn().mockReturnValue(mockGetCurrentUser), + }, + authz: {}, + } as unknown as SecurityPluginSetup; + + getSecuritySolutionDashboardsRoute(server.router, logger, securitySetup); + }); + + it('should return dashboards with Security Solution tags', async () => { + (getSecuritySolutionDashboards as jest.Mock).mockResolvedValue({ + response: mockGetDashboardsResult, + }); + + const response = await server.inject(mockRequest, requestContextMock.convertContext(context)); + + expect(response.status).toEqual(200); + expect(response.body).toEqual(mockGetDashboardsResult); + }); + + it('should return error', async () => { + const error = { + statusCode: 500, + message: 'Internal Server Error', + }; + (getSecuritySolutionDashboards as jest.Mock).mockResolvedValue({ + response: null, + error, + }); + + const response = await server.inject(mockRequest, requestContextMock.convertContext(context)); + + expect(response.status).toEqual(error.statusCode); + expect(response.body.message).toEqual(`Failed to get dashboards - ${error.message}`); + }); +}); diff --git a/x-pack/plugins/security_solution/server/lib/dashboards/routes/get_security_solution_dashboards.ts b/x-pack/plugins/security_solution/server/lib/dashboards/routes/get_security_solution_dashboards.ts new file mode 100644 index 0000000000000..828a890f963c3 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/dashboards/routes/get_security_solution_dashboards.ts @@ -0,0 +1,56 @@ +/* + * 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 type { Logger } from '@kbn/core/server'; +import { i18n } from '@kbn/i18n'; + +import { INTERNAL_DASHBOARDS_URL } from '../../../../common/constants'; +import type { SetupPlugins } from '../../../plugin'; +import type { SecuritySolutionPluginRouter } from '../../../types'; +import { buildSiemResponse } from '../../detection_engine/routes/utils'; +import { buildFrameworkRequest } from '../../timeline/utils/common'; +import { getSecuritySolutionDashboards } from '../helpers'; + +export const getSecuritySolutionDashboardsRoute = ( + router: SecuritySolutionPluginRouter, + logger: Logger, + security: SetupPlugins['security'] +) => { + router.get( + { + path: INTERNAL_DASHBOARDS_URL, + validate: false, + options: { + tags: ['access:securitySolution'], + }, + }, + async (context, request, response) => { + const siemResponse = buildSiemResponse(response); + + const frameworkRequest = await buildFrameworkRequest(context, security, request); + const savedObjectsClient = (await frameworkRequest.context.core).savedObjects.client; + + const { response: dashboards, error } = await getSecuritySolutionDashboards({ + logger, + savedObjectsClient, + }); + if (!error && dashboards != null) { + return response.ok({ body: dashboards }); + } else { + return siemResponse.error({ + statusCode: error?.statusCode ?? 500, + body: i18n.translate( + 'xpack.securitySolution.dashboards.getSecuritySolutionDashboardsErrorTitle', + { + values: { message: error?.message }, + defaultMessage: `Failed to get dashboards - {message}`, + } + ), + }); + } + } + ); +}; diff --git a/x-pack/plugins/security_solution/server/lib/dashboards/routes/get_security_solution_tags.test.ts b/x-pack/plugins/security_solution/server/lib/dashboards/routes/get_security_solution_tags.test.ts new file mode 100644 index 0000000000000..8f59087ff9ee6 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/dashboards/routes/get_security_solution_tags.test.ts @@ -0,0 +1,81 @@ +/* + * 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 type { Logger } from '@kbn/core/server'; +import type { SecurityPluginSetup } from '@kbn/security-plugin/server'; +import { INTERNAL_TAGS_URL, SECURITY_TAG_NAME } from '../../../../common/constants'; +import { + serverMock, + requestContextMock, + mockGetCurrentUser, + requestMock, +} from '../../detection_engine/routes/__mocks__'; +import { getOrCreateSecurityTag } from '../helpers'; +import { mockGetTagsResult } from '../__mocks__'; +import { getSecuritySolutionTagsRoute } from './get_security_solution_tags'; +jest.mock('../helpers', () => ({ getOrCreateSecurityTag: jest.fn() })); + +describe('getSecuritySolutionTagsRoute', () => { + let server: ReturnType; + let securitySetup: SecurityPluginSetup; + const { context } = requestContextMock.createTools(); + const logger = { error: jest.fn() } as unknown as Logger; + const mockRequest = requestMock.create({ + method: 'get', + path: INTERNAL_TAGS_URL, + }); + beforeEach(() => { + jest.clearAllMocks(); + server = serverMock.create(); + + securitySetup = { + authc: { + getCurrentUser: jest.fn().mockReturnValue(mockGetCurrentUser), + }, + authz: {}, + } as unknown as SecurityPluginSetup; + + getSecuritySolutionTagsRoute(server.router, logger, securitySetup); + }); + + it('should return tags with Security Solution tags', async () => { + (getOrCreateSecurityTag as jest.Mock).mockResolvedValue({ + response: mockGetTagsResult, + }); + + const response = await server.inject(mockRequest, requestContextMock.convertContext(context)); + + expect(response.status).toEqual(200); + expect(response.body).toMatchInlineSnapshot(` + Array [ + Object { + "color": "#4bc922", + "description": "Security Solution auto-generated tag", + "id": "de7ad1f0-ccc8-11ed-9175-1b0d4269ff48", + "name": "Security Solution", + }, + ] + `); + }); + + it('should return error', async () => { + const error = { + statusCode: 500, + message: 'Internal Server Error', + }; + (getOrCreateSecurityTag as jest.Mock).mockResolvedValue({ + response: null, + error, + }); + + const response = await server.inject(mockRequest, requestContextMock.convertContext(context)); + + expect(response.status).toEqual(error.statusCode); + expect(response.body.message).toEqual( + `Failed to create ${SECURITY_TAG_NAME} tag - ${error.message}` + ); + }); +}); diff --git a/x-pack/plugins/security_solution/server/lib/dashboards/routes/get_security_solution_tags.ts b/x-pack/plugins/security_solution/server/lib/dashboards/routes/get_security_solution_tags.ts new file mode 100644 index 0000000000000..8c70a114ba647 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/dashboards/routes/get_security_solution_tags.ts @@ -0,0 +1,64 @@ +/* + * 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 type { Logger } from '@kbn/core/server'; +import { i18n } from '@kbn/i18n'; + +import { INTERNAL_TAGS_URL, SECURITY_TAG_NAME } from '../../../../common/constants'; +import type { SetupPlugins } from '../../../plugin'; +import type { SecuritySolutionPluginRouter } from '../../../types'; +import { buildSiemResponse } from '../../detection_engine/routes/utils'; +import { buildFrameworkRequest } from '../../timeline/utils/common'; +import { getOrCreateSecurityTag } from '../helpers'; + +export const getSecuritySolutionTagsRoute = ( + router: SecuritySolutionPluginRouter, + logger: Logger, + security: SetupPlugins['security'] +) => { + router.get( + { + path: INTERNAL_TAGS_URL, + validate: false, + options: { + tags: ['access:securitySolution'], + }, + }, + async (context, request, response) => { + const siemResponse = buildSiemResponse(response); + + const frameworkRequest = await buildFrameworkRequest(context, security, request); + const savedObjectsClient = (await frameworkRequest.context.core).savedObjects.client; + + const { response: tags, error } = await getOrCreateSecurityTag({ + logger, + savedObjectsClient, + }); + + if (tags && !error) { + return response.ok({ + body: tags.map(({ id, attributes: { name, description, color } }) => ({ + id, + name, + description, + color, + })), + }); + } else { + return siemResponse.error({ + statusCode: error?.statusCode ?? 500, + body: i18n.translate( + 'xpack.securitySolution.dashboards.getSecuritySolutionTagsErrorTitle', + { + values: { tagName: SECURITY_TAG_NAME, message: error?.message }, + defaultMessage: `Failed to create {tagName} tag - {message}`, + } + ), + }); + } + } + ); +}; diff --git a/x-pack/plugins/security_solution/server/lib/dashboards/routes/index.ts b/x-pack/plugins/security_solution/server/lib/dashboards/routes/index.ts new file mode 100644 index 0000000000000..be21be7f3e24a --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/dashboards/routes/index.ts @@ -0,0 +1,8 @@ +/* + * 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 { getSecuritySolutionDashboardsRoute } from './get_security_solution_dashboards'; +export { getSecuritySolutionTagsRoute } from './get_security_solution_tags'; diff --git a/x-pack/plugins/security_solution/server/lib/dashboards/saved_objects/tags/create_tag.ts b/x-pack/plugins/security_solution/server/lib/dashboards/saved_objects/tags/create_tag.ts new file mode 100644 index 0000000000000..27646630b4f01 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/dashboards/saved_objects/tags/create_tag.ts @@ -0,0 +1,58 @@ +/* + * 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 type { + SavedObject, + SavedObjectReference, + SavedObjectsClientContract, +} from '@kbn/core/server'; +import type { TagAttributes } from '@kbn/saved-objects-tagging-plugin/common'; +import type { OutputError } from '@kbn/securitysolution-es-utils'; +import { transformError } from '@kbn/securitysolution-es-utils'; + +interface CreateTagParams { + savedObjectsClient: SavedObjectsClientContract; + tagName: string; + description: string; + color: string; + references?: SavedObjectReference[]; +} + +interface CreateTagResponse { + error?: OutputError; + response: SavedObject | null; +} + +export const createTag = async ({ + savedObjectsClient, + tagName, + description, + color, + references, +}: CreateTagParams): Promise => { + const TYPE = 'tag'; + try { + const createdTag = await savedObjectsClient.create( + TYPE, + { + name: tagName, + description, + color, + }, + { references } + ); + + return { + response: createdTag, + }; + } catch (e) { + return { + error: transformError(e), + response: null, + }; + } +}; diff --git a/x-pack/plugins/security_solution/server/lib/dashboards/saved_objects/tags/find_tags_by_name.ts b/x-pack/plugins/security_solution/server/lib/dashboards/saved_objects/tags/find_tags_by_name.ts new file mode 100644 index 0000000000000..76cc55a950e0e --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/dashboards/saved_objects/tags/find_tags_by_name.ts @@ -0,0 +1,40 @@ +/* + * 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 type { SavedObjectsClientContract, SavedObjectsFindResult } from '@kbn/core/server'; +import type { TagAttributes } from '@kbn/saved-objects-tagging-plugin/common'; +import type { OutputError } from '@kbn/securitysolution-es-utils'; +import { transformError } from '@kbn/securitysolution-es-utils'; + +export const findTagsByName = async ({ + savedObjectsClient, + search, +}: { + savedObjectsClient: SavedObjectsClientContract; + search: string; +}): Promise<{ + response: Array> | null; + error?: OutputError; +}> => { + try { + const tagResponse = await savedObjectsClient.find({ + type: 'tag', + search, + searchFields: ['name'], + sortField: 'updated_at', + sortOrder: 'desc', + }); + return { + response: tagResponse.saved_objects.filter(({ attributes: { name } }) => name === search), + }; + } catch (e) { + return { + error: transformError(e), + response: null, + }; + } +}; diff --git a/x-pack/plugins/synthetics/e2e/journeys/uptime/private_locations/index.ts b/x-pack/plugins/security_solution/server/lib/dashboards/saved_objects/tags/index.ts similarity index 72% rename from x-pack/plugins/synthetics/e2e/journeys/uptime/private_locations/index.ts rename to x-pack/plugins/security_solution/server/lib/dashboards/saved_objects/tags/index.ts index f25661c71d082..e9af2f18e5931 100644 --- a/x-pack/plugins/synthetics/e2e/journeys/uptime/private_locations/index.ts +++ b/x-pack/plugins/security_solution/server/lib/dashboards/saved_objects/tags/index.ts @@ -5,5 +5,5 @@ * 2.0. */ -export * from './manage_locations'; -export * from './add_monitor_private_location'; +export { createTag } from './create_tag'; +export { findTagsByName } from './find_tags_by_name'; diff --git a/x-pack/plugins/security_solution/server/lib/risk_score/prebuilt_saved_objects/helpers/bulk_create_saved_objects.ts b/x-pack/plugins/security_solution/server/lib/risk_score/prebuilt_saved_objects/helpers/bulk_create_saved_objects.ts index 7cd493cff2b74..96feae5a7c7b4 100644 --- a/x-pack/plugins/security_solution/server/lib/risk_score/prebuilt_saved_objects/helpers/bulk_create_saved_objects.ts +++ b/x-pack/plugins/security_solution/server/lib/risk_score/prebuilt_saved_objects/helpers/bulk_create_saved_objects.ts @@ -38,9 +38,10 @@ export const bulkCreateSavedObjects = async ({ spaceId, }); - const tagResult = tagResponse?.hostRiskScoreDashboards ?? tagResponse?.userRiskScoreDashboards; + const riskScoreTagResult = + tagResponse?.hostRiskScoreDashboards ?? tagResponse?.userRiskScoreDashboards; - if (!tagResult?.success) { + if (!riskScoreTagResult?.success) { return tagResponse; } @@ -79,7 +80,11 @@ export const bulkCreateSavedObjects = async ({ id: idReplaceMappings[so.id] ?? so.id, references: [ ...references, - { id: tagResult?.body?.id, name: tagResult?.body?.name, type: tagResult?.body?.type }, + { + id: riskScoreTagResult?.body?.id, + name: riskScoreTagResult?.body?.name, + type: riskScoreTagResult?.body?.type, + }, ], }; }); diff --git a/x-pack/plugins/security_solution/server/lib/risk_score/prebuilt_saved_objects/helpers/find_or_create_tag.ts b/x-pack/plugins/security_solution/server/lib/risk_score/prebuilt_saved_objects/helpers/find_or_create_tag.ts index 9bb15266f2f71..fddb9dd0e47d0 100644 --- a/x-pack/plugins/security_solution/server/lib/risk_score/prebuilt_saved_objects/helpers/find_or_create_tag.ts +++ b/x-pack/plugins/security_solution/server/lib/risk_score/prebuilt_saved_objects/helpers/find_or_create_tag.ts @@ -9,9 +9,9 @@ import type { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-ser import { transformError } from '@kbn/securitysolution-es-utils'; import { i18n } from '@kbn/i18n'; import type { RiskScoreEntity } from '../../../../../common/search_strategy'; -import type { Tag } from './utils'; import { RISK_SCORE_TAG_DESCRIPTION, getRiskScoreTagName } from './utils'; import type { BulkCreateSavedObjectsResult } from '../types'; +import { createTag, findTagsByName } from '../../../dashboards/saved_objects/tags'; export const findRiskScoreTag = async ({ savedObjectsClient, @@ -20,17 +20,9 @@ export const findRiskScoreTag = async ({ savedObjectsClient: SavedObjectsClientContract; search: string; }) => { - const tagResponse = await savedObjectsClient.find({ - type: 'tag', - search, - searchFields: ['name'], - sortField: 'updated_at', - sortOrder: 'desc', - }); + const { response: tagResponse } = await findTagsByName({ savedObjectsClient, search }); - const existingRiskScoreTag = tagResponse.saved_objects.find( - ({ attributes }) => attributes.name === search - ); + const existingRiskScoreTag = tagResponse?.find(({ attributes }) => attributes.name === search); return existingRiskScoreTag ? { @@ -85,23 +77,26 @@ export const findOrCreateRiskScoreTag = async ({ }, }; } else { - try { - const { id: tagId } = await savedObjectsClient.create('tag', { - name: tagName, - description: RISK_SCORE_TAG_DESCRIPTION, - color: '#6edb7f', - }); + const { error, response: createTagResponse } = await createTag({ + savedObjectsClient, + tagName, + description: RISK_SCORE_TAG_DESCRIPTION, + color: '#6edb7f', + }); + if (!error && createTagResponse?.id) { return { [savedObjectTemplate]: { success: true, error: null, - body: { ...tag, id: tagId }, + body: { ...tag, id: createTagResponse?.id }, }, }; - } catch (e) { + } else { logger.error( - `${savedObjectTemplate} cannot be installed as failed to create the tag: ${tagName}` + `${savedObjectTemplate} cannot be installed as failed to create the tag: ${tagName} - ${JSON.stringify( + error?.message + )}` ); return { [savedObjectTemplate]: { diff --git a/x-pack/plugins/security_solution/server/lib/risk_score/prebuilt_saved_objects/helpers/utils.ts b/x-pack/plugins/security_solution/server/lib/risk_score/prebuilt_saved_objects/helpers/utils.ts index 74a84a49eb92d..9392b157e6d56 100644 --- a/x-pack/plugins/security_solution/server/lib/risk_score/prebuilt_saved_objects/helpers/utils.ts +++ b/x-pack/plugins/security_solution/server/lib/risk_score/prebuilt_saved_objects/helpers/utils.ts @@ -8,12 +8,6 @@ import { RiskScoreEntity } from '../../../../../common/search_strategy'; import type { SavedObjectTemplate } from '../types'; -export interface Tag { - id: string; - name: string; - description: string; -} - export const HOST_RISK_SCORE = 'Host Risk Score'; export const USER_RISK_SCORE = 'User Risk Score'; diff --git a/x-pack/plugins/security_solution/server/routes/index.ts b/x-pack/plugins/security_solution/server/routes/index.ts index af26823416a2b..2c88194291af9 100644 --- a/x-pack/plugins/security_solution/server/routes/index.ts +++ b/x-pack/plugins/security_solution/server/routes/index.ts @@ -71,6 +71,10 @@ import { readPrebuiltDevToolContentRoute, } from '../lib/risk_score/routes'; import { registerManageExceptionsRoutes } from '../lib/exceptions/api/register_routes'; +import { + getSecuritySolutionDashboardsRoute, + getSecuritySolutionTagsRoute, +} from '../lib/dashboards/routes'; export const initRoutes = ( router: SecuritySolutionPluginRouter, @@ -157,6 +161,10 @@ export const initRoutes = ( deletePrebuiltSavedObjectsRoute(router, security); getRiskScoreIndexStatusRoute(router); installRiskScoresRoute(router, logger, security); + + // Dashboards + getSecuritySolutionDashboardsRoute(router, logger, security); + getSecuritySolutionTagsRoute(router, logger, security); const { previewTelemetryUrlEnabled } = config.experimentalFeatures; if (previewTelemetryUrlEnabled) { // telemetry preview endpoint for e2e integration tests only at the moment. diff --git a/x-pack/plugins/synthetics/common/requests/get_certs_request_body.ts b/x-pack/plugins/synthetics/common/requests/get_certs_request_body.ts index ce6e0ec96313c..72ed6346df188 100644 --- a/x-pack/plugins/synthetics/common/requests/get_certs_request_body.ts +++ b/x-pack/plugins/synthetics/common/requests/get_certs_request_body.ts @@ -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, }; }); diff --git a/x-pack/plugins/synthetics/common/runtime_types/certs.ts b/x-pack/plugins/synthetics/common/runtime_types/certs.ts index b7231da2bb1be..f7de2280d4b65 100644 --- a/x-pack/plugins/synthetics/common/runtime_types/certs.ts +++ b/x-pack/plugins/synthetics/common/runtime_types/certs.ts @@ -29,6 +29,7 @@ export type GetCertsParams = t.TypeOf; export const CertMonitorType = t.partial({ name: t.string, id: t.string, + configId: t.string, url: t.string, }); diff --git a/x-pack/plugins/synthetics/e2e/journeys/synthetics/test_now_mode.journey.ts b/x-pack/plugins/synthetics/e2e/journeys/synthetics/test_now_mode.journey.ts index df7d3da5bd177..846f36f45542d 100644 --- a/x-pack/plugins/synthetics/e2e/journeys/synthetics/test_now_mode.journey.ts +++ b/x-pack/plugins/synthetics/e2e/journeys/synthetics/test_now_mode.journey.ts @@ -136,8 +136,10 @@ journey(`TestNowMode`, async ({ page, params }) => { await services.addTestSummaryDocument({ testRunId, docType: 'stepEnd', stepIndex: 1 }); await page.waitForSelector('text=1 step completed'); - await page.waitForSelector('text=Go to https://www.google.com'); - await page.waitForSelector('text=1.42 s'); + await page.waitForSelector( + '.euiTableRowCell--hideForMobile :has-text("Go to https://www.google.com")' + ); + await page.waitForSelector('.euiTableRowCell--hideForMobile :has-text("1.42 s")'); await page.waitForSelector('text=Complete'); }); @@ -146,7 +148,7 @@ journey(`TestNowMode`, async ({ page, params }) => { await retry.tryForTime(90 * 1000, async () => { await page.waitForSelector('text=2 steps completed'); await page.waitForSelector('text="Go to step 2"'); - await page.waitForSelector('text=788 ms'); + await page.waitForSelector('div:has-text("788 ms")'); await page.waitForSelector('text=IN PROGRESS'); }); }); diff --git a/x-pack/plugins/synthetics/e2e/journeys/synthetics/test_run_details.journey.ts b/x-pack/plugins/synthetics/e2e/journeys/synthetics/test_run_details.journey.ts index 080ca78d71358..5db5a55a4759d 100644 --- a/x-pack/plugins/synthetics/e2e/journeys/synthetics/test_run_details.journey.ts +++ b/x-pack/plugins/synthetics/e2e/journeys/synthetics/test_run_details.journey.ts @@ -65,6 +65,6 @@ journey(`TestRunDetailsPage`, async ({ page, params }) => { await page.waitForSelector('text=Test run details'); await page.waitForSelector('text=Go to https://www.google.com'); - await page.waitForSelector('text=After 2.12 s'); + await page.waitForSelector('div:has-text("After 2.12 s")'); }); }); diff --git a/x-pack/plugins/synthetics/e2e/journeys/uptime/index.ts b/x-pack/plugins/synthetics/e2e/journeys/uptime/index.ts index af59317232acf..a507c0d111b5d 100644 --- a/x-pack/plugins/synthetics/e2e/journeys/uptime/index.ts +++ b/x-pack/plugins/synthetics/e2e/journeys/uptime/index.ts @@ -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'; diff --git a/x-pack/plugins/synthetics/e2e/journeys/uptime/monitor_details.journey.ts b/x-pack/plugins/synthetics/e2e/journeys/uptime/monitor_details.journey.ts deleted file mode 100644 index 2265af524b2c2..0000000000000 --- a/x-pack/plugins/synthetics/e2e/journeys/uptime/monitor_details.journey.ts +++ /dev/null @@ -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(); - }); -}); diff --git a/x-pack/plugins/synthetics/e2e/journeys/uptime/monitor_details/ping_redirects.journey.ts b/x-pack/plugins/synthetics/e2e/journeys/uptime/monitor_details/ping_redirects.journey.ts index 9d753a75c92d7..af0d38de81775 100644 --- a/x-pack/plugins/synthetics/e2e/journeys/uptime/monitor_details/ping_redirects.journey.ts +++ b/x-pack/plugins/synthetics/e2e/journeys/uptime/monitor_details/ping_redirects.journey.ts @@ -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, diff --git a/x-pack/plugins/synthetics/e2e/journeys/uptime/monitor_management.journey.ts b/x-pack/plugins/synthetics/e2e/journeys/uptime/monitor_management.journey.ts deleted file mode 100644 index df39cf7cc52eb..0000000000000 --- a/x-pack/plugins/synthetics/e2e/journeys/uptime/monitor_management.journey.ts +++ /dev/null @@ -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; - monitorDetails: Record; -}) => { - 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); - }); - } -); diff --git a/x-pack/plugins/synthetics/e2e/journeys/uptime/monitor_management_enablement.journey.ts b/x-pack/plugins/synthetics/e2e/journeys/uptime/monitor_management_enablement.journey.ts deleted file mode 100644 index b62f6b3f5a3f4..0000000000000 --- a/x-pack/plugins/synthetics/e2e/journeys/uptime/monitor_management_enablement.journey.ts +++ /dev/null @@ -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(); - }); - } -); diff --git a/x-pack/plugins/synthetics/e2e/journeys/uptime/monitor_name.journey.ts b/x-pack/plugins/synthetics/e2e/journeys/uptime/monitor_name.journey.ts deleted file mode 100644 index 5c35c7de082a0..0000000000000 --- a/x-pack/plugins/synthetics/e2e/journeys/uptime/monitor_name.journey.ts +++ /dev/null @@ -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); - }); -}); diff --git a/x-pack/plugins/synthetics/e2e/journeys/uptime/private_locations/add_monitor_private_location.ts b/x-pack/plugins/synthetics/e2e/journeys/uptime/private_locations/add_monitor_private_location.ts deleted file mode 100644 index 23a8d2cb96414..0000000000000 --- a/x-pack/plugins/synthetics/e2e/journeys/uptime/private_locations/add_monitor_private_location.ts +++ /dev/null @@ -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 - ); - }); -}); diff --git a/x-pack/plugins/synthetics/e2e/journeys/uptime/private_locations/manage_locations.ts b/x-pack/plugins/synthetics/e2e/journeys/uptime/private_locations/manage_locations.ts deleted file mode 100644 index c5b5195f076d6..0000000000000 --- a/x-pack/plugins/synthetics/e2e/journeys/uptime/private_locations/manage_locations.ts +++ /dev/null @@ -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")'); - }; -}); diff --git a/x-pack/plugins/synthetics/e2e/journeys/uptime/read_only_user/monitor_management.ts b/x-pack/plugins/synthetics/e2e/journeys/uptime/read_only_user/monitor_management.ts deleted file mode 100644 index c7c44d2e6bc0e..0000000000000 --- a/x-pack/plugins/synthetics/e2e/journeys/uptime/read_only_user/monitor_management.ts +++ /dev/null @@ -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(); - }); - } -); diff --git a/x-pack/plugins/synthetics/e2e/journeys/uptime/step_duration.journey.ts b/x-pack/plugins/synthetics/e2e/journeys/uptime/step_duration.journey.ts index f77b0ef4e88f5..2dc781fcbf90f 100644 --- a/x-pack/plugins/synthetics/e2e/journeys/uptime/step_duration.journey.ts +++ b/x-pack/plugins/synthetics/e2e/journeys/uptime/step_duration.journey.ts @@ -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}`); }); diff --git a/x-pack/plugins/synthetics/e2e/page_objects/uptime/monitor_details.tsx b/x-pack/plugins/synthetics/e2e/page_objects/uptime/monitor_details.tsx index 150f6992da81e..a1d1add0bf147 100644 --- a/x-pack/plugins/synthetics/e2e/page_objects/uptime/monitor_details.tsx +++ b/x-pack/plugins/synthetics/e2e/page_objects/uptime/monitor_details.tsx @@ -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', + }); + }, }; } diff --git a/x-pack/plugins/synthetics/e2e/page_objects/uptime/monitor_management.tsx b/x-pack/plugins/synthetics/e2e/page_objects/uptime/monitor_management.tsx deleted file mode 100644 index 5bbe3bd8e8def..0000000000000 --- a/x-pack/plugins/synthetics/e2e/page_objects/uptime/monitor_management.tsx +++ /dev/null @@ -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) { - 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; - 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; - } - }, - }; -} diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/components/location_status_badges.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/components/location_status_badges.tsx index 3115597135a96..8ba6fb3bc54ff 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/components/location_status_badges.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/components/location_status_badges.tsx @@ -38,9 +38,18 @@ export const LocationStatusBadges = ({ const locationsToDisplay = locations.slice(0, toDisplay); return ( - + {locationsToDisplay.map((loc) => ( - + = ({ title, hasBorder = true, hasShadow = false, children, titleLeftAlign, ...props }) => { + { title?: string; titleLeftAlign?: boolean; margin?: string } & EuiPanelProps +> = ({ + title, + hasBorder = true, + hasShadow = false, + children, + titleLeftAlign, + margin, + ...props +}) => { const { euiTheme } = useEuiTheme(); return ( @@ -19,7 +27,7 @@ export const PanelWithTitle: React.FC<

{label ?? VIEW_DETAILS} @@ -44,10 +50,11 @@ export const StepDetailsLinkIcon = ({ return ( diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/browser_steps_list.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/browser_steps_list.tsx index 17a9c4077e372..508b9d29c04a9 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/browser_steps_list.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/browser_steps_list.tsx @@ -6,7 +6,14 @@ */ import { i18n } from '@kbn/i18n'; -import React, { CSSProperties, ReactElement, useCallback, useEffect, useState } from 'react'; +import React, { + CSSProperties, + ReactElement, + PropsWithChildren, + useCallback, + useEffect, + useState, +} from 'react'; import { EuiBasicTable, EuiBasicTableColumn, @@ -14,7 +21,10 @@ import { EuiFlexGroup, EuiFlexItem, EuiText, + EuiTextProps, + EuiTitle, useEuiTheme, + useIsWithinMinBreakpoint, } from '@elastic/eui'; import { EuiThemeComputed } from '@elastic/eui/src/services/theme/types'; @@ -22,7 +32,11 @@ import { StepTabs } from '../../test_run_details/step_tabs'; import { ResultDetails } from './result_details'; import { JourneyStep } from '../../../../../../common/runtime_types'; import { JourneyStepScreenshotContainer } from '../screenshot/journey_step_screenshot_container'; -import { ScreenshotImageSize, THUMBNAIL_SCREENSHOT_SIZE } from '../screenshot/screenshot_size'; +import { + ScreenshotImageSize, + THUMBNAIL_SCREENSHOT_SIZE, + THUMBNAIL_SCREENSHOT_SIZE_MOBILE, +} from '../screenshot/screenshot_size'; import { StepDetailsLinkIcon } from '../links/step_details_link'; import { parseBadgeStatus, getTextColorForMonitorStatus } from './status_badge'; @@ -61,6 +75,7 @@ export const BrowserStepsList = ({ const [itemIdToExpandedRowMap, setItemIdToExpandedRowMap] = useState< Record >({}); + const isTabletOrGreater = useIsWithinMinBreakpoint('s'); const toggleDetails = useCallback( (item: JourneyStep) => { @@ -118,8 +133,13 @@ export const BrowserStepsList = ({ field: 'synthetics.step.index', name: '#', render: (stepIndex: number, item: JourneyStep) => ( - + + {stepIndex} + ), + mobileOptions: { + show: false, + }, }, ] : []), @@ -139,15 +159,20 @@ export const BrowserStepsList = ({ /> ), mobileOptions: { - render: (item: JourneyStep) => ( - - - {item.synthetics?.step?.index!}. {item.synthetics?.step?.name} - - + render: (step: JourneyStep) => ( + ), - header: SCREENSHOT_LABEL, + header: false, enlarge: true, + width: '100%', }, }, { @@ -166,6 +191,9 @@ export const BrowserStepsList = ({ ); }, + mobileOptions: { + show: false, + }, }, { field: 'synthetics.step.status', @@ -177,6 +205,9 @@ export const BrowserStepsList = ({ isExpanded={Boolean(itemIdToExpandedRowMap[item._id]) && !testNowMode} /> ), + mobileOptions: { + show: false, + }, }, ...(showLastSuccessful ? [ @@ -189,6 +220,9 @@ export const BrowserStepsList = ({ isExpanded={Boolean(itemIdToExpandedRowMap[item._id])} /> ), + mobileOptions: { + show: false, + }, }, ] : [ @@ -208,7 +242,6 @@ export const BrowserStepsList = ({ align: 'right', field: 'timestamp', name: '', - mobileOptions: { show: false }, render: (_val: string, item) => ( ), + mobileOptions: { show: false }, }, ]; return ( <> { if (itemIdToExpandedRowMap[row._id]) { return { @@ -234,8 +269,8 @@ export const BrowserStepsList = ({ loading={loading} columns={columns} error={error?.message} - isExpandable={true} - hasActions={true} + isExpandable={showExpand} + hasActions={false} items={stepEnds} noItemsMessage={ loading @@ -254,15 +289,16 @@ export const BrowserStepsList = ({ ); }; -const StepNumber = ({ - stepIndex, +const StyleForStepStatus = ({ step, + textSize = 's', euiTheme, -}: { - stepIndex: number; + children, +}: PropsWithChildren<{ step: JourneyStep; + textSize?: EuiTextProps['size']; euiTheme: EuiThemeComputed; -}) => { +}>) => { const status = parseBadgeStatus(step.synthetics?.step?.status ?? ''); return ( @@ -270,14 +306,107 @@ const StepNumber = ({ css={{ fontWeight: euiTheme.font.weight.bold, }} - size="s" + size={textSize} color={euiTheme.colors[getTextColorForMonitorStatus(status)] as CSSProperties['color']} > - {stepIndex} + {children} ); }; +const MobileRowDetails = ({ + journeyStep, + showStepNumber, + showLastSuccessful, + stepsLoading, + isExpanded, + isTestNowMode, + euiTheme, +}: { + journeyStep: JourneyStep; + showStepNumber: boolean; + showLastSuccessful: boolean; + stepsLoading: boolean; + isExpanded: boolean; + isTestNowMode: boolean; + euiTheme: EuiThemeComputed; +}) => { + return ( + + +

+ + {showStepNumber && journeyStep.synthetics?.step?.index + ? `${journeyStep.synthetics.step.index}. ` + : null}{' '} + {journeyStep.synthetics?.step?.name} + +

+
+ + +
+ + {[ + { + title: RESULT_LABEL, + description: ( + + ), + }, + ...[ + showLastSuccessful + ? { + title: LAST_SUCCESSFUL, + description: ( + + ), + } + : { + title: STEP_DURATION, + description: , + }, + ], + ].map(({ title, description }) => ( + + {title} + {description} + + ))} + +
+
+ +
+ ); +}; + const RESULT_LABEL = i18n.translate('xpack.synthetics.monitor.result.label', { defaultMessage: 'Result', }); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/status_badge.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/status_badge.tsx index acfca86c2dea0..38b853d0e8f25 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/status_badge.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/status_badge.tsx @@ -16,7 +16,7 @@ export const StatusBadge = ({ status }: { status: MonitorStatus }) => { } return ( - + {status === 'succeeded' ? COMPLETE_LABEL : status === 'failed' ? FAILED_LABEL : SKIPPED_LABEL} ); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/page_template/synthetics_page_template.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/page_template/synthetics_page_template.tsx new file mode 100644 index 0000000000000..b9ce0e722f24e --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/page_template/synthetics_page_template.tsx @@ -0,0 +1,31 @@ +/* + * 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 { LazyObservabilityPageTemplateProps } from '@kbn/observability-plugin/public'; +import React from 'react'; +import { euiStyled } from '@kbn/kibana-react-plugin/common'; +import { useKibana } from '@kbn/kibana-react-plugin/public'; + +import { ClientPluginsStart } from '../../../../../plugin'; + +export const WrappedPageTemplate = (props: LazyObservabilityPageTemplateProps) => { + const { observability } = useKibana().services; + const PageTemplateComponent = observability.navigation.PageTemplate; + + return ; +}; + +export const SyntheticsPageTemplateComponent = euiStyled(WrappedPageTemplate)` + &&& { + .euiPageHeaderContent__top { + flex-wrap: wrap; + .euiTitle { + min-width: 160px; + } + } + } +`; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/screenshot/journey_screenshot_dialog.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/screenshot/journey_screenshot_dialog.tsx index 818541b1f73ea..f24ba4d5e9f4a 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/screenshot/journey_screenshot_dialog.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/screenshot/journey_screenshot_dialog.tsx @@ -27,6 +27,7 @@ import { EuiOutsideClickDetector, useIsWithinMaxBreakpoint, } from '@elastic/eui'; +import { euiStyled } from '@kbn/kibana-react-plugin/common'; import { SYNTHETICS_API_URLS } from '../../../../../../common/constants'; import { SyntheticsSettingsContext } from '../../../contexts'; @@ -118,7 +119,7 @@ export const JourneyScreenshotDialog = ({ }} onKeyDown={onKeyDown} > - + - + ) : null} - + - + {stepCountLabel} @@ -206,6 +207,17 @@ export const JourneyScreenshotDialog = ({ ) : null; }; +const ModalBodyStyled = euiStyled(EuiModalBody)` + &&& { + & > div { + display: flex; + justify-content: center; + align-items: center; + margin-top: 24px; + } + } +`; + export const getScreenshotUrl = ({ basePath, checkGroup, diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/screenshot/screenshot_size.ts b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/screenshot/screenshot_size.ts index 0d1e09949b188..c735ae59a0308 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/screenshot/screenshot_size.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/screenshot/screenshot_size.ts @@ -16,6 +16,7 @@ export type ScreenshotImageSize = | 'full'; export const THUMBNAIL_SCREENSHOT_SIZE: ScreenshotImageSize = [96, 64]; +export const THUMBNAIL_SCREENSHOT_SIZE_MOBILE: ScreenshotImageSize = [180, 112]; export const POPOVER_SCREENSHOT_SIZE: ScreenshotImageSize = [640, 360]; export function getConfinedScreenshotSize( diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/advanced/index.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/advanced/index.tsx index ac6ebcb7ef360..4b69da4ad70c3 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/advanced/index.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/advanced/index.tsx @@ -41,8 +41,8 @@ export const AdvancedConfig = ({ readOnly }: { readOnly: boolean }) => { title={

{configGroup.title}

} fullWidth key={configGroup.title} - descriptionFlexItemProps={{ style: { minWidth: 200 } }} - fieldFlexItemProps={{ style: { minWidth: 500 } }} + descriptionFlexItemProps={{ style: { minWidth: 208 } }} + fieldFlexItemProps={{ style: { minWidth: 208 } }} style={{ flexWrap: 'wrap' }} > {configGroup.components.map((field) => { diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/fields/code_editor.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/fields/code_editor.tsx index 976a7ea1c666a..34a696389717f 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/fields/code_editor.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/fields/code_editor.tsx @@ -7,12 +7,14 @@ import React from 'react'; import styled from 'styled-components'; +import useThrottle from 'react-use/lib/useThrottle'; 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'; +import { useDimensions } from '../../../hooks'; const CodeEditorContainer = styled(EuiPanel)` padding: 0; @@ -39,29 +41,39 @@ export const CodeEditor = ({ height = '250px', readOnly, }: CodeEditorProps) => { + const { elementRef: containerRef, width: containerWidth } = useDimensions(); + const containerWidthThrottled = useThrottle(containerWidth, 500); + return ( - - + } + borderRadius="none" + hasShadow={false} + hasBorder={true} > - - - + + + + + ); }; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/fields/request_body_field.test.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/fields/request_body_field.test.tsx index 90e4d457764c1..a472c3231053b 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/fields/request_body_field.test.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/fields/request_body_field.test.tsx @@ -10,10 +10,13 @@ import 'jest-canvas-mock'; import React, { useState, useCallback } from 'react'; import userEvent from '@testing-library/user-event'; import { fireEvent, waitFor } from '@testing-library/react'; +import { mockGlobals } from '../../../utils/testing'; import { render } from '../../../utils/testing/rtl_helpers'; import { RequestBodyField } from './request_body_field'; import { Mode } from '../types'; +mockGlobals(); + jest.mock('@elastic/eui/lib/services/accessibility/html_id_generator', () => ({ htmlIdGenerator: () => () => `id-${Math.random()}`, })); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/fields/source_field.test.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/fields/source_field.test.tsx index b52cfe346a80d..f661d0f25b4af 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/fields/source_field.test.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/fields/source_field.test.tsx @@ -7,9 +7,12 @@ import React from 'react'; import userEvent from '@testing-library/user-event'; +import { mockGlobals } from '../../../utils/testing'; import { render } from '../../../utils/testing/rtl_helpers'; import { SourceField } from './source_field'; +mockGlobals(); + jest.mock('@elastic/eui/lib/services/accessibility/html_id_generator', () => ({ ...jest.requireActual('@elastic/eui/lib/services/accessibility/html_id_generator'), htmlIdGenerator: () => () => `id-${Math.random()}`, diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/form/field_config.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/form/field_config.tsx index 934a895c47d34..fe2035efb481f 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/form/field_config.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/form/field_config.tsx @@ -1118,7 +1118,7 @@ export const FIELD = (readOnly?: boolean): FieldMap => ({ { value: DEFAULT_BROWSER_ADVANCED_FIELDS[ConfigKey.THROTTLING_CONFIG], inputDisplay: ( - + {i18n.translate('xpack.synthetics.monitorConfig.throttling.options.default', { diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/form/submit.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/form/submit.tsx index 12c382fd94147..27d5f699c3b51 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/form/submit.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/form/submit.tsx @@ -56,7 +56,7 @@ export const ActionBar = ({ readOnly = false }: { readOnly: boolean }) => { ) : ( <> - + {isEdit && defaultValues && (
@@ -84,7 +84,7 @@ export const ActionBar = ({ readOnly = false }: { readOnly: boolean }) => { - + { const original = jest.requireActual('@kbn/kibana-react-plugin/public'); return { diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/monitor_edit_page.test.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/monitor_edit_page.test.tsx index 4052840f21d95..bd9bc9d57c4aa 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/monitor_edit_page.test.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/monitor_edit_page.test.tsx @@ -6,12 +6,15 @@ */ import React from 'react'; +import { mockGlobals } from '../../utils/testing'; import { render } from '../../utils/testing/rtl_helpers'; import { MonitorEditPage } from './monitor_edit_page'; import { ConfigKey } from '../../../../../common/runtime_types'; import * as observabilityPublic from '@kbn/observability-plugin/public'; +mockGlobals(); + jest.mock('@kbn/observability-plugin/public'); jest.mock('@kbn/kibana-react-plugin/public', () => { diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/steps/step.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/steps/step.tsx index c2a7578cdaade..48ea642366a78 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/steps/step.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/steps/step.tsx @@ -16,10 +16,10 @@ interface Props { export const Step = ({ description, children }: Props) => { return ( - + {description} - {children} + {children} ); }; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/steps/step_config.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/steps/step_config.tsx index c8d162393299f..d8f74ba2eeb85 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/steps/step_config.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/steps/step_config.tsx @@ -49,7 +49,7 @@ const MONITOR_DETAILS_STEP = (readOnly: boolean = false): Step => ({ }); const SCRIPT_RECORDER_BTNS = ( - + + {link} - + {ACTIVE_LABEL} ); }, + mobileOptions: { + header: false, + }, }, ...(isBrowserType ? [ @@ -169,6 +175,7 @@ export const ErrorsList = ({
- + - - + + {monitorId && ( - - + + diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_history/monitor_history.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_history/monitor_history.tsx index 4d9fb7bc6e0d9..ce0c35152957c 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_history/monitor_history.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_history/monitor_history.tsx @@ -55,8 +55,8 @@ export const MonitorHistory = () => { - - + + {/* @ts-expect-error Current @elastic/eui has the wrong types for the ref */} @@ -127,7 +127,7 @@ export const MonitorHistory = () => { - +

{DURATION_TREND_LABEL}

diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_status/monitor_status_header.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_status/monitor_status_header.tsx index c2df7ae05d7a9..78cf664de2f4b 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_status/monitor_status_header.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_status/monitor_status_header.tsx @@ -7,12 +7,12 @@ import React from 'react'; import { EuiButtonEmpty, EuiFlexGroup, EuiFlexItem, EuiText, EuiTitle } from '@elastic/eui'; -import { useHistory } from 'react-router-dom'; +import { useSyntheticsSettingsContext } from '../../../contexts'; +import { useSelectedLocation } from '../hooks/use_selected_location'; import { ConfigKey } from '../../../../../../common/runtime_types'; import { MONITOR_HISTORY_ROUTE } from '../../../../../../common/constants'; import { stringifyUrlParams } from '../../../utils/url_params'; -import { useGetUrlParams } from '../../../hooks'; import { useSelectedMonitor } from '../hooks/use_selected_monitor'; @@ -25,9 +25,18 @@ export const MonitorStatusHeader = ({ periodCaption, showViewHistoryButton, }: MonitorStatusPanelProps) => { - const history = useHistory(); - const params = useGetUrlParams(); + const { basePath } = useSyntheticsSettingsContext(); const { monitor } = useSelectedMonitor(); + const selectedLocation = useSelectedLocation(); + const search = stringifyUrlParams({ + locationId: selectedLocation?.id, + dateRangeStart: 'now-24h', + dateRangeEnd: 'now', + }); + const viewDetailsUrl = `${basePath}/app/synthetics${MONITOR_HISTORY_ROUTE.replace( + ':monitorId', + monitor?.[ConfigKey.CONFIG_ID] ?? '' + )}${search}`; const isLast24Hours = from === 'now-24h' && to === 'now'; const periodCaptionText = !!periodCaption @@ -43,45 +52,33 @@ export const MonitorStatusHeader = ({ css={{ marginBottom: 0, }} + wrap={true} > - - -

{labels.STATUS_LABEL}

-
-
- {periodCaptionText ? ( + - - {periodCaptionText} - + +

{labels.STATUS_LABEL}

+
- ) : null} - + {periodCaptionText ? ( + + + {periodCaptionText} + + + ) : null} +
{showViewHistoryButton ? ( - - - {labels.VIEW_HISTORY_LABEL} - - + + {labels.VIEW_HISTORY_LABEL} + ) : null}
); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/last_test_run.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/last_test_run.tsx index d09f6672e73bd..1a7446625a769 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/last_test_run.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/last_test_run.tsx @@ -60,6 +60,7 @@ export const LastTestRun = () => { latestPing={latestPing} loading={loading} stepsLoading={stepsLoading} + isErrorDetails={false} /> ); }; @@ -99,9 +100,7 @@ export const LastTestRunComponent = ({ color="danger" iconType="warning" > - {isErrorDetails ? ( - <> - ) : ( + {isErrorDetails ? null : ( - + {TitleNode} - + 0 ? 'fail' : 'success')} /> - + {lastRunTimestamp} {isBrowserMonitor ? ( - + - +

@@ -98,7 +98,7 @@ export const MonitorAlerts = ({ - + - + - + { const { from, to } = useMonitorRangeFrom(); const monitorId = useMonitorQueryId(); + const isFlyoutOpen = !!useTestFlyoutOpen(); const dateLabel = from === 'now-30d/d' ? LAST_30_DAYS_LABEL : TO_DATE_LABEL; @@ -42,12 +44,12 @@ export const MonitorSummary = () => { return ( - - + + - - + + @@ -60,39 +62,45 @@ export const MonitorSummary = () => { - - - - - - - - - - - - - - - {monitorId && ( - - )} - - - {monitorId && ( - - )} - + + + + + + + + + + + + + + + + + + + + {monitorId && ( + + )} + + + {monitorId && ( + + )} + + @@ -125,11 +133,11 @@ export const MonitorSummary = () => { showViewHistoryButton={true} /> - - + + - + diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/test_runs_table.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/test_runs_table.tsx index ade5f47376804..854e600ddae90 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/test_runs_table.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/test_runs_table.tsx @@ -9,9 +9,20 @@ import React, { MouseEvent, useMemo, useState } from 'react'; import { useHistory, useParams } from 'react-router-dom'; import { useSelector } from 'react-redux'; import { i18n } from '@kbn/i18n'; -import { EuiBasicTable, EuiBasicTableColumn, EuiPanel, EuiText } from '@elastic/eui'; +import { + EuiBasicTable, + EuiBasicTableColumn, + EuiButtonEmpty, + EuiFlexGroup, + EuiFlexItem, + EuiPanel, + EuiText, + useIsWithinMinBreakpoint, +} from '@elastic/eui'; import { Criteria } from '@elastic/eui/src/components/basic_table/basic_table'; import { EuiTableSortingType } from '@elastic/eui/src/components/basic_table/table_types'; +import { THUMBNAIL_SCREENSHOT_SIZE_MOBILE } from '../../common/screenshot/screenshot_size'; +import { getErrorDetailsUrl } from '../monitor_errors/errors_list'; import { TestRunsTableHeader } from './test_runs_table_header'; import { MONITOR_TYPES } from '../../../../../../common/constants'; @@ -29,7 +40,7 @@ import { useSelectedMonitor } from '../hooks/use_selected_monitor'; import { useSelectedLocation } from '../hooks/use_selected_location'; import { useMonitorPings } from '../hooks/use_monitor_pings'; import { JourneyLastScreenshot } from '../../common/screenshot/journey_last_screenshot'; -import { useSyntheticsRefreshContext } from '../../../contexts'; +import { useSyntheticsRefreshContext, useSyntheticsSettingsContext } from '../../../contexts'; type SortableField = 'timestamp' | 'monitor.status' | 'monitor.duration.us'; @@ -47,6 +58,7 @@ export const TestRunsTable = ({ showViewHistoryButton = true, }: TestRunsTableProps) => { const history = useHistory(); + const { basePath } = useSyntheticsSettingsContext(); const { monitorId } = useParams<{ monitorId: string }>(); const [page, setPage] = useState({ index: 0, size: 10 }); @@ -71,6 +83,7 @@ export const TestRunsTable = ({ const pingsError = useSelector(selectPingsError); const { monitor } = useSelectedMonitor(); const selectedLocation = useSelectedLocation(); + const isTabletOrGreater = useIsWithinMinBreakpoint('s'); const isBrowserMonitor = monitor?.[ConfigKey.MONITOR_TYPE] === DataStream.BROWSER; @@ -105,6 +118,18 @@ export const TestRunsTable = ({ timestamp={timestamp} /> ), + mobileOptions: { + header: false, + render: (item) => ( + + + + ), + }, }, ] : []) as Array>), @@ -117,6 +142,17 @@ export const TestRunsTable = ({ render: (timestamp: string, ping: Ping) => ( ), + mobileOptions: { + header: false, + render: (item) => ( + + ), + }, }, { align: 'left', @@ -125,6 +161,9 @@ export const TestRunsTable = ({ name: RESULT_LABEL, sortable: true, render: (status: string) => , + mobileOptions: { + show: false, + }, }, { align: 'left', @@ -134,6 +173,9 @@ export const TestRunsTable = ({ render: (errorMessage: string) => ( {errorMessage?.length > 0 ? errorMessage : '-'} ), + mobileOptions: { + show: false, + }, }, { align: 'right', @@ -142,6 +184,9 @@ export const TestRunsTable = ({ name: DURATION_LABEL, sortable: true, render: (durationUs: number) => {formatTestDuration(durationUs)}, + mobileOptions: { + show: false, + }, }, ]; @@ -150,7 +195,6 @@ export const TestRunsTable = ({ return {}; } return { - height: '85px', 'data-test-subj': `row-${item.monitor.check_group}`, onClick: (evt: MouseEvent) => { const targetElem = evt.target as HTMLElement; @@ -180,6 +224,7 @@ export const TestRunsTable = ({ pings={pings} /> { + return ( + + + + + + + + {ping?.state?.id! && + ping.config_id && + locationId && + parseBadgeStatus(ping?.monitor?.status ?? 'skipped') === 'failed' ? ( + + {i18n.translate('xpack.synthetics.monitorDetails.summary.viewErrorDetails', { + defaultMessage: 'View error details', + })} + + ) : null} + + + + + {[ + { + title: DURATION_LABEL, + description: formatTestDuration(ping?.monitor?.duration?.us), + }, + ].map(({ title, description }) => ( + + {title} + {description} + + ))} + + + ); +}; + export const LAST_10_TEST_RUNS = i18n.translate( 'xpack.synthetics.monitorDetails.summary.lastTenTestRuns', { diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/management/disabled_callout.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/management/disabled_callout.tsx new file mode 100644 index 0000000000000..de92009534dcd --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/management/disabled_callout.tsx @@ -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 ; + } + + if (!showDisableCallout) { + return null; + } + + return ( + +

{labels.CALLOUT_MANAGEMENT_DESCRIPTION}

+ {enablement.canEnable || loading ? ( + { + enableSynthetics(); + }} + isLoading={loading} + > + {labels.SYNTHETICS_ENABLE_LABEL} + + ) : ( +

+ {labels.CALLOUT_MANAGEMENT_CONTACT_ADMIN}{' '} + + {labels.LEARN_MORE_LABEL} + +

+ )} +
+ ); +}; diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/pages/monitor_management/invalid_api_key_callout.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/management/invalid_api_key_callout.tsx similarity index 89% rename from x-pack/plugins/synthetics/public/legacy_uptime/pages/monitor_management/invalid_api_key_callout.tsx rename to x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/management/invalid_api_key_callout.tsx index f19c2e2af70a8..837ce81efb7f9 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/pages/monitor_management/invalid_api_key_callout.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/management/invalid_api_key_callout.tsx @@ -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 ( <>

{CALLOUT_MANAGEMENT_DESCRIPTION}

- {enablement.canEnable ? ( + {enablement.canEnable || loading ? ( { onClick={() => { enableSynthetics(); }} + isLoading={loading} > {SYNTHETICS_ENABLE_LABEL} diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/management/monitor_stats/monitor_stats.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/management/monitor_stats/monitor_stats.tsx index 3d94f0c5a43bf..67ce536fc3c8e 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/management/monitor_stats/monitor_stats.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/management/monitor_stats/monitor_stats.tsx @@ -35,7 +35,7 @@ export const MonitorStats = ({ @@ -56,7 +56,13 @@ export const MonitorStats = ({ diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/monitors_page.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/monitors_page.tsx index f6b5770601108..f8714f668dd93 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/monitors_page.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/monitors_page.tsx @@ -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 ? ( - <> - -

{labels.CALLOUT_MANAGEMENT_DESCRIPTION}

- {canEnable ? ( - { - enableSynthetics(); - }} - > - {labels.SYNTHETICS_ENABLE_LABEL} - - ) : ( -

- {labels.CALLOUT_MANAGEMENT_CONTACT_ADMIN}{' '} - - {labels.LEARN_MORE_LABEL} - -

- )} -
- - - ) : null} + {showEmptyState && } diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/overview_grid.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/overview_grid.tsx index 8183cf363595a..53f14deb0dab5 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/overview_grid.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/overview_grid.tsx @@ -68,7 +68,12 @@ export const OverviewGrid = memo(() => { return ( <> - + { return ( <> + diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/test_now_mode/test_result_header.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/test_now_mode/test_result_header.tsx index bdecc2911bdae..d8f57a6337e9c 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/test_now_mode/test_result_header.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/test_now_mode/test_result_header.tsx @@ -62,7 +62,10 @@ export function TestResultHeader({ {isCompleted ? ( - 0 ? 'danger' : 'success'}> + 0 ? 'danger' : 'success'} + css={{ maxWidth: 'max-content' }} + > {summaryDoc?.summary?.down! > 0 ? FAILED_LABEL : COMPLETED_LABEL} diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/test_run_details/components/step_info.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/test_run_details/components/step_info.tsx index 62d94a52138c2..0e13c7efa251d 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/test_run_details/components/step_info.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/test_run_details/components/step_info.tsx @@ -44,17 +44,17 @@ export const StepMetaInfo = ({ const isFailed = step.synthetics.step?.status === 'failed'; return ( - +

{STEP_NAME}

{step?.synthetics.step?.name} - + - + {AFTER_LABEL} {formatTestDuration(step?.synthetics.step?.duration.us)} diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/test_run_details/step_screenshot_details.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/test_run_details/step_screenshot_details.tsx index d3239e34617f1..ad3d18683ee27 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/test_run_details/step_screenshot_details.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/test_run_details/step_screenshot_details.tsx @@ -25,8 +25,8 @@ export const StepScreenshotDetails = ({ return ( - - + + {step ? ( { <> {!hasNoSteps && ( - - + + @@ -86,7 +86,7 @@ export const TestRunDetails = () => { - + diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/hooks/use_enablement.ts b/x-pack/plugins/synthetics/public/apps/synthetics/hooks/use_enablement.ts index 3d21b482c2279..394da8aefc086 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/hooks/use_enablement.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/hooks/use_enablement.ts @@ -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: { diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/routes.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/routes.tsx index d60c4c25d95a1..487e82e5538d9 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/routes.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/routes.tsx @@ -21,6 +21,7 @@ import { useInspectorContext } from '@kbn/observability-plugin/public'; import { useSyntheticsPrivileges } from './hooks/use_synthetics_priviliges'; import { ClientPluginsStart } from '../../plugin'; import { getMonitorsRoute } from './components/monitors_page/route_config'; +import { SyntheticsPageTemplateComponent } from './components/common/page_template/synthetics_page_template'; import { getMonitorDetailsRoute } from './components/monitor_details/route_config'; import { getStepDetailsRoute } from './components/step_details_page/route_config'; import { getTestRunDetailsRoute } from './components/test_run_details/route_config'; @@ -54,13 +55,6 @@ const baseTitle = i18n.translate('xpack.synthetics.routes.baseTitle', { defaultMessage: 'Synthetics - Kibana', }); -export const MONITOR_MANAGEMENT_LABEL = i18n.translate( - 'xpack.synthetics.monitorManagement.heading', - { - defaultMessage: 'Monitor Management', - } -); - const getRoutes = ( euiTheme: EuiThemeComputed, history: ReturnType, @@ -177,7 +171,7 @@ const RouteInit: React.FC> = ({ path, title } }; export const PageRouter: FC = () => { - const { application, observability } = useKibana().services; + const { application } = useKibana().services; const { addInspectorRequest } = useInspectorContext(); const { euiTheme } = useEuiTheme(); const history = useHistory(); @@ -189,7 +183,6 @@ export const PageRouter: FC = () => { location, application.getUrlForApp(PLUGIN.SYNTHETICS_PLUGIN_ID) ); - const PageTemplateComponent = observability.navigation.PageTemplate; apiService.addInspectorRequest = addInspectorRequest; @@ -209,21 +202,21 @@ export const PageRouter: FC = () => {
- {isUnPrivileged || } - +
) )} ( - + { , ]} /> - + )} /> diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/synthetics_enablement/actions.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/synthetics_enablement/actions.ts index 0c7abffd1b289..7369ce0917e5a 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/state/synthetics_enablement/actions.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/state/synthetics_enablement/actions.ts @@ -24,7 +24,9 @@ export const disableSyntheticsFailure = createAction( ); export const enableSynthetics = createAction('[SYNTHETICS_ENABLEMENT] ENABLE'); -export const enableSyntheticsSuccess = createAction<{}>('[SYNTHETICS_ENABLEMENT] ENABLE SUCCESS'); +export const enableSyntheticsSuccess = createAction( + '[SYNTHETICS_ENABLEMENT] ENABLE SUCCESS' +); export const enableSyntheticsFailure = createAction( '[SYNTHETICS_ENABLEMENT] ENABLE FAILURE' ); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/synthetics_enablement/api.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/synthetics_enablement/api.ts index 4593f241b41f5..62b48676e3965 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/state/synthetics_enablement/api.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/state/synthetics_enablement/api.ts @@ -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 => { return await apiService.post(API_URLS.SYNTHETICS_ENABLEMENT); }; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/synthetics_enablement/index.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/synthetics_enablement/index.ts index 06ec4dd3b26b6..62cbce9bfe05b 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/state/synthetics_enablement/index.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/state/synthetics_enablement/index.ts @@ -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; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/utils/testing/__mocks__/resize_observer.mock.ts b/x-pack/plugins/synthetics/public/apps/synthetics/utils/testing/__mocks__/resize_observer.mock.ts new file mode 100644 index 0000000000000..b13767609f76f --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/utils/testing/__mocks__/resize_observer.mock.ts @@ -0,0 +1,20 @@ +/* + * 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 class MockResizeObserver implements ResizeObserver { + private elements: Set = new Set(); + + observe(target: Element) { + this.elements.add(target); + } + unobserve(target: Element) { + this.elements.delete(target); + } + disconnect() { + this.elements.clear(); + } +} diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/utils/testing/index.ts b/x-pack/plugins/synthetics/public/apps/synthetics/utils/testing/index.ts index cebc9f5030293..8ba89472a8cf3 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/utils/testing/index.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/utils/testing/index.ts @@ -5,4 +5,5 @@ * 2.0. */ +export * from './mock_globals'; export * from './rtl_helpers'; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/utils/testing/mock_globals.ts b/x-pack/plugins/synthetics/public/apps/synthetics/utils/testing/mock_globals.ts new file mode 100644 index 0000000000000..7c987d9afb402 --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/utils/testing/mock_globals.ts @@ -0,0 +1,12 @@ +/* + * 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 { MockResizeObserver } from './__mocks__/resize_observer.mock'; + +export function mockGlobals() { + global.ResizeObserver = MockResizeObserver; +} diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/certificates/__snapshots__/cert_monitors.test.tsx.snap b/x-pack/plugins/synthetics/public/legacy_uptime/components/certificates/__snapshots__/cert_monitors.test.tsx.snap index 417ee20250c79..2a3a77be7d4f3 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/certificates/__snapshots__/cert_monitors.test.tsx.snap +++ b/x-pack/plugins/synthetics/public/legacy_uptime/components/certificates/__snapshots__/cert_monitors.test.tsx.snap @@ -6,18 +6,14 @@ exports[`CertMonitors renders expected elements for valid props 1`] = ` - + bad-ssl-dashboard + @@ -25,18 +21,14 @@ exports[`CertMonitors renders expected elements for valid props 1`] = ` - + elastic + @@ -44,18 +36,14 @@ exports[`CertMonitors renders expected elements for valid props 1`] = ` - + extended-validation + diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/certificates/cert_monitors.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/certificates/cert_monitors.tsx index 0688aae060fba..a1efc7829afc5 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/certificates/cert_monitors.tsx +++ b/x-pack/plugins/synthetics/public/legacy_uptime/components/certificates/cert_monitors.tsx @@ -21,7 +21,7 @@ export const CertMonitors: React.FC = ({ monitors }) => { {ind > 0 && ', '} - + {mon.name || mon.id} diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/certificates/certificates_list.test.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/certificates/certificates_list.test.tsx index ff54267b8b8b4..5c995cda5872a 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/certificates/certificates_list.test.tsx +++ b/x-pack/plugins/synthetics/public/legacy_uptime/components/certificates/certificates_list.test.tsx @@ -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', () => { diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/certificates/translations.ts b/x-pack/plugins/synthetics/public/legacy_uptime/components/certificates/translations.ts index 7b7c3205cfc08..fa91fdc8b45fa 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/certificates/translations.ts +++ b/x-pack/plugins/synthetics/public/legacy_uptime/components/certificates/translations.ts @@ -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', { diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/common/__snapshots__/monitor_page_link.test.tsx.snap b/x-pack/plugins/synthetics/public/legacy_uptime/components/common/__snapshots__/monitor_page_link.test.tsx.snap index c7c269df1e9b8..81378daef19a5 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/common/__snapshots__/monitor_page_link.test.tsx.snap +++ b/x-pack/plugins/synthetics/public/legacy_uptime/components/common/__snapshots__/monitor_page_link.test.tsx.snap @@ -1,23 +1,15 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`MonitorPageLink component renders a help link when link parameters present 1`] = ` - - - + `; exports[`MonitorPageLink component renders the link properly 1`] = ` - - - + `; diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/common/header/manage_monitors_btn.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/common/header/manage_monitors_btn.tsx index 627f89b718742..26408e6206b7b 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/common/header/manage_monitors_btn.tsx +++ b/x-pack/plugins/synthetics/public/legacy_uptime/components/common/header/manage_monitors_btn.tsx @@ -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 ( - - - + + + + + ); }; -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.', }); diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/common/monitor_page_link.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/common/monitor_page_link.tsx index a107be1e15b1d..3af03f38e8eff 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/common/monitor_page_link.tsx +++ b/x-pack/plugins/synthetics/public/legacy_uptime/components/common/monitor_page_link.tsx @@ -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 = ({ children, monitorId, + configId, linkParameters, }) => { + const basePath = useKibana().services.http?.basePath.get(); + if (configId) { + return ( + + {children} + + ); + } + 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 ( - - - {children} - - + + {children} + ); }; diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/browser/advanced_fields.test.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/browser/advanced_fields.test.tsx deleted file mode 100644 index 74ead8c6906d5..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/browser/advanced_fields.test.tsx +++ /dev/null @@ -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('', () => { - const WrappedComponent = ({ - defaultValues = defaultConfig, - defaultSimpleFields = defaultBrowserSimpleFields, - validate = defaultValidation, - children, - onFieldBlur, - }: { - defaultValues?: BrowserAdvancedFieldsType; - defaultSimpleFields?: BrowserSimpleFields; - validate?: Validation; - children?: React.ReactNode; - onFieldBlur?: (field: ConfigKey) => void; - }) => { - return ( - - - - - {children} - - - - - ); - }; - - it('renders BrowserAdvancedFields', () => { - const { getByLabelText } = render(); - - 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(); - - 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(); - - 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(); - - 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({upstreamFieldsText}); - - const upstream = getByText(upstreamFieldsText) as HTMLInputElement; - expect(upstream).toBeInTheDocument(); - }); -}); diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/browser/advanced_fields.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/browser/advanced_fields.tsx deleted file mode 100644 index 8fe90c800f309..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/browser/advanced_fields.tsx +++ /dev/null @@ -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( - ({ validate, children, minColumnWidth, onFieldBlur }) => { - const { fields, setFields } = useBrowserAdvancedFieldsContext(); - - const handleInputChange = useCallback( - ({ value, configKey }: { value: unknown; configKey: ConfigKey }) => { - setFields((prevFields) => ({ ...prevFields, [configKey]: value })); - }, - [setFields] - ); - - return ( - - - - -

- } - description={ - - } - > - - - - - } - data-test-subj="syntheticsBrowserIgnoreHttpsErrors" - > - - } - onChange={(event) => - handleInputChange({ - value: event.target.checked, - configKey: ConfigKey.IGNORE_HTTPS_ERRORS, - }) - } - onBlur={() => onFieldBlur?.(ConfigKey.IGNORE_HTTPS_ERRORS)} - /> - - - - } - labelAppend={} - helpText={ - - } - > - - handleInputChange({ - value: event.target.value, - configKey: ConfigKey.SCREENSHOTS, - }) - } - onBlur={() => onFieldBlur?.(ConfigKey.SCREENSHOTS)} - data-test-subj="syntheticsBrowserScreenshots" - /> - - - } - labelAppend={} - helpText={ - - } - > - - handleInputChange({ value, configKey: ConfigKey.SYNTHETICS_ARGS }) - } - onBlur={() => onFieldBlur?.(ConfigKey.SYNTHETICS_ARGS)} - data-test-subj="syntheticsBrowserSyntheticsArgs" - /> - - - - - {children} - - ); - } -); - -const requestMethodOptions = Object.values(ScreenshotOption).map((option) => ({ - value: option, - text: option.replace(/-/g, ' '), -})); diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/browser/script_recorder_fields.test.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/browser/script_recorder_fields.test.tsx deleted file mode 100644 index b19ca47ab76a5..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/browser/script_recorder_fields.test.tsx +++ /dev/null @@ -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('', () => { - let file: File; - const testScript = 'step(() => {})'; - const WrappedComponent = ({ - isEditable = true, - script = '', - fileName = '', - }: { - isEditable?: boolean; - script?: string; - fileName?: string; - }) => { - return ( - - - - ); - }; - - beforeEach(() => { - jest.clearAllMocks(); - file = new File([testScript], 'samplescript.js', { type: 'text/javascript' }); - }); - - it('renders ScriptRecorderFields', () => { - const { getByText, queryByText } = render(); - - 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(); - - 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(); - 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(); - - 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( - - ); - - 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: '' }); - }); - }); -}); diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/browser/script_recorder_fields.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/browser/script_recorder_fields.tsx deleted file mode 100644 index 7fb2f8382ae34..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/browser/script_recorder_fields.tsx +++ /dev/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 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 ( - <> - - - - - - {isEditable && script ? ( - - - {fileName} - - - ) : ( - - )} - {script && ( - <> - - - - setShowScript(true)} - iconType="editorCodeBlock" - iconSide="right" - > - - - - - {isEditable && ( - onChange({ scriptText: '', fileName: '' })} - iconType="trash" - iconSide="right" - color="danger" - > - - - )} - - - - )} - {showScript && ( - setShowScript(false)} - aria-labelledby="syntheticsBrowserScriptBlockHeader" - closeButtonProps={{ 'aria-label': CLOSE_BUTTON_LABEL }} - > - - - - {fileName || PLACEHOLDER_FILE_NAME} - - - -
- - {script} - -
-
- )} - - ); -} - -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', - } -); diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/browser/simple_fields.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/browser/simple_fields.tsx deleted file mode 100644 index 5c8516d56ddf9..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/browser/simple_fields.tsx +++ /dev/null @@ -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(({ 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 ( - - - } - isInvalid={!!validate[ConfigKey.SCHEDULE]?.(fields as Partial)} - error={ - - } - > - - handleInputChange({ - value: schedule, - configKey: ConfigKey.SCHEDULE, - }) - } - onBlur={() => onFieldBlur(ConfigKey.SCHEDULE)} - number={fields[ConfigKey.SCHEDULE].number} - unit={fields[ConfigKey.SCHEDULE].unit} - /> - - - } - > - ({ - 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] - )} - /> - - - ); -}); diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/browser/source_field.test.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/browser/source_field.test.tsx deleted file mode 100644 index 2199ac4b0296b..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/browser/source_field.test.tsx +++ /dev/null @@ -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) => ( - <> - - - ), - }; -}); - -const onChange = jest.fn(); -const onBlur = jest.fn(); - -describe('', () => { - const WrappedComponent = ({ - defaultConfig, - }: Omit & Partial) => { - return ( - - - - - - ); - }; - - beforeEach(() => { - jest.clearAllMocks(); - }); - - it('selects inline script by default', () => { - render(); - - expect( - screen.getByText('Runs Synthetic test scripts that are defined inline.') - ).toBeInTheDocument(); - }); - - it('does not show ZipUrl source type', async () => { - render(); - - expect(screen.queryByTestId('syntheticsSourceTab__zipUrl')).not.toBeInTheDocument(); - }); - - it('shows params for all source types', async () => { - const { getByText, getByTestId } = render(); - - const inlineTab = getByTestId('syntheticsSourceTab__inline'); - fireEvent.click(inlineTab); - - expect(getByText('Parameters')).toBeInTheDocument(); - - const recorder = getByTestId('syntheticsSourceTab__scriptRecorder'); - fireEvent.click(recorder); - - expect(getByText('Parameters')).toBeInTheDocument(); - }); -}); diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/browser/source_field.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/browser/source_field.tsx deleted file mode 100644 index 5a6cfef79bdd5..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/browser/source_field.tsx +++ /dev/null @@ -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(getDefaultTab(defaultConfig)); - const [config, setConfig] = useState(defaultConfig); - - useEffect(() => { - onChange(config); - }, [config, onChange]); - - const isSourceInlineInvalid = - validate?.[ConfigKey.SOURCE_INLINE]?.({ - [ConfigKey.SOURCE_INLINE]: config.inlineScript, - }) ?? false; - - const params = ( - - } - labelAppend={} - helpText={ - params.value }} - /> - } - > - { - setConfig((prevConfig) => ({ ...prevConfig, params: code })); - onFieldBlur(ConfigKey.PARAMS); - }} - value={config.params} - data-test-subj="syntheticsBrowserParams" - /> - - ); - - const tabs = [ - { - id: 'syntheticsBrowserInlineConfig', - name: ( - - ), - 'data-test-subj': `syntheticsSourceTab__inline`, - content: ( - <> - - } - helpText={ - - } - > - { - setConfig((prevConfig) => ({ ...prevConfig, inlineScript: code })); - onFieldBlur(ConfigKey.SOURCE_INLINE); - }} - value={config.inlineScript} - /> - - {params} - - ), - }, - { - id: 'syntheticsBrowserScriptRecorderConfig', - name: ( - - - - - - - - - ), - 'data-test-subj': 'syntheticsSourceTab__scriptRecorder', - content: ( - <> - - setConfig((prevConfig) => ({ - ...prevConfig, - inlineScript: scriptText, - isGeneratedScript: true, - fileName, - })) - } - script={config.inlineScript} - fileName={config.fileName} - /> - - {params} - - ), - }, - ]; - - return ( - 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; - } -`; diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/browser/throttling_fields.test.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/browser/throttling_fields.test.tsx deleted file mode 100644 index 54fec313c5ebe..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/browser/throttling_fields.test.tsx +++ /dev/null @@ -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('', () => { - 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; - validate?: Validation; - onFieldBlur?: (field: ConfigKey) => void; - }) => { - const policyConfigValues = { ...defaultPolicyConfigValues, ...policyConfigOverrides }; - - return ( - - - - - - - - - - ); - }; - - it('renders ThrottlingFields', () => { - const { getByLabelText, getByTestId } = render(); - - 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(); - - const enableSwitch = getByTestId('syntheticsBrowserIsThrottlingEnabled'); - userEvent.click(enableSwitch); - expect(enableSwitch).not.toBeChecked(); - }); - - it('for the download option', () => { - const { getByLabelText } = render(); - - 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(); - - 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(); - 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(); - - const enableSwitch = getByTestId('syntheticsBrowserIsThrottlingEnabled'); - fireEvent.focus(enableSwitch); - fireEvent.blur(enableSwitch); - expect(onFieldBlur).toHaveBeenCalledWith(ConfigKey.IS_THROTTLING_ENABLED); - }); - - it('for throttling inputs', () => { - const { getByLabelText } = render(); - - 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(); - - 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(); - - 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(); - - 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( - - ); - - 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( - - ); - - 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( - - ); - - 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(); - - 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(); - }); -}); diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/browser/throttling_fields.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/browser/throttling_fields.tsx deleted file mode 100644 index 2e1ea848eb49b..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/browser/throttling_fields.tsx +++ /dev/null @@ -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 ( - - } - color="warning" - iconType="warning" - > - - - ); -}; - -export const ThrottlingExceededCallout = () => { - return ( - - } - color="warning" - iconType="warning" - > - - - ); -}; - -export const ThrottlingExceededMessage = ({ - throttlingField, - limit, -}: { - throttlingField: string; - limit: number; -}) => { - return ( - - ); -}; - -export const ThrottlingFields = memo( - ({ 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 ? ( - <> - - - } - labelAppend={} - isInvalid={ - (validate ? !!validate[ConfigKey.DOWNLOAD_SPEED]?.(fields) : false) || - exceedsDownloadLimits - } - error={ - exceedsDownloadLimits ? ( - - ) : ( - - ) - } - > - { - handleInputChange({ - value: event.target.value, - configKey: ConfigKey.DOWNLOAD_SPEED, - }); - }} - onBlur={() => onFieldBlur?.(ConfigKey.DOWNLOAD_SPEED)} - data-test-subj="syntheticsBrowserDownloadSpeed" - append={ - - Mbps - - } - readOnly={readOnly} - /> - - - } - labelAppend={} - isInvalid={ - (validate ? !!validate[ConfigKey.UPLOAD_SPEED]?.(fields) : false) || exceedsUploadLimits - } - error={ - exceedsUploadLimits ? ( - - ) : ( - - ) - } - > - - handleInputChange({ - value: event.target.value, - configKey: ConfigKey.UPLOAD_SPEED, - }) - } - onBlur={() => onFieldBlur?.(ConfigKey.UPLOAD_SPEED)} - data-test-subj="syntheticsBrowserUploadSpeed" - append={ - - Mbps - - } - readOnly={readOnly} - /> - - - } - labelAppend={} - isInvalid={validate ? !!validate[ConfigKey.LATENCY]?.(fields) : false} - error={ - - } - > - - handleInputChange({ - value: event.target.value, - configKey: ConfigKey.LATENCY, - }) - } - onBlur={() => onFieldBlur?.(ConfigKey.LATENCY)} - data-test-subj="syntheticsBrowserLatency" - append={ - - ms - - } - readOnly={readOnly} - /> - - - ) : ( - <> - - - - ); - - return ( - - -

- } - description={ - - } - > - - } - onChange={(event) => - handleInputChange({ - value: event.target.checked, - configKey: ConfigKey.IS_THROTTLING_ENABLED, - }) - } - onBlur={() => onFieldBlur?.(ConfigKey.IS_THROTTLING_ENABLED)} - disabled={readOnly} - /> - {isThrottlingEnabled && (exceedsDownloadLimits || exceedsUploadLimits) ? ( - <> - - - - ) : null} - {throttlingInputs} - - ); - } -); diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/browser/uploader.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/browser/uploader.tsx deleted file mode 100644 index ce65818d23fc3..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/browser/uploader.tsx +++ /dev/null @@ -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); - const [error, setError] = useState(null); - const filePickerRef = useRef(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 ( - - - - ); -} - -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.', - } -); diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/code_editor.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/code_editor.tsx deleted file mode 100644 index 60f6284cbf32e..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/code_editor.tsx +++ /dev/null @@ -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 ( - - - - - - ); -}; - -const MonacoCodeContainer = euiStyled.div` - & > .kibanaCodeEditor { - z-index: 0; - } -`; diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/combo_box.test.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/combo_box.test.tsx deleted file mode 100644 index 7d615b2ecea1d..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/combo_box.test.tsx +++ /dev/null @@ -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('', () => { - const onChange = jest.fn(); - const selectedOptions: string[] = []; - - it('renders ComboBox', () => { - const { getByTestId } = render( - - ); - - expect(getByTestId('syntheticsFleetComboBox')).toBeInTheDocument(); - }); - - it('calls onBlur', () => { - const onBlur = jest.fn(); - const { getByTestId } = render( - - ); - - const combobox = getByTestId('syntheticsFleetComboBox'); - fireEvent.focus(combobox); - fireEvent.blur(combobox); - - expect(onBlur).toHaveBeenCalledTimes(1); - }); -}); diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/combo_box.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/combo_box.tsx deleted file mode 100644 index 553d94a9dc53a..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/combo_box.tsx +++ /dev/null @@ -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> - >(selectedOptions.map((option) => ({ label: option, key: option }))); - const [isInvalid, setInvalid] = useState(false); - - const onOptionsChange = useCallback( - (options: Array>) => { - 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 ( - - 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; -}; diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/common/common_fields.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/common/common_fields.tsx deleted file mode 100644 index 5e0f0e4a44100..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/common/common_fields.tsx +++ /dev/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 ( - <> - - } - labelAppend={} - helpText={ - - } - > - - onChange({ - value: event.target.value, - configKey: ConfigKey.APM_SERVICE_NAME, - }) - } - onBlur={() => onFieldBlur?.(ConfigKey.APM_SERVICE_NAME)} - data-test-subj="syntheticsAPMServiceName" - /> - - {!isBrowser && ( - - } - isInvalid={!!validate[ConfigKey.TIMEOUT]?.(fields)} - error={ - parseInt(fields[ConfigKey.TIMEOUT] || '', 10) < 0 ? ( - - ) : ( - - ) - } - helpText={ - - } - > - - onChange({ - value: event.target.value, - configKey: ConfigKey.TIMEOUT, - }) - } - onBlur={() => onFieldBlur?.(ConfigKey.TIMEOUT)} - step={'any'} - /> - - )} - - } - labelAppend={} - helpText={ - - } - > - onChange({ value, configKey: ConfigKey.TAGS })} - onBlur={() => onFieldBlur?.(ConfigKey.TAGS)} - data-test-subj="syntheticsTags" - /> - - - ); -} diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/common/described_form_group_with_wrap.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/common/described_form_group_with_wrap.tsx deleted file mode 100644 index c029be5fbef22..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/common/described_form_group_with_wrap.tsx +++ /dev/null @@ -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};` : '')} - } - } -`; diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/common/enabled.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/common/enabled.tsx deleted file mode 100644 index 22d8116ceacce..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/common/enabled.tsx +++ /dev/null @@ -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 ( - <> - - } - > - - } - data-test-subj="syntheticsEnabled" - checked={fields[ConfigKey.ENABLED]} - onChange={(event) => - onChange({ - value: event.target.checked, - configKey: ConfigKey.ENABLED, - }) - } - onBlur={() => onBlur?.()} - disabled={readOnly} - /> - - - ); -} diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/common/normalizers.test.ts b/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/common/normalizers.test.ts deleted file mode 100644 index caeeac915a911..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/common/normalizers.test.ts +++ /dev/null @@ -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']); - }); - }); -}); diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/common/normalizers.ts b/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/common/normalizers.ts deleted file mode 100644 index 1b46f732dc123..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/common/normalizers.ts +++ /dev/null @@ -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; - -/** - * 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(key: string, defaultValues: Fields): Normalizer { - return (fields: NewPackagePolicyInput['vars']) => - fields?.[key]?.value ?? defaultValues[key as keyof Fields]; -} - -export function getJsonToJavascriptNormalizer( - key: string, - defaultValues: Fields -): Normalizer { - return (fields: NewPackagePolicyInput['vars']) => - jsonToJavascriptNormalizer(fields?.[key]?.value) ?? defaultValues[key as keyof Fields]; -} - -export function getCronNormalizer(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), -}; diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/common/simple_fields_wrapper.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/common/simple_fields_wrapper.tsx deleted file mode 100644 index 73d49769248e3..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/common/simple_fields_wrapper.tsx +++ /dev/null @@ -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 ( - <> - onFieldBlur?.(ConfigKey.ENABLED)} - /> - {children} - - - ); -}; diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/common/tls_options.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/common/tls_options.tsx deleted file mode 100644 index 3103a6b90912e..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/common/tls_options.tsx +++ /dev/null @@ -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 = memo( - ({ onChange, defaultValues = defaultConfig, tlsRole }) => { - const [verificationVersionInputRef, setVerificationVersionInputRef] = - useState(null); - const [hasVerificationVersionError, setHasVerificationVersionError] = useState< - string | undefined - >(undefined); - - const [config, setConfig] = useState(defaultValues); - - useEffect(() => { - onChange(config); - }, [config, onChange]); - - const onVerificationVersionChange = ( - selectedVersionOptions: Array> - ) => { - 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 ( - - - - - - ), - }} - > - - } - helpText={ - config.verificationMode ? verificationModeHelpText[config.verificationMode] : '' - } - > - { - const verificationMode = event.target.value as VerificationMode; - setConfig((prevConfig) => ({ - ...prevConfig, - verificationMode, - })); - }} - data-test-subj="syntheticsTLSVerificationMode" - /> - - {config.verificationMode === VerificationMode.NONE && ( - <> - - - } - color="warning" - size="s" - > -

- -

-
- - - )} - - } - error={hasVerificationVersionError} - isInvalid={hasVerificationVersionError !== undefined} - > - ({ - label: version, - }))} - inputRef={setVerificationVersionInputRef} - onChange={onVerificationVersionChange} - onSearchChange={onSearchChange} - onBlur={onBlur} - /> - - - } - helpText={ - - } - labelAppend={} - > - { - 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" - /> - - - {tlsRoleLabels[tlsRole]}{' '} - - - } - helpText={ - - } - labelAppend={} - > - { - 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" - /> - - - {tlsRoleLabels[tlsRole]}{' '} - - - } - helpText={ - - } - labelAppend={} - > - { - 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" - /> - - - {tlsRoleLabels[tlsRole]}{' '} - - - } - helpText={ - - } - labelAppend={} - > - { - const keyPassphrase = event.target.value; - setConfig((prevConfig) => ({ - ...prevConfig, - keyPassphrase, - })); - }} - data-test-subj="syntheticsTLSCertKeyPassphrase" - /> - -
- ); - } -); - -const tlsRoleLabels = { - client: ( - - ), - 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, -})); diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/contexts/browser_context.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/contexts/browser_context.tsx deleted file mode 100644 index e89e4dcd1d5bd..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/contexts/browser_context.tsx +++ /dev/null @@ -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>; - 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) => { - 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(defaultValues); - - const value = useMemo(() => { - return { fields, setFields, defaultValues }; - }, [fields, defaultValues]); - - return ; -}; - -export const useBrowserSimpleFieldsContext = () => useContext(BrowserSimpleFieldsContext); diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/contexts/browser_context_advanced.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/contexts/browser_context_advanced.tsx deleted file mode 100644 index d81e1dc631ee2..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/contexts/browser_context_advanced.tsx +++ /dev/null @@ -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>; - 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) => { - 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(defaultValues); - - const value = useMemo(() => { - return { fields, setFields, defaultValues }; - }, [fields, defaultValues]); - - return ; -}; - -export const useBrowserAdvancedFieldsContext = () => useContext(BrowserAdvancedFieldsContext); diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/contexts/browser_provider.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/contexts/browser_provider.tsx deleted file mode 100644 index 309784576818f..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/contexts/browser_provider.tsx +++ /dev/null @@ -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; - const advancedKeys = Object.keys(defaultBrowserAdvancedFields) as Array< - keyof BrowserAdvancedFields - >; - const formattedDefaultSimpleFields = formatDefaultValues( - simpleKeys, - defaultValues || {} - ); - const formattedDefaultAdvancedFields = formatDefaultValues( - advancedKeys, - defaultValues || {} - ); - const simpleFields: BrowserSimpleFields | undefined = defaultValues - ? formattedDefaultSimpleFields - : undefined; - const advancedFields: BrowserAdvancedFields | undefined = defaultValues - ? formattedDefaultAdvancedFields - : undefined; - return ( - - - {children} - - - ); -}; diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/contexts/http_context.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/contexts/http_context.tsx deleted file mode 100644 index d45dfc01f0de6..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/contexts/http_context.tsx +++ /dev/null @@ -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>; - 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) => { - 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(defaultValues); - - const value = useMemo(() => { - return { fields, setFields, defaultValues }; - }, [fields, defaultValues]); - - return ; -}; - -export const useHTTPSimpleFieldsContext = () => useContext(HTTPSimpleFieldsContext); diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/contexts/http_context_advanced.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/contexts/http_context_advanced.tsx deleted file mode 100644 index 549f314bc3d3a..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/contexts/http_context_advanced.tsx +++ /dev/null @@ -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>; - 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) => { - 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(defaultValues); - - const value = useMemo(() => { - return { fields, setFields, defaultValues }; - }, [fields, defaultValues]); - - return ; -}; - -export const useHTTPAdvancedFieldsContext = () => useContext(HTTPAdvancedFieldsContext); diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/contexts/http_provider.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/contexts/http_provider.tsx deleted file mode 100644 index 9141594dd5ccf..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/contexts/http_provider.tsx +++ /dev/null @@ -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; - const advancedKeys = Object.keys(defaultHTTPAdvancedFields) as Array; - const formattedDefaultHTTPSimpleFields = formatDefaultValues( - simpleKeys, - defaultValues || {} - ); - const formattedDefaultHTTPAdvancedFields = formatDefaultValues( - advancedKeys, - defaultValues || {} - ); - const httpAdvancedFields = defaultValues ? formattedDefaultHTTPAdvancedFields : undefined; - const httpSimpleFields: HTTPSimpleFields | undefined = defaultValues - ? formattedDefaultHTTPSimpleFields - : undefined; - return ( - - - {children} - - - ); -}; diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/contexts/icmp_context.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/contexts/icmp_context.tsx deleted file mode 100644 index 4e7667650ff10..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/contexts/icmp_context.tsx +++ /dev/null @@ -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>; - 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) => { - 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(defaultValues); - - const value = useMemo(() => { - return { fields, setFields, defaultValues }; - }, [fields, defaultValues]); - - return ; -}; - -export const useICMPSimpleFieldsContext = () => useContext(ICMPSimpleFieldsContext); diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/contexts/index.ts b/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/contexts/index.ts deleted file mode 100644 index e44011bc24844..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/contexts/index.ts +++ /dev/null @@ -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'; diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/contexts/policy_config_context.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/contexts/policy_config_context.tsx deleted file mode 100644 index cf91e60c6da2d..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/contexts/policy_config_context.tsx +++ /dev/null @@ -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>; - setName: React.Dispatch>; - setLocations: React.Dispatch>; - setIsTLSEnabled: React.Dispatch>; - setNamespace: React.Dispatch>; - 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) => { - throw new Error('setMonitorType was not initialized, set it when you invoke the context'); - }, - setName: (_name: React.SetStateAction) => { - throw new Error('setName was not initialized, set it when you invoke the context'); - }, - setLocations: (_locations: React.SetStateAction) => { - throw new Error('setLocations was not initialized, set it when you invoke the context'); - }, - setIsTLSEnabled: (_isTLSEnabled: React.SetStateAction) => { - throw new Error('setIsTLSEnabled was not initialized, set it when you invoke the context'); - }, - setNamespace: (_namespace: React.SetStateAction) => { - 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({ - 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(defaultMonitorType); - const [name, setName] = useState(defaultName); - const [locations, setLocations] = useState(defaultLocations); - const [isTLSEnabled, setIsTLSEnabled] = useState(defaultIsTLSEnabled); - const [namespace, setNamespace] = useState(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 ; -} - -export function usePolicyConfigContext() { - return useContext(PolicyConfigContext); -} diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/contexts/synthetics_context_providers.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/contexts/synthetics_context_providers.tsx deleted file mode 100644 index d8d53da500082..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/contexts/synthetics_context_providers.tsx +++ /dev/null @@ -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; -} - -export const SyntheticsProviders = ({ - children, - httpDefaultValues, - tcpDefaultValues, - icmpDefaultValues, - browserDefaultValues, - tlsDefaultValues, - policyDefaultValues, -}: Props) => { - return ( - - - - - - - {children} - - - - - - - ); -}; diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/contexts/tcp_context.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/contexts/tcp_context.tsx deleted file mode 100644 index 9b3e1052100e9..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/contexts/tcp_context.tsx +++ /dev/null @@ -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>; - 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) => { - 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(defaultValues); - - const value = useMemo(() => { - return { fields, setFields, defaultValues }; - }, [fields, defaultValues]); - - return ; -}; - -export const useTCPSimpleFieldsContext = () => useContext(TCPSimpleFieldsContext); diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/contexts/tcp_context_advanced.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/contexts/tcp_context_advanced.tsx deleted file mode 100644 index 8d1174ed96bc6..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/contexts/tcp_context_advanced.tsx +++ /dev/null @@ -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>; - 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) => { - 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(defaultValues); - - const value = useMemo(() => { - return { fields, setFields, defaultValues }; - }, [fields, defaultValues]); - - return ; -}; - -export const useTCPAdvancedFieldsContext = () => useContext(TCPAdvancedFieldsContext); diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/contexts/tcp_provider.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/contexts/tcp_provider.tsx deleted file mode 100644 index 76877319c0c48..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/contexts/tcp_provider.tsx +++ /dev/null @@ -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; - const advancedKeys = Object.keys(defaultTCPAdvancedFields) as Array; - const formattedDefaultSimpleFields = formatDefaultValues( - simpleKeys, - defaultValues || {} - ); - const formattedDefaultAdvancedFields = formatDefaultValues( - advancedKeys, - defaultValues || {} - ); - const simpleFields: TCPSimpleFields | undefined = defaultValues - ? formattedDefaultSimpleFields - : undefined; - const advancedFields: TCPAdvancedFields | undefined = defaultValues - ? formattedDefaultAdvancedFields - : undefined; - return ( - - - {children} - - - ); -}; diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/contexts/tls_fields_context.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/contexts/tls_fields_context.tsx deleted file mode 100644 index 14ec64bf640b7..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/contexts/tls_fields_context.tsx +++ /dev/null @@ -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>; - 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) => { - 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(defaultValues); - - const value = useMemo(() => { - return { fields, setFields, defaultValues }; - }, [fields, defaultValues]); - - return ; -}; - -export const useTLSFieldsContext = () => useContext(TLSFieldsContext); diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/custom_fields.test.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/custom_fields.test.tsx deleted file mode 100644 index 6531024bea28f..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/custom_fields.test.tsx +++ /dev/null @@ -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) => ( - { - props.onChange(e.jsonContent); - }} - /> - ), - }; -}); - -const defaultValidation = centralValidation[DataStream.HTTP]; - -const defaultHTTPConfig = defaultConfig[DataStream.HTTP]; -const defaultTCPConfig = defaultConfig[DataStream.TCP]; - -describe('', () => { - let onFieldBlurMock: jest.Mock | undefined; - - const WrappedComponent = ({ - validate = defaultValidation, - isEditable = false, - dataStreams = [DataStream.HTTP, DataStream.TCP, DataStream.ICMP, DataStream.BROWSER], - onFieldBlur = onFieldBlurMock, - }) => { - return ( - - - - - - - - - - - - - - ); - }; - - beforeEach(() => { - onFieldBlurMock = undefined; - jest.resetAllMocks(); - }); - - it('renders CustomFields', async () => { - const { getByText, getByLabelText, queryByLabelText } = render( - - ); - 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(); - 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(); - 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(); - 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( - - ); - 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(); - 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(); - 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(); - - 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( - - ); - - 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( - - ); - - 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( - - ); - - const monitorTypeSelect = queryByLabelText('Monitor Type') as HTMLInputElement; - fireEvent.click(monitorTypeSelect); - fireEvent.blur(monitorTypeSelect); - expect(onFieldBlurMock).toHaveBeenCalledWith(ConfigKey.MONITOR_TYPE); - }); -}); diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/custom_fields.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/custom_fields.tsx deleted file mode 100644 index 874aab7fa7299..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/custom_fields.tsx +++ /dev/null @@ -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( - ({ 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 ( - onFieldBlur?.(field)} /> - ); - case DataStream.ICMP: - return ( - onFieldBlur?.(field)} /> - ); - case DataStream.TCP: - return ( - onFieldBlur?.(field)} /> - ); - case DataStream.BROWSER: - return ( - onFieldBlur?.(field)} - /> - ); - default: - return null; - } - }; - - const isWithInUptime = window.location.pathname.includes('/app/uptime'); - - return ( - - - - - } - description={ - - } - data-test-subj="monitorSettingsSection" - > - - - {children} - {!isEditable && ( - - } - isInvalid={ - !!validate[ConfigKey.MONITOR_TYPE]?.({ - [ConfigKey.MONITOR_TYPE]: monitorType as DataStream, - }) - } - error={ - - } - > - setMonitorType(event.target.value as DataStream)} - onBlur={() => onFieldBlur?.(ConfigKey.MONITOR_TYPE)} - data-test-subj="syntheticsMonitorTypeField" - /> - - )} - - {isBrowser && !isWithInUptime && ( - - } - size="s" - > - elastic-agent-complete, - link: ( - - - - ), - }} - /> - - )} - - {renderSimpleFields(monitorType)} - - - - {(isHTTP || isTCP) && ( - - - - } - description={ - - } - id="uptimeFleetIsTLSEnabled" - > - - } - onChange={(event) => setIsTLSEnabled(event.target.checked)} - /> - - - )} - - {isHTTP && ( - - {appendAdvancedFields} - - )} - {isTCP && ( - - {appendAdvancedFields} - - )} - {isBrowser && ( - - {appendAdvancedFields} - - )} - {isICMP && {appendAdvancedFields}} - - ); - } -); diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/header_field.test.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/header_field.test.tsx deleted file mode 100644 index 668669dab6a26..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/header_field.test.tsx +++ /dev/null @@ -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('', () => { - const onChange = jest.fn(); - const onBlur = jest.fn(); - const defaultValue = {}; - - afterEach(() => { - jest.resetAllMocks(); - }); - - it('renders HeaderField', () => { - const { getByText, getByTestId } = render( - - ); - - 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( - - ); - - 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( - - ); - 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( - - ); - 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( - - ); - - await waitFor(() => { - expect(onChange).toBeCalledWith({ - 'Content-Type': contentTypes[Mode.PLAINTEXT], - }); - }); - }); -}); diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/header_field.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/header_field.tsx deleted file mode 100644 index 112a0879d404d..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/header_field.tsx +++ /dev/null @@ -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; - onChange: (value: Record) => 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((key) => { - return [key || '', defaultValue[key] || '']; // key, value - }), - ]; - const [headers, setHeaders] = useState(formattedDefaultValues); - - useEffect(() => { - const formattedHeaders = headers.reduce((acc: Record, 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 ( - - } - defaultPairs={headers} - onChange={setHeaders} - onBlur={() => onBlur?.()} - data-test-subj={dataTestSubj} - /> - ); -}; - -export const contentTypes: Record = { - [Mode.JSON]: ContentType.JSON, - [Mode.PLAINTEXT]: ContentType.TEXT, - [Mode.XML]: ContentType.XML, - [Mode.FORM]: ContentType.FORM, -}; diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/helpers/context_helpers.ts b/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/helpers/context_helpers.ts deleted file mode 100644 index acd8bdf95ce85..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/helpers/context_helpers.ts +++ /dev/null @@ -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( - keys: Array, - defaultValues: Partial -) { - return keys.reduce((acc: any, currentValue) => { - const key = currentValue as keyof Fields; - acc[key] = defaultValues?.[key]; - return acc; - }, {}) as Fields; -} diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/hooks/use_policy.ts b/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/hooks/use_policy.ts deleted file mode 100644 index e0c7eb860f214..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/hooks/use_policy.ts +++ /dev/null @@ -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; -}; diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/http/advanced_fields.test.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/http/advanced_fields.test.tsx deleted file mode 100644 index 18937d5dc512f..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/http/advanced_fields.test.tsx +++ /dev/null @@ -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) => ( - { - props.onChange(e.jsonContent); - }} - /> - ), - }; -}); - -const defaultValidation = centralValidation[DataStream.HTTP]; - -describe('', () => { - const onFieldBlur = jest.fn(); - - const WrappedComponent = ({ - defaultValues, - validate = defaultValidation, - children, - }: { - defaultValues?: HTTPAdvancedFieldsType; - validate?: Validation; - children?: React.ReactNode; - }) => { - return ( - - - {children} - - - ); - }; - - it('renders HTTPAdvancedFields', () => { - const { getByText, getByLabelText } = render(); - - 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(); - - 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(); - - 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({upstreamFieldsText}); - - const upstream = getByText(upstreamFieldsText) as HTMLInputElement; - expect(upstream).toBeInTheDocument(); - }); -}); diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/http/advanced_fields.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/http/advanced_fields.tsx deleted file mode 100644 index e2afb91e3f684..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/http/advanced_fields.tsx +++ /dev/null @@ -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( - ({ validate, children, minColumnWidth, onFieldBlur }) => { - const { fields, setFields } = useHTTPAdvancedFieldsContext(); - const handleInputChange = useCallback( - ({ value, configKey }: { value: unknown; configKey: ConfigKey }) => { - setFields((prevFields) => ({ ...prevFields, [configKey]: value })); - }, - [setFields] - ); - - return ( - - } - data-test-subj="syntheticsHTTPAdvancedFieldsAccordion" - > - - - - - } - description={ - - } - data-test-subj="httpAdvancedFieldsSection" - > - - - } - labelAppend={} - helpText={ - - } - > - - handleInputChange({ - value: event.target.value, - configKey: ConfigKey.USERNAME, - }) - } - onBlur={() => onFieldBlur?.(ConfigKey.USERNAME)} - data-test-subj="syntheticsUsername" - /> - - - } - labelAppend={} - helpText={ - - } - > - - handleInputChange({ - value: event.target.value, - configKey: ConfigKey.PASSWORD, - }) - } - onBlur={() => onFieldBlur?.(ConfigKey.PASSWORD)} - data-test-subj="syntheticsPassword" - /> - - - } - labelAppend={} - helpText={ - - } - > - - handleInputChange({ - value: event.target.value, - configKey: ConfigKey.PROXY_URL, - }) - } - onBlur={() => onFieldBlur?.(ConfigKey.PROXY_URL)} - data-test-subj="syntheticsProxyUrl" - /> - - - } - helpText={ - - } - > - - handleInputChange({ - value: event.target.value, - configKey: ConfigKey.REQUEST_METHOD_CHECK, - }) - } - onBlur={() => onFieldBlur?.(ConfigKey.REQUEST_METHOD_CHECK)} - data-test-subj="syntheticsRequestMethod" - /> - - - } - labelAppend={} - isInvalid={!!validate[ConfigKey.REQUEST_HEADERS_CHECK]?.(fields)} - error={ - - } - helpText={ - - } - > - - handleInputChange({ - value, - configKey: ConfigKey.REQUEST_HEADERS_CHECK, - }), - [handleInputChange] - )} - onBlur={() => onFieldBlur?.(ConfigKey.REQUEST_HEADERS_CHECK)} - data-test-subj="syntheticsRequestHeaders" - /> - - - } - labelAppend={} - helpText={ - - } - fullWidth - > - - handleInputChange({ - value, - configKey: ConfigKey.REQUEST_BODY_CHECK, - }), - [handleInputChange] - )} - onBlur={() => onFieldBlur?.(ConfigKey.REQUEST_BODY_CHECK)} - /> - - - - - - - } - description={ - - } - > - - - - http.response.body.headers - - } - data-test-subj="syntheticsIndexResponseHeaders" - > - - } - onChange={(event) => - handleInputChange({ - value: event.target.checked, - configKey: ConfigKey.RESPONSE_HEADERS_INDEX, - }) - } - onBlur={() => onFieldBlur?.(ConfigKey.RESPONSE_HEADERS_INDEX)} - /> - - - - http.response.body.contents - - } - > - - handleInputChange({ value: policy, configKey: ConfigKey.RESPONSE_BODY_INDEX }), - [handleInputChange] - )} - onBlur={() => onFieldBlur?.(ConfigKey.RESPONSE_BODY_INDEX)} - /> - - - - - - } - description={ - - } - > - - } - labelAppend={} - isInvalid={!!validate[ConfigKey.RESPONSE_STATUS_CHECK]?.(fields)} - error={ - - } - 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.', - } - )} - > - - handleInputChange({ - value, - configKey: ConfigKey.RESPONSE_STATUS_CHECK, - }) - } - onBlur={() => onFieldBlur?.(ConfigKey.RESPONSE_STATUS_CHECK)} - data-test-subj="syntheticsResponseStatusCheck" - /> - - - } - labelAppend={} - isInvalid={!!validate[ConfigKey.RESPONSE_HEADERS_CHECK]?.(fields)} - error={[ - , - ]} - helpText={ - - } - > - - handleInputChange({ - value, - configKey: ConfigKey.RESPONSE_HEADERS_CHECK, - }), - [handleInputChange] - )} - onBlur={() => onFieldBlur?.(ConfigKey.RESPONSE_HEADERS_CHECK)} - data-test-subj="syntheticsResponseHeaders" - /> - - - } - labelAppend={} - 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.', - } - )} - > - - handleInputChange({ - value, - configKey: ConfigKey.RESPONSE_BODY_CHECK_POSITIVE, - }), - [handleInputChange] - )} - onBlur={() => onFieldBlur?.(ConfigKey.RESPONSE_BODY_CHECK_POSITIVE)} - data-test-subj="syntheticsResponseBodyCheckPositive" - /> - - - } - labelAppend={} - 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.', - } - )} - > - - handleInputChange({ - value, - configKey: ConfigKey.RESPONSE_BODY_CHECK_NEGATIVE, - }), - [handleInputChange] - )} - onBlur={() => onFieldBlur?.(ConfigKey.RESPONSE_BODY_CHECK_NEGATIVE)} - data-test-subj="syntheticsResponseBodyCheckNegative" - /> - - - {children} - - ); - } -); - -const requestMethodOptions = Object.values(HTTPMethod).map((method) => ({ - value: method, - text: method, -})); diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/http/simple_fields.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/http/simple_fields.tsx deleted file mode 100644 index 703310398385f..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/http/simple_fields.tsx +++ /dev/null @@ -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(({ validate, onFieldBlur }) => { - const { fields, setFields } = useHTTPSimpleFieldsContext(); - const handleInputChange = useCallback( - ({ value, configKey }: { value: unknown; configKey: ConfigKey }) => { - setFields((prevFields) => ({ ...prevFields, [configKey]: value })); - }, - [setFields] - ); - - return ( - - - } - isInvalid={!!validate[ConfigKey.URLS]?.(fields)} - error={ - - } - > - - handleInputChange({ value: event.target.value, configKey: ConfigKey.URLS }) - } - onBlur={() => onFieldBlur(ConfigKey.URLS)} - data-test-subj="syntheticsUrlField" - /> - - - } - isInvalid={!!validate[ConfigKey.SCHEDULE]?.(fields)} - error={ - - } - > - - handleInputChange({ - value: schedule, - configKey: ConfigKey.SCHEDULE, - }) - } - onBlur={() => onFieldBlur(ConfigKey.SCHEDULE)} - number={fields[ConfigKey.SCHEDULE].number} - unit={fields[ConfigKey.SCHEDULE].unit} - /> - - - } - isInvalid={!!validate[ConfigKey.MAX_REDIRECTS]?.(fields)} - error={ - - } - labelAppend={} - helpText={ - - } - > - - handleInputChange({ - value: event.target.value, - configKey: ConfigKey.MAX_REDIRECTS, - }) - } - onBlur={() => onFieldBlur(ConfigKey.MAX_REDIRECTS)} - /> - - - ); -}); diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/icmp/advanced_fields.test.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/icmp/advanced_fields.test.tsx deleted file mode 100644 index d2956fec8e04f..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/icmp/advanced_fields.test.tsx +++ /dev/null @@ -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('', () => { - const WrappedComponent = ({ children }: { children?: React.ReactNode }) => ( - {children} - ); - - it('renders upstream fields', () => { - const upstreamFieldsText = 'Monitor Advanced field section'; - const { getByText, getByTestId } = render( - {upstreamFieldsText} - ); - - const upstream = getByText(upstreamFieldsText) as HTMLInputElement; - const accordion = getByTestId('syntheticsICMPAdvancedFieldsAccordion') as HTMLInputElement; - expect(upstream).toBeInTheDocument(); - expect(accordion).toBeInTheDocument(); - }); -}); diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/icmp/advanced_fields.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/icmp/advanced_fields.tsx deleted file mode 100644 index 7aac2de3cf14e..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/icmp/advanced_fields.tsx +++ /dev/null @@ -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 ( - - } - data-test-subj="syntheticsICMPAdvancedFieldsAccordion" - > - - {children} - - ); - } - - return <>; -}; diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/icmp/simple_fields.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/icmp/simple_fields.tsx deleted file mode 100644 index b971d1727c03b..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/icmp/simple_fields.tsx +++ /dev/null @@ -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(({ validate, onFieldBlur }) => { - const { fields, setFields } = useICMPSimpleFieldsContext(); - const handleInputChange = useCallback( - ({ value, configKey }: { value: unknown; configKey: ConfigKey }) => { - setFields((prevFields) => ({ ...prevFields, [configKey]: value })); - }, - [setFields] - ); - - return ( - - - } - isInvalid={!!validate[ConfigKey.HOSTS]?.(fields)} - error={ - - } - > - - handleInputChange({ - value: event.target.value, - configKey: ConfigKey.HOSTS, - }) - } - onBlur={() => onFieldBlur(ConfigKey.HOSTS)} - data-test-subj="syntheticsICMPHostField" - /> - - - } - isInvalid={!!validate[ConfigKey.SCHEDULE]?.(fields)} - error={ - - } - > - - handleInputChange({ - value: schedule, - configKey: ConfigKey.SCHEDULE, - }) - } - onBlur={() => onFieldBlur(ConfigKey.SCHEDULE)} - number={fields[ConfigKey.SCHEDULE].number} - unit={fields[ConfigKey.SCHEDULE].unit} - /> - - - } - isInvalid={!!validate[ConfigKey.WAIT]?.(fields)} - error={ - - } - labelAppend={} - helpText={ - - } - > - - handleInputChange({ - value: event.target.value, - configKey: ConfigKey.WAIT, - }) - } - onBlur={() => onFieldBlur(ConfigKey.WAIT)} - step={'any'} - /> - - - ); -}); diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/index_response_body_field.test.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/index_response_body_field.test.tsx deleted file mode 100644 index d69f66d688e35..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/index_response_body_field.test.tsx +++ /dev/null @@ -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('', () => { - const defaultDefaultValue = ResponseBodyIndexPolicy.ON_ERROR; - const onChange = jest.fn(); - const onBlur = jest.fn(); - const WrappedComponent = ({ defaultValue = defaultDefaultValue }) => { - return ( - - ); - }; - - afterEach(() => { - jest.resetAllMocks(); - }); - - it('renders ResponseBodyIndexField', () => { - const { getByText, getByTestId } = render(); - 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(); - 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(); - 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(); - 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( - - ); - 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); - }); - }); -}); diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/index_response_body_field.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/index_response_body_field.tsx deleted file mode 100644 index aff500151e685..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/index_response_body_field.tsx +++ /dev/null @@ -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( - defaultValue !== ResponseBodyIndexPolicy.NEVER ? defaultValue : ResponseBodyIndexPolicy.ON_ERROR - ); - const [checked, setChecked] = useState(defaultValue !== ResponseBodyIndexPolicy.NEVER); - - useEffect(() => { - if (checked) { - setPolicy(policy); - onChange(policy); - } else { - onChange(ResponseBodyIndexPolicy.NEVER); - } - }, [checked, policy, setPolicy, onChange]); - - useEffect(() => { - onChange(policy); - }, [onChange, policy]); - - return ( - - - - } - onChange={(event) => { - const checkedEvent = event.target.checked; - setChecked(checkedEvent); - }} - onBlur={() => onBlur?.()} - /> - - {checked && ( - - { - setPolicy(event.target.value as ResponseBodyIndexPolicy); - }} - onBlur={() => onBlur?.()} - /> - - )} - - ); -}; - -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', - } - ), - }, -]; diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/key_value_field.test.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/key_value_field.test.tsx deleted file mode 100644 index bffe4fd761908..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/key_value_field.test.tsx +++ /dev/null @@ -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('', () => { - const onChange = jest.fn(); - const onBlur = jest.fn(); - const defaultDefaultValue = [['', '']] as Pair[]; - const WrappedComponent = ({ - defaultValue = defaultDefaultValue, - addPairControlLabel = 'Add pair', - }) => { - return ( - - ); - }; - - afterEach(() => { - jest.resetAllMocks(); - }); - - it('renders KeyValuePairsField', () => { - const { getByText } = render(); - expect(getByText('Key')).toBeInTheDocument(); - expect(getByText('Value')).toBeInTheDocument(); - - expect(getByText('Add pair')).toBeInTheDocument(); - }); - - it('calls onBlur', () => { - const { getByText, getByTestId } = render(); - 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( - - ); - - 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]]); - }); - }); -}); diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/key_value_field.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/key_value_field.tsx deleted file mode 100644 index 153d537ebec0e..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/key_value_field.tsx +++ /dev/null @@ -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(defaultPairs); - - const handleOnChange = useCallback( - (event: React.ChangeEvent, 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 ( -
- - - - - {addPairControlLabel} - - - - - - - { - - } - - - { - - } - - - ), - } - : undefined - } - > - {pairs.map((pair, index) => { - const [key, value] = pair; - return ( - - - - handleDeletePair(index)} - /> - - } - startControl={ - handleOnChange(event, index, true)} - onBlur={() => onBlur?.()} - /> - } - endControl={ - handleOnChange(event, index, false)} - onBlur={() => onBlur?.()} - /> - } - delimiter=":" - /> - - - ); - })} - -
- ); -}; diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/optional_label.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/optional_label.tsx deleted file mode 100644 index a9db178a84bb3..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/optional_label.tsx +++ /dev/null @@ -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 ( - - - - ); -}; diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/request_body_field.test.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/request_body_field.test.tsx deleted file mode 100644 index 6dad2b9b6efd9..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/request_body_field.test.tsx +++ /dev/null @@ -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) => ( - { - props.onChange(e.jsonContent); - }} - /> - ), - }; -}); - -describe('', () => { - const defaultMode = Mode.PLAINTEXT; - const defaultValue = 'sample value'; - const WrappedComponent = () => { - const [config, setConfig] = useState({ - type: defaultMode, - value: defaultValue, - }); - - return ( - setConfig({ type: code.type as Mode, value: code.value }), - [setConfig] - )} - /> - ); - }; - - it('renders RequestBodyField', () => { - const { getByText, getByLabelText } = render(); - - 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( - - ); - - // 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(); - }); - }); -}); diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/request_body_field.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/request_body_field.tsx deleted file mode 100644 index 19cb8cda978f8..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/request_body_field.tsx +++ /dev/null @@ -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>({ - [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, 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: ( - { - 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: ( - { - 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: ( - { - 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: ( - - } - defaultPairs={defaultFormPairs} - onChange={onChangeFormFields} - onBlur={() => onBlur?.()} - /> - ), - }, - ]; - - return ( - 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', - } - ), -}; diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/schedule_field.test.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/schedule_field.test.tsx deleted file mode 100644 index 77f987da0df0d..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/schedule_field.test.tsx +++ /dev/null @@ -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('', () => { - const number = '1'; - const unit = ScheduleUnit.MINUTES; - const onBlur = jest.fn(); - const WrappedComponent = ({ - allowedScheduleUnits, - }: Omit) => { - const [config, setConfig] = useState({ - number, - unit, - }); - - return ( - - setConfig(value)} - onBlur={onBlur} - /> - - ); - }; - - afterEach(() => { - jest.resetAllMocks(); - }); - - it('shows all options by default (allowedScheduleUnits is not provided)', () => { - const { getByText } = render(); - expect(getByText('Minutes')).toBeInTheDocument(); - expect(getByText('Seconds')).toBeInTheDocument(); - }); - - it('shows only Minutes when allowedScheduleUnits = [ScheduleUnit.Minutes])', () => { - const { queryByText } = render( - - ); - expect(queryByText('Minutes')).toBeInTheDocument(); - expect(queryByText('Seconds')).not.toBeInTheDocument(); - }); - - it('shows only Seconds when allowedScheduleUnits = [ScheduleUnit.Seconds])', () => { - const { queryByText } = render( - - ); - expect(queryByText('Minutes')).not.toBeInTheDocument(); - expect(queryByText('Seconds')).toBeInTheDocument(); - }); - - it('only accepts whole number when allowedScheduleUnits = [ScheduleUnit.Minutes])', async () => { - const { getByTestId } = render( - - ); - 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(); - 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(); - 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( - - ); - 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); - }); -}); diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/schedule_field.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/schedule_field.tsx deleted file mode 100644 index 70224b6df7047..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/schedule_field.tsx +++ /dev/null @@ -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 ( - - - { - 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} - /> - - - { - const updatedUnit = event.target.value; - onChange({ number, unit: updatedUnit as ScheduleUnit }); - }} - onBlur={() => onBlur()} - disabled={readOnly} - /> - - - ); -}; - -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, - }, -]; diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/synthetics_policy_create_extension.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/synthetics_policy_create_extension.tsx index dbd83dac27d05..2129db378b5dc 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/synthetics_policy_create_extension.tsx +++ b/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/synthetics_policy_create_extension.tsx @@ -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 diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/synthetics_policy_edit_extension_wrapper.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/synthetics_policy_edit_extension_wrapper.tsx index 4b563e0f0e76b..682b583f207a4 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/synthetics_policy_edit_extension_wrapper.tsx +++ b/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/synthetics_policy_edit_extension_wrapper.tsx @@ -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'; /** diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/tcp/advanced_fields.test.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/tcp/advanced_fields.test.tsx deleted file mode 100644 index e070d07158766..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/tcp/advanced_fields.test.tsx +++ /dev/null @@ -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('', () => { - const WrappedComponent = ({ - defaultValues = defaultConfig, - children, - onFieldBlur, - }: { - defaultValues?: TCPAdvancedFieldsType; - children?: React.ReactNode; - onFieldBlur?: (field: ConfigKey) => void; - }) => { - return ( - - {children} - - ); - }; - - it('renders TCPAdvancedFields', () => { - const { getByLabelText } = render(); - - 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(); - - 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(); - - 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(); - - 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({upstreamFieldsText}); - - const upstream = getByText(upstreamFieldsText) as HTMLInputElement; - expect(upstream).toBeInTheDocument(); - }); -}); diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/tcp/advanced_fields.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/tcp/advanced_fields.tsx deleted file mode 100644 index adc23ecffe3fd..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/tcp/advanced_fields.tsx +++ /dev/null @@ -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(({ children, minColumnWidth, onFieldBlur }) => { - const { fields, setFields } = useTCPAdvancedFieldsContext(); - - const handleInputChange = useCallback( - ({ value, configKey }: { value: unknown; configKey: ConfigKey }) => { - setFields((prevFields) => ({ ...prevFields, [configKey]: value })); - }, - [setFields] - ); - - return ( - - - - - - } - description={ - - } - > - - - } - labelAppend={} - helpText={ - - } - > - - handleInputChange({ - value: event.target.value, - configKey: ConfigKey.PROXY_URL, - }) - } - onBlur={() => onFieldBlur?.(ConfigKey.PROXY_URL)} - data-test-subj="syntheticsProxyUrl" - /> - - {!!fields[ConfigKey.PROXY_URL] && ( - - - } - onChange={(event) => - handleInputChange({ - value: event.target.checked, - configKey: ConfigKey.PROXY_USE_LOCAL_RESOLVER, - }) - } - /> - - )} - - } - labelAppend={} - helpText={ - - } - > - - handleInputChange({ - value: event.target.value, - configKey: ConfigKey.REQUEST_SEND_CHECK, - }), - [handleInputChange] - )} - onBlur={() => onFieldBlur?.(ConfigKey.REQUEST_SEND_CHECK)} - data-test-subj="syntheticsTCPRequestSendCheck" - /> - - - - - - } - description={ - - } - > - - } - labelAppend={} - helpText={ - - } - > - - handleInputChange({ - value: event.target.value, - configKey: ConfigKey.RESPONSE_RECEIVE_CHECK, - }), - [handleInputChange] - )} - onBlur={() => onFieldBlur?.(ConfigKey.RESPONSE_RECEIVE_CHECK)} - data-test-subj="syntheticsTCPResponseReceiveCheck" - /> - - - {children} - - ); -}); diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/tcp/simple_fields.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/tcp/simple_fields.tsx deleted file mode 100644 index b9f5c7a990b49..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/tcp/simple_fields.tsx +++ /dev/null @@ -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(({ validate, onFieldBlur }) => { - const { fields, setFields } = useTCPSimpleFieldsContext(); - const handleInputChange = useCallback( - ({ value, configKey }: { value: unknown; configKey: ConfigKey }) => { - setFields((prevFields) => ({ ...prevFields, [configKey]: value })); - }, - [setFields] - ); - - return ( - - - } - isInvalid={!!validate[ConfigKey.HOSTS]?.(fields)} - error={ - - } - > - - handleInputChange({ - value: event.target.value, - configKey: ConfigKey.HOSTS, - }) - } - onBlur={() => onFieldBlur(ConfigKey.HOSTS)} - data-test-subj="syntheticsTCPHostField" - /> - - - - } - isInvalid={!!validate[ConfigKey.SCHEDULE]?.(fields)} - error={ - - } - > - - handleInputChange({ - value: schedule, - configKey: ConfigKey.SCHEDULE, - }) - } - onBlur={() => onFieldBlur(ConfigKey.SCHEDULE)} - number={fields[ConfigKey.SCHEDULE].number} - unit={fields[ConfigKey.SCHEDULE].unit} - /> - - - ); -}); diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/tls/normalizers.ts b/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/tls/normalizers.ts deleted file mode 100644 index eb52494d2cb96..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/tls/normalizers.ts +++ /dev/null @@ -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; - -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]; diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/tls_fields.test.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/tls_fields.test.tsx deleted file mode 100644 index 8f38669976ef4..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/tls_fields.test.tsx +++ /dev/null @@ -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('', () => { - const WrappedComponent = ({ isEnabled = true }: { isEnabled?: boolean }) => { - return ( - - - - - - ); - }; - it('renders TLSFields', () => { - const { getByLabelText, getByText } = render(); - - 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(); - - 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(); - - 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(); - - 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(); - }); -}); diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/tls_fields.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/tls_fields.tsx deleted file mode 100644 index 8b62792973115..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/tls_fields.tsx +++ /dev/null @@ -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 ? ( - - ) : null; -}; diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/types.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/types.tsx deleted file mode 100644 index 0a5de311c5cb3..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/types.tsx +++ /dev/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 = { - [ConfigKey.DOWNLOAD_SPEED]: ThrottlingSuffix.DOWNLOAD, - [ConfigKey.UPLOAD_SPEED]: ThrottlingSuffix.UPLOAD, - [ConfigKey.LATENCY]: ThrottlingSuffix.LATENCY, -}; diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/action_bar/action_bar.test.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/action_bar/action_bar.test.tsx deleted file mode 100644 index e903f4d23424b..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/action_bar/action_bar.test.tsx +++ /dev/null @@ -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('', () => { - 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(); - - act(() => { - userEvent.click(screen.getByText('Save monitor')); - }); - - expect(setMonitor).toBeCalledWith({ monitor, id: undefined }); - }); - - it('does not call setMonitor until submission', () => { - render(); - - 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(); - - 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(); - - 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( - - ); - - act(() => { - userEvent.click(screen.getByText('Save monitor')); - }); - - expect(onSave).toBeCalled(); - }); -}); diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/action_bar/action_bar.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/action_bar/action_bar.tsx deleted file mode 100644 index 7748a56304116..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/action_bar/action_bar.tsx +++ /dev/null @@ -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(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 ? ( - - ) : ( - - - - {DISCARD_LABEL} - - - {!isReadOnly ? ( - - - - {!isValid && hasBeenSubmitted && VALIDATION_ERROR_LABEL} - - - {onTestNow && ( - - {/* Popover is used instead of EuiTooltip until the resolution of https://github.com/elastic/eui/issues/5604 */} - { - setIsPopoverOpen(false); - }} - > - 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} - - } - isOpen={isPopoverOpen} - > - -

- {isTestRunInProgress - ? TEST_SCHEDULED_LABEL - : isOnlyPrivateLocations || (isValid && !isAnyPublicLocationSelected) - ? PRIVATE_AVAILABLE_LABEL - : TEST_NOW_DESCRIPTION} -

-
-
-
-
- )} - - - - {monitorId ? UPDATE_MONITOR_LABEL : SAVE_MONITOR_LABEL} - - -
-
- ) : null} -
- ); -}; - -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', -}); diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/action_bar/action_bar_errors.test.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/action_bar/action_bar_errors.test.tsx deleted file mode 100644 index bb40077dab78d..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/action_bar/action_bar_errors.test.tsx +++ /dev/null @@ -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(' 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(, { - 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, - }) - ); - }); - }); -}); diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/action_bar/action_bar_portal.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/action_bar/action_bar_portal.tsx deleted file mode 100644 index 39bf7e70eb7c2..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/action_bar/action_bar_portal.tsx +++ /dev/null @@ -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 ( - - - - ); -}; diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/add_monitor_btn.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/add_monitor_btn.tsx deleted file mode 100644 index 85aab71cefeb1..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/add_monitor_btn.tsx +++ /dev/null @@ -1,238 +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 { EuiButton, EuiFlexItem, EuiFlexGroup, EuiToolTip, EuiSwitch } from '@elastic/eui'; -import { useHistory } from 'react-router-dom'; -import { useKibana } from '@kbn/kibana-react-plugin/public'; -import { useLocations } from './hooks/use_locations'; -import { ClientPluginsSetup, ClientPluginsStart } from '../../../plugin'; -import { kibanaService } from '../../state/kibana_service'; -import { MONITOR_ADD_ROUTE } from '../../../../common/constants'; -import { useEnablement } from './hooks/use_enablement'; -import { useSyntheticsServiceAllowed } from './hooks/use_service_allowed'; - -export const AddMonitorBtn = () => { - const history = useHistory(); - const [isEnabling, setIsEnabling] = useState(false); - const [isDisabling, setIsDisabling] = useState(false); - const { - error, - loading: enablementLoading, - enablement, - disableSynthetics, - enableSynthetics, - totalMonitors, - } = useEnablement(); - const { isEnabled, canEnable, areApiKeysEnabled } = enablement || {}; - - const { locations } = useLocations(); - - useEffect(() => { - if (isEnabling && isEnabled) { - setIsEnabling(false); - kibanaService.toasts.addSuccess({ - title: SYNTHETICS_ENABLE_SUCCESS, - toastLifeTimeMs: 3000, - }); - } else if (isDisabling && !isEnabled) { - setIsDisabling(false); - kibanaService.toasts.addSuccess({ - title: SYNTHETICS_DISABLE_SUCCESS, - toastLifeTimeMs: 3000, - }); - } else if (isEnabling && error) { - setIsEnabling(false); - kibanaService.toasts.addDanger({ - title: SYNTHETICS_ENABLE_FAILURE, - toastLifeTimeMs: 3000, - }); - } else if (isDisabling && error) { - kibanaService.toasts.addDanger({ - title: SYNTHETICS_DISABLE_FAILURE, - toastLifeTimeMs: 3000, - }); - } - }, [isEnabled, isEnabling, isDisabling, error]); - - const handleSwitch = () => { - if (isEnabled) { - setIsDisabling(true); - disableSynthetics(); - } else { - setIsEnabling(true); - enableSynthetics(); - } - }; - - const getShowSwitch = () => { - if (isEnabled) { - return canEnable; - } else if (!isEnabled) { - return canEnable && (totalMonitors || 0) > 0; - } - }; - - const getSwitchToolTipContent = () => { - if (!isEnabled) { - return SYNTHETICS_ENABLE_TOOL_TIP_MESSAGE; - } else if (isEnabled) { - return SYNTHETICS_DISABLE_TOOL_TIP_MESSAGE; - } else if (!areApiKeysEnabled) { - return API_KEYS_DISABLED_TOOL_TIP_MESSAGE; - } else { - return ''; - } - }; - - const { isAllowed, loading: allowedLoading } = useSyntheticsServiceAllowed(); - - const loading = allowedLoading || enablementLoading; - - const kServices = useKibana().services; - - const canSave: boolean = !!kServices?.application?.capabilities.uptime.save; - - const canSaveIntegrations: boolean = - !!kServices?.fleet?.authz.integrations.writeIntegrationPolicies; - - const isCloud = useKibana().services?.cloud?.isCloudEnabled; - - const canSavePrivate: boolean = Boolean(isCloud) || canSaveIntegrations; - - return ( - - - {getShowSwitch() && !loading && ( - - handleSwitch()} - data-test-subj="syntheticsEnableSwitch" - /> - - )} - {getShowSwitch() && loading && ( - {}} - /> - )} - - - - - {ADD_MONITOR_LABEL} - - - - - ); -}; - -const PRIVATE_LOCATIONS_NOT_ALLOWED_MESSAGE = i18n.translate( - 'xpack.synthetics.monitorManagement.privateLocationsNotAllowedMessage', - { - defaultMessage: - 'You do not have permission to add monitors to private locations. Contact your administrator to request access.', - } -); - -const ADD_MONITOR_LABEL = i18n.translate('xpack.synthetics.monitorManagement.addMonitorLabel', { - defaultMessage: 'Add monitor', -}); - -const SYNTHETICS_ENABLE_LABEL = i18n.translate( - 'xpack.synthetics.monitorManagement.syntheticsEnableLabel', - { - defaultMessage: 'Enable', - } -); - -const SYNTHETICS_ENABLE_FAILURE = i18n.translate( - 'xpack.synthetics.monitorManagement.syntheticsEnabledFailure', - { - defaultMessage: 'Monitor Management was not able to be enabled. Please contact support.', - } -); - -const SYNTHETICS_DISABLE_FAILURE = i18n.translate( - 'xpack.synthetics.monitorManagement.syntheticsDisabledFailure', - { - defaultMessage: 'Monitor Management was not able to be disabled. Please contact support.', - } -); - -const SYNTHETICS_ENABLE_SUCCESS = i18n.translate( - 'xpack.synthetics.monitorManagement.syntheticsEnableSuccess', - { - defaultMessage: 'Monitor Management enabled successfully.', - } -); - -const SYNTHETICS_DISABLE_SUCCESS = i18n.translate( - 'xpack.synthetics.monitorManagement.syntheticsDisabledSuccess', - { - defaultMessage: 'Monitor Management disabled successfully.', - } -); - -const SYNTHETICS_DISABLED_MESSAGE = i18n.translate( - 'xpack.synthetics.monitorManagement.syntheticsDisabled', - { - defaultMessage: - 'Monitor Management is currently disabled. Please contact an administrator to enable Monitor Management.', - } -); - -const SYNTHETICS_ENABLE_TOOL_TIP_MESSAGE = i18n.translate( - 'xpack.synthetics.monitorManagement.syntheticsEnableToolTip', - { - defaultMessage: - 'Enable Monitor Management to create lightweight and real-browser monitors from locations around the world.', - } -); - -const SYNTHETICS_DISABLE_TOOL_TIP_MESSAGE = i18n.translate( - 'xpack.synthetics.monitorManagement.syntheticsDisableToolTip', - { - defaultMessage: - 'Disabling Monitor Management will immediately stop the execution of monitors in all test locations and prevent the creation of new monitors.', - } -); - -const API_KEYS_DISABLED_TOOL_TIP_MESSAGE = i18n.translate( - 'xpack.synthetics.monitorManagement.apiKeysDisabledToolTip', - { - defaultMessage: - 'API Keys are disabled for this cluster. Monitor Management requires the use of API keys to write back to your Elasticsearch cluster. To enable API keys, please contact an administrator.', - } -); diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/content/index.ts b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/content/index.ts deleted file mode 100644 index 8c88fb8944a23..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/content/index.ts +++ /dev/null @@ -1,36 +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 { i18n } from '@kbn/i18n'; - -export const SYNTHETICS_ENABLE_FAILURE = i18n.translate( - 'xpack.synthetics.monitorManagement.syntheticsEnabledFailure', - { - defaultMessage: 'Monitor Management was not able to be enabled. Please contact support.', - } -); - -export const SYNTHETICS_DISABLE_FAILURE = i18n.translate( - 'xpack.synthetics.monitorManagement.syntheticsDisabledFailure', - { - defaultMessage: 'Monitor Management was not able to be disabled. Please contact support.', - } -); - -export const SYNTHETICS_ENABLE_SUCCESS = i18n.translate( - 'xpack.synthetics.monitorManagement.syntheticsEnableSuccess', - { - defaultMessage: 'Monitor Management enabled successfully.', - } -); - -export const SYNTHETICS_DISABLE_SUCCESS = i18n.translate( - 'xpack.synthetics.monitorManagement.syntheticsDisabledSuccess', - { - defaultMessage: 'Monitor Management disabled successfully.', - } -); diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/edit_monitor_config.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/edit_monitor_config.tsx deleted file mode 100644 index 04d86c8042c37..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/edit_monitor_config.tsx +++ /dev/null @@ -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, { useMemo } from 'react'; -import { useTrackPageview } from '@kbn/observability-plugin/public'; -import { - ConfigKey, - MonitorFields, - TLSFields, - DataStream, - ScheduleUnit, - SourceType, - ThrottlingOptions, -} from '../../../../common/runtime_types'; -import { SyntheticsProviders } from '../fleet_package/contexts'; -import { PolicyConfig } from '../fleet_package/types'; -import { MonitorConfig } from './monitor_config/monitor_config'; -import { DEFAULT_NAMESPACE_STRING } from '../../../../common/constants/monitor_defaults'; - -interface Props { - monitor: MonitorFields; - throttling: ThrottlingOptions; -} - -export const EditMonitorConfig = ({ monitor, throttling }: Props) => { - useTrackPageview({ app: 'uptime', path: 'edit-monitor' }); - useTrackPageview({ app: 'uptime', path: 'edit-monitor', delay: 15000 }); - - const { - enableTLS: isTLSEnabled, - fullConfig: fullDefaultConfig, - monitorTypeConfig: defaultConfig, - monitorType, - tlsConfig: defaultTLSConfig, - } = useMemo(() => { - let enableTLS = false; - const getDefaultConfig = () => { - const type: DataStream = monitor[ConfigKey.MONITOR_TYPE] as DataStream; - - const tlsConfig: TLSFields = { - [ConfigKey.TLS_CERTIFICATE_AUTHORITIES]: monitor[ConfigKey.TLS_CERTIFICATE_AUTHORITIES], - [ConfigKey.TLS_CERTIFICATE]: monitor[ConfigKey.TLS_CERTIFICATE], - [ConfigKey.TLS_KEY]: monitor[ConfigKey.TLS_KEY], - [ConfigKey.TLS_KEY_PASSPHRASE]: monitor[ConfigKey.TLS_KEY_PASSPHRASE], - [ConfigKey.TLS_VERIFICATION_MODE]: monitor[ConfigKey.TLS_VERIFICATION_MODE], - [ConfigKey.TLS_VERSION]: monitor[ConfigKey.TLS_VERSION], - }; - - enableTLS = Boolean(monitor[ConfigKey.METADATA]?.is_tls_enabled); - - const formattedDefaultConfig: Partial = { - [type]: monitor, - }; - - return { - fullConfig: formattedDefaultConfig, - monitorTypeConfig: monitor, - tlsConfig, - monitorType: type, - enableTLS, - }; - }; - - return getDefaultConfig(); - }, [monitor]); - - return ( - - - - ); -}; diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/hooks/use_enablement.ts b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/hooks/use_enablement.ts deleted file mode 100644 index e9ebd839f47fd..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/hooks/use_enablement.ts +++ /dev/null @@ -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 { useEffect, useCallback } from 'react'; -import { useDispatch, useSelector } from 'react-redux'; -import { monitorManagementListSelector } from '../../../state/selectors'; -import { - getSyntheticsEnablement, - disableSynthetics, - enableSynthetics, -} from '../../../state/actions'; - -export function useEnablement() { - const dispatch = useDispatch(); - - const { - loading: { enablement: loading }, - error: { enablement: error }, - enablement, - list: { total }, - } = useSelector(monitorManagementListSelector); - - useEffect(() => { - if (!enablement) { - dispatch(getSyntheticsEnablement()); - } - }, [dispatch, enablement]); - - return { - enablement: { - areApiKeysEnabled: enablement?.areApiKeysEnabled, - canManageApiKeys: enablement?.canManageApiKeys, - canEnable: enablement?.canEnable, - isEnabled: enablement?.isEnabled, - }, - invalidApiKeyError: enablement ? !Boolean(enablement?.isValidApiKey) : false, - error, - loading, - totalMonitors: total, - enableSynthetics: useCallback(() => dispatch(enableSynthetics()), [dispatch]), - disableSynthetics: useCallback(() => dispatch(disableSynthetics()), [dispatch]), - }; -} diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/hooks/use_format_monitor.ts b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/hooks/use_format_monitor.ts deleted file mode 100644 index 5d600a10daf39..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/hooks/use_format_monitor.ts +++ /dev/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 { useEffect, useRef, useState } from 'react'; -import { ConfigKey, DataStream, MonitorFields } from '../../../../../common/runtime_types'; -import { Validation } from '../../../../../common/types'; - -interface Props { - monitorType: DataStream; - defaultConfig: Partial; - config: Partial; - validate: Record; -} - -export const useFormatMonitor = ({ monitorType, defaultConfig, config, validate }: Props) => { - const [isValid, setIsValid] = useState(false); - const currentConfig = useRef>(defaultConfig); - - useEffect(() => { - const configKeys = Object.keys(config) as ConfigKey[]; - const validationKeys = Object.keys(validate[monitorType]) as ConfigKey[]; - const configDidUpdate = configKeys.some((key) => config[key] !== currentConfig.current[key]); - const isValidT = - !!config.name && !validationKeys.find((key) => validate[monitorType]?.[key]?.(config)); - - // prevent an infinite loop of updating the policy - if (configDidUpdate) { - currentConfig.current = config; - setIsValid(isValidT); - } - }, [config, currentConfig, validate, monitorType]); - - return { - config, - isValid, - }; -}; diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/hooks/use_inline_errors.test.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/hooks/use_inline_errors.test.tsx deleted file mode 100644 index efa1147b5e50f..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/hooks/use_inline_errors.test.tsx +++ /dev/null @@ -1,96 +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 { renderHook } from '@testing-library/react-hooks'; -import { MockRedux } from '../../../lib/helper/rtl_helpers'; -import { useInlineErrors } from './use_inline_errors'; -import { DEFAULT_THROTTLING } from '../../../../../common/runtime_types'; -import * as obsvPlugin from '@kbn/observability-plugin/public/hooks/use_es_search'; - -function mockNow(date: string | number | Date) { - const fakeNow = new Date(date).getTime(); - return jest.spyOn(Date, 'now').mockReturnValue(fakeNow); -} - -describe('useInlineErrors', function () { - it('it returns result as expected', async function () { - mockNow('2022-01-02T00:00:00.000Z'); - const spy = jest.spyOn(obsvPlugin, 'useEsSearch'); - - const { result } = renderHook(() => useInlineErrors({ onlyInvalidMonitors: true }), { - wrapper: MockRedux, - }); - - expect(result.current).toEqual({ - loading: true, - }); - - expect(spy).toHaveBeenNthCalledWith( - 3, - { - body: { - collapse: { field: 'monitor.id' }, - query: { - bool: { - filter: [ - { exists: { field: 'summary' } }, - { exists: { field: 'error' } }, - { - bool: { - minimum_should_match: 1, - should: [ - { match_phrase: { 'error.message': 'journey did not finish executing' } }, - { match_phrase: { 'error.message': 'ReferenceError:' } }, - ], - }, - }, - { - range: { - 'monitor.timespan': { - gte: '2022-01-01T23:40:00.000Z', - lte: '2022-01-02T00:00:00.000Z', - }, - }, - }, - { bool: { must_not: { exists: { field: 'run_once' } } } }, - ], - }, - }, - size: 1000, - sort: [{ '@timestamp': 'desc' }], - }, - index: 'synthetics-*', - }, - [ - { - error: { monitorList: null, serviceLocations: null, enablement: null }, - enablement: null, - list: { - monitors: [], - page: 1, - perPage: 10, - total: null, - syncErrors: null, - absoluteTotal: 0, - }, - loading: { monitorList: false, serviceLocations: false, enablement: false }, - locations: [], - syntheticsService: { - loading: false, - signupUrl: null, - }, - throttling: DEFAULT_THROTTLING, - }, - 1641081600000, - true, - '@timestamp', - 'desc', - ], - { name: 'getInvalidMonitors' } - ); - }); -}); diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/hooks/use_inline_errors.ts b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/hooks/use_inline_errors.ts deleted file mode 100644 index 1bdd7e287461c..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/hooks/use_inline_errors.ts +++ /dev/null @@ -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 { useSelector } from 'react-redux'; -import { useMemo } from 'react'; -import { useEsSearch } from '@kbn/observability-plugin/public'; -import { monitorManagementListSelector } from '../../../state/selectors'; -import { Ping } from '../../../../../common/runtime_types'; -import { - EXCLUDE_RUN_ONCE_FILTER, - getTimeSpanFilter, -} from '../../../../../common/constants/client_defaults'; -import { useUptimeRefreshContext } from '../../../contexts/uptime_refresh_context'; -import { useInlineErrorsCount } from './use_inline_errors_count'; -import { SYNTHETICS_INDEX_PATTERN } from '../../../../../common/constants'; - -const sortFieldMap: Record = { - ['name.keyword']: 'monitor.name', - ['urls.keyword']: 'url.full', - ['type.keyword']: 'monitor.type', - '@timestamp': '@timestamp', -}; - -export const getInlineErrorFilters = () => [ - { - exists: { - field: 'summary', - }, - }, - { - exists: { - field: 'error', - }, - }, - { - bool: { - minimum_should_match: 1, - should: [ - { - match_phrase: { - 'error.message': 'journey did not finish executing', - }, - }, - { - match_phrase: { - 'error.message': 'ReferenceError:', - }, - }, - ], - }, - }, - getTimeSpanFilter(), - EXCLUDE_RUN_ONCE_FILTER, -]; - -export function useInlineErrors({ - onlyInvalidMonitors, - sortField = '@timestamp', - sortOrder = 'desc', -}: { - onlyInvalidMonitors?: boolean; - sortField?: string; - sortOrder?: 'asc' | 'desc'; -}) { - const monitorList = useSelector(monitorManagementListSelector); - - const { lastRefresh } = useUptimeRefreshContext(); - - const configIds = monitorList.list.monitors.map((monitor) => monitor.id); - - const doFetch = configIds.length > 0 || onlyInvalidMonitors; - - const { data, loading } = useEsSearch( - { - index: doFetch ? SYNTHETICS_INDEX_PATTERN : '', - body: { - size: 1000, - query: { - bool: { - filter: getInlineErrorFilters(), - }, - }, - collapse: { field: 'monitor.id' }, - sort: [{ [sortFieldMap[sortField]]: sortOrder }], - }, - }, - [monitorList, lastRefresh, doFetch, sortField, sortOrder], - { name: 'getInvalidMonitors' } - ); - - const { count, loading: countLoading } = useInlineErrorsCount(); - - return useMemo(() => { - const errorSummaries = data?.hits.hits.map(({ _source: source }) => ({ - ...(source as Ping), - timestamp: (source as any)['@timestamp'], - })); - - return { loading: loading || countLoading, errorSummaries, count }; - }, [count, countLoading, data, loading]); -} diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/hooks/use_inline_errors_count.test.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/hooks/use_inline_errors_count.test.tsx deleted file mode 100644 index 468c63768442b..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/hooks/use_inline_errors_count.test.tsx +++ /dev/null @@ -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 { renderHook } from '@testing-library/react-hooks'; -import { MockRedux } from '../../../lib/helper/rtl_helpers'; -import { useInlineErrorsCount } from './use_inline_errors_count'; -import * as obsvPlugin from '@kbn/observability-plugin/public/hooks/use_es_search'; -import { DEFAULT_THROTTLING } from '../../../../../common/runtime_types'; - -function mockNow(date: string | number | Date) { - const fakeNow = new Date(date).getTime(); - return jest.spyOn(Date, 'now').mockReturnValue(fakeNow); -} - -describe('useInlineErrorsCount', function () { - it('it returns result as expected', async function () { - mockNow('2022-01-02T00:00:00.000Z'); - const spy = jest.spyOn(obsvPlugin, 'useEsSearch'); - - const { result } = renderHook(() => useInlineErrorsCount(), { - wrapper: MockRedux, - }); - - expect(result.current).toEqual({ - loading: true, - }); - - expect(spy).toHaveBeenNthCalledWith( - 2, - { - body: { - aggs: { total: { cardinality: { field: 'monitor.id' } } }, - query: { - bool: { - filter: [ - { exists: { field: 'summary' } }, - { exists: { field: 'error' } }, - { - bool: { - minimum_should_match: 1, - should: [ - { match_phrase: { 'error.message': 'journey did not finish executing' } }, - { match_phrase: { 'error.message': 'ReferenceError:' } }, - ], - }, - }, - { - range: { - 'monitor.timespan': { - gte: '2022-01-01T23:40:00.000Z', - lte: '2022-01-02T00:00:00.000Z', - }, - }, - }, - { bool: { must_not: { exists: { field: 'run_once' } } } }, - ], - }, - }, - size: 0, - }, - index: 'synthetics-*', - }, - [ - { - error: { monitorList: null, serviceLocations: null, enablement: null }, - list: { - monitors: [], - page: 1, - perPage: 10, - total: null, - syncErrors: null, - absoluteTotal: 0, - }, - enablement: null, - loading: { monitorList: false, serviceLocations: false, enablement: false }, - locations: [], - syntheticsService: { - loading: false, - signupUrl: null, - }, - throttling: DEFAULT_THROTTLING, - }, - 1641081600000, - ], - { name: 'getInvalidMonitorsCount' } - ); - }); -}); diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/hooks/use_inline_errors_count.ts b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/hooks/use_inline_errors_count.ts deleted file mode 100644 index 154d7921d3b75..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/hooks/use_inline_errors_count.ts +++ /dev/null @@ -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 { useSelector } from 'react-redux'; -import { useMemo } from 'react'; -import { useEsSearch } from '@kbn/observability-plugin/public'; -import { monitorManagementListSelector } from '../../../state/selectors'; -import { useUptimeRefreshContext } from '../../../contexts/uptime_refresh_context'; -import { getInlineErrorFilters } from './use_inline_errors'; -import { SYNTHETICS_INDEX_PATTERN } from '../../../../../common/constants'; - -export function useInlineErrorsCount() { - const monitorList = useSelector(monitorManagementListSelector); - - const { lastRefresh } = useUptimeRefreshContext(); - - const { data, loading } = useEsSearch( - { - index: SYNTHETICS_INDEX_PATTERN, - body: { - size: 0, - query: { - bool: { - filter: getInlineErrorFilters(), - }, - }, - aggs: { - total: { - cardinality: { field: 'monitor.id' }, - }, - }, - }, - }, - [monitorList, lastRefresh], - { name: 'getInvalidMonitorsCount' } - ); - - return useMemo(() => { - const errorSummariesCount = data?.aggregations?.total.value; - - return { loading, count: errorSummariesCount }; - }, [data, loading]); -} diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/hooks/use_invalid_monitors.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/hooks/use_invalid_monitors.tsx deleted file mode 100644 index ea63a19c14f6c..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/hooks/use_invalid_monitors.tsx +++ /dev/null @@ -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 moment from 'moment'; -import { useKibana } from '@kbn/kibana-react-plugin/public'; -import { useFetcher } from '@kbn/observability-plugin/public'; -import { Ping, SyntheticsMonitor } from '../../../../../common/runtime_types'; -import { syntheticsMonitorType } from '../../../../../common/types/saved_objects'; - -export const useInvalidMonitors = (errorSummaries?: Ping[]) => { - const { savedObjects } = useKibana().services; - - const ids = (errorSummaries ?? []).map((summary) => ({ - id: summary.config_id!, - type: syntheticsMonitorType, - })); - - return useFetcher(async () => { - if (ids.length > 0) { - const response = await savedObjects?.client.bulkResolve(ids); - if (response) { - const { resolved_objects: resolvedObjects } = response; - return resolvedObjects - .filter((sv) => { - if (sv.saved_object.updatedAt) { - const errorSummary = errorSummaries?.find( - (summary) => summary.config_id === sv.saved_object.id - ); - if (errorSummary) { - return moment(sv.saved_object.updatedAt).isBefore(moment(errorSummary.timestamp)); - } - } - - return !Boolean(sv.saved_object.error); - }) - .map(({ saved_object: savedObject }) => ({ - ...savedObject, - updated_at: savedObject.updatedAt!, - created_at: savedObject.createdAt!, - })); - } - } - }, [errorSummaries]); -}; diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/hooks/use_locations.test.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/hooks/use_locations.test.tsx deleted file mode 100644 index ea2d9d99d436f..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/hooks/use_locations.test.tsx +++ /dev/null @@ -1,74 +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 { renderHook } from '@testing-library/react-hooks'; -import { MockRedux } from '../../../lib/helper/rtl_helpers'; -import { useLocations } from './use_locations'; - -import * as reactRedux from 'react-redux'; -import { getServiceLocations } from '../../../state/actions'; - -import { DEFAULT_THROTTLING } from '../../../../../common/runtime_types'; - -describe('useExpViewTimeRange', function () { - const dispatch = jest.fn(); - jest.spyOn(reactRedux, 'useDispatch').mockReturnValue(dispatch); - it('updates lens attributes with report type from storage', async function () { - renderHook(() => useLocations(), { - wrapper: MockRedux, - }); - - expect(dispatch).toBeCalledWith(getServiceLocations()); - }); - - it('returns loading and error from redux store', async function () { - const throttling = DEFAULT_THROTTLING; - - const error = new Error('error'); - const loading = true; - const state = { - monitorManagementList: { - throttling, - list: { - perPage: 10, - page: 1, - total: 0, - monitors: [], - syncErrors: null, - absoluteTotal: 0, - }, - locations: [], - enablement: null, - error: { - serviceLocations: error, - monitorList: null, - enablement: null, - }, - loading: { - monitorList: false, - serviceLocations: loading, - enablement: false, - }, - syntheticsService: { - loading: false, - signupUrl: null, - }, - }, - }; - - const Wrapper = ({ children }: { children: React.ReactNode }) => { - return {children}; - }; - const { result } = renderHook(() => useLocations(), { - wrapper: Wrapper, - }); - - expect(result.current).toEqual({ loading, error, throttling, locations: [] }); - }); -}); diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/hooks/use_locations.ts b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/hooks/use_locations.ts deleted file mode 100644 index 637ba047005fd..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/hooks/use_locations.ts +++ /dev/null @@ -1,35 +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 { useEffect } from 'react'; -import { useDispatch, useSelector } from 'react-redux'; -import { getServiceLocations } from '../../../state/actions'; -import { monitorManagementListSelector } from '../../../state/selectors'; - -export function useLocations() { - const dispatch = useDispatch(); - const { - error: { serviceLocations: serviceLocationsError }, - loading: { serviceLocations: serviceLocationsLoading }, - locations, - throttling, - locationsLoaded, - } = useSelector(monitorManagementListSelector); - - useEffect(() => { - if (!locationsLoaded) { - dispatch(getServiceLocations()); - } - }, [dispatch, locations, locationsLoaded]); - - return { - throttling, - locations, - error: serviceLocationsError, - loading: serviceLocationsLoading, - }; -} diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/hooks/use_monitor_list.ts b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/hooks/use_monitor_list.ts deleted file mode 100644 index cf6361bee0e37..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/hooks/use_monitor_list.ts +++ /dev/null @@ -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 { useEffect, useReducer, Reducer } from 'react'; -import { useDispatch } from 'react-redux'; -import { useParams } from 'react-router-dom'; -import { getMonitors } from '../../../state/actions'; -import { ConfigKey } from '../../../../../common/constants/monitor_management'; -import { MonitorManagementListPageState } from '../monitor_list/monitor_list'; - -export function useMonitorList() { - const dispatch = useDispatch(); - - const [pageState, dispatchPageAction] = useReducer( - monitorManagementPageReducer, - { - pageIndex: 1, // saved objects page index is base 1 - pageSize: 10, - sortOrder: 'asc', - sortField: `${ConfigKey.NAME}.keyword`, - } - ); - - const { pageIndex, pageSize, sortField, sortOrder } = pageState as MonitorManagementListPageState; - - const { type: viewType = 'all' } = useParams<{ type: 'all' | 'invalid' }>(); - - useEffect(() => { - if (viewType === 'all') { - dispatch(getMonitors({ page: pageIndex, perPage: pageSize, sortOrder, sortField })); - } - }, [dispatch, pageIndex, pageSize, sortField, sortOrder, viewType, pageState]); - - return { - pageState, - dispatchPageAction, - viewType, - }; -} - -export type MonitorManagementPageAction = - | { - type: 'update'; - payload: MonitorManagementListPageState; - } - | { type: 'refresh' }; - -const monitorManagementPageReducer: Reducer< - MonitorManagementListPageState, - MonitorManagementPageAction -> = (state: MonitorManagementListPageState, action: MonitorManagementPageAction) => { - switch (action.type) { - case 'update': - return { - ...state, - ...action.payload, - }; - case 'refresh': - return { ...state }; - default: - throw new Error(`Action "${(action as MonitorManagementPageAction)?.type}" not recognizable`); - } -}; diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/hooks/use_private_location_permission.ts b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/hooks/use_private_location_permission.ts deleted file mode 100644 index a4e25b650b049..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/hooks/use_private_location_permission.ts +++ /dev/null @@ -1,25 +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 { useKibana } from '@kbn/kibana-react-plugin/public'; -import { ClientPluginsStart } from '../../../../plugin'; -import { BrowserFields, ConfigKey } from '../../../../../common/runtime_types'; - -export function usePrivateLocationPermissions(monitor?: BrowserFields) { - const { fleet } = useKibana().services; - - const canSaveIntegrations: boolean = Boolean(fleet?.authz.integrations.writeIntegrationPolicies); - const canReadAgentPolicies = Boolean(fleet?.authz.fleet.readAgentPolicies); - - const locations = (monitor as BrowserFields)?.[ConfigKey.LOCATIONS]; - - const hasPrivateLocation = locations?.some((location) => !location.isServiceManaged); - - const canUpdatePrivateMonitor = !(hasPrivateLocation && !canSaveIntegrations); - - return { canUpdatePrivateMonitor, canReadAgentPolicies }; -} diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/hooks/use_run_once_errors.ts b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/hooks/use_run_once_errors.ts deleted file mode 100644 index 098154f4e20ce..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/hooks/use_run_once_errors.ts +++ /dev/null @@ -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 { useEffect, useMemo, useState } from 'react'; -import { i18n } from '@kbn/i18n'; -import { Locations, ServiceLocationErrors } from '../../../../../common/runtime_types'; - -export function useRunOnceErrors({ - testRunId, - serviceError, - errors, - locations, -}: { - testRunId: string; - serviceError: null | Error; - errors: ServiceLocationErrors; - locations: Locations; -}) { - const [locationErrors, setLocationErrors] = useState([]); - const [runOnceServiceError, setRunOnceServiceError] = useState(null); - const publicLocations = useMemo( - () => (locations ?? []).filter((loc) => loc.isServiceManaged), - [locations] - ); - - useEffect(() => { - setLocationErrors([]); - setRunOnceServiceError(null); - }, [testRunId]); - - useEffect(() => { - if (locationErrors.length || errors.length) { - setLocationErrors(errors); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [errors]); - - useEffect(() => { - if (runOnceServiceError?.message !== serviceError?.message) { - setRunOnceServiceError(serviceError); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [serviceError]); - - const locationsById: Record = useMemo( - () => (publicLocations as Locations).reduce((acc, cur) => ({ ...acc, [cur.id]: cur }), {}), - [publicLocations] - ); - - const expectPings = - publicLocations.length - (locationErrors ?? []).filter(({ locationId }) => !!locationId).length; - - const hasBlockingError = - !!runOnceServiceError || - (locationErrors?.length && locationErrors?.length === publicLocations.length); - - const errorMessages = useMemo(() => { - if (hasBlockingError) { - return [{ name: 'Error', message: PushErrorService, title: PushErrorLabel }]; - } else if (locationErrors?.length > 0) { - // If only some of the locations were unsuccessful - return locationErrors - .map(({ locationId }) => locationsById[locationId]) - .filter((location) => !!location) - .map((location) => ({ - name: 'Error', - message: getLocationTestErrorLabel(location.label), - title: RunErrorLabel, - })); - } - - return []; - }, [locationsById, locationErrors, hasBlockingError]); - - return { - expectPings, - hasBlockingError, - blockingErrorMessage: hasBlockingError ? PushErrorService : null, - errorMessages, - }; -} - -const PushErrorLabel = i18n.translate('xpack.synthetics.testRun.pushErrorLabel', { - defaultMessage: 'Push error', -}); - -const RunErrorLabel = i18n.translate('xpack.synthetics.testRun.runErrorLabel', { - defaultMessage: 'Error running test', -}); - -const getLocationTestErrorLabel = (locationName: string) => - i18n.translate('xpack.synthetics.testRun.runErrorLocation', { - defaultMessage: 'Failed to run monitor on location {locationName}.', - values: { locationName }, - }); - -const PushErrorService = i18n.translate('xpack.synthetics.testRun.pushError', { - defaultMessage: 'Failed to push the monitor to service.', -}); diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/hooks/use_service_allowed.ts b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/hooks/use_service_allowed.ts deleted file mode 100644 index 2fd85f2ca0a39..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/hooks/use_service_allowed.ts +++ /dev/null @@ -1,21 +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 { useDispatch, useSelector } from 'react-redux'; -import { useEffect } from 'react'; -import { syntheticsServiceAllowedSelector } from '../../../state/selectors'; -import { getSyntheticsServiceAllowed } from '../../../state/actions'; - -export const useSyntheticsServiceAllowed = () => { - const dispatch = useDispatch(); - - useEffect(() => { - dispatch(getSyntheticsServiceAllowed.get()); - }, [dispatch]); - - return useSelector(syntheticsServiceAllowedSelector); -}; diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/loader/loader.test.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/loader/loader.test.tsx deleted file mode 100644 index 15a64973b2811..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/loader/loader.test.tsx +++ /dev/null @@ -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 React from 'react'; -import { screen } from '@testing-library/react'; -import { render } from '../../../lib/helper/rtl_helpers'; -import { Loader } from './loader'; - -describe('', () => { - beforeEach(() => { - jest.clearAllMocks(); - }); - - it('shows children when loading and error are both false', () => { - render( - - {'children'} - - ); - - expect(screen.getByText('children')).toBeInTheDocument(); - }); - - it('shows loading when loading is true', () => { - render( - - {'children'} - - ); - - expect(screen.getByText('loading')).toBeInTheDocument(); - }); - - it('shows error content when error is true ', () => { - render( - - {'children'} - - ); - - expect(screen.getByText('A problem occurred')).toBeInTheDocument(); - expect(screen.getByText('Please try again')).toBeInTheDocument(); - }); -}); diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/loader/loader.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/loader/loader.tsx deleted file mode 100644 index b40fb64a39576..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/loader/loader.tsx +++ /dev/null @@ -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 { EuiEmptyPrompt, EuiLoadingLogo, EuiSpacer } from '@elastic/eui'; - -interface Props { - loading: boolean; - loadingTitle: React.ReactNode; - error: boolean; - errorTitle?: React.ReactNode; - errorBody?: React.ReactNode; - children: React.ReactNode; -} - -export const Loader = ({ - loading, - loadingTitle, - error, - errorTitle, - errorBody, - children, -}: Props) => { - return ( - <> - {!loading && !error ? children : null} - {error && !loading ? ( - <> - - {errorTitle}} - body={

{errorBody}

} - /> - - ) : null} - {loading ? ( - } - title={

{loadingTitle}

} - data-test-subj="uptimeLoader" - /> - ) : null} - - ); -}; diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/manage_locations/add_location_flyout.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/manage_locations/add_location_flyout.tsx deleted file mode 100644 index b9684ac4dd91c..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/manage_locations/add_location_flyout.tsx +++ /dev/null @@ -1,122 +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 { FormProvider } from 'react-hook-form'; -import { - EuiButtonEmpty, - EuiCallOut, - EuiFlexGroup, - EuiFlexItem, - EuiFlyoutBody, - EuiFlyoutFooter, - EuiFlyoutHeader, - EuiTitle, - EuiFlyout, - EuiButton, -} from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; -import { PrivateLocation } from '../../../../../common/runtime_types'; -import { - NEED_FLEET_READ_AGENT_POLICIES_PERMISSION, - NEED_PERMISSIONS, -} from './manage_locations_flyout'; -import { usePrivateLocationPermissions } from '../hooks/use_private_location_permission'; -import { LocationForm } from './location_form'; -import { useFormWrapped } from '../../../../hooks/use_form_wrapped'; - -export const AddLocationFlyout = ({ - onSubmit, - setIsOpen, - privateLocations, -}: { - onSubmit: (val: PrivateLocation) => void; - setIsOpen: (val: boolean) => void; - privateLocations: PrivateLocation[]; -}) => { - const form = useFormWrapped({ - mode: 'onSubmit', - reValidateMode: 'onChange', - shouldFocusError: true, - defaultValues: { - label: '', - agentPolicyId: '', - id: '', - geo: { - lat: 0, - lon: 0, - }, - concurrentMonitors: 1, - }, - }); - - const { handleSubmit } = form; - - const { canReadAgentPolicies } = usePrivateLocationPermissions(); - - const closeFlyout = () => { - setIsOpen(false); - }; - - return ( - - - - -

{ADD_PRIVATE_LOCATION}

-
-
- - {!canReadAgentPolicies && ( - -

{NEED_FLEET_READ_AGENT_POLICIES_PERMISSION}

-
- )} - -
- - - - - {CANCEL_LABEL} - - - - - {SAVE_LABEL} - - - - -
-
- ); -}; - -const ADD_PRIVATE_LOCATION = i18n.translate( - 'xpack.synthetics.monitorManagement.addPrivateLocations', - { - defaultMessage: 'Add private location', - } -); - -const CANCEL_LABEL = i18n.translate('xpack.synthetics.monitorManagement.cancelLabel', { - defaultMessage: 'Cancel', -}); - -const SAVE_LABEL = i18n.translate('xpack.synthetics.monitorManagement.saveLabel', { - defaultMessage: 'Save', -}); diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/manage_locations/agent_policy_needed.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/manage_locations/agent_policy_needed.tsx deleted file mode 100644 index 4ed62a4d417b2..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/manage_locations/agent_policy_needed.tsx +++ /dev/null @@ -1,63 +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 { EuiLink, EuiButton, EuiEmptyPrompt, EuiTitle } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; -import { LEARN_MORE, READ_DOCS } from './empty_locations'; -import { useUptimeSettingsContext } from '../../../contexts/uptime_settings_context'; - -export const AgentPolicyNeeded = () => { - const { basePath } = useUptimeSettingsContext(); - - return ( - {AGENT_POLICY_NEEDED}} - body={

{ADD_AGENT_POLICY_DESCRIPTION}

} - actions={ - - {CREATE_AGENT_POLICY} - - } - footer={ - <> - -

{LEARN_MORE}

-
- - {READ_DOCS} - - - } - /> - ); -}; - -const CREATE_AGENT_POLICY = i18n.translate('xpack.synthetics.monitorManagement.createAgentPolicy', { - defaultMessage: 'Create agent policy', -}); - -const AGENT_POLICY_NEEDED = i18n.translate('xpack.synthetics.monitorManagement.agentPolicyNeeded', { - defaultMessage: 'No agent policies found', -}); -const ADD_AGENT_POLICY_DESCRIPTION = i18n.translate( - 'xpack.synthetics.monitorManagement.addAgentPolicyDesc', - { - defaultMessage: - 'Private locations require an Agent policy. In order to add a private location, first you must create an Agent policy in Fleet.', - } -); diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/manage_locations/delete_location.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/manage_locations/delete_location.tsx deleted file mode 100644 index 5179858413ac7..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/manage_locations/delete_location.tsx +++ /dev/null @@ -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 } from 'react'; -import { EuiButtonIcon, EuiConfirmModal, EuiToolTip } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; -import { useKibana } from '@kbn/kibana-react-plugin/public'; - -export const DeleteLocation = ({ - loading, - id, - locationMonitors, - onDelete, -}: { - id: string; - loading?: boolean; - onDelete: (id: string) => void; - locationMonitors: Array<{ id: string; count: number }>; -}) => { - const monCount = locationMonitors?.find((l) => l.id === id)?.count ?? 0; - const canDelete = monCount === 0; - - const canSave: boolean = !!useKibana().services?.application?.capabilities.uptime.save; - - const [isModalOpen, setIsModalOpen] = useState(false); - - const deleteModal = ( - setIsModalOpen(false)} - onConfirm={() => onDelete(id)} - cancelButtonText={CANCEL_LABEL} - confirmButtonText={CONFIRM_LABEL} - buttonColor="danger" - defaultFocusedButton="confirm" - > -

{ARE_YOU_SURE_LABEL}

-
- ); - - return ( - <> - {isModalOpen && deleteModal} - - { - setIsModalOpen(true); - }} - isDisabled={!canDelete || !canSave} - /> - - - ); -}; - -const DELETE_LABEL = i18n.translate('xpack.synthetics.monitorManagement.delete', { - defaultMessage: 'Delete location', -}); - -const CONFIRM_LABEL = i18n.translate('xpack.synthetics.monitorManagement.deleteLocationLabel', { - defaultMessage: 'Delete location', -}); - -const CANCEL_LABEL = i18n.translate('xpack.synthetics.monitorManagement.cancelLabel', { - defaultMessage: 'Cancel', -}); - -const ARE_YOU_SURE_LABEL = i18n.translate('xpack.synthetics.monitorManagement.areYouSure', { - defaultMessage: 'Are you sure you want to delete this location?', -}); diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/manage_locations/empty_locations.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/manage_locations/empty_locations.tsx deleted file mode 100644 index b12b85fc1471e..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/manage_locations/empty_locations.tsx +++ /dev/null @@ -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 from 'react'; -import { EuiEmptyPrompt, EuiButton, EuiLink, EuiText } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; -import { useDispatch } from 'react-redux'; -import { setAddingNewPrivateLocation, setManageFlyoutOpen } from '../../../state/private_locations'; - -export const EmptyLocations = ({ - inFlyout = true, - setIsAddingNew, - disabled, -}: { - inFlyout?: boolean; - disabled?: boolean; - setIsAddingNew?: (val: boolean) => void; -}) => { - const dispatch = useDispatch(); - - return ( - {ADD_FIRST_LOCATION}} - titleSize="s" - body={ - - {!inFlyout ? FIRST_MONITOR : ''} {START_ADDING_LOCATIONS_DESCRIPTION} - - } - actions={ - { - setIsAddingNew?.(true); - dispatch(setManageFlyoutOpen(true)); - dispatch(setAddingNewPrivateLocation(true)); - }} - > - {ADD_LOCATION} - - } - footer={ - - {LEARN_MORE} - - } - /> - ); -}; - -export const PrivateLocationDocsLink = ({ label }: { label?: string }) => ( - - {label ?? READ_DOCS} - -); - -const FIRST_MONITOR = i18n.translate('xpack.synthetics.monitorManagement.firstLocationMonitor', { - defaultMessage: 'In order to create a monitor, you will need to add a location first.', -}); - -const ADD_FIRST_LOCATION = i18n.translate('xpack.synthetics.monitorManagement.firstLocation', { - defaultMessage: 'Add your first private location', -}); - -const START_ADDING_LOCATIONS_DESCRIPTION = i18n.translate( - 'xpack.synthetics.monitorManagement.startAddingLocationsDescription', - { - defaultMessage: - 'Private locations allow you to run monitors from your own premises. They require an Elastic agent and Agent policy which you can control and maintain via Fleet.', - } -); - -const ADD_LOCATION = i18n.translate('xpack.synthetics.monitorManagement.addLocation', { - defaultMessage: 'Add location', -}); - -export const READ_DOCS = i18n.translate('xpack.synthetics.monitorManagement.readDocs', { - defaultMessage: 'read the docs', -}); - -export const LEARN_MORE = i18n.translate('xpack.synthetics.monitorManagement.learnMore', { - defaultMessage: 'For more information,', -}); diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/manage_locations/hooks/use_location_monitors.test.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/manage_locations/hooks/use_location_monitors.test.tsx deleted file mode 100644 index 137c8aa37ac60..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/manage_locations/hooks/use_location_monitors.test.tsx +++ /dev/null @@ -1,65 +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 { renderHook } from '@testing-library/react-hooks'; - -import { useLocationMonitors } from './use_location_monitors'; -import { defaultCore, WrappedHelper } from '../../../../../apps/synthetics/utils/testing'; - -describe('useLocationMonitors', () => { - it('returns expected results', async () => { - const { result, waitFor } = renderHook(() => useLocationMonitors(), { wrapper: WrappedHelper }); - - await waitFor(() => result.current.loading === false); - - expect(result.current).toStrictEqual({ locationMonitors: [], loading: false }); - expect(defaultCore.savedObjects.client.find).toHaveBeenCalledWith({ - aggs: { - locations: { - terms: { field: 'synthetics-monitor.attributes.locations.id', size: 10000 }, - }, - }, - perPage: 0, - type: 'synthetics-monitor', - }); - }); - - it('returns expected results after data', async () => { - defaultCore.savedObjects.client.find = jest.fn().mockReturnValue({ - aggregations: { - locations: { - buckets: [ - { key: 'Test', doc_count: 5 }, - { key: 'Test 1', doc_count: 0 }, - ], - }, - }, - }); - - const { result, waitForNextUpdate } = renderHook(() => useLocationMonitors(), { - wrapper: WrappedHelper, - }); - - expect(result.current).toStrictEqual({ locationMonitors: [], loading: true }); - - await waitForNextUpdate(); - - expect(result.current).toStrictEqual({ - loading: false, - locationMonitors: [ - { - id: 'Test', - count: 5, - }, - { - id: 'Test 1', - count: 0, - }, - ], - }); - }); -}); diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/manage_locations/hooks/use_location_monitors.ts b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/manage_locations/hooks/use_location_monitors.ts deleted file mode 100644 index a05756eddffdb..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/manage_locations/hooks/use_location_monitors.ts +++ /dev/null @@ -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 { useMemo } from 'react'; -import { useFetcher } from '@kbn/observability-plugin/public'; -import { useKibana } from '@kbn/kibana-react-plugin/public'; -import { - monitorAttributes, - syntheticsMonitorType, -} from '../../../../../../common/types/saved_objects'; - -interface AggsResponse { - locations: { - buckets: Array<{ - key: string; - doc_count: number; - }>; - }; -} - -export const useLocationMonitors = () => { - const { savedObjects } = useKibana().services; - - const { data, loading } = useFetcher(() => { - const aggs = { - locations: { - terms: { - field: `${monitorAttributes}.locations.id`, - size: 10000, - }, - }, - }; - return savedObjects?.client.find({ - type: syntheticsMonitorType, - perPage: 0, - aggs, - }); - }, []); - - return useMemo(() => { - if (data?.aggregations) { - const newValues = (data.aggregations as AggsResponse)?.locations.buckets.map( - ({ key, doc_count: count }) => ({ id: key, count }) - ); - - return { locationMonitors: newValues, loading }; - } - return { locationMonitors: [], loading }; - }, [data, loading]); -}; diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/manage_locations/hooks/use_locations_api.test.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/manage_locations/hooks/use_locations_api.test.tsx deleted file mode 100644 index 91ed400d3bf80..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/manage_locations/hooks/use_locations_api.test.tsx +++ /dev/null @@ -1,156 +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 { renderHook } from '@testing-library/react-hooks'; - -import { defaultCore, WrappedHelper } from '../../../../../apps/synthetics/utils/testing'; -import { usePrivateLocationsAPI } from './use_locations_api'; - -describe('usePrivateLocationsAPI', () => { - it('returns expected results', () => { - const { result } = renderHook(() => usePrivateLocationsAPI({ isOpen: false }), { - wrapper: WrappedHelper, - }); - - expect(result.current).toEqual( - expect.objectContaining({ - loading: true, - privateLocations: [], - }) - ); - expect(defaultCore.savedObjects.client.get).toHaveBeenCalledWith( - 'synthetics-privates-locations', - 'synthetics-privates-locations-singleton' - ); - }); - defaultCore.savedObjects.client.get = jest.fn().mockReturnValue({ - attributes: { - locations: [ - { - id: 'Test', - agentPolicyId: 'testPolicy', - }, - ], - }, - }); - it('returns expected results after data', async () => { - const { result, waitForNextUpdate } = renderHook( - () => usePrivateLocationsAPI({ isOpen: true }), - { - wrapper: WrappedHelper, - } - ); - - expect(result.current).toEqual( - expect.objectContaining({ - loading: true, - privateLocations: [], - }) - ); - - await waitForNextUpdate(); - - expect(result.current).toEqual( - expect.objectContaining({ - loading: false, - privateLocations: [ - { - id: 'Test', - agentPolicyId: 'testPolicy', - }, - ], - }) - ); - }); - - it('adds location on submit', async () => { - const { result, waitForNextUpdate } = renderHook( - () => usePrivateLocationsAPI({ isOpen: true }), - { - wrapper: WrappedHelper, - } - ); - - await waitForNextUpdate(); - - result.current.onSubmit({ - id: 'new', - agentPolicyId: 'newPolicy', - label: 'new', - concurrentMonitors: 1, - geo: { - lat: 0, - lon: 0, - }, - }); - - await waitForNextUpdate(); - - expect(defaultCore.savedObjects.client.create).toHaveBeenCalledWith( - 'synthetics-privates-locations', - { - locations: [ - { id: 'Test', agentPolicyId: 'testPolicy' }, - { - concurrentMonitors: 1, - id: 'newPolicy', - geo: { - lat: 0, - lon: 0, - }, - label: 'new', - agentPolicyId: 'newPolicy', - }, - ], - }, - { id: 'synthetics-privates-locations-singleton', overwrite: true } - ); - }); - - it('deletes location on delete', async () => { - defaultCore.savedObjects.client.get = jest.fn().mockReturnValue({ - attributes: { - locations: [ - { - id: 'Test', - agentPolicyId: 'testPolicy', - }, - { - id: 'Test1', - agentPolicyId: 'testPolicy1', - }, - ], - }, - }); - - const { result, waitForNextUpdate } = renderHook( - () => usePrivateLocationsAPI({ isOpen: true }), - { - wrapper: WrappedHelper, - } - ); - - await waitForNextUpdate(); - - result.current.onDelete('Test'); - - await waitForNextUpdate(); - - expect(defaultCore.savedObjects.client.create).toHaveBeenLastCalledWith( - 'synthetics-privates-locations', - { - locations: [ - { - id: 'Test1', - agentPolicyId: 'testPolicy1', - }, - ], - }, - { id: 'synthetics-privates-locations-singleton', overwrite: true } - ); - }); -}); diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/manage_locations/hooks/use_locations_api.ts b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/manage_locations/hooks/use_locations_api.ts deleted file mode 100644 index caa603f5db6b5..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/manage_locations/hooks/use_locations_api.ts +++ /dev/null @@ -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 { useFetcher } from '@kbn/observability-plugin/public'; -import { useState } from 'react'; -import { useKibana } from '@kbn/kibana-react-plugin/public'; -import { PrivateLocation } from '../../../../../../common/runtime_types'; -import { - setSyntheticsPrivateLocations, - getSyntheticsPrivateLocations, -} from '../../../../state/private_locations/api'; - -export const usePrivateLocationsAPI = ({ isOpen }: { isOpen: boolean }) => { - const [formData, setFormData] = useState(); - const [deleteId, setDeleteId] = useState(); - const [privateLocations, setPrivateLocations] = useState([]); - - const { savedObjects } = useKibana().services; - - const { loading: fetchLoading } = useFetcher(async () => { - const result = await getSyntheticsPrivateLocations(savedObjects?.client!); - setPrivateLocations(result); - return result; - }, [isOpen]); - - const { loading: saveLoading } = useFetcher(async () => { - if (privateLocations && formData) { - const existingLocations = privateLocations.filter((loc) => loc.id !== formData.agentPolicyId); - - const result = await setSyntheticsPrivateLocations(savedObjects?.client!, { - locations: [...(existingLocations ?? []), { ...formData, id: formData.agentPolicyId }], - }); - setPrivateLocations(result.locations); - setFormData(undefined); - return result; - } - }, [formData, privateLocations]); - - const onSubmit = (data: PrivateLocation) => { - setFormData(data); - }; - - const onDelete = (id: string) => { - setDeleteId(id); - }; - - const { loading: deleteLoading } = useFetcher(async () => { - if (deleteId) { - const result = await setSyntheticsPrivateLocations(savedObjects?.client!, { - locations: (privateLocations ?? []).filter((loc) => loc.id !== deleteId), - }); - setPrivateLocations(result.locations); - setDeleteId(undefined); - return result; - } - }, [deleteId, privateLocations]); - - return { - onSubmit, - onDelete, - loading: fetchLoading || saveLoading || deleteLoading, - privateLocations, - }; -}; diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/manage_locations/location_form.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/manage_locations/location_form.tsx deleted file mode 100644 index a97c408e28f8b..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/manage_locations/location_form.tsx +++ /dev/null @@ -1,117 +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 { - EuiFieldText, - EuiForm, - EuiFormRow, - EuiSpacer, - EuiCallOut, - EuiCode, - EuiLink, -} from '@elastic/eui'; -import { useSelector } from 'react-redux'; -import { i18n } from '@kbn/i18n'; -import { useFormContext, useFormState } from 'react-hook-form'; -import { AgentPolicyNeeded } from './agent_policy_needed'; -import { PrivateLocation } from '../../../../../common/runtime_types'; -import { PolicyHostsField } from './policy_hosts'; -import { selectAgentPolicies } from '../../../state/private_locations'; - -export const LocationForm = ({ - privateLocations, -}: { - onDiscard?: () => void; - privateLocations: PrivateLocation[]; -}) => { - const { data } = useSelector(selectAgentPolicies); - const { control, register } = useFormContext(); - const { errors } = useFormState(); - - return ( - <> - {data?.items.length === 0 && } - - - { - return privateLocations.some((loc) => loc.label === val) - ? NAME_ALREADY_EXISTS - : undefined; - }, - })} - /> - - - - - -

- { - elastic-agent-complete, - link: ( - - - - ), - }} - /> - } -

-
-
- - ); -}; - -export const AGENT_CALLOUT_TITLE = i18n.translate( - 'xpack.synthetics.monitorManagement.agentCallout.title', - { - defaultMessage: 'Requirement', - } -); - -export const LOCATION_NAME_LABEL = i18n.translate( - 'xpack.synthetics.monitorManagement.locationName', - { - defaultMessage: 'Location name', - } -); - -const NAME_ALREADY_EXISTS = i18n.translate('xpack.synthetics.monitorManagement.alreadyExists', { - defaultMessage: 'Location name already exists.', -}); - -const NAME_REQUIRED = i18n.translate('xpack.synthetics.monitorManagement.nameRequired', { - defaultMessage: 'Location name is required', -}); diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/manage_locations/locations_list.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/manage_locations/locations_list.tsx deleted file mode 100644 index 7f03643fecbb2..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/manage_locations/locations_list.tsx +++ /dev/null @@ -1,30 +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 { i18n } from '@kbn/i18n'; -import { PrivateLocationsTable } from './locations_table'; -import { PrivateLocation } from '../../../../../common/runtime_types'; - -export const PrivateLocationsList = ({ - privateLocations, - onDelete, -}: { - privateLocations: PrivateLocation[]; - onDelete: (id: string) => void; -}) => { - return ; -}; - -export const MONITORS = i18n.translate('xpack.synthetics.monitorManagement.monitors', { - defaultMessage: 'Monitors', -}); - -export const AGENT_POLICY_LABEL = i18n.translate('xpack.synthetics.monitorManagement.agentPolicy', { - defaultMessage: 'Agent Policy', -}); diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/manage_locations/locations_table.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/manage_locations/locations_table.tsx deleted file mode 100644 index 426c962e81498..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/manage_locations/locations_table.tsx +++ /dev/null @@ -1,100 +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 } from 'react'; -import { EuiBasicTable, EuiSpacer, EuiHorizontalRule } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; -import { DeleteLocation } from './delete_location'; -import { useLocationMonitors } from './hooks/use_location_monitors'; -import { PolicyName } from './policy_name'; -import { PrivateLocation } from '../../../../../common/runtime_types'; -import { LOCATION_NAME_LABEL } from './location_form'; -import { AGENT_POLICY_LABEL, MONITORS } from './locations_list'; - -export const PrivateLocationsTable = ({ - onDelete, - privateLocations, -}: { - onDelete: (id: string) => void; - privateLocations: PrivateLocation[]; -}) => { - const { locationMonitors, loading } = useLocationMonitors(); - - const [pageIndex, setPageIndex] = useState(0); - const [pageSize, setPageSize] = useState(10); - - const columns = [ - { - field: 'label', - name: LOCATION_NAME_LABEL, - }, - { - field: 'monitors', - name: MONITORS, - align: 'right' as const, - }, - { - field: 'agentPolicyId', - name: AGENT_POLICY_LABEL, - render: (agentPolicyId: string) => , - }, - - { - field: 'id', - name: ACTIONS_LABEL, - align: 'right' as const, - render: (id: string) => ( - - ), - }, - ]; - - const pagination = { - pageIndex, - pageSize, - totalItemCount: privateLocations.length, - pageSizeOptions: [5, 10, 0], - showPerPageOptions: true, - }; - - const items = privateLocations.map((location) => ({ - ...location, - monitors: locationMonitors?.find((l) => l.id === location.id)?.count ?? 0, - })); - - return ( -
- - - - tableLayout="auto" - tableCaption={PRIVATE_LOCATIONS} - items={items} - columns={columns} - pagination={pagination} - onChange={({ page = {} }) => { - const { index, size } = page; - setPageIndex(index ?? 0); - setPageSize(size ?? 5); - }} - /> -
- ); -}; - -const PRIVATE_LOCATIONS = i18n.translate('xpack.synthetics.monitorManagement.privateLocations', { - defaultMessage: 'Private locations', -}); - -const ACTIONS_LABEL = i18n.translate('xpack.synthetics.monitorManagement.actions', { - defaultMessage: 'Actions', -}); diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/manage_locations/manage_empty_state.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/manage_locations/manage_empty_state.tsx deleted file mode 100644 index 84a04c1bae03c..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/manage_locations/manage_empty_state.tsx +++ /dev/null @@ -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 React, { FC } from 'react'; -import { useSelector } from 'react-redux'; -import { AgentPolicyNeeded } from './agent_policy_needed'; -import { PrivateLocation } from '../../../../../common/runtime_types'; -import { EmptyLocations } from './empty_locations'; -import { selectAgentPolicies } from '../../../state/private_locations'; - -export const ManageEmptyState: FC<{ - privateLocations: PrivateLocation[]; - hasFleetPermissions: boolean; - setIsAddingNew: (val: boolean) => void; -}> = ({ children, privateLocations, setIsAddingNew, hasFleetPermissions }) => { - const { data: agentPolicies } = useSelector(selectAgentPolicies); - - if (agentPolicies?.total === 0) { - return ; - } - - if (privateLocations.length === 0) { - return ; - } - - return <>{children}; -}; diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/manage_locations/manage_locations.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/manage_locations/manage_locations.tsx deleted file mode 100644 index 39bfa74fccc0a..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/manage_locations/manage_locations.tsx +++ /dev/null @@ -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 React from 'react'; -import { InPortal } from 'react-reverse-portal'; -import { ManageLocationsFlyout } from './manage_locations_flyout'; -import { ManageLocationsPortalNode } from '../../../pages/monitor_management/portals'; - -export const ManageLocationsPortal = () => { - return ( - - - - ); -}; diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/manage_locations/manage_locations_flyout.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/manage_locations/manage_locations_flyout.tsx deleted file mode 100644 index 27da08121a1c9..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/manage_locations/manage_locations_flyout.tsx +++ /dev/null @@ -1,182 +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 } from 'react'; -import { - EuiFlyout, - EuiButtonEmpty, - EuiFlyoutHeader, - EuiTitle, - EuiFlyoutBody, - EuiFlyoutFooter, - EuiFlexGroup, - EuiFlexItem, - EuiButton, - EuiCallOut, - EuiLoadingSpinner, -} from '@elastic/eui'; -import { useDispatch, useSelector } from 'react-redux'; -import { i18n } from '@kbn/i18n'; -import { useKibana } from '@kbn/kibana-react-plugin/public'; -import { ManageEmptyState } from './manage_empty_state'; -import { useEnablement } from '../hooks/use_enablement'; -import { AddLocationFlyout } from './add_location_flyout'; -import { ClientPluginsStart } from '../../../../plugin'; -import { getServiceLocations } from '../../../state/actions'; -import { PrivateLocationsList } from './locations_list'; -import { usePrivateLocationsAPI } from './hooks/use_locations_api'; -import { - getAgentPoliciesAction, - selectAddingNewPrivateLocation, - selectManageFlyoutOpen, - setAddingNewPrivateLocation, - setManageFlyoutOpen, -} from '../../../state/private_locations'; -import { PrivateLocation } from '../../../../../common/runtime_types'; - -export const ManageLocationsFlyout = () => { - const dispatch = useDispatch(); - - const { isEnabled } = useEnablement().enablement; - - const setIsOpen = (val: boolean) => dispatch(setManageFlyoutOpen(val)); - - const isOpen = useSelector(selectManageFlyoutOpen); - const isAddingNew = useSelector(selectAddingNewPrivateLocation); - - const setIsAddingNew = (val: boolean) => dispatch(setAddingNewPrivateLocation(val)); - - const { onSubmit, loading, privateLocations, onDelete } = usePrivateLocationsAPI({ - isOpen, - }); - - const { fleet } = useKibana().services; - - const hasFleetPermissions = Boolean(fleet?.authz.fleet.readAgentPolicies); - - const canSave: boolean = !!useKibana().services?.application?.capabilities.uptime.save; - - useEffect(() => { - if (isOpen) { - dispatch(getAgentPoliciesAction.get()); - } - }, [dispatch, isOpen]); - - const closeFlyout = () => { - setIsOpen(false); - dispatch(getServiceLocations()); - }; - - const handleSubmit = (formData: PrivateLocation) => { - onSubmit(formData); - setIsAddingNew(false); - }; - - const flyout = ( - - - -

{PRIVATE_LOCATIONS}

-
-
- - {loading ? ( - - ) : ( - - - - )} - {!hasFleetPermissions && ( - -

{NEED_FLEET_READ_AGENT_POLICIES_PERMISSION}

-
- )} -
- - - - - {CLOSE_LABEL} - - - - - setIsAddingNew(true)} - > - {ADD_LABEL} - - - - -
- ); - - return ( -
- {isEnabled && ( - setIsOpen(true)} - > - {PRIVATE_LOCATIONS} - - )} - {isOpen && !isAddingNew ? flyout : null} - {isAddingNew ? ( - - ) : null} -
- ); -}; - -const PRIVATE_LOCATIONS = i18n.translate( - 'xpack.synthetics.monitorManagement.managePrivateLocations', - { - defaultMessage: 'Private locations', - } -); - -const CLOSE_LABEL = i18n.translate('xpack.synthetics.monitorManagement.closeLabel', { - defaultMessage: 'Close', -}); - -const ADD_LABEL = i18n.translate('xpack.synthetics.monitorManagement.addLocation', { - defaultMessage: 'Add location', -}); - -export const NEED_PERMISSIONS = i18n.translate( - 'xpack.synthetics.monitorManagement.needPermissions', - { - defaultMessage: 'Need permissions', - } -); - -export const NEED_FLEET_READ_AGENT_POLICIES_PERMISSION = i18n.translate( - 'xpack.synthetics.monitorManagement.needFleetReadAgentPoliciesPermissionUptime', - { - defaultMessage: - 'You are not authorized to access Fleet. Fleet permissions are required to create new private locations.', - } -); diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/manage_locations/policy_hosts.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/manage_locations/policy_hosts.tsx deleted file mode 100644 index 7a1d59e611e6b..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/manage_locations/policy_hosts.tsx +++ /dev/null @@ -1,138 +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 { Controller, FieldErrors, Control } from 'react-hook-form'; -import { useSelector } from 'react-redux'; - -import { - EuiFlexGroup, - EuiFlexItem, - EuiFormRow, - EuiHealth, - EuiSuperSelect, - EuiText, - EuiToolTip, -} from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; - -import { PrivateLocation } from '../../../../../common/runtime_types'; -import { selectAgentPolicies } from '../../../state/private_locations'; - -export const PolicyHostsField = ({ - errors, - control, - privateLocations, -}: { - errors: FieldErrors; - control: Control; - privateLocations: PrivateLocation[]; -}) => { - const { data } = useSelector(selectAgentPolicies); - - const policyHostsOptions = data?.items.map((item) => { - const hasLocation = privateLocations.find((location) => location.agentPolicyId === item.id); - return { - disabled: Boolean(hasLocation), - value: item.id, - inputDisplay: ( - - {item.name} - - ), - 'data-test-subj': item.name, - dropdownDisplay: ( - - <> - - {item.name} - - - - -

- {AGENTS_LABEL} {item.agents} -

-
-
- - -

{item.description}

-
-
-
- -
- ), - }; - }); - - return ( - - ( - - )} - /> - - ); -}; - -const AGENTS_LABEL = i18n.translate('xpack.synthetics.monitorManagement.agentsLabel', { - defaultMessage: 'Agents: ', -}); - -const SELECT_POLICY_HOSTS = i18n.translate('xpack.synthetics.monitorManagement.selectPolicyHost', { - defaultMessage: 'Select agent policy', -}); - -const SELECT_POLICY_HOSTS_HELP_TEXT = i18n.translate( - 'xpack.synthetics.monitorManagement.selectPolicyHost.helpText', - { - defaultMessage: 'We recommend using a single Elastic agent per agent policy.', - } -); - -const POLICY_HOST_LABEL = i18n.translate('xpack.synthetics.monitorManagement.policyHost', { - defaultMessage: 'Agent policy', -}); diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/manage_locations/policy_name.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/manage_locations/policy_name.tsx deleted file mode 100644 index 2af3c6dcf65f2..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/manage_locations/policy_name.tsx +++ /dev/null @@ -1,51 +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 { EuiLink, EuiText, EuiTextColor } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; -import { useSelector } from 'react-redux'; -import { useUptimeSettingsContext } from '../../../contexts/uptime_settings_context'; -import { usePrivateLocationPermissions } from '../hooks/use_private_location_permission'; -import { selectAgentPolicies } from '../../../state/private_locations'; - -export const PolicyName = ({ agentPolicyId }: { agentPolicyId: string }) => { - const { canReadAgentPolicies } = usePrivateLocationPermissions(); - - const { basePath } = useUptimeSettingsContext(); - - const { data: policies } = useSelector(selectAgentPolicies); - - const policy = policies?.items.find((policyT) => policyT.id === agentPolicyId); - - return ( - -

- {canReadAgentPolicies && ( - - {policy ? ( - - {policy?.name} - - ) : ( - - {POLICY_IS_DELETED} - - )} - - )} -

-
- ); -}; - -const POLICY_IS_DELETED = i18n.translate('xpack.synthetics.monitorManagement.deletedPolicy', { - defaultMessage: 'Policy is deleted', -}); diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/mocks/locations.ts b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/mocks/locations.ts deleted file mode 100644 index b4f23bed097cb..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/mocks/locations.ts +++ /dev/null @@ -1,35 +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 const mockLocation = { - label: 'US Central', - id: 'us_central', - geo: { - lat: 1, - lon: 1, - }, - url: 'url', -}; - -export const mockLocationsState = { - monitorManagementList: { - locations: [mockLocation], - list: { - monitors: [], - perPage: 10, - page: 1, - total: 0, - }, - error: { - serviceLocations: null, - monitorList: null, - }, - loading: { - serviceLocations: false, - monitorList: false, - }, - }, -}; diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_config/locations.test.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_config/locations.test.tsx deleted file mode 100644 index 821934bbaa123..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_config/locations.test.tsx +++ /dev/null @@ -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 from 'react'; -import { fireEvent, screen } from '@testing-library/react'; -import { within } from '@testing-library/dom'; -import { render } from '../../../lib/helper/rtl_helpers'; -import { ServiceLocations } from './locations'; -import { LocationStatus } from '../../../../../common/runtime_types'; - -describe('', () => { - const setLocations = jest.fn(); - const location = { - label: 'US Central', - id: 'us-central', - geo: { - lat: 1, - lon: 1, - }, - url: 'url', - isServiceManaged: true, - }; - const locationTestSubId = `syntheticsServiceLocation--${location.id}`; - const state = { - monitorManagementList: { - locations: [location], - list: { - monitors: [], - perPage: 10, - page: 1, - total: 0, - }, - error: { - serviceLocations: null, - monitorList: null, - }, - loading: { - serviceLocations: false, - monitorList: false, - }, - }, - }; - - beforeEach(() => { - jest.clearAllMocks(); - }); - - it('renders locations', () => { - render( - , - { state } - ); - - expect(screen.queryByText('US Central')).toBeInTheDocument(); - }); - - it('shows invalid error', async () => { - render( - , - { state } - ); - - expect(screen.getByText('At least one service location must be specified')).toBeInTheDocument(); - }); - - it('checks unchecks location', () => { - const { getByTestId } = render( - , - { state } - ); - - const checkbox = getByTestId(locationTestSubId) as HTMLInputElement; - expect(checkbox.checked).toEqual(false); - fireEvent.click(checkbox); - - expect(setLocations).toHaveBeenCalled(); - }); - - it('calls onBlur', () => { - const onBlur = jest.fn(); - const { getByTestId } = render( - , - { state } - ); - - const checkbox = getByTestId(locationTestSubId) as HTMLInputElement; - fireEvent.click(checkbox); - fireEvent.blur(checkbox); - expect(onBlur).toHaveBeenCalledTimes(1); - }); - - it('shows experimental badges next to experimental locations', () => { - const multiLocations = [ - { ...location, id: 'L1', label: 'first', status: LocationStatus.EXPERIMENTAL }, - { ...location, id: 'L2', label: 'second', status: LocationStatus.GA }, - { ...location, id: 'L3', label: 'third', status: LocationStatus.EXPERIMENTAL }, - { ...location, id: 'L4', label: 'fourth', status: LocationStatus.GA }, - ]; - - const { getByTestId } = render( - , - { - state: { - monitorManagementList: { ...state.monitorManagementList, locations: multiLocations }, - }, - } - ); - - multiLocations.forEach((expectedLocation) => { - const locationText = getByTestId(`syntheticsServiceLocationText--${expectedLocation.id}`); - - within(locationText).getByText(expectedLocation.label); - - if (expectedLocation.status !== LocationStatus.GA) { - within(locationText).getByText('Tech Preview'); - } else { - const techPreviewBadge = within(locationText).queryByText('Tech Preview'); - expect(techPreviewBadge).not.toBeInTheDocument(); - } - }); - }); -}); diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_config/locations.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_config/locations.tsx deleted file mode 100644 index 3bcaa51c48f77..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_config/locations.tsx +++ /dev/null @@ -1,154 +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, useEffect } from 'react'; -import { useSelector } from 'react-redux'; -import { i18n } from '@kbn/i18n'; -import { EuiCheckboxGroup, EuiFormRow, EuiText, EuiBadge, EuiIconTip } from '@elastic/eui'; -import { useKibana } from '@kbn/kibana-react-plugin/public'; -import { useRouteMatch } from 'react-router-dom'; -import { formatLocation } from '../../../../../common/utils/location_formatter'; -import { monitorManagementListSelector } from '../../../state/selectors'; -import { MonitorServiceLocations, LocationStatus } from '../../../../../common/runtime_types'; -import { ClientPluginsStart } from '../../../../plugin'; -import { MONITOR_EDIT_ROUTE } from '../../../../../common/constants'; - -interface Props { - selectedLocations: MonitorServiceLocations; - setLocations: React.Dispatch>; - isInvalid: boolean; - onBlur?: () => void; - readOnly?: boolean; -} - -export const ServiceLocations = ({ - selectedLocations, - setLocations, - isInvalid, - onBlur, - readOnly = false, -}: Props) => { - const [error, setError] = useState(null); - const [checkboxIdToSelectedMap, setCheckboxIdToSelectedMap] = useState>( - {} - ); - const { locations } = useSelector(monitorManagementListSelector); - - const isEditMonitor = useRouteMatch(MONITOR_EDIT_ROUTE); - - const onLocationChange = (optionId: string) => { - const isSelected = !checkboxIdToSelectedMap[optionId]; - const location = locations.find((loc) => loc.id === optionId); - if (isSelected) { - setLocations((prevLocations) => - location ? [...prevLocations, formatLocation(location)] : prevLocations - ); - } else { - setLocations((prevLocations) => [...prevLocations].filter((loc) => loc.id !== optionId)); - } - setError(null); - }; - - const errorMessage = error ?? (isInvalid ? VALIDATION_ERROR : null); - - const kServices = useKibana().services; - - const canSaveIntegrations: boolean = - !!kServices?.fleet?.authz.integrations.writeIntegrationPolicies; - - useEffect(() => { - const newCheckboxIdToSelectedMap = selectedLocations.reduce>( - (acc, location) => { - acc[location.id] = true; - return acc; - }, - {} - ); - setCheckboxIdToSelectedMap(newCheckboxIdToSelectedMap); - }, [selectedLocations]); - - return ( - - { - let badge = - location.status !== LocationStatus.GA ? ( - {TECH_PREVIEW_LABEL} - ) : null; - if (!location.isServiceManaged) { - badge = {PRIVATE_LABEL}; - } - const invalidBadge = location.isInvalid ? ( - {INVALID_LABEL} - ) : null; - - const isPrivateDisabled = - !location.isServiceManaged && (Boolean(location.isInvalid) || !canSaveIntegrations); - - const iconTip = - isPrivateDisabled && !canSaveIntegrations ? ( - - ) : null; - - const label = ( - - {location.label} {badge} {invalidBadge} - {iconTip} - - ); - return { - ...location, - label, - disabled: isPrivateDisabled && !isEditMonitor?.isExact, - 'data-test-subj': `syntheticsServiceLocation--${location.id}`, - }; - })} - idToSelectedMap={checkboxIdToSelectedMap} - onChange={(id) => onLocationChange(id)} - onBlur={() => onBlur?.()} - disabled={readOnly} - /> - - ); -}; - -const VALIDATION_ERROR = i18n.translate( - 'xpack.synthetics.monitorManagement.serviceLocationsValidationError', - { - defaultMessage: 'At least one service location must be specified', - } -); - -export const LOCATIONS_LABEL = i18n.translate( - 'xpack.synthetics.monitorManagement.monitorLocationsLabel', - { - defaultMessage: 'Monitor locations', - } -); - -export const TECH_PREVIEW_LABEL = i18n.translate( - 'xpack.synthetics.monitorManagement.techPreviewLabel', - { - defaultMessage: 'Tech Preview', - } -); - -export const PRIVATE_LABEL = i18n.translate('xpack.synthetics.monitorManagement.privateLabel', { - defaultMessage: 'Private', -}); - -export const INVALID_LABEL = i18n.translate('xpack.synthetics.monitorManagement.invalidLabel', { - defaultMessage: 'Invalid', -}); - -export const CANNOT_SAVE_INTEGRATION_LABEL = i18n.translate( - 'xpack.synthetics.monitorManagement.cannotSaveIntegrationUptime', - { - defaultMessage: - 'You are not authorized to update integrations. Integrations write permissions are required.', - } -); diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_config/monitor_advanced_fields.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_config/monitor_advanced_fields.tsx deleted file mode 100644 index a1cf51b740f52..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_config/monitor_advanced_fields.tsx +++ /dev/null @@ -1,97 +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 { EuiFieldText, EuiFormRow, EuiLink, EuiSpacer } from '@elastic/eui'; -import { FormattedMessage } from '@kbn/i18n-react'; -import React, { memo } from 'react'; -import { useKibana } from '@kbn/kibana-react-plugin/public'; -import { ConfigKey } from '../../../../../common/runtime_types'; -import type { Validation } from '../../../../../common/types'; -import { DescribedFormGroupWithWrap } from '../../fleet_package/common/described_form_group_with_wrap'; -import { usePolicyConfigContext } from '../../fleet_package/contexts'; - -interface Props { - validate: Validation; - minColumnWidth?: string; - onFieldBlur?: (field: ConfigKey) => void; -} - -export const MonitorManagementAdvancedFields = memo( - ({ validate, minColumnWidth, onFieldBlur }) => { - const { namespace, setNamespace } = usePolicyConfigContext(); - - const namespaceErrorMsg = validate[ConfigKey.NAMESPACE]?.({ - [ConfigKey.NAMESPACE]: namespace, - }); - const isNamespaceInvalid = !!namespaceErrorMsg; - const { services } = useKibana(); - - return ( - - - - } - description={ - - } - data-test-subj="monitorAdvancedFieldsSection" - > - - - } - helpText={ - - - - ), - }} - /> - } - > - setNamespace(event.target.value)} - required={true} - isInvalid={isNamespaceInvalid} - fullWidth={true} - name="namespace" - onBlur={() => onFieldBlur?.(ConfigKey.NAMESPACE)} - /> - - - ); - } -); diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_config/monitor_config.test.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_config/monitor_config.test.tsx deleted file mode 100644 index 833ab9b23e452..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_config/monitor_config.test.tsx +++ /dev/null @@ -1,61 +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 userEvent from '@testing-library/user-event'; - -import React from 'react'; -import { render } from '../../../lib/helper/rtl_helpers'; -import { - BrowserContextProvider, - HTTPContextProvider, - ICMPSimpleFieldsContextProvider, - PolicyConfigContextProvider, - TCPContextProvider, - TLSFieldsContextProvider, -} from '../../fleet_package/contexts'; -import { MonitorConfig } from './monitor_config'; - -describe('', () => { - const WrappedComponent = ({ isEditable = true, isEdit = false }) => { - return ( - - - - - - - - - - - - - - ); - }; - - beforeEach(() => { - jest.resetAllMocks(); - }); - - it('renders MonitorConfig', async () => { - const { getByLabelText } = render(); - const monitorName = getByLabelText('Monitor name') as HTMLInputElement; - expect(monitorName).toBeInTheDocument(); - }); - - it('only shows validation errors when field is interacted with', async () => { - const { getByLabelText, queryByText } = render(); - const monitorName = getByLabelText('Monitor name') as HTMLInputElement; - expect(monitorName).toBeInTheDocument(); - - userEvent.clear(monitorName); - expect(queryByText('Monitor name is required')).toBeNull(); - fireEvent.blur(monitorName); - expect(queryByText('Monitor name is required')).not.toBeNull(); - }); -}); diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_config/monitor_config.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_config/monitor_config.tsx deleted file mode 100644 index 5383bac84814b..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_config/monitor_config.tsx +++ /dev/null @@ -1,126 +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, useState } from 'react'; - -import { - EuiFlyoutBody, - EuiFlyoutHeader, - EuiFlyout, - EuiSpacer, - EuiFlyoutFooter, - EuiButtonEmpty, -} from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; -import { v4 as uuidv4 } from 'uuid'; -import { usePolicyConfigContext } from '../../fleet_package/contexts'; - -import { usePolicy } from '../../fleet_package/hooks/use_policy'; -import { validate } from '../validation'; -import { ActionBarPortal } from '../action_bar/action_bar_portal'; -import { useFormatMonitor } from '../hooks/use_format_monitor'; -import { MonitorFields } from './monitor_fields'; -import { TestNowMode, TestRun } from '../test_now_mode/test_now_mode'; -import { MonitorFields as MonitorFieldsType } from '../../../../../common/runtime_types'; -import { DEFAULT_FIELDS } from '../../../../../common/constants/monitor_defaults'; - -export const MonitorConfig = ({ isEdit = false }: { isEdit: boolean }) => { - const { monitorType } = usePolicyConfigContext(); - - /* raw policy config compatible with the UI. Save this to saved objects */ - const policyConfig = usePolicy(); - - /* Policy config that heartbeat can read. Send this to the service. - This type of helper should ideally be moved to task manager where we are syncing the config. - We can process validation (isValid) and formatting for heartbeat (formattedMonitor) separately - We don't need to save the heartbeat compatible version in saved objects */ - const { isValid, config } = useFormatMonitor({ - monitorType, - validate, - config: policyConfig[monitorType] as Partial, - defaultConfig: DEFAULT_FIELDS[monitorType] as Partial, - }); - - const [hasBeenSubmitted, setHasBeenSubmitted] = useState(false); - const [testRun, setTestRun] = useState(); - const [isTestRunInProgress, setIsTestRunInProgress] = useState(false); - const [isFlyoutOpen, setIsFlyoutOpen] = useState(false); - - const handleFormSubmit = () => { - setHasBeenSubmitted(true); - }; - - const handleTestNow = () => { - if (config) { - setTestRun({ id: uuidv4(), monitor: config as MonitorFieldsType }); - setIsTestRunInProgress(true); - setIsFlyoutOpen(true); - } - }; - - const handleTestDone = useCallback(() => { - setIsTestRunInProgress(false); - }, [setIsTestRunInProgress]); - - const handleFlyoutClose = useCallback(() => { - handleTestDone(); - setIsFlyoutOpen(false); - }, [handleTestDone, setIsFlyoutOpen]); - - const flyout = isFlyoutOpen && config && ( - - - - - - - - - - {CLOSE_LABEL} - - - - ); - - return ( - <> - - - {flyout} - - - - ); -}; - -const TEST_RESULT = i18n.translate('xpack.synthetics.monitorManagement.testResult', { - defaultMessage: 'Test result', -}); - -const CLOSE_LABEL = i18n.translate('xpack.synthetics.monitorManagement.closeButtonLabel', { - defaultMessage: 'Close', -}); diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_config/monitor_fields.test.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_config/monitor_fields.test.tsx deleted file mode 100644 index 4866f8cc55e45..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_config/monitor_fields.test.tsx +++ /dev/null @@ -1,132 +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 userEvent from '@testing-library/user-event'; - -import React from 'react'; -import { - ConfigKey, - DataStream, - HTTPFields, - BrowserFields, - SourceType, -} from '../../../../../common/runtime_types'; -import { render } from '../../../lib/helper/rtl_helpers'; -import { - BrowserContextProvider, - HTTPContextProvider, - ICMPSimpleFieldsContextProvider, - PolicyConfigContextProvider, - TCPContextProvider, - TLSFieldsContextProvider, -} from '../../fleet_package/contexts'; -import { DEFAULT_FIELDS } from '../../../../../common/constants/monitor_defaults'; -import { MonitorFields } from './monitor_fields'; - -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) => ( - { - props.onChange(e.jsonContent); - }} - /> - ), - }; -}); - -const defaultHTTPConfig = DEFAULT_FIELDS[DataStream.HTTP] as HTTPFields; -const defaultBrowserConfig = DEFAULT_FIELDS[DataStream.BROWSER]; - -describe('', () => { - const WrappedComponent = ({ - isEditable = true, - isFormSubmitted = false, - defaultSimpleHttpFields = defaultHTTPConfig, - defaultBrowserFields = defaultBrowserConfig, - readOnly = false, - }: { - isEditable?: boolean; - isFormSubmitted?: boolean; - defaultSimpleHttpFields?: HTTPFields; - defaultBrowserFields?: BrowserFields; - readOnly?: boolean; - }) => { - return ( - - - - - - - - - - - - - - ); - }; - - beforeEach(() => { - jest.resetAllMocks(); - }); - - it('renders MonitorFields', async () => { - const { getByLabelText } = render(); - const monitorName = getByLabelText('URL') as HTMLInputElement; - expect(monitorName).toBeInTheDocument(); - }); - - it('only shows validation errors when field has been interacted with', async () => { - const { getByLabelText, queryByText } = render(); - const monitorName = getByLabelText('Monitor name') as HTMLInputElement; - expect(monitorName).toBeInTheDocument(); - - userEvent.clear(monitorName); - expect(queryByText('Monitor name is required')).toBeNull(); - fireEvent.blur(monitorName); - expect(queryByText('Monitor name is required')).not.toBeNull(); - }); - - it('shows all validations errors when form is submitted', async () => { - const httpInvalidValues = { ...defaultHTTPConfig, [ConfigKey.NAME]: '', [ConfigKey.URLS]: '' }; - const { queryByText } = render( - - ); - - expect(queryByText('Monitor name is required')).not.toBeNull(); - expect(queryByText('URL is required')).not.toBeNull(); - }); - - it('is readonly when source type is project', async () => { - const name = 'monitor name'; - const browserFields = { - ...defaultBrowserConfig, - [ConfigKey.NAME]: name, - }; - const { getByText } = render( - - ); - - expect(getByText(/read-only/)).toBeInTheDocument(); - }); -}); diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_config/monitor_fields.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_config/monitor_fields.tsx deleted file mode 100644 index 8d8ff4fbd0d99..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_config/monitor_fields.tsx +++ /dev/null @@ -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. - */ - -import React, { useMemo, useState } from 'react'; -import { EuiForm } from '@elastic/eui'; -import { ConfigKey, DataStream, SourceType } from '../../../../../common/runtime_types'; -import { usePolicyConfigContext } from '../../fleet_package/contexts'; - -import { CustomFields } from '../../fleet_package/custom_fields'; -import { validate } from '../validation'; -import { MonitorNameAndLocation } from './monitor_name_location'; -import { MonitorManagementAdvancedFields } from './monitor_advanced_fields'; -import { ProjectReadonlyView } from './project_readonly_view/project_readonly_view'; - -const MIN_COLUMN_WRAP_WIDTH = '360px'; - -export const MonitorFields = ({ isFormSubmitted = false }: { isFormSubmitted?: boolean }) => { - const { monitorType, sourceType } = usePolicyConfigContext(); - - const [touchedFieldsHash, setTouchedFieldsHash] = useState>({}); - - const fieldValidation = useMemo(() => { - const validatorsHash = { ...validate[monitorType] }; - if (!isFormSubmitted) { - Object.keys(validatorsHash).map((key) => { - if (!touchedFieldsHash[key]) { - validatorsHash[key as ConfigKey] = undefined; - } - }); - } - - return validatorsHash; - }, [isFormSubmitted, monitorType, touchedFieldsHash]); - - const handleFieldBlur = (field: ConfigKey) => { - setTouchedFieldsHash((hash) => ({ ...hash, [field]: true })); - }; - - return ( - - {sourceType === SourceType.PROJECT ? ( - - ) : ( - - } - onFieldBlur={handleFieldBlur} - > - - - )} - - ); -}; diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_config/monitor_name_location.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_config/monitor_name_location.tsx deleted file mode 100644 index f123d2e9800f8..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_config/monitor_name_location.tsx +++ /dev/null @@ -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 React, { useEffect, useState } from 'react'; -import { i18n } from '@kbn/i18n'; -import { FormattedMessage } from '@kbn/i18n-react'; -import { EuiFormRow, EuiFieldText } from '@elastic/eui'; -import { ConfigKey } from '../../../../../common/runtime_types'; -import { Validation } from '../../../../../common/types'; -import { usePolicyConfigContext } from '../../fleet_package/contexts'; -import { ServiceLocations } from './locations'; -import { useMonitorName } from './use_monitor_name'; - -interface Props { - validate?: Validation; - onFieldBlur?: (field: ConfigKey) => void; - readOnly?: boolean; -} - -export const MonitorNameAndLocation = ({ validate, onFieldBlur, readOnly }: Props) => { - const { name, setName, locations = [], setLocations } = usePolicyConfigContext(); - const isNameInvalid = validate ? !!validate[ConfigKey.NAME]?.({ [ConfigKey.NAME]: name }) : false; - const isLocationsInvalid = validate - ? !!validate[ConfigKey.LOCATIONS]?.({ - [ConfigKey.LOCATIONS]: locations, - }) - : false; - - const [localName, setLocalName] = useState(name); - - const { validName, nameAlreadyExists } = useMonitorName({ search: localName }); - - useEffect(() => { - setName(validName); - }, [setName, validName]); - - return ( - <> - - } - fullWidth={false} - isInvalid={isNameInvalid || nameAlreadyExists} - error={ - nameAlreadyExists ? ( - NAME_ALREADY_EXISTS - ) : ( - - ) - } - > - setLocalName(event.target.value)} - onBlur={() => onFieldBlur?.(ConfigKey.NAME)} - data-test-subj="monitorManagementMonitorName" - readOnly={readOnly} - /> - - onFieldBlur?.(ConfigKey.LOCATIONS)} - readOnly={readOnly} - /> - - ); -}; - -const NAME_ALREADY_EXISTS = i18n.translate( - 'xpack.synthetics.monitorManagement.duplicateNameError', - { - defaultMessage: 'Monitor name already exists.', - } -); diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_config/project_readonly_view/project_readonly_view.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_config/project_readonly_view/project_readonly_view.tsx deleted file mode 100644 index 435164a4b0765..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_config/project_readonly_view/project_readonly_view.tsx +++ /dev/null @@ -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 { ProjectTCPReadonlyFields } from './read_only_tcp_fields'; -import { ProjectICMPReadonlyFields } from './read_only_icmp_fields'; -import { ProjectHTTPReadonlyFields } from './read_only_http_fields'; -import { usePolicyConfigContext } from '../../../fleet_package/contexts'; -import { ProjectBrowserReadonlyFields } from './read_only_browser_fields'; - -const MIN_COLUMN_WRAP_WIDTH = '360px'; - -export const ProjectReadonlyView = () => { - const { monitorType } = usePolicyConfigContext(); - - switch (monitorType) { - case 'browser': - return ; - case 'http': - return ; - case 'tcp': - return ; - case 'icmp': - return ; - default: - } - - return ; -}; diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_config/project_readonly_view/read_only_browser_fields.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_config/project_readonly_view/read_only_browser_fields.tsx deleted file mode 100644 index 87920827ff5a9..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_config/project_readonly_view/read_only_browser_fields.tsx +++ /dev/null @@ -1,122 +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 { i18n } from '@kbn/i18n'; -import { EuiAccordion, EuiFormRow, EuiFieldText, EuiSpacer, EuiCode } from '@elastic/eui'; -import { OptionalLabel } from '../../../fleet_package/optional_label'; -import { ProjectReadonlyCommonFields } from './readonly_common_fields'; -import { ConfigKey, MonacoEditorLangId } from '../../../../../../common/runtime_types'; -import { - useBrowserAdvancedFieldsContext, - useBrowserSimpleFieldsContext, -} from '../../../fleet_package/contexts'; -import { ThrottlingFields } from '../../../fleet_package/browser/throttling_fields'; -import { DescribedFormGroupWithWrap } from '../../../fleet_package/common/described_form_group_with_wrap'; -import { CodeEditor } from '../../../fleet_package/code_editor'; - -const noop = () => {}; - -export const ProjectBrowserReadonlyFields = ({ minColumnWidth }: { minColumnWidth: string }) => { - const { fields: advancedFields } = useBrowserAdvancedFieldsContext(); - const { fields } = useBrowserSimpleFieldsContext(); - - const paramsField = ( - - } - labelAppend={} - helpText={ - params.value }} - /> - } - > - - - ); - - return ( - <> - - - - - - - - - } - description={ - - } - minColumnWidth={minColumnWidth} - > - - - } - helpText={ - - } - > - - - - - - - - ); -}; diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_config/project_readonly_view/read_only_http_fields.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_config/project_readonly_view/read_only_http_fields.tsx deleted file mode 100644 index 3e2bf89e64f55..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_config/project_readonly_view/read_only_http_fields.tsx +++ /dev/null @@ -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 from 'react'; -import { EuiFieldText, EuiFormRow } from '@elastic/eui'; -import { FormattedMessage } from '@kbn/i18n-react'; -import { useHTTPSimpleFieldsContext } from '../../../fleet_package/contexts'; -import { ConfigKey } from '../../../../../../common/constants/monitor_management'; -import { ProjectReadonlyCommonFields } from './readonly_common_fields'; -const noop = () => {}; - -export const ProjectHTTPReadonlyFields = ({ minColumnWidth }: { minColumnWidth: string }) => { - const { fields } = useHTTPSimpleFieldsContext(); - - const urlField = ( - - } - fullWidth={false} - > - - - ); - - return ( - <> - - - ); -}; diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_config/project_readonly_view/read_only_icmp_fields.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_config/project_readonly_view/read_only_icmp_fields.tsx deleted file mode 100644 index 93ade661cd697..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_config/project_readonly_view/read_only_icmp_fields.tsx +++ /dev/null @@ -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 from 'react'; -import { EuiFieldText, EuiFormRow } from '@elastic/eui'; -import { FormattedMessage } from '@kbn/i18n-react'; -import { useICMPSimpleFieldsContext } from '../../../fleet_package/contexts'; -import { ConfigKey } from '../../../../../../common/constants/monitor_management'; -import { ProjectReadonlyCommonFields } from './readonly_common_fields'; -const noop = () => {}; - -export const ProjectICMPReadonlyFields = ({ minColumnWidth }: { minColumnWidth: string }) => { - const { fields } = useICMPSimpleFieldsContext(); - - const urlField = ( - - } - fullWidth={false} - > - - - ); - - return ( - <> - - - ); -}; diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_config/project_readonly_view/read_only_tcp_fields.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_config/project_readonly_view/read_only_tcp_fields.tsx deleted file mode 100644 index 2a165e21af529..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_config/project_readonly_view/read_only_tcp_fields.tsx +++ /dev/null @@ -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 from 'react'; -import { EuiFieldText, EuiFormRow } from '@elastic/eui'; -import { FormattedMessage } from '@kbn/i18n-react'; -import { useTCPSimpleFieldsContext } from '../../../fleet_package/contexts'; -import { ConfigKey } from '../../../../../../common/constants/monitor_management'; -import { ProjectReadonlyCommonFields } from './readonly_common_fields'; -const noop = () => {}; - -export const ProjectTCPReadonlyFields = ({ minColumnWidth }: { minColumnWidth: string }) => { - const { fields } = useTCPSimpleFieldsContext(); - - const urlField = ( - - } - fullWidth={false} - > - - - ); - - return ( - <> - - - ); -}; diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_config/project_readonly_view/readonly_common_fields.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_config/project_readonly_view/readonly_common_fields.tsx deleted file mode 100644 index 1dffbde06576c..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_config/project_readonly_view/readonly_common_fields.tsx +++ /dev/null @@ -1,126 +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 { EuiCallOut, EuiFormRow, EuiSpacer } from '@elastic/eui'; -import { FormattedMessage } from '@kbn/i18n-react'; -import { - BrowserSimpleFields, - HTTPSimpleFields, - ICMPSimpleFields, - TCPSimpleFields, -} from '../../../../../../common/runtime_types'; -import { DescribedFormGroupWithWrap } from '../../../fleet_package/common/described_form_group_with_wrap'; -import { MonitorNameAndLocation } from '../monitor_name_location'; -import { Enabled } from '../../../fleet_package/common/enabled'; -import { ScheduleField } from '../../../fleet_package/schedule_field'; -import { ConfigKey } from '../../../../../../common/constants/monitor_management'; -import { OptionalLabel } from '../../../fleet_package/optional_label'; -import { ComboBox } from '../../../fleet_package/combo_box'; - -const noop = () => {}; - -export const ProjectReadonlyCommonFields = ({ - minColumnWidth, - extraFields, - fields, -}: { - minColumnWidth: string; - extraFields?: JSX.Element; - fields: TCPSimpleFields | HTTPSimpleFields | ICMPSimpleFields | BrowserSimpleFields; -}) => { - return ( - <> - - } - iconType="document" - > -

- -

-
- - - - - } - description={ - - } - data-test-subj="monitorSettingsSection" - minColumnWidth={minColumnWidth} - > - - - - } - error={ - - } - > - - - - } - helpText={ - - } - labelAppend={} - > - - - {extraFields} - - - - ); -}; diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_config/use_monitor_name.test.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_config/use_monitor_name.test.tsx deleted file mode 100644 index c9af6ca329ebc..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_config/use_monitor_name.test.tsx +++ /dev/null @@ -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 { defaultCore, WrappedHelper } from '../../../lib/helper/rtl_helpers'; -import { renderHook } from '@testing-library/react-hooks'; -import { useMonitorName } from './use_monitor_name'; - -import { useParams, Router } from 'react-router-dom'; - -const mockRouter = { - useParams, - ...Router, -}; - -jest.mock('react-router-dom', () => ({ - ...jest.requireActual('react-router-dom'), - useParams: jest.fn().mockReturnValue({}), -})); - -describe('useMonitorName', () => { - it('returns expected results', () => { - const { result } = renderHook(() => useMonitorName({}), { wrapper: WrappedHelper }); - - expect(result.current).toStrictEqual({ nameAlreadyExists: false, validName: '' }); - expect(defaultCore.savedObjects.client.find).toHaveBeenCalledWith({ - aggs: { - monitorNames: { - terms: { field: 'synthetics-monitor.attributes.name.keyword', size: 10000 }, - }, - }, - perPage: 0, - type: 'synthetics-monitor', - }); - }); - - it('returns expected results after data', async () => { - defaultCore.savedObjects.client.find = jest.fn().mockReturnValue({ - aggregations: { - monitorNames: { - buckets: [{ key: 'Test' }, { key: 'Test 1' }], - }, - }, - }); - - const { result, waitForNextUpdate } = renderHook(() => useMonitorName({ search: 'Test' }), { - wrapper: WrappedHelper, - }); - - expect(result.current).toStrictEqual({ nameAlreadyExists: false, validName: 'Test' }); - - await waitForNextUpdate(); - - expect(result.current).toStrictEqual({ nameAlreadyExists: true, validName: '' }); - }); - - it('returns expected results after data while editing monitor', async () => { - defaultCore.savedObjects.client.find = jest.fn().mockReturnValue({ - aggregations: { - monitorNames: { - buckets: [{ key: 'Test' }, { key: 'Test 1' }], - }, - }, - }); - - jest.spyOn(mockRouter, 'useParams').mockReturnValue({ monitorId: 'test-id' }); - - const { result, waitForNextUpdate } = renderHook(() => useMonitorName({ search: 'Test' }), { - wrapper: WrappedHelper, - }); - - expect(result.current).toStrictEqual({ nameAlreadyExists: false, validName: 'Test' }); - - await waitForNextUpdate(); - - expect(result.current).toStrictEqual({ nameAlreadyExists: false, validName: 'Test' }); - }); -}); diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_config/use_monitor_name.ts b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_config/use_monitor_name.ts deleted file mode 100644 index e8d3848856a2b..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_config/use_monitor_name.ts +++ /dev/null @@ -1,64 +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 { useEffect, useState } from 'react'; -import { useFetcher } from '@kbn/observability-plugin/public'; -import { useKibana } from '@kbn/kibana-react-plugin/public'; -import { useParams } from 'react-router-dom'; -import { syntheticsMonitorType } from '../../../../../common/types/saved_objects'; - -interface AggsResponse { - monitorNames: { - buckets: Array<{ - key: string; - }>; - }; -} - -export const useMonitorName = ({ search = '' }: { search?: string }) => { - const [values, setValues] = useState([]); - - const { monitorId } = useParams<{ monitorId: string }>(); - - const { savedObjects } = useKibana().services; - - const { data } = useFetcher(() => { - const aggs = { - monitorNames: { - terms: { - field: `${syntheticsMonitorType}.attributes.name.keyword`, - size: 10000, - }, - }, - }; - return savedObjects?.client.find({ - type: syntheticsMonitorType, - perPage: 0, - aggs, - }); - }, []); - - useEffect(() => { - if (data?.aggregations) { - const newValues = (data.aggregations as AggsResponse)?.monitorNames.buckets.map(({ key }) => - key.toLowerCase() - ); - if (monitorId && newValues.includes(search.toLowerCase())) { - setValues(newValues.filter((val) => val !== search.toLowerCase())); - } else { - setValues(newValues); - } - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [data, monitorId]); - - const hasMonitor = Boolean( - search && values && values.length > 0 && values?.includes(search.trim().toLowerCase()) - ); - - return { nameAlreadyExists: hasMonitor, validName: hasMonitor ? '' : search }; -}; diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_list/actions.test.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_list/actions.test.tsx deleted file mode 100644 index 4c3d80a4d68cd..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_list/actions.test.tsx +++ /dev/null @@ -1,71 +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 } from '@testing-library/react'; -import { render } from '../../../lib/helper/rtl_helpers'; -import { - MonitorManagementListResult, - BrowserFields, - ConfigKey, - SourceType, -} from '../../../../../common/runtime_types'; - -import { Actions } from './actions'; - -describe('', () => { - const onUpdate = jest.fn(); - - it('navigates to edit monitor flow on edit pencil', () => { - render( - - ); - - expect(screen.getByLabelText('Edit monitor')).toHaveAttribute( - 'href', - '/app/uptime/edit-monitor/test-id' - ); - }); - - it('allows deleting for project monitors', () => { - render( - - ); - - expect(screen.getByLabelText('Delete monitor')).not.toBeDisabled(); - }); -}); diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_list/actions.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_list/actions.tsx deleted file mode 100644 index 9442920eee1f6..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_list/actions.tsx +++ /dev/null @@ -1,97 +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, { useContext } from 'react'; -import { i18n } from '@kbn/i18n'; -import { EuiButtonIcon, EuiFlexItem, EuiFlexGroup, EuiToolTip } from '@elastic/eui'; -import moment from 'moment'; -import { usePrivateLocationPermissions } from '../hooks/use_private_location_permission'; -import { CANNOT_SAVE_INTEGRATION_LABEL } from '../monitor_config/locations'; -import { UptimeSettingsContext } from '../../../contexts'; -import { DeleteMonitor } from './delete_monitor'; -import { InlineError } from './inline_error'; -import { - BrowserFields, - ConfigKey, - MonitorManagementListResult, - SourceType, - Ping, -} from '../../../../../common/runtime_types'; - -interface Props { - configId: string; - name: string; - isDisabled?: boolean; - onUpdate: () => void; - errorSummaries?: Ping[]; - monitors: MonitorManagementListResult['monitors']; -} - -export const Actions = ({ - configId, - name, - onUpdate, - isDisabled, - errorSummaries, - monitors, -}: Props) => { - const { basePath } = useContext(UptimeSettingsContext); - - let errorSummary = errorSummaries?.find((summary) => summary.config_id === configId); - - const monitor = monitors.find((monitorT) => monitorT.id === configId); - const isProjectMonitor = - (monitor?.attributes as BrowserFields)[ConfigKey.MONITOR_SOURCE_TYPE] === SourceType.PROJECT; - - if (errorSummary && monitor) { - const summaryIsBeforeUpdate = moment(monitor.updated_at).isBefore( - moment(errorSummary.timestamp) - ); - if (!summaryIsBeforeUpdate) { - errorSummary = undefined; - } - } - - const { canUpdatePrivateMonitor } = usePrivateLocationPermissions( - monitor?.attributes as BrowserFields - ); - - return ( - - - - - - - - - - {errorSummary && ( - - - - )} - - ); -}; - -const EDIT_MONITOR_LABEL = i18n.translate('xpack.synthetics.monitorManagement.editMonitorLabel', { - defaultMessage: 'Edit monitor', -}); diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_list/all_monitors.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_list/all_monitors.tsx deleted file mode 100644 index ac6fab66bbb1e..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_list/all_monitors.tsx +++ /dev/null @@ -1,34 +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 { useSelector } from 'react-redux'; -import { MonitorManagementList, MonitorManagementListPageState } from './monitor_list'; -import { monitorManagementListSelector } from '../../../state/selectors'; -import { MonitorManagementList as MonitorManagementListState } from '../../../state/reducers/monitor_management'; -import { Ping } from '../../../../../common/runtime_types'; - -interface Props { - pageState: MonitorManagementListPageState; - monitorList: MonitorManagementListState; - onPageStateChange: (state: MonitorManagementListPageState) => void; - onUpdate: () => void; - errorSummaries: Ping[]; -} -export const AllMonitors = ({ pageState, onPageStateChange, onUpdate, errorSummaries }: Props) => { - const monitorList = useSelector(monitorManagementListSelector); - - return ( - - ); -}; diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_list/api_key_btn.test.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_list/api_key_btn.test.tsx deleted file mode 100644 index 96482466f84e3..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_list/api_key_btn.test.tsx +++ /dev/null @@ -1,38 +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 userEvent from '@testing-library/user-event'; -import { screen } from '@testing-library/react'; -import { render } from '../../../lib/helper/rtl_helpers'; -import { ApiKeyBtn } from './api_key_btn'; - -describe('', () => { - const setLoadAPIKey = jest.fn(); - - it('calls delete monitor on monitor deletion', () => { - render(); - - expect(screen.getByText('Generate API key')).toBeInTheDocument(); - userEvent.click(screen.getByTestId('uptimeMonitorManagementApiKeyGenerate')); - expect(setLoadAPIKey).toHaveBeenCalled(); - }); - - it('shows correct content on loading', () => { - render(); - - expect(screen.getByText('Generating API key')).toBeInTheDocument(); - }); - - it('shows api key when available and hides button', () => { - const apiKey = 'sampleApiKey'; - render(); - - expect(screen.getByText(apiKey)).toBeInTheDocument(); - expect(screen.queryByText('Generate API key')).not.toBeInTheDocument(); - }); -}); diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_list/api_key_btn.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_list/api_key_btn.tsx deleted file mode 100644 index 9fa3943892b4e..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_list/api_key_btn.tsx +++ /dev/null @@ -1,84 +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 { EuiButton, EuiCodeBlock, EuiSpacer, EuiText, EuiCallOut } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; - -export const ApiKeyBtn = ({ - apiKey, - loading, - setLoadAPIKey, -}: { - loading?: boolean; - apiKey?: string; - setLoadAPIKey: (val: boolean) => void; -}) => { - return ( - <> - - {!apiKey && ( - <> - { - setLoadAPIKey(true); - }} - data-test-subj="uptimeMonitorManagementApiKeyGenerate" - > - {loading ? GET_API_KEY_LOADING_LABEL : GET_API_KEY_LABEL} - - - - )} - {apiKey && ( - <> - - - - {API_KEY_LABEL} - - - - {apiKey} - - - )} - - ); -}; - -const API_KEY_LABEL = i18n.translate('xpack.synthetics.monitorManagement.apiKey.label', { - defaultMessage: 'API key', -}); - -const GET_API_KEY_LABEL = i18n.translate('xpack.synthetics.monitorManagement.getApiKey.label', { - defaultMessage: 'Generate API key', -}); - -const API_KEY_WARNING_LABEL = i18n.translate( - 'xpack.synthetics.monitorManagement.apiKeyWarning.label', - { - defaultMessage: - 'This API key will only be shown once. Please keep a copy for your own records.', - } -); - -const GET_API_KEY_LOADING_LABEL = i18n.translate( - 'xpack.synthetics.monitorManagement.getAPIKeyLabel.loading', - { - defaultMessage: 'Generating API key', - } -); diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_list/delete_monitor.test.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_list/delete_monitor.test.tsx deleted file mode 100644 index 5612f6298e8ed..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_list/delete_monitor.test.tsx +++ /dev/null @@ -1,90 +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, fireEvent, waitFor } from '@testing-library/react'; -import { render } from '../../../lib/helper/rtl_helpers'; -import * as fetchers from '../../../state/api/monitor_management'; -import { Actions } from './actions'; -import { DeleteMonitor } from './delete_monitor'; -import { - BrowserFields, - ConfigKey, - MonitorManagementListResult, - SourceType, -} from '../../../../../common/runtime_types'; - -import { createRealStore } from '../../../lib/helper/helper_with_redux'; - -describe('', () => { - const onUpdate = jest.fn(); - - it('calls delete monitor on monitor deletion', async () => { - const deleteMonitor = jest.spyOn(fetchers, 'deleteMonitor'); - const id = 'test-id'; - const store = createRealStore(); - render(, { - store, - }); - - const dispatchSpy = jest.spyOn(store, 'dispatch'); - - expect(deleteMonitor).not.toBeCalled(); - - fireEvent.click(screen.getByRole('button')); - - fireEvent.click(screen.getByTestId('confirmModalConfirmButton')); - - expect(dispatchSpy).toHaveBeenCalledWith({ - payload: { - id: 'test-id', - name: 'sample name', - }, - type: 'DELETE_MONITOR', - }); - - expect(store.getState().deleteMonitor.loading.includes(id)).toEqual(true); - - expect(await screen.findByLabelText('Loading')).toBeTruthy(); - }); - - it('calls set refresh when deletion is successful', async () => { - const id = 'test-id'; - const name = 'sample monitor'; - const store = createRealStore(); - - render( - , - { store } - ); - - fireEvent.click(screen.getByRole('button')); - - fireEvent.click(screen.getByTestId('confirmModalConfirmButton')); - - await waitFor(() => { - expect(onUpdate).toHaveBeenCalled(); - }); - - expect(store.getState().deleteMonitor.deletedMonitorIds.includes(id)).toEqual(true); - }); -}); diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_list/delete_monitor.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_list/delete_monitor.tsx deleted file mode 100644 index d82613d7a2ab0..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_list/delete_monitor.tsx +++ /dev/null @@ -1,121 +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 { i18n } from '@kbn/i18n'; -import React, { useEffect, useState } from 'react'; -import { EuiButtonIcon, EuiCallOut, EuiConfirmModal, EuiSpacer } from '@elastic/eui'; - -import { useDispatch, useSelector } from 'react-redux'; -import { deleteMonitorAction } from '../../../state/actions/delete_monitor'; -import { AppState } from '../../../state'; -import { - ProjectMonitorDisclaimer, - PROJECT_MONITOR_TITLE, -} from '../../../../apps/synthetics/components/monitors_page/management/monitor_list_table/delete_monitor'; -import { - deleteMonitorLoadingSelector, - deleteMonitorSuccessSelector, -} from '../../../state/selectors'; - -export const DeleteMonitor = ({ - configId, - name, - onUpdate, - isDisabled, - isProjectMonitor, -}: { - configId: string; - name: string; - isDisabled?: boolean; - isProjectMonitor?: boolean; - onUpdate: () => void; -}) => { - const [isDeleteModalVisible, setIsDeleteModalVisible] = useState(false); - - const isDeleting = useSelector((state: AppState) => - deleteMonitorLoadingSelector(state, configId) - ); - - const isSuccessfullyDeleted = useSelector((state: AppState) => - deleteMonitorSuccessSelector(state, configId) - ); - - const dispatch = useDispatch(); - - const onConfirmDelete = () => { - dispatch(deleteMonitorAction.get({ id: configId, name })); - setIsDeleteModalVisible(false); - }; - - const showDeleteModal = () => setIsDeleteModalVisible(true); - - const handleDelete = () => { - showDeleteModal(); - }; - - useEffect(() => { - if (isSuccessfullyDeleted) { - onUpdate(); - } - }, [onUpdate, isSuccessfullyDeleted]); - - const destroyModal = ( - setIsDeleteModalVisible(false)} - onConfirm={onConfirmDelete} - cancelButtonText={NO_LABEL} - confirmButtonText={YES_LABEL} - buttonColor="danger" - defaultFocusedButton="confirm" - > - {isProjectMonitor && ( - <> - -

- -

-
- - - )} -
- ); - - return ( - <> - - - {isDeleteModalVisible && destroyModal} - - ); -}; - -const YES_LABEL = i18n.translate('xpack.synthetics.monitorManagement.yesLabel', { - defaultMessage: 'Delete', -}); - -const NO_LABEL = i18n.translate('xpack.synthetics.monitorManagement.noLabel', { - defaultMessage: 'Cancel', -}); - -const DELETE_MONITOR_LABEL = i18n.translate( - 'xpack.synthetics.monitorManagement.deleteMonitorLabel', - { - defaultMessage: 'Delete monitor', - } -); diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_list/enablement_empty_state.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_list/enablement_empty_state.tsx deleted file mode 100644 index c2b4ea96209fb..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_list/enablement_empty_state.tsx +++ /dev/null @@ -1,184 +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, useEffect, useRef } from 'react'; -import { i18n } from '@kbn/i18n'; -import { FormattedMessage } from '@kbn/i18n-react'; -import { - EuiEmptyPrompt, - EuiButton, - EuiTitle, - EuiLink, - EuiCallOut, - EuiText, - EuiSpacer, -} from '@elastic/eui'; -import { useEnablement } from '../hooks/use_enablement'; -import { kibanaService } from '../../../state/kibana_service'; -import { SYNTHETICS_ENABLE_SUCCESS, SYNTHETICS_DISABLE_SUCCESS } from '../content'; - -export const EnablementEmptyState = ({ focusButton }: { focusButton: boolean }) => { - const { error, enablement, enableSynthetics, loading } = useEnablement(); - const [isEnabling, setIsEnabling] = useState(false); - const { isEnabled, canEnable } = enablement; - const buttonRef = useRef(null); - - useEffect(() => { - if (isEnabling && isEnabled) { - setIsEnabling(false); - kibanaService.toasts.addSuccess({ - title: SYNTHETICS_ENABLE_SUCCESS, - toastLifeTimeMs: 3000, - }); - } else if (isEnabling && error) { - setIsEnabling(false); - kibanaService.toasts.addSuccess({ - title: SYNTHETICS_DISABLE_SUCCESS, - toastLifeTimeMs: 3000, - }); - } - }, [isEnabled, isEnabling, error]); - - const handleEnableSynthetics = () => { - enableSynthetics(); - setIsEnabling(true); - }; - - useEffect(() => { - if (focusButton) { - buttonRef.current?.focus(); - } - }, [focusButton]); - - return !isEnabled && !loading ? ( - <> - - {canEnable ? MONITOR_MANAGEMENT_ENABLEMENT_LABEL : MONITOR_MANAGEMENT_DISABLED_LABEL} - - } - body={ -

- {canEnable - ? MONITOR_MANAGEMENT_ENABLEMENT_MESSAGE - : MONITOR_MANAGEMENT_DISABLED_MESSAGE} -

- } - actions={ - canEnable ? ( - - {MONITOR_MANAGEMENT_ENABLEMENT_BTN_LABEL} - - ) : null - } - footer={ - <> - -

{LEARN_MORE_LABEL}

-
- - {DOCS_LABEL} - - - } - /> - - - - - - - {"Elastic's Beta Release Terms"} - - ), - }} - /> - - - - - -
- } - /> - - ) : null; -}; - -const MONITOR_MANAGEMENT_ENABLEMENT_LABEL = i18n.translate( - 'xpack.synthetics.monitorManagement.emptyState.enablement.enabled.title', - { - defaultMessage: 'Enable Monitor Management', - } -); - -const MONITOR_MANAGEMENT_DISABLED_LABEL = i18n.translate( - 'xpack.synthetics.monitorManagement.emptyState.enablement.disabled.title', - { - defaultMessage: 'Monitor Management is disabled', - } -); - -const MONITOR_MANAGEMENT_ENABLEMENT_MESSAGE = i18n.translate( - 'xpack.synthetics.monitorManagement.emptyState.enablement', - { - defaultMessage: - 'Enable Monitor Management to run lightweight and real-browser monitors from hosted testing locations around the world. Enabling Monitor Management will generate an API key to allow the Synthetics Service to write back to your Elasticsearch cluster.', - } -); - -const MONITOR_MANAGEMENT_DISABLED_MESSAGE = i18n.translate( - 'xpack.synthetics.monitorManagement.emptyState.enablement.disabledDescription', - { - defaultMessage: - 'Monitor Management is currently disabled. Monitor Management allows you to run lightweight and real-browser monitors from hosted testing locations around the world. To enable Monitor Management, please contact an administrator.', - } -); - -const MONITOR_MANAGEMENT_ENABLEMENT_BTN_LABEL = i18n.translate( - 'xpack.synthetics.monitorManagement.emptyState.enablement.title', - { - defaultMessage: 'Enable', - } -); - -const DOCS_LABEL = i18n.translate('xpack.synthetics.monitorManagement.emptyState.enablement.doc', { - defaultMessage: 'Read the docs', -}); - -const LEARN_MORE_LABEL = i18n.translate( - 'xpack.synthetics.monitorManagement.emptyState.enablement.learnMore', - { - defaultMessage: 'Want to learn more?', - } -); diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_list/inline_error.test.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_list/inline_error.test.tsx deleted file mode 100644 index 1cf05d7697e60..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_list/inline_error.test.tsx +++ /dev/null @@ -1,62 +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 } from '@testing-library/react'; -import { render } from '../../../lib/helper/rtl_helpers'; -import { InlineError } from './inline_error'; - -describe('', () => { - it('calls delete monitor on monitor deletion', () => { - render(); - - expect( - screen.getByLabelText( - 'journey did not finish executing, 0 steps ran. Click for more details.' - ) - ).toBeInTheDocument(); - }); -}); - -const errorSummary = { - docId: 'testDoc', - summary: { up: 0, down: 1 }, - agent: { - name: 'cron-686f0ce427dfd7e3-27465864-sqs5l', - id: '778fe9c6-bbd1-47d4-a0be-73f8ba9cec61', - type: 'heartbeat', - ephemeral_id: 'bc1a961f-1fbc-4253-aee0-633a8f6fc56e', - version: '8.1.0', - }, - synthetics: { type: 'heartbeat/summary' }, - monitor: { - name: 'Browser monitor', - check_group: 'f5480358-a9da-11ec-bced-6274e5883bd7', - id: '3a5553a0-a949-11ec-b7ca-c3b39fffa2af', - timespan: { lt: '2022-03-22T12:27:02.563Z', gte: '2022-03-22T12:24:02.563Z' }, - type: 'browser', - status: 'down', - }, - error: { type: 'io', message: 'journey did not finish executing, 0 steps ran' }, - url: {}, - observer: { - geo: { - continent_name: 'North America', - city_name: 'Iowa', - country_iso_code: 'US', - name: 'North America - US Central', - location: '41.8780, 93.0977', - }, - hostname: 'cron-686f0ce427dfd7e3-27465864-sqs5l', - ip: ['10.1.9.110'], - mac: ['62:74:e5:88:3b:d7'], - }, - ecs: { version: '8.0.0' }, - config_id: '3a5553a0-a949-11ec-b7ca-c3b39fffa2af', - data_stream: { namespace: 'default', type: 'synthetics', dataset: 'browser' }, - timestamp: '2022-03-22T12:24:02.563Z', -}; diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_list/inline_error.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_list/inline_error.tsx deleted file mode 100644 index cb9a5d13a5b1c..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_list/inline_error.tsx +++ /dev/null @@ -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 React, { useState } from 'react'; -import { EuiButtonIcon, EuiToolTip } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; -import { Ping } from '../../../../../common/runtime_types'; -import { StdErrorPopover } from './stderr_logs_popover'; - -export const InlineError = ({ errorSummary }: { errorSummary: Ping }) => { - const [isOpen, setIsOpen] = useState(false); - - const errorMessage = - errorSummary.monitor.type === 'browser' - ? getInlineErrorLabel(errorSummary.error?.message) - : errorSummary.error?.message; - - return ( - - setIsOpen(true)} - color="danger" - /> - - } - /> - ); -}; - -export const getInlineErrorLabel = (message?: string) => { - return i18n.translate('xpack.synthetics.monitorList.statusColumn.error.message', { - defaultMessage: '{message}. Click for more details.', - values: { message }, - }); -}; - -export const ERROR_LOGS_LABEL = i18n.translate( - 'xpack.synthetics.monitorList.statusColumn.error.logs', - { - defaultMessage: 'Error logs', - } -); diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_list/invalid_monitors.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_list/invalid_monitors.tsx deleted file mode 100644 index 2212faf66b8f1..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_list/invalid_monitors.tsx +++ /dev/null @@ -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. - */ - -import React from 'react'; -import { useSelector } from 'react-redux'; -import { MonitorManagementList, MonitorManagementListPageState } from './monitor_list'; -import { monitorManagementListSelector } from '../../../state/selectors'; -import { - MonitorManagementListResult, - Ping, - DEFAULT_THROTTLING, -} from '../../../../../common/runtime_types'; - -interface Props { - loading: boolean; - pageState: MonitorManagementListPageState; - monitorSavedObjects?: MonitorManagementListResult['monitors']; - onPageStateChange: (state: MonitorManagementListPageState) => void; - onUpdate: () => void; - errorSummaries: Ping[]; - invalidTotal: number; -} -export const InvalidMonitors = ({ - loading: summariesLoading, - pageState, - onPageStateChange, - onUpdate, - errorSummaries, - invalidTotal, - monitorSavedObjects, -}: Props) => { - const { pageSize, pageIndex } = pageState; - - const startIndex = (pageIndex - 1) * pageSize; - - const monitorList = useSelector(monitorManagementListSelector); - - return ( - - ); -}; diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_list/list_tabs.test.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_list/list_tabs.test.tsx deleted file mode 100644 index 07a3e36f6ca78..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_list/list_tabs.test.tsx +++ /dev/null @@ -1,35 +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 } from '@testing-library/react'; -import { render } from '../../../lib/helper/rtl_helpers'; -import { MonitorListTabs } from './list_tabs'; - -describe('', () => { - it('calls delete monitor on monitor deletion', () => { - const onPageStateChange = jest.fn(); - render( - - ); - - expect(screen.getByText('All monitors')).toBeInTheDocument(); - expect(screen.getByText('Invalid monitors')).toBeInTheDocument(); - - expect(onPageStateChange).toHaveBeenCalledTimes(1); - expect(onPageStateChange).toHaveBeenCalledWith({ - pageIndex: 1, - pageSize: 10, - sortField: 'name.keyword', - sortOrder: 'asc', - }); - }); -}); diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_list/list_tabs.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_list/list_tabs.tsx deleted file mode 100644 index ec3023e1019d9..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_list/list_tabs.tsx +++ /dev/null @@ -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 { - EuiTabs, - EuiTab, - EuiNotificationBadge, - EuiFlexGroup, - EuiFlexItem, - EuiButton, -} from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; -import React, { useState, Fragment, useEffect } from 'react'; -import { useHistory, useParams } from 'react-router-dom'; -import { useUptimeRefreshContext } from '../../../contexts/uptime_refresh_context'; -import { MonitorManagementListPageState } from './monitor_list'; -import { ConfigKey } from '../../../../../common/runtime_types'; - -export const MonitorListTabs = ({ - invalidTotal, - onUpdate, - onPageStateChange, -}: { - invalidTotal: number; - onUpdate: () => void; - onPageStateChange: (state: MonitorManagementListPageState) => void; -}) => { - const [selectedTabId, setSelectedTabId] = useState('all'); - - const { refreshApp } = useUptimeRefreshContext(); - - const history = useHistory(); - - const { type: viewType = 'all' } = useParams<{ type: 'all' | 'invalid' }>(); - - useEffect(() => { - setSelectedTabId(viewType); - onPageStateChange({ - pageIndex: 1, - pageSize: 10, - sortOrder: 'asc', - sortField: `${ConfigKey.NAME}.keyword`, - }); - }, [viewType, onPageStateChange]); - - const tabs = [ - { - id: 'all', - name: ALL_MONITORS_LABEL, - content: , - href: history.createHref({ pathname: '/manage-monitors' }), - disabled: false, - }, - { - id: 'invalid', - name: INVALID_MONITORS_LABEL, - append: ( - - {invalidTotal} - - ), - href: history.createHref({ pathname: '/manage-monitors/invalid' }), - content: , - disabled: invalidTotal === 0, - }, - ]; - - const onSelectedTabChanged = (id: string) => { - setSelectedTabId(id); - }; - - const renderTabs = () => { - return tabs.map((tab, index) => ( - onSelectedTabChanged(tab.id)} - isSelected={tab.id === selectedTabId} - disabled={tab.disabled} - append={tab.append} - > - {tab.name} - - )); - }; - - return ( - - - {renderTabs()} - - - { - onUpdate(); - refreshApp(); - }} - > - {REFRESH_LABEL} - - - - ); -}; - -export const REFRESH_LABEL = i18n.translate('xpack.synthetics.monitorList.refresh', { - defaultMessage: 'Refresh', -}); - -export const INVALID_MONITORS_LABEL = i18n.translate( - 'xpack.synthetics.monitorList.invalidMonitors', - { - defaultMessage: 'Invalid monitors', - } -); - -export const ALL_MONITORS_LABEL = i18n.translate('xpack.synthetics.monitorList.allMonitors', { - defaultMessage: 'All monitors', -}); diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_list/management_settings.test.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_list/management_settings.test.tsx deleted file mode 100644 index 7bd105d385757..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_list/management_settings.test.tsx +++ /dev/null @@ -1,75 +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 * as observabilityPublic from '@kbn/observability-plugin/public'; -import userEvent from '@testing-library/user-event'; -import { screen } from '@testing-library/react'; -import { render, makeUptimePermissionsCore } from '../../../lib/helper/rtl_helpers'; -import { ManagementSettings } from './management_settings'; - -jest.mock('@kbn/observability-plugin/public'); - -describe('', () => { - const state = { - monitorManagementList: { - enablement: { - canManageApiKeys: true, - }, - }, - }; - - beforeAll(() => { - jest.spyOn(observabilityPublic, 'useFetcher').mockReturnValue({ - data: undefined, - status: observabilityPublic.FETCH_STATUS.SUCCESS, - refetch: () => {}, - }); - }); - - it('shows popover on click', () => { - jest.spyOn(observabilityPublic, 'useFetcher').mockReturnValue({ - data: undefined, - status: observabilityPublic.FETCH_STATUS.SUCCESS, - refetch: () => {}, - }); - render(); - - expect(screen.queryByText('Generate API key')).not.toBeInTheDocument(); - userEvent.click(screen.getByTestId('uptimeMonitorManagementApiKeyPopoverTrigger')); - expect( - screen.getByText(/Use an API key to push monitors remotely from a CLI or CD pipeline/) - ).toBeInTheDocument(); - }); - - it('shows appropriate content when user does not have correct uptime save permissions', () => { - // const apiKey = 'sampleApiKey'; - render(, { - state, - core: makeUptimePermissionsCore({ save: false }), - }); - - userEvent.click(screen.getByTestId('uptimeMonitorManagementApiKeyPopoverTrigger')); - expect(screen.getByText(/Please contact your administrator./)).toBeInTheDocument(); - }); - - it('shows appropriate content when user does not api key management permissions', () => { - render(, { - state: { - monitorManagementList: { - enablement: { - canManageApiKeys: false, - }, - }, - }, - core: makeUptimePermissionsCore({ save: true }), - }); - - userEvent.click(screen.getByTestId('uptimeMonitorManagementApiKeyPopoverTrigger')); - expect(screen.getByText(/Please contact your administrator./)).toBeInTheDocument(); - }); -}); diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_list/management_settings.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_list/management_settings.tsx deleted file mode 100644 index 2b45971a4d123..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_list/management_settings.tsx +++ /dev/null @@ -1,142 +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 { useKibana } from '@kbn/kibana-react-plugin/public'; -import { EuiPopover, EuiPopoverTitle, EuiText, EuiButtonEmpty, EuiLink } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; -import { useFetcher } from '@kbn/observability-plugin/public'; -import { ApiKeyBtn } from './api_key_btn'; -import { fetchServiceAPIKey } from '../../../state/api'; -import { ClientPluginsStart } from '../../../../plugin'; -import { useEnablement } from '../hooks/use_enablement'; - -const syntheticsTestRunDocsLink = - 'https://www.elastic.co/guide/en/observability/current/synthetic-run-tests.html'; - -export const ManagementSettings = () => { - const { - enablement: { canManageApiKeys }, - } = useEnablement(); - const [apiKey, setApiKey] = useState(undefined); - const [isPopoverOpen, setIsPopoverOpen] = useState(false); - const [loadAPIKey, setLoadAPIKey] = useState(false); - - const kServices = useKibana().services; - const canSaveIntegrations: boolean = - !!kServices?.fleet?.authz.integrations.writeIntegrationPolicies; - - const { data, loading } = useFetcher(async () => { - if (loadAPIKey) { - return fetchServiceAPIKey(); - } - return null; - }, [loadAPIKey]); - - useEffect(() => { - setApiKey(data?.apiKey.encoded); - }, [data]); - - const canSave: boolean = !!useKibana().services?.application?.capabilities.uptime.save; - - return ( - { - if (!isPopoverOpen) { - setIsPopoverOpen(true); - } - }} - data-test-subj="uptimeMonitorManagementApiKeyPopoverTrigger" - > - {API_KEYS_LABEL} - - } - closePopover={() => { - setApiKey(undefined); - setLoadAPIKey(false); - setIsPopoverOpen(false); - }} - style={{ margin: 'auto' }} - > -
- {canSave && canManageApiKeys ? ( - <> - {GET_API_KEY_GENERATE} - - {GET_API_KEY_LABEL_DESCRIPTION} {!canSaveIntegrations ? `${API_KEY_DISCLAIMER} ` : ''} - - {LEARN_MORE_LABEL} - - - - - ) : ( - <> - {GET_API_KEY_GENERATE} - - {GET_API_KEY_REDUCED_PERMISSIONS_LABEL}{' '} - - {LEARN_MORE_LABEL} - - - - )} -
-
- ); -}; - -const API_KEYS_LABEL = i18n.translate('xpack.synthetics.monitorManagement.getAPIKeyLabel.label', { - defaultMessage: 'API Keys', -}); - -const LEARN_MORE_LABEL = i18n.translate('xpack.synthetics.monitorManagement.learnMore.label', { - defaultMessage: 'Learn more', -}); - -const GET_API_KEY_GENERATE = i18n.translate( - 'xpack.synthetics.monitorManagement.getAPIKeyLabel.generate', - { - defaultMessage: 'Generate API Key', - } -); - -const GET_API_KEY_LABEL_DESCRIPTION = i18n.translate( - 'xpack.synthetics.monitorManagement.getAPIKeyLabel.description', - { - defaultMessage: 'Use an API key to push monitors remotely from a CLI or CD pipeline.', - } -); - -const API_KEY_DISCLAIMER = i18n.translate( - 'xpack.synthetics.monitorManagement.getAPIKeyLabel.disclaimer', - { - defaultMessage: - 'Please note: In order to use push monitors using private testing locations, you must generate this API key with a user who has Fleet and Integrations write permissions.', - } -); - -const GET_API_KEY_REDUCED_PERMISSIONS_LABEL = i18n.translate( - 'xpack.synthetics.monitorManagement.getAPIKeyReducedPermissions.description', - { - defaultMessage: - 'Use an API key to push monitors remotely from a CLI or CD pipeline. To generate an API key, you must have permissions to manage API keys and Uptime write access. Please contact your administrator.', - } -); diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_list/management_settings_portal.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_list/management_settings_portal.tsx deleted file mode 100644 index 31351393d94ff..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_list/management_settings_portal.tsx +++ /dev/null @@ -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 { APIKeysPortalNode } from '../../../pages/monitor_management/portals'; - -import { ManagementSettings } from './management_settings'; - -export const ManagementSettingsPortal = () => { - return ( - - - - ); -}; diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_list/monitor_async_error.test.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_list/monitor_async_error.test.tsx deleted file mode 100644 index a561904be141e..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_list/monitor_async_error.test.tsx +++ /dev/null @@ -1,122 +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 { screen } from '@testing-library/react'; -import React from 'react'; -import { DEFAULT_THROTTLING, LocationStatus } from '../../../../../common/runtime_types'; -import { render } from '../../../lib/helper/rtl_helpers'; -import { MonitorManagementList as MonitorManagementListState } from '../../../state/reducers/monitor_management'; -import { MonitorAsyncError } from './monitor_async_error'; - -describe('', () => { - const location1 = 'US Central'; - const location2 = 'US North'; - const reason1 = 'Unauthorized'; - const reason2 = 'Forbidden'; - const status1 = 401; - const status2 = 403; - const state = { - monitorManagementList: { - throttling: DEFAULT_THROTTLING, - enablement: null, - list: { - absoluteTotal: 6, - perPage: 5, - page: 1, - total: 6, - monitors: [], - syncErrors: [ - { - locationId: 'us_central', - error: { - reason: reason1, - status: status1, - }, - }, - { - locationId: 'us_north', - error: { - reason: reason2, - status: status2, - }, - }, - ], - }, - locations: [ - { - id: 'us_central', - label: location1, - geo: { - lat: 0, - lon: 0, - }, - url: '', - isServiceManaged: true, - status: LocationStatus.GA, - }, - { - id: 'us_north', - label: location2, - geo: { - lat: 0, - lon: 0, - }, - url: '', - isServiceManaged: true, - status: LocationStatus.GA, - }, - ], - error: { - serviceLocations: null, - monitorList: null, - enablement: null, - }, - loading: { - monitorList: true, - serviceLocations: false, - enablement: false, - }, - syntheticsService: { - loading: false, - signupUrl: null, - }, - } as MonitorManagementListState, - }; - - it('renders when errors are defined', () => { - render(, { state }); - - expect(screen.getByText(new RegExp(reason1))).toBeInTheDocument(); - expect(screen.getByText(new RegExp(`${status1}`))).toBeInTheDocument(); - expect(screen.getByText(new RegExp(reason2))).toBeInTheDocument(); - expect(screen.getByText(new RegExp(`${status2}`))).toBeInTheDocument(); - expect(screen.getByText(new RegExp(location1))).toBeInTheDocument(); - expect(screen.getByText(new RegExp(location2))).toBeInTheDocument(); - }); - - it('renders null when errors are empty', () => { - render(, { - state: { - ...state, - monitorManagementList: { - ...state.monitorManagementList, - list: { - ...state.monitorManagementList.list, - syncErrors: [], - }, - }, - }, - }); - - expect(screen.queryByText(new RegExp(reason1))).not.toBeInTheDocument(); - expect(screen.queryByText(new RegExp(`${status1}`))).not.toBeInTheDocument(); - expect(screen.queryByText(new RegExp(reason2))).not.toBeInTheDocument(); - expect(screen.queryByText(new RegExp(`${status2}`))).not.toBeInTheDocument(); - expect(screen.queryByText(new RegExp(location1))).not.toBeInTheDocument(); - expect(screen.queryByText(new RegExp(location2))).not.toBeInTheDocument(); - }); -}); diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_list/monitor_async_error.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_list/monitor_async_error.tsx deleted file mode 100644 index 0c0b514316edf..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_list/monitor_async_error.tsx +++ /dev/null @@ -1,90 +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 } from 'react'; -import { useSelector } from 'react-redux'; -import { i18n } from '@kbn/i18n'; -import { FormattedMessage } from '@kbn/i18n-react'; -import { EuiButton, EuiCallOut, EuiSpacer } from '@elastic/eui'; -import { monitorManagementListSelector } from '../../../state/selectors'; - -export const MonitorAsyncError = () => { - const [isDismissed, setIsDismissed] = useState(false); - const { list, locations } = useSelector(monitorManagementListSelector); - const syncErrors = list.syncErrors; - const hasSyncErrors = syncErrors && syncErrors.length > 0; - - return hasSyncErrors && !isDismissed ? ( - <> - - } - color="warning" - iconType="warning" - > -

- -

-
    - {Object.values(syncErrors ?? {}).map((e) => { - return ( -
  • - {`${ - locations.find((location) => location.id === e.locationId)?.label - } - ${STATUS_LABEL}: ${e.error?.status ?? NOT_AVAILABLE_LABEL}; ${REASON_LABEL}: ${ - e.error?.reason ?? NOT_AVAILABLE_LABEL - }`} -
  • - ); - })} -
- setIsDismissed(true)} - color="warning" - > - {DISMISS_LABEL} - -
- - - ) : null; -}; - -const REASON_LABEL = i18n.translate( - 'xpack.synthetics.monitorManagement.monitorSync.failure.reasonLabel', - { - defaultMessage: 'Reason', - } -); - -const STATUS_LABEL = i18n.translate( - 'xpack.synthetics.monitorManagement.monitorSync.failure.statusLabel', - { - defaultMessage: 'Status', - } -); - -const NOT_AVAILABLE_LABEL = i18n.translate( - 'xpack.synthetics.monitorManagement.monitorSync.failure.notAvailable', - { - defaultMessage: 'Not available', - } -); - -const DISMISS_LABEL = i18n.translate( - 'xpack.synthetics.monitorManagement.monitorSync.failure.dismissLabel', - { - defaultMessage: 'Dismiss', - } -); diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_list/monitor_enabled.test.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_list/monitor_enabled.test.tsx deleted file mode 100644 index 9d30d985f09d9..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_list/monitor_enabled.test.tsx +++ /dev/null @@ -1,79 +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 } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; -import { ConfigKey, DataStream, SyntheticsMonitor } from '../../../../../common/runtime_types'; -import { render } from '../../../lib/helper/rtl_helpers'; -import { FETCH_STATUS } from '@kbn/observability-plugin/public'; -import { spyOnUseFetcher } from '../../../lib/helper/spy_use_fetcher'; -import { MonitorEnabled } from './monitor_enabled'; - -describe('', () => { - const onUpdate = jest.fn(); - const testMonitor = { - [ConfigKey.MONITOR_TYPE]: DataStream.HTTP, - [ConfigKey.ENABLED]: true, - } as unknown as SyntheticsMonitor; - - const assertMonitorEnabled = (button: HTMLButtonElement) => - expect(button).toHaveAttribute('aria-checked', 'true'); - const assertMonitorDisabled = (button: HTMLButtonElement) => - expect(button).toHaveAttribute('aria-checked', 'false'); - - let useFetcher: jest.SpyInstance; - - beforeEach(() => { - useFetcher?.mockClear(); - useFetcher = spyOnUseFetcher({}); - }); - - it('correctly renders "enabled" state', () => { - render(); - - const switchButton = screen.getByRole('switch') as HTMLButtonElement; - assertMonitorEnabled(switchButton); - }); - - it('correctly renders "disabled" state', () => { - render( - - ); - - const switchButton = screen.getByRole('switch') as HTMLButtonElement; - assertMonitorDisabled(switchButton); - }); - - it('toggles on click', () => { - render(); - - const switchButton = screen.getByRole('switch') as HTMLButtonElement; - userEvent.click(switchButton); - assertMonitorDisabled(switchButton); - userEvent.click(switchButton); - assertMonitorEnabled(switchButton); - }); - - it('is disabled while request is in progress', () => { - useFetcher.mockReturnValue({ - data: {}, - status: FETCH_STATUS.LOADING, - refetch: () => {}, - }); - - render(); - const switchButton = screen.getByRole('switch') as HTMLButtonElement; - userEvent.click(switchButton); - - expect(switchButton).toHaveAttribute('disabled'); - }); -}); diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_list/monitor_enabled.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_list/monitor_enabled.tsx deleted file mode 100644 index 7353e3b316ebc..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_list/monitor_enabled.tsx +++ /dev/null @@ -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 { EuiSwitch, EuiProgress, EuiSwitchEvent } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; -import React, { useEffect, useState } from 'react'; -import { useKibana } from '@kbn/kibana-react-plugin/public'; -import { FETCH_STATUS, useFetcher } from '@kbn/observability-plugin/public'; -import { usePrivateLocationPermissions } from '../hooks/use_private_location_permission'; -import { - BrowserFields, - ConfigKey, - EncryptedSyntheticsMonitor, -} from '../../../../../common/runtime_types'; -import { setMonitor } from '../../../state/api'; - -interface Props { - id: string; - monitor: EncryptedSyntheticsMonitor; - onUpdate: () => void; - isDisabled?: boolean; -} - -export const MonitorEnabled = ({ id, monitor, onUpdate, isDisabled }: Props) => { - const [isEnabled, setIsEnabled] = useState(null); - - const { notifications } = useKibana(); - - const { canUpdatePrivateMonitor } = usePrivateLocationPermissions(monitor as BrowserFields); - - const { status } = useFetcher(() => { - if (isEnabled !== null) { - return setMonitor({ id, monitor: { ...monitor, [ConfigKey.ENABLED]: isEnabled } }); - } - }, [isEnabled]); - - useEffect(() => { - if (status === FETCH_STATUS.FAILURE) { - notifications.toasts.danger({ - title: ( -

- {getMonitorEnabledUpdateFailureMessage(monitor[ConfigKey.NAME])} -

- ), - toastLifeTimeMs: 3000, - }); - setIsEnabled(null); - } else if (status === FETCH_STATUS.SUCCESS) { - notifications.toasts.success({ - title: ( -

- {isEnabled - ? getMonitorEnabledSuccessLabel(monitor[ConfigKey.NAME]) - : getMonitorDisabledSuccessLabel(monitor[ConfigKey.NAME])} -

- ), - toastLifeTimeMs: 3000, - }); - onUpdate(); - } - }, [status]); // eslint-disable-line react-hooks/exhaustive-deps - - const enabled = isEnabled ?? monitor[ConfigKey.ENABLED]; - const isLoading = status === FETCH_STATUS.LOADING; - - const handleEnabledChange = (event: EuiSwitchEvent) => { - const checked = event.target.checked; - setIsEnabled(checked); - }; - - return ( -
- - {isLoading ? ( - - ) : null} -
- ); -}; - -const ENABLE_MONITOR_LABEL = i18n.translate( - 'xpack.synthetics.monitorManagement.enableMonitorLabel', - { - defaultMessage: 'Enable monitor', - } -); - -const DISABLE_MONITOR_LABEL = i18n.translate( - 'xpack.synthetics.monitorManagement.disableMonitorLabel', - { - defaultMessage: 'Disable monitor', - } -); - -const getMonitorEnabledSuccessLabel = (name: string) => - i18n.translate('xpack.synthetics.monitorManagement.monitorEnabledSuccessMessage', { - defaultMessage: 'Monitor {name} enabled successfully.', - values: { name }, - }); - -const getMonitorDisabledSuccessLabel = (name: string) => - i18n.translate('xpack.synthetics.monitorManagement.monitorDisabledSuccessMessage', { - defaultMessage: 'Monitor {name} disabled successfully.', - values: { name }, - }); - -const getMonitorEnabledUpdateFailureMessage = (name: string) => - i18n.translate('xpack.synthetics.monitorManagement.monitorEnabledUpdateFailureMessage', { - defaultMessage: 'Unable to update monitor {name}.', - values: { name }, - }); diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_list/monitor_list.test.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_list/monitor_list.test.tsx deleted file mode 100644 index 574b9536a3c41..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_list/monitor_list.test.tsx +++ /dev/null @@ -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 { screen } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; -import { waitForEuiPopoverOpen } from '@elastic/eui/lib/test/rtl'; -import React from 'react'; -import { - ConfigKey, - DataStream, - HTTPFields, - ScheduleUnit, - DEFAULT_THROTTLING, -} from '../../../../../common/runtime_types'; -import { render } from '../../../lib/helper/rtl_helpers'; -import { MonitorManagementList as MonitorManagementListState } from '../../../state/reducers/monitor_management'; -import { MonitorManagementList, MonitorManagementListPageState } from './monitor_list'; - -describe('', () => { - const onUpdate = jest.fn(); - const onPageStateChange = jest.fn(); - const monitors = []; - for (let i = 0; i < 12; i++) { - monitors.push({ - id: `test-monitor-id-${i}`, - updated_at: '123', - created_at: '123', - attributes: { - config_id: `test-monitor-id-${i}`, - name: `test-monitor-${i}`, - enabled: true, - schedule: { - unit: ScheduleUnit.MINUTES, - number: `${i * 10}`, - }, - urls: `https://test-${i}.co`, - type: DataStream.HTTP, - tags: [`tag-${i}`], - } as HTTPFields, - }); - } - const state = { - monitorManagementList: { - throttling: DEFAULT_THROTTLING, - list: { - perPage: 5, - page: 1, - total: 6, - monitors, - syncErrors: null, - absoluteTotal: 6, - }, - locations: [], - enablement: null, - error: { - serviceLocations: null, - monitorList: null, - enablement: null, - }, - loading: { - monitorList: true, - serviceLocations: false, - enablement: false, - }, - syntheticsService: { - loading: false, - signupUrl: null, - }, - } as MonitorManagementListState, - }; - - const pageState: MonitorManagementListPageState = { - pageIndex: 1, - pageSize: 10, - sortField: `${ConfigKey.NAME}.keyword`, - sortOrder: 'asc', - }; - - it.each(monitors)('navigates to edit monitor flow on edit pencil', (monitor) => { - render( - , - { state } - ); - - expect(screen.getByText(monitor.attributes.name)).toBeInTheDocument(); - expect(screen.getByText(monitor.attributes.urls)).toBeInTheDocument(); - monitor.attributes.tags.forEach((tag) => { - expect(screen.getByText(tag)).toBeInTheDocument(); - }); - expect(screen.getByText(monitor.attributes.schedule.number)).toBeInTheDocument(); - }); - - it('handles changing per page', async () => { - render( - , - { state } - ); - - userEvent.click(screen.getByTestId('tablePaginationPopoverButton')); - await waitForEuiPopoverOpen(); - - userEvent.click(screen.getByText('10 rows')); - - expect(onPageStateChange).toBeCalledWith(expect.objectContaining({ pageSize: 10 })); - }); - - it('handles refreshing and changing page when navigating to the next page', async () => { - const { getByTestId } = render( - , - { state } - ); - - userEvent.click(getByTestId('pagination-button-next')); - - expect(onPageStateChange).toBeCalledWith(expect.objectContaining({ pageIndex: 2 })); - }); -}); diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_list/monitor_list.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_list/monitor_list.tsx deleted file mode 100644 index 44c4436522cb6..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_list/monitor_list.tsx +++ /dev/null @@ -1,243 +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 { - Criteria, - EuiBasicTable, - EuiBasicTableColumn, - EuiLink, - EuiPanel, - EuiSpacer, - EuiText, - useIsWithinMinBreakpoint, -} from '@elastic/eui'; -import { EuiTableSortingType } from '@elastic/eui/src/components/basic_table/table_types'; -import { i18n } from '@kbn/i18n'; -import React, { useCallback, useContext, useMemo } from 'react'; -import { useKibana } from '@kbn/kibana-react-plugin/public'; -import { PROJECT_LABEL } from '../../../../../common/translations/translations'; -import { - CommonFields, - ConfigKey, - FetchMonitorManagementListQueryArgs, - ICMPSimpleFields, - Ping, - ServiceLocations, - EncryptedSyntheticsMonitorWithId, - TCPSimpleFields, - BrowserFields, -} from '../../../../../common/runtime_types'; -import { UptimeSettingsContext } from '../../../contexts'; -import { MonitorManagementList as MonitorManagementListState } from '../../../state/reducers/monitor_management'; -import * as labels from '../../overview/monitor_list/translations'; -import { Actions } from './actions'; -import { MonitorEnabled } from './monitor_enabled'; -import { MonitorLocations } from './monitor_locations'; -import { ManagementSettingsPortal } from './management_settings_portal'; -import { MonitorTags } from './tags'; - -export interface MonitorManagementListPageState { - pageIndex: number; - pageSize: number; - sortField: - | `${ConfigKey.URLS}.keyword` - | `${ConfigKey.NAME}.keyword` - | `${ConfigKey.MONITOR_TYPE}.keyword`; - sortOrder: NonNullable; -} - -interface Props { - pageState: MonitorManagementListPageState; - monitorList: MonitorManagementListState; - onPageStateChange: (state: MonitorManagementListPageState) => void; - onUpdate: () => void; - errorSummaries?: Ping[]; - statusSummaries?: Ping[]; -} - -export const MonitorManagementList = ({ - pageState: { pageIndex, pageSize, sortField, sortOrder }, - monitorList: { - list, - error: { monitorList: error }, - loading: { monitorList: loading }, - }, - onPageStateChange, - onUpdate, - errorSummaries, -}: Props) => { - const { basePath } = useContext(UptimeSettingsContext); - const isXl = useIsWithinMinBreakpoint('xxl'); - - const { total } = list as MonitorManagementListState['list']; - const monitors: EncryptedSyntheticsMonitorWithId[] = useMemo( - () => - list.monitors.map((monitor) => ({ - ...monitor.attributes, - })), - [list.monitors] - ); - - const handleOnChange = useCallback( - ({ - page = { index: 0, size: 10 }, - sort = { field: ConfigKey.NAME, direction: 'asc' }, - }: Criteria) => { - const { index, size } = page; - const { field, direction } = sort; - - onPageStateChange({ - pageIndex: index + 1, // page index for Saved Objects is base 1 - pageSize: size, - sortField: `${field}.keyword` as MonitorManagementListPageState['sortField'], - sortOrder: direction, - }); - }, - [onPageStateChange] - ); - - const pagination = { - pageIndex: pageIndex - 1, // page index for EuiBasicTable is base 0 - pageSize, - totalItemCount: total || 0, - pageSizeOptions: [5, 10, 25, 50, 100], - }; - - const sorting: EuiTableSortingType = { - sort: { - field: sortField.replace('.keyword', '') as keyof EncryptedSyntheticsMonitorWithId, - direction: sortOrder, - }, - }; - - const canEdit: boolean = !!useKibana().services?.application?.capabilities.uptime.save; - - const columns = [ - { - align: 'left' as const, - field: ConfigKey.NAME as string, - name: i18n.translate('xpack.synthetics.monitorManagement.monitorList.monitorName', { - defaultMessage: 'Monitor name', - }), - sortable: true, - render: (name: string, monitor: EncryptedSyntheticsMonitorWithId) => ( - - {name} - - ), - }, - { - align: 'left' as const, - field: ConfigKey.MONITOR_TYPE, - name: i18n.translate('xpack.synthetics.monitorManagement.monitorList.monitorType', { - defaultMessage: 'Monitor type', - }), - sortable: true, - }, - { - align: 'left' as const, - field: ConfigKey.TAGS, - name: i18n.translate('xpack.synthetics.monitorManagement.monitorList.tags', { - defaultMessage: 'Tags', - }), - render: (tags: string[]) => (tags ? : null), - }, - { - align: 'left' as const, - field: ConfigKey.LOCATIONS, - name: i18n.translate('xpack.synthetics.monitorManagement.monitorList.locations', { - defaultMessage: 'Locations', - }), - render: (locations: ServiceLocations) => - locations ? : null, - }, - { - align: 'left' as const, - field: ConfigKey.PROJECT_ID, - name: PROJECT_LABEL, - render: (value: string) => (value ? {value} : null), - }, - { - align: 'left' as const, - field: ConfigKey.SCHEDULE, - name: i18n.translate('xpack.synthetics.monitorManagement.monitorList.schedule', { - defaultMessage: 'Frequency (min)', - }), - render: (schedule: CommonFields[ConfigKey.SCHEDULE]) => schedule?.number, - }, - { - align: 'left' as const, - field: ConfigKey.URLS, - name: i18n.translate('xpack.synthetics.monitorManagement.monitorList.URL', { - defaultMessage: 'URL', - }), - sortable: true, - render: (urls: string, { hosts }: TCPSimpleFields | ICMPSimpleFields) => urls || hosts, - textOnly: true, - }, - { - align: 'left' as const, - field: ConfigKey.ENABLED as string, - name: i18n.translate('xpack.synthetics.monitorManagement.monitorList.enabled', { - defaultMessage: 'Enabled', - }), - render: (_enabled: boolean, monitor: EncryptedSyntheticsMonitorWithId) => ( - - ), - }, - { - align: 'left' as const, - name: i18n.translate('xpack.synthetics.monitorManagement.monitorList.actions', { - defaultMessage: 'Actions', - }), - render: (fields: EncryptedSyntheticsMonitorWithId) => ( - - ), - }, - ] as Array>; - - return ( - - - - - - ); -}; diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_list/monitor_list_container.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_list/monitor_list_container.tsx deleted file mode 100644 index fee3cd84ab120..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_list/monitor_list_container.tsx +++ /dev/null @@ -1,97 +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, Dispatch } from 'react'; -import { useSelector } from 'react-redux'; -import { useParams } from 'react-router-dom'; -import { useTrackPageview } from '@kbn/observability-plugin/public'; -import { useLocations } from '../hooks/use_locations'; -import { EmptyLocations } from '../manage_locations/empty_locations'; -import { monitorManagementListSelector } from '../../../state/selectors'; -import { MonitorAsyncError } from './monitor_async_error'; -import { useInlineErrors } from '../hooks/use_inline_errors'; -import { MonitorListTabs } from './list_tabs'; -import { AllMonitors } from './all_monitors'; -import { InvalidMonitors } from './invalid_monitors'; -import { useInvalidMonitors } from '../hooks/use_invalid_monitors'; -import { MonitorManagementListPageState } from './monitor_list'; -import { MonitorManagementPageAction } from '../hooks/use_monitor_list'; - -export const MonitorListContainer = ({ - isEnabled, - pageState, - dispatchPageAction, -}: { - isEnabled?: boolean; - pageState: MonitorManagementListPageState; - dispatchPageAction: Dispatch; -}) => { - const onPageStateChange = useCallback( - (state) => { - dispatchPageAction({ type: 'update', payload: state }); - }, - [dispatchPageAction] - ); - - const onUpdate = useCallback(() => { - dispatchPageAction({ type: 'refresh' }); - }, [dispatchPageAction]); - - useTrackPageview({ app: 'uptime', path: 'monitors' }); - useTrackPageview({ app: 'uptime', path: 'monitors', delay: 15000 }); - - const monitorList = useSelector(monitorManagementListSelector); - - const { type: viewType = 'all' } = useParams<{ type: 'all' | 'invalid' }>(); - const { errorSummaries, loading, count } = useInlineErrors({ - onlyInvalidMonitors: viewType === 'invalid', - sortField: pageState.sortField, - sortOrder: pageState.sortOrder, - }); - - const { data: monitorSavedObjects, loading: objectsLoading } = useInvalidMonitors(errorSummaries); - - const { locations } = useLocations(); - - if (!isEnabled && monitorList.list.total === 0) { - return null; - } - - if (isEnabled && monitorList.list.total === 0 && locations.length === 0) { - return ; - } - - return ( - <> - - - {viewType === 'all' ? ( - - ) : ( - - )} - - ); -}; diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_list/monitor_locations.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_list/monitor_locations.tsx deleted file mode 100644 index 5f9ad96bca464..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_list/monitor_locations.tsx +++ /dev/null @@ -1,51 +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 } from 'react'; -import { EuiBadge, EuiBadgeGroup } from '@elastic/eui'; -import { ServiceLocations, ServiceLocation } from '../../../../../common/runtime_types'; -import { EXPAND_LOCATIONS_LABEL } from '../../overview/monitor_list/columns/translations'; -import { useLocations } from '../hooks/use_locations'; - -interface Props { - locations: ServiceLocations; -} - -const INITIAL_LIMIT = 3; - -export const MonitorLocations = ({ locations }: Props) => { - const { locations: allLocations } = useLocations(); - const [toDisplay, setToDisplay] = useState(INITIAL_LIMIT); - - const locationsToDisplay = locations.slice(0, toDisplay); - - return ( - - {locationsToDisplay.map((location: ServiceLocation) => ( - - {`${allLocations.find((loc) => loc.id === location.id)?.label}`} - - ))} - {locations.length > toDisplay && ( - { - setToDisplay(locations.length); - }} - onClickAriaLabel={EXPAND_LOCATIONS_LABEL} - > - +{locations.length - INITIAL_LIMIT} - - )} - - ); -}; diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_list/stderr_logs_popover.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_list/stderr_logs_popover.tsx deleted file mode 100644 index bdafa20adfcac..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_list/stderr_logs_popover.tsx +++ /dev/null @@ -1,58 +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 { EuiPopover } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; -import styled from 'styled-components'; -import { StdErrorLogs } from '../../synthetics/check_steps/stderr_logs'; - -export const StdErrorPopover = ({ - checkGroup, - button, - isOpen, - setIsOpen, - summaryMessage, -}: { - isOpen: boolean; - setIsOpen: (val: boolean) => void; - checkGroup: string; - summaryMessage?: string; - button: JSX.Element; -}) => { - return ( - setIsOpen(false)} button={button}> - - - - - ); -}; - -const Container = styled.div` - width: 650px; - height: 400px; - overflow: scroll; -`; - -export const getInlineErrorLabel = (message?: string) => { - return i18n.translate('xpack.synthetics.monitorList.statusColumn.error.messageLabel', { - defaultMessage: '{message}. Click for more details.', - values: { message }, - }); -}; - -export const ERROR_LOGS_LABEL = i18n.translate( - 'xpack.synthetics.monitorList.statusColumn.error.logs', - { - defaultMessage: 'Error logs', - } -); diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_list/tags.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_list/tags.tsx deleted file mode 100644 index be2baab760391..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_list/tags.tsx +++ /dev/null @@ -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, { useState } from 'react'; -import { EuiBadge, EuiBadgeGroup } from '@elastic/eui'; -import { EXPAND_TAGS_LABEL } from '../../overview/monitor_list/columns/translations'; - -interface Props { - tags: string[]; -} - -export const MonitorTags = ({ tags }: Props) => { - const [toDisplay, setToDisplay] = useState(5); - - const tagsToDisplay = tags.slice(0, toDisplay); - - return ( - - {tagsToDisplay.map((tag) => ( - // filtering only makes sense in monitor list, where we have summary - - {tag} - - ))} - {tags.length > toDisplay && ( - { - setToDisplay(tags.length); - }} - onClickAriaLabel={EXPAND_TAGS_LABEL} - > - +{tags.length - 5} - - )} - - ); -}; diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/test_now_mode/browser/browser_test_results.test.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/test_now_mode/browser/browser_test_results.test.tsx deleted file mode 100644 index 6b66fbb68cc3e..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/test_now_mode/browser/browser_test_results.test.tsx +++ /dev/null @@ -1,271 +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 } from '@testing-library/react'; -import { render } from '../../../../lib/helper/rtl_helpers'; -import { kibanaService } from '../../../../state/kibana_service'; -import * as runOnceHooks from './use_browser_run_once_monitors'; -import { BrowserTestRunResult } from './browser_test_results'; -import { fireEvent } from '@testing-library/dom'; - -describe('BrowserTestRunResult', function () { - const onDone = jest.fn(); - let testId: string; - - beforeEach(() => { - testId = 'test-id'; - jest.resetAllMocks(); - }); - - it('should render properly', async function () { - render( - - ); - expect(await screen.findByText('Test result')).toBeInTheDocument(); - expect(await screen.findByText('0 steps completed')).toBeInTheDocument(); - const dataApi = (kibanaService.core as any).data.search; - - expect(dataApi.search).toHaveBeenCalledTimes(1); - expect(dataApi.search).toHaveBeenLastCalledWith( - { - params: { - body: { - query: { - bool: { - filter: [ - { term: { config_id: testId } }, - { - terms: { - 'synthetics.type': ['heartbeat/summary', 'journey/start'], - }, - }, - ], - }, - }, - sort: [{ '@timestamp': 'desc' }], - }, - index: 'synthetics-*', - size: 1000, - }, - }, - { legacyHitsTotal: false } - ); - }); - - it('should display results', async function () { - jest.spyOn(runOnceHooks, 'useBrowserRunOnceMonitors').mockReturnValue({ - data, - summariesLoading: false, - stepLoadingInProgress: false, - expectedSummariesLoaded: true, - lastUpdated: Date.now(), - checkGroupResults: [ - { - checkGroupId: 'c01406bf-7467-11ec-9858-aa31996e0afe', - stepsLoading: false, - journeyStarted: true, - summaryDoc: summaryDoc._source, - journeyDoc: summaryDoc._source, - steps: [stepEndDoc._source], - completedSteps: 1, - }, - ], - }); - - render( - - ); - - expect(await screen.findByText('Test result')).toBeInTheDocument(); - - expect(await screen.findByText('COMPLETED')).toBeInTheDocument(); - expect(await screen.findByText('Took 22 seconds')).toBeInTheDocument(); - expect(await screen.findByText('1 step completed')).toBeInTheDocument(); - - fireEvent.click(await screen.findByTestId('expandResults')); - - expect(await screen.findByText('Go to https://www.elastic.co/')).toBeInTheDocument(); - expect(await screen.findByText('21.8 seconds')).toBeInTheDocument(); - - // Calls onDone on completion - expect(onDone).toHaveBeenCalled(); - }); -}); - -const journeyStartDoc = { - _index: '.ds-synthetics-browser-default-2022.01.11-000002', - _id: 'J1pLU34B6BrWThBwS4Fb', - _score: null, - _source: { - agent: { - name: 'job-78df368e085a796b-x9cbm', - id: 'df497635-644b-43ba-97a6-2f4dce1ea93b', - type: 'heartbeat', - ephemeral_id: 'e24d9e65-ae5f-4088-9a79-01dd504a1403', - version: '8.0.0', - }, - package: { name: '@elastic/synthetics', version: '1.0.0-beta.17' }, - os: { platform: 'linux' }, - synthetics: { - package_version: '1.0.0-beta.17', - journey: { name: 'inline', id: 'inline' }, - payload: { - source: - 'async ({ page, context, browser, params }) => {\n scriptFn.apply(null, [core_1.step, page, context, browser, params, expect_1.expect]);\n }', - params: {}, - }, - index: 0, - type: 'journey/start', - }, - monitor: { - name: 'Test Browser monitor - inline', - id: '3e11e70a-41b9-472c-a465-7c9b76b1a085-inline', - timespan: { lt: '2022-01-13T11:58:49.463Z', gte: '2022-01-13T11:55:49.463Z' }, - check_group: 'c01406bf-7467-11ec-9858-aa31996e0afe', - type: 'browser', - }, - '@timestamp': '2022-01-13T11:55:49.462Z', - ecs: { version: '8.0.0' }, - config_id: '3e11e70a-41b9-472c-a465-7c9b76b1a085', - data_stream: { namespace: 'default', type: 'synthetics', dataset: 'browser' }, - run_once: true, - event: { - agent_id_status: 'auth_metadata_missing', - ingested: '2022-01-13T11:55:50Z', - dataset: 'browser', - }, - }, - sort: [1642074949462], -}; - -const summaryDoc: any = { - _index: '.ds-synthetics-browser-default-2022.01.11-000002', - _id: 'Ix5LU34BPllLwAMpqlfi', - _score: null, - _source: { - summary: { up: 1, down: 0 }, - agent: { - name: 'job-78df368e085a796b-x9cbm', - id: 'df497635-644b-43ba-97a6-2f4dce1ea93b', - type: 'heartbeat', - ephemeral_id: 'e24d9e65-ae5f-4088-9a79-01dd504a1403', - version: '8.0.0', - }, - synthetics: { - journey: { name: 'inline', id: 'inline', tags: null }, - type: 'heartbeat/summary', - }, - monitor: { - duration: { us: 21754383 }, - name: 'Test Browser monitor - inline', - check_group: 'c01406bf-7467-11ec-9858-aa31996e0afe', - id: '3e11e70a-41b9-472c-a465-7c9b76b1a085-inline', - timespan: { lt: '2022-01-13T11:59:13.567Z', gte: '2022-01-13T11:56:13.567Z' }, - type: 'browser', - status: 'up', - }, - url: { - path: '/', - scheme: 'https', - port: 443, - domain: 'www.elastic.co', - full: 'https://www.elastic.co/', - }, - '@timestamp': '2022-01-13T11:56:11.217Z', - ecs: { version: '8.0.0' }, - config_id: '3e11e70a-41b9-472c-a465-7c9b76b1a085', - data_stream: { namespace: 'default', type: 'synthetics', dataset: 'browser' }, - run_once: true, - event: { - agent_id_status: 'auth_metadata_missing', - ingested: '2022-01-13T11:56:14Z', - dataset: 'browser', - }, - }, - sort: [1642074971217], -}; - -const stepEndDoc: any = { - _index: '.ds-synthetics-browser-default-2022.01.11-000002', - _id: 'M1pLU34B6BrWThBwoIGk', - _score: null, - _source: { - agent: { - name: 'job-78df368e085a796b-x9cbm', - id: 'df497635-644b-43ba-97a6-2f4dce1ea93b', - ephemeral_id: 'e24d9e65-ae5f-4088-9a79-01dd504a1403', - type: 'heartbeat', - version: '8.0.0', - }, - package: { name: '@elastic/synthetics', version: '1.0.0-beta.17' }, - os: { platform: 'linux' }, - synthetics: { - package_version: '1.0.0-beta.17', - journey: { name: 'inline', id: 'inline' }, - payload: { - source: "async () => {\n await page.goto('https://www.elastic.co/');\n}", - url: 'https://www.elastic.co/', - status: 'succeeded', - }, - index: 12, - step: { - duration: { us: 21751370 }, - name: 'Go to https://www.elastic.co/', - index: 1, - status: 'succeeded', - }, - type: 'step/end', - }, - monitor: { - name: 'Test Browser monitor - inline', - id: '3e11e70a-41b9-472c-a465-7c9b76b1a085-inline', - timespan: { lt: '2022-01-13T11:59:11.250Z', gte: '2022-01-13T11:56:11.250Z' }, - check_group: 'c01406bf-7467-11ec-9858-aa31996e0afe', - type: 'browser', - }, - url: { - path: '/', - scheme: 'https', - port: 443, - domain: 'www.elastic.co', - full: 'https://www.elastic.co/', - }, - '@timestamp': '2022-01-13T11:56:11.216Z', - ecs: { version: '8.0.0' }, - config_id: '3e11e70a-41b9-472c-a465-7c9b76b1a085', - data_stream: { namespace: 'default', type: 'synthetics', dataset: 'browser' }, - run_once: true, - event: { - agent_id_status: 'auth_metadata_missing', - ingested: '2022-01-13T11:56:12Z', - dataset: 'browser', - }, - }, - sort: [1642074971216], -}; - -const data: any = { - took: 4, - timed_out: false, - _shards: { total: 8, successful: 8, skipped: 2, failed: 0 }, - hits: { - total: 3, - max_score: null, - hits: [journeyStartDoc, stepEndDoc, summaryDoc], - }, -}; diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/test_now_mode/browser/browser_test_results.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/test_now_mode/browser/browser_test_results.tsx deleted file mode 100644 index c1aa80e88ccdc..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/test_now_mode/browser/browser_test_results.tsx +++ /dev/null @@ -1,139 +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 { useEffect } from 'react'; -import * as React from 'react'; -import { EuiAccordion, EuiText, EuiFlexGroup, EuiFlexItem, EuiLoadingSpinner } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; -import styled from 'styled-components'; -import { StepsList } from '../../../synthetics/check_steps/steps_list'; -import { CheckGroupResult, useBrowserRunOnceMonitors } from './use_browser_run_once_monitors'; -import { TestResultHeader } from '../test_result_header'; -import { StdErrorLogs } from '../../../synthetics/check_steps/stderr_logs'; - -interface Props { - monitorId: string; - isMonitorSaved: boolean; - expectPings: number; - onDone: () => void; -} -export const BrowserTestRunResult = ({ monitorId, isMonitorSaved, expectPings, onDone }: Props) => { - const { summariesLoading, expectedSummariesLoaded, stepLoadingInProgress, checkGroupResults } = - useBrowserRunOnceMonitors({ - configId: monitorId, - expectSummaryDocs: expectPings, - }); - - useEffect(() => { - if (expectedSummariesLoaded) { - onDone(); - } - }, [onDone, expectedSummariesLoaded]); - - return ( - <> - {checkGroupResults.map((checkGroupResult) => { - const { checkGroupId, journeyStarted, summaryDoc, stepsLoading, steps, completedSteps } = - checkGroupResult; - const isStepsLoading = !summariesLoading && journeyStarted && summaryDoc && stepsLoading; - const isStepsLoadingFailed = - summaryDoc && !summariesLoading && !stepLoadingInProgress && steps.length === 0; - - return ( - - {isStepsLoading && ( - - - {LOADING_STEPS} - - - - - - )} - {isStepsLoadingFailed && ( - {summaryDoc?.error?.message ?? FAILED_TO_RUN} - )} - - {isStepsLoadingFailed && - summaryDoc?.error?.message?.includes('journey did not finish executing') && ( - - )} - - {completedSteps > 0 && ( - - )} - - ); - })} - - ); -}; - -const AccordionWrapper = styled(EuiAccordion)` - .euiAccordion__buttonContent { - width: 100%; - } -`; - -function getButtonContent({ - journeyDoc, - summaryDoc, - checkGroupId, - journeyStarted, - completedSteps, -}: CheckGroupResult) { - return ( -
- - -

- - {i18n.translate('xpack.synthetics.monitorManagement.stepCompleted', { - defaultMessage: - '{stepCount, number} {stepCount, plural, one {step} other {steps}} completed', - values: { - stepCount: completedSteps ?? 0, - }, - })} - -

-
-
- ); -} - -const FAILED_TO_RUN = i18n.translate('xpack.synthetics.monitorManagement.failedRun', { - defaultMessage: 'Failed to run steps', -}); - -const LOADING_STEPS = i18n.translate('xpack.synthetics.monitorManagement.loadingSteps', { - defaultMessage: 'Loading steps...', -}); diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/test_now_mode/browser/use_browser_run_once_monitors.test.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/test_now_mode/browser/use_browser_run_once_monitors.test.tsx deleted file mode 100644 index 332af4403e180..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/test_now_mode/browser/use_browser_run_once_monitors.test.tsx +++ /dev/null @@ -1,59 +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 { renderHook } from '@testing-library/react-hooks'; -import { useBrowserRunOnceMonitors } from './use_browser_run_once_monitors'; -import * as resultHook from './use_browser_run_once_monitors'; -import { WrappedHelper } from '../../../../lib/helper/rtl_helpers'; - -describe('useBrowserRunOnceMonitors', function () { - it('should return results as expected', function () { - jest.spyOn(resultHook, 'useBrowserEsResults').mockReturnValue({ - loading: false, - data: { - took: 4, - timed_out: false, - _shards: { total: 8, successful: 8, skipped: 2, failed: 0 }, - hits: { - total: { value: 3, relation: 'eq' }, - max_score: null, - hits: [], - }, - }, - }); - - const { result } = renderHook( - () => - useBrowserRunOnceMonitors({ - configId: 'test-id', - testRunId: 'test-run-id', - expectSummaryDocs: 1, - }), - { - wrapper: WrappedHelper, - } - ); - - expect(result.current).toEqual({ - data: undefined, - expectedSummariesLoaded: false, - lastUpdated: expect.any(Number), - stepLoadingInProgress: true, - summariesLoading: true, - checkGroupResults: [ - { - checkGroupId: 'placeholder-check-group-0', - completedSteps: 0, - steps: [], - summaryDoc: undefined, - journeyStarted: false, - stepsLoading: true, - }, - ], - }); - }); -}); diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/test_now_mode/browser/use_browser_run_once_monitors.ts b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/test_now_mode/browser/use_browser_run_once_monitors.ts deleted file mode 100644 index 39215ab22fdf0..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/test_now_mode/browser/use_browser_run_once_monitors.ts +++ /dev/null @@ -1,284 +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 { useEffect, useState, useRef } from 'react'; -import { createEsParams, useEsSearch, useFetcher } from '@kbn/observability-plugin/public'; -import { JourneyStep } from '../../../../../../common/runtime_types'; -import { useTickTick } from '../use_tick_tick'; -import { fetchJourneySteps } from '../../../../state/api/journey'; -import { isStepEnd } from '../../../synthetics/check_steps/steps_list'; -import { SYNTHETICS_INDEX_PATTERN } from '../../../../../../common/constants'; - -export interface CheckGroupResult { - checkGroupId: string; - journeyStarted: boolean; - journeyDoc?: JourneyStep; - summaryDoc?: JourneyStep; - steps: JourneyStep[]; - stepsLoading: boolean; - completedSteps: number; -} - -export const useBrowserEsResults = ({ - configId, - testRunId, - lastRefresh, -}: { - configId: string; - testRunId?: string; - lastRefresh: number; -}) => { - return useEsSearch( - createEsParams({ - index: SYNTHETICS_INDEX_PATTERN, - body: { - sort: [ - { - '@timestamp': 'desc', - }, - ], - query: { - bool: { - filter: [ - { - term: { - config_id: configId, - }, - }, - { - terms: { - 'synthetics.type': ['heartbeat/summary', 'journey/start'], - }, - }, - ...(testRunId - ? [ - { - term: { - test_run_id: testRunId, - }, - }, - ] - : []), - ], - }, - }, - }, - size: 1000, - }), - [configId, lastRefresh], - { name: 'TestRunData' } - ); -}; - -export const useBrowserRunOnceMonitors = ({ - configId, - testRunId, - skipDetails = false, - refresh = true, - expectSummaryDocs, -}: { - configId: string; - testRunId?: string; - refresh?: boolean; - skipDetails?: boolean; - expectSummaryDocs: number; -}) => { - const { refreshTimer, lastRefresh } = useTickTick(5 * 1000, refresh); - - const [checkGroupResults, setCheckGroupResults] = useState(() => { - return new Array(expectSummaryDocs) - .fill({ - checkGroupId: '', - journeyStarted: false, - steps: [], - stepsLoading: false, - completedSteps: 0, - } as CheckGroupResult) - .map((emptyCheckGroup, index) => ({ - ...emptyCheckGroup, - checkGroupId: `placeholder-check-group-${index}`, - })); - }); - - const lastUpdated = useRef<{ checksum: string; time: number }>({ - checksum: '', - time: Date.now(), - }); - - const { data, loading: summariesLoading } = useBrowserEsResults({ - configId, - testRunId, - lastRefresh, - }); - - useEffect(() => { - const hits = data?.hits.hits; - - if (hits && hits.length > 0) { - const allDocs = (hits ?? []).map(({ _source }) => _source as JourneyStep); - const checkGroupsById = allDocs - .filter( - (doc) => - doc.synthetics?.type === 'journey/start' || doc.synthetics?.type === 'heartbeat/summary' - ) - .reduce( - (acc, cur) => ({ - ...acc, - [cur.monitor.check_group]: { - checkGroupId: cur.monitor.check_group, - journeyStarted: true, - journeyDoc: cur, - summaryDoc: null, - steps: [], - stepsLoading: false, - completedSteps: 0, - }, - }), - {} - ) as Record; - - allDocs.forEach((step) => { - if (step.synthetics?.type === 'heartbeat/summary') { - checkGroupsById[step.monitor.check_group].summaryDoc = step; - } - }); - - const checkGroups = Object.values(checkGroupsById); - const finishedCheckGroups = checkGroups.filter((group) => !!group.summaryDoc); - - if (finishedCheckGroups.length >= expectSummaryDocs) { - clearInterval(refreshTimer); - } - - replaceCheckGroupResults(checkGroups); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [expectSummaryDocs, data, refreshTimer]); - - // Loading steps for browser runs - const checkGroupIds = checkGroupResults.map(({ checkGroupId }) => checkGroupId); - const checkGroupCheckSum = checkGroupIds.reduce((acc, cur) => acc + cur, ''); - const { loading: stepLoadingInProgress } = useFetcher(() => { - if (checkGroupIds.length && !skipDetails) { - setCheckGroupResults((prevState) => { - return prevState.map((result) => ({ ...result, stepsLoading: true })); - }); - - return Promise.all( - checkGroupIds.map((id) => { - return fetchJourneySteps({ - checkGroup: id, - }) - .then((stepsData) => { - updateCheckGroupResult(stepsData.checkGroup, { - steps: stepsData.steps, - completedSteps: stepsData.steps.filter(isStepEnd).length, - }); - - return stepsData; - }) - .finally(() => { - updateCheckGroupResult(id, { - stepsLoading: false, - }); - }); - }) - ); - } - - return Promise.resolve(null); - }, [checkGroupCheckSum, setCheckGroupResults, lastRefresh]); - - // Whenever a new found document is fetched, update lastUpdated - useEffect(() => { - const currentChecksum = getCheckGroupChecksum(checkGroupResults); - if (checkGroupCheckSum !== lastUpdated.current.checksum) { - // Mutating lastUpdated - lastUpdated.current.checksum = currentChecksum; - lastUpdated.current.time = Date.now(); - } - }, [checkGroupResults, checkGroupCheckSum]); - - const updateCheckGroupResult = (id: string, result: Partial) => { - setCheckGroupResults((prevState) => { - return prevState.map((r) => { - if (id !== r.checkGroupId) { - return r; - } - - return mergeCheckGroups(r, result); - }) as CheckGroupResult[]; - }); - }; - - const replaceCheckGroupResults = (curCheckGroups: CheckGroupResult[]) => { - const emptyCheckGroups = checkGroupResults.filter((group) => - group.checkGroupId.startsWith('placeholder-check-group') - ); - - // Padding the collection with placeholders so that rows could be shown on UI with loading state - const paddedCheckGroups = - curCheckGroups.length < expectSummaryDocs - ? [ - ...curCheckGroups, - ...emptyCheckGroups.slice(-1 * (expectSummaryDocs - curCheckGroups.length)), - ] - : curCheckGroups; - - setCheckGroupResults((prevCheckGroups) => { - const newIds = paddedCheckGroups.map(({ checkGroupId }) => checkGroupId); - const newById: Record = paddedCheckGroups.reduce( - (acc, cur) => ({ ...acc, [cur.checkGroupId]: cur }), - {} - ); - const oldById: Record = prevCheckGroups.reduce( - (acc, cur) => ({ ...acc, [cur.checkGroupId]: cur }), - {} - ); - - return newIds.map((id) => mergeCheckGroups(oldById[id], newById[id])); - }); - }; - - return { - data, - summariesLoading, - stepLoadingInProgress, - expectedSummariesLoaded: - checkGroupResults.filter(({ summaryDoc }) => !!summaryDoc).length >= expectSummaryDocs, - checkGroupResults, - lastUpdated: lastUpdated.current.time, - }; -}; - -function mergeCheckGroups(prev: CheckGroupResult, curr: Partial) { - // Once completed steps has been determined and shown, don't lower the number on UI due to re-fetch - const completedSteps = curr.completedSteps - ? Math.max(prev?.completedSteps ?? 0, curr.completedSteps ?? 0) - : prev?.completedSteps ?? 0; - - let steps = curr.steps ?? []; - if (steps.length === 0 && (prev?.steps ?? []).length > 0) { - steps = prev.steps; - } - - return { - ...(prev ?? {}), - ...curr, - steps, - completedSteps, - }; -} - -function getCheckGroupChecksum(checkGroupResults: CheckGroupResult[]) { - return checkGroupResults.reduce((acc, cur) => { - return ( - acc + cur?.journeyDoc?._id ?? - '' + cur?.summaryDoc?._id ?? - '' + (cur?.steps ?? []).reduce((stepAcc, { _id }) => stepAcc + _id, '') - ); - }, ''); -} diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/test_now_mode/simple/simple_test_results.test.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/test_now_mode/simple/simple_test_results.test.tsx deleted file mode 100644 index 44441513e2a11..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/test_now_mode/simple/simple_test_results.test.tsx +++ /dev/null @@ -1,217 +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 } from '@testing-library/react'; -import { render } from '../../../../lib/helper/rtl_helpers'; -import { SimpleTestResults } from './simple_test_results'; -import { kibanaService } from '../../../../state/kibana_service'; -import * as runOnceHooks from './use_simple_run_once_monitors'; -import { Ping } from '../../../../../../common/runtime_types'; - -describe('SimpleTestResults', function () { - const onDone = jest.fn(); - let testId: string; - - beforeEach(() => { - testId = 'test-id'; - jest.resetAllMocks(); - }); - - it('should render properly', async function () { - render(); - expect(await screen.findByText('Test result')).toBeInTheDocument(); - const dataApi = (kibanaService.core as any).data.search; - - expect(dataApi.search).toHaveBeenCalledTimes(1); - expect(dataApi.search).toHaveBeenLastCalledWith( - { - params: { - body: { - query: { - bool: { - filter: [{ term: { config_id: testId } }, { exists: { field: 'summary' } }], - }, - }, - sort: [{ '@timestamp': 'desc' }], - }, - index: 'synthetics-*', - size: 1000, - }, - }, - { legacyHitsTotal: false } - ); - }); - - it('should displays results', async function () { - const doc = data.hits.hits[0]; - jest.spyOn(runOnceHooks, 'useSimpleRunOnceMonitors').mockReturnValue({ - data: data as any, - summaryDocs: [ - { - ...(doc._source as unknown as Ping), - timestamp: (doc._source as unknown as Record)?.['@timestamp'], - docId: doc._id, - }, - ], - loading: false, - lastUpdated: Date.now(), - }); - - render(); - - expect(await screen.findByText('Test result')).toBeInTheDocument(); - - expect(await screen.findByText('COMPLETED')).toBeInTheDocument(); - expect(await screen.findByText('191 ms')).toBeInTheDocument(); - expect(await screen.findByText('151.101.2.217')).toBeInTheDocument(); - expect(await screen.findByText('Checked Jan 12, 2022 11:54:27 AM')).toBeInTheDocument(); - expect(await screen.findByText('Took 191 ms')).toBeInTheDocument(); - - // Calls onDone on completion - expect(onDone).toHaveBeenCalled(); - - screen.debug(); - }); -}); - -const data = { - took: 201, - timed_out: false, - _shards: { total: 8, successful: 8, skipped: 0, failed: 0 }, - hits: { - total: 1, - max_score: null, - hits: [ - { - _index: '.ds-synthetics-http-default-2022.01.11-000002', - _id: '6h42T34BPllLwAMpWCjo', - _score: null, - _source: { - tcp: { rtt: { connect: { us: 11480 } } }, - summary: { up: 1, down: 0 }, - agent: { - name: 'job-2b730ffa8811ff-knvmz', - id: 'a3ed3007-4261-40a9-ad08-7a8384cce7f5', - type: 'heartbeat', - ephemeral_id: '7ffe97e3-15a3-4d76-960e-8f0488998e3c', - version: '8.0.0', - }, - resolve: { rtt: { us: 46753 }, ip: '151.101.2.217' }, - monitor: { - duration: { us: 191528 }, - ip: '151.101.2.217', - name: 'Elastic HTTP', - check_group: '4d7fd600-73c8-11ec-b035-2621090844ff', - id: 'e5a3a871-b5c3-49cf-a798-3860028e7a6b', - timespan: { lt: '2022-01-12T16:57:27.443Z', gte: '2022-01-12T16:54:27.443Z' }, - type: 'http', - status: 'up', - }, - url: { - scheme: 'https', - port: 443, - domain: 'www.elastic.co', - full: 'https://www.elastic.co', - }, - '@timestamp': '2022-01-12T16:54:27.252Z', - ecs: { version: '8.0.0' }, - config_id: 'e5a3a871-b5c3-49cf-a798-3860028e7a6b', - data_stream: { namespace: 'default', type: 'synthetics', dataset: 'http' }, - run_once: true, - http: { - rtt: { - response_header: { us: 61231 }, - total: { us: 144630 }, - write_request: { us: 93 }, - content: { us: 25234 }, - validate: { us: 86466 }, - }, - response: { - headers: { - 'X-Dns-Prefetch-Control': 'off', - Server: 'my-server', - 'Access-Control-Allow-Origin': '*', - 'X-Timer': 'S1642006467.362040,VS0,VE51', - 'Referrer-Policy': 'strict-origin-when-cross-origin', - 'X-Frame-Options': 'SAMEORIGIN', - 'Strict-Transport-Security': 'max-age=0', - Etag: '"29d46-xv8YFxCD32Ncbzip9bXU5q9QSvg"', - 'X-Served-By': 'cache-sea4462-SEA, cache-pwk4941-PWK', - 'Content-Security-Policy': - "frame-ancestors 'self' https://*.elastic.co https://elasticsandbox.docebosaas.com https://elastic.docebosaas.com https://www.gather.town;", - 'Set-Cookie': - 'euid=2b70f3d5-56bc-49f1-a64f-50d352914207; Expires=Tuesday, 19 January 2038 01:00:00 GMT; Path=/; Domain=.elastic.co;', - 'X-Change-Language': 'true', - 'Content-Length': '171334', - Age: '1591', - 'Content-Type': 'text/html; charset=utf-8', - 'X-Powered-By': 'Next.js', - 'X-Cache': 'HIT, MISS', - 'X-Content-Type-Options': 'nosniff', - 'X-Download-Options': 'noopen', - Date: 'Wed, 12 Jan 2022 16:54:27 GMT', - Via: '1.1 varnish, 1.1 varnish', - 'Accept-Ranges': 'bytes', - 'Cache-Control': 'max-age=86400', - 'X-Xss-Protection': '1; mode=block', - Vary: 'Accept-Language, X-Change-Language, Accept-Encoding', - 'Elastic-Vi': '2b70f3d5-56bc-49f1-a64f-50d352914207', - 'X-Cache-Hits': '425, 0', - }, - status_code: 200, - mime_type: 'text/html; charset=utf-8', - body: { - bytes: 171334, - hash: '29e5b1a1949dc4d253399874b161049030639d70c5164a5235e039bb4b95f9fd', - }, - }, - }, - tls: { - established: true, - cipher: 'ECDHE-RSA-AES-128-GCM-SHA256', - certificate_not_valid_before: '2021-11-26T19:42:12.000Z', - server: { - x509: { - not_after: '2022-12-28T19:42:11.000Z', - public_key_exponent: 65537, - not_before: '2021-11-26T19:42:12.000Z', - subject: { - distinguished_name: 'CN=www.elastic.co', - common_name: 'www.elastic.co', - }, - public_key_algorithm: 'RSA', - signature_algorithm: 'SHA256-RSA', - public_key_size: 2048, - serial_number: '2487880865947729006738430997169012636', - issuer: { - distinguished_name: - 'CN=GlobalSign Atlas R3 DV TLS CA H2 2021,O=GlobalSign nv-sa,C=BE', - common_name: 'GlobalSign Atlas R3 DV TLS CA H2 2021', - }, - }, - hash: { - sha1: '21099729d121d9707ca6c1b642032a97ea2dcb74', - sha256: '55715c58c7e0939aa9b8989df59082ce33c1b274678e7913fd0c269f33103b02', - }, - }, - rtt: { handshake: { us: 46409 } }, - version: '1.2', - certificate_not_valid_after: '2022-12-28T19:42:11.000Z', - version_protocol: 'tls', - }, - event: { - agent_id_status: 'auth_metadata_missing', - ingested: '2022-01-12T16:54:28Z', - dataset: 'http', - }, - }, - sort: [1642006467252], - }, - ], - }, -}; diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/test_now_mode/simple/simple_test_results.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/test_now_mode/simple/simple_test_results.tsx deleted file mode 100644 index 580c936272b59..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/test_now_mode/simple/simple_test_results.tsx +++ /dev/null @@ -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, { useEffect, useState } from 'react'; -import { useSimpleRunOnceMonitors } from './use_simple_run_once_monitors'; -import { Ping } from '../../../../../../common/runtime_types'; -import { PingListTable } from '../../../monitor/ping_list/ping_list_table'; -import { TestResultHeader } from '../test_result_header'; - -interface Props { - monitorId: string; - expectPings: number; - onDone: () => void; -} -export function SimpleTestResults({ monitorId, expectPings, onDone }: Props) { - const [summaryDocsCache, setSummaryDocsCache] = useState([]); - const { summaryDocs, loading } = useSimpleRunOnceMonitors({ - configId: monitorId, - expectSummaryDocs: expectPings, - }); - - useEffect(() => { - if (summaryDocs) { - setSummaryDocsCache((prevState: Ping[]) => { - const prevById: Record = prevState.reduce( - (acc, cur) => ({ ...acc, [cur.docId]: cur }), - {} - ); - return summaryDocs.map((updatedDoc) => ({ - ...updatedDoc, - ...(prevById[updatedDoc.docId] ?? {}), - })); - }); - - if (summaryDocs.length >= expectPings) { - onDone(); - } - } - }, [expectPings, summaryDocs, onDone]); - - return ( - <> - = expectPings)} - /> - {summaryDocs && } - - ); -} diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/test_now_mode/simple/use_simple_run_once_monitors.ts b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/test_now_mode/simple/use_simple_run_once_monitors.ts deleted file mode 100644 index f0282aaad47a2..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/test_now_mode/simple/use_simple_run_once_monitors.ts +++ /dev/null @@ -1,108 +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, useRef } from 'react'; -import { createEsParams, useEsSearch } from '@kbn/observability-plugin/public'; -import { Ping } from '../../../../../../common/runtime_types'; -import { useTickTick } from '../use_tick_tick'; -import { SYNTHETICS_INDEX_PATTERN } from '../../../../../../common/constants'; - -export const useSimpleRunOnceMonitors = ({ - configId, - expectSummaryDocs, - testRunId, -}: { - configId: string; - expectSummaryDocs: number; - testRunId?: string; -}) => { - const { refreshTimer, lastRefresh } = useTickTick(2 * 1000, false); - - const { data, loading } = useEsSearch( - createEsParams({ - index: SYNTHETICS_INDEX_PATTERN, - body: { - sort: [ - { - '@timestamp': 'desc', - }, - ], - query: { - bool: { - filter: [ - { - term: { - config_id: configId, - }, - }, - { - exists: { - field: 'summary', - }, - }, - ...(testRunId - ? [ - { - term: { - test_run_id: testRunId, - }, - }, - ] - : []), - ], - }, - }, - }, - size: 1000, - }), - [configId, lastRefresh], - { name: 'TestRunData' } - ); - - const lastUpdated = useRef<{ checksum: string; time: number }>({ - checksum: '', - time: Date.now(), - }); - - return useMemo(() => { - const docs = data?.hits.hits ?? []; - - // Whenever a new found document is fetched, update lastUpdated - const docsChecksum = docs - .map(({ _id }: { _id: string }) => _id) - .reduce((acc, cur) => acc + cur, ''); - if (docsChecksum !== lastUpdated.current.checksum) { - // Mutating lastUpdated - lastUpdated.current.checksum = docsChecksum; - lastUpdated.current.time = Date.now(); - } - - if (docs.length > 0) { - if (docs.length >= expectSummaryDocs) { - clearInterval(refreshTimer); - } - - return { - data, - loading, - summaryDocs: docs.map((doc) => ({ - ...(doc._source as Ping), - timestamp: (doc._source as Record)?.['@timestamp'], - docId: doc._id, - })), - lastUpdated: lastUpdated.current.time, - }; - } - - return { - data, - loading, - summaryDocs: null, - lastUpdated: lastUpdated.current.time, - }; - }, [expectSummaryDocs, data, loading, refreshTimer]); -}; diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/test_now_mode/test_now_mode.test.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/test_now_mode/test_now_mode.test.tsx deleted file mode 100644 index f77a926cf097f..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/test_now_mode/test_now_mode.test.tsx +++ /dev/null @@ -1,61 +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 } from '@testing-library/react'; -import { render } from '../../../lib/helper/rtl_helpers'; -import { TestNowMode } from './test_now_mode'; -import { kibanaService } from '../../../state/kibana_service'; -import { Locations, MonitorFields, LocationStatus } from '../../../../../common/runtime_types'; -import * as runOnceErrorHooks from '../hooks/use_run_once_errors'; - -describe('TestNowMode', function () { - const locations: Locations = [ - { - id: 'test-location-id', - label: 'Test Location', - geo: { lat: 33.333, lon: 73.333 }, - url: 'test-url', - isServiceManaged: true, - status: LocationStatus.GA, - }, - ]; - const testMonitor = { - id: 'test-monitor-id', - type: 'browser', - locations, - } as unknown as MonitorFields; - const testRun = { id: 'test-run-id', monitor: testMonitor }; - const onDone = jest.fn(); - - beforeEach(() => { - jest.spyOn(runOnceErrorHooks, 'useRunOnceErrors').mockReturnValue({ - expectPings: 1, - hasBlockingError: false, - blockingErrorMessage: '', - errorMessages: [], - }); - }); - - afterEach(() => { - jest.resetAllMocks(); - }); - - it('should render properly', async function () { - render(); - expect(await screen.findByText('PENDING')).toBeInTheDocument(); - - expect(await screen.findByText('0 steps completed')).toBeInTheDocument(); - - expect(kibanaService.core.http.post).toHaveBeenCalledTimes(1); - - expect(kibanaService.core.http.post).toHaveBeenLastCalledWith( - expect.stringContaining('/internal/uptime/service/monitors/run_once/'), - { body: JSON.stringify(testMonitor), method: 'POST' } - ); - }); -}); diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/test_now_mode/test_now_mode.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/test_now_mode/test_now_mode.tsx deleted file mode 100644 index ba65a878dfd63..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/test_now_mode/test_now_mode.tsx +++ /dev/null @@ -1,120 +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 { - EuiCallOut, - EuiFlexGroup, - EuiFlexItem, - EuiLoadingSpinner, - EuiPanel, - EuiSpacer, -} from '@elastic/eui'; -import { useFetcher } from '@kbn/observability-plugin/public'; -import { useRunOnceErrors } from '../hooks/use_run_once_errors'; -import { TestRunResult } from './test_run_results'; -import { - Locations, - MonitorFields, - ServiceLocationErrors, -} from '../../../../../common/runtime_types'; -import { runOnceMonitor } from '../../../state/api'; -import { kibanaService } from '../../../state/kibana_service'; - -export interface TestRun { - id: string; - monitor: MonitorFields; -} - -export function TestNowMode({ - testRun, - isMonitorSaved, - onDone, -}: { - testRun?: TestRun; - isMonitorSaved: boolean; - onDone: () => void; -}) { - const [serviceError, setServiceError] = useState(null); - - const { data, loading: isPushing } = useFetcher(() => { - if (testRun) { - return runOnceMonitor({ - monitor: testRun.monitor, - id: testRun.id, - }) - .then((d) => { - setServiceError(null); - return d; - }) - .catch((error) => setServiceError(error)); - } - return new Promise((resolve) => resolve(null)); - }, [testRun]); - - const errors = (data as { errors?: ServiceLocationErrors })?.errors; - - const { hasBlockingError, blockingErrorMessage, expectPings, errorMessages } = useRunOnceErrors({ - testRunId: testRun?.id ?? '', - serviceError, - errors: errors ?? [], - locations: (testRun?.monitor.locations ?? []) as Locations, - }); - - useEffect(() => { - errorMessages.forEach( - ({ name, message, title }: { name: string; message: string; title: string }) => { - kibanaService.toasts.addError({ name, message }, { title }); - } - ); - }, [errorMessages]); - - useEffect(() => { - if (!isPushing && (!testRun || hasBlockingError)) { - onDone(); - } - }, [testRun, hasBlockingError, isPushing, onDone]); - - if (!testRun) { - return null; - } - - return ( - - {isPushing && ( - - {PushingLabel} - - )} - - {(hasBlockingError && !isPushing && ( - - )) || - null} - - {testRun && !hasBlockingError && !isPushing && ( - - - - - - )} - - - ); -} - -const PushingLabel = i18n.translate('xpack.synthetics.testRun.pushing.description', { - defaultMessage: 'Pushing the monitor to service...', -}); diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/test_now_mode/test_result_header.test.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/test_now_mode/test_result_header.test.tsx deleted file mode 100644 index 07b2d59e0751f..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/test_now_mode/test_result_header.test.tsx +++ /dev/null @@ -1,46 +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 } from '@testing-library/react'; -import { render } from '../../../lib/helper/rtl_helpers'; -import { TestResultHeader } from './test_result_header'; - -describe('TestResultHeader', function () { - it('should render properly', async function () { - render(); - expect(await screen.findByText('Test result')).toBeInTheDocument(); - expect(await screen.findByText('PENDING')).toBeInTheDocument(); - }); - - it('should render in progress state', async function () { - render(); - - expect(await screen.findByText('Test result')).toBeInTheDocument(); - expect(await screen.findByText('IN PROGRESS')).toBeInTheDocument(); - }); - - it('should render completed state', async function () { - render( - - ); - expect(await screen.findByText('Test result')).toBeInTheDocument(); - expect(await screen.findByText('COMPLETED')).toBeInTheDocument(); - expect(await screen.findByText('Took 1 ms')).toBeInTheDocument(); - }); -}); diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/test_now_mode/test_result_header.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/test_now_mode/test_result_header.tsx deleted file mode 100644 index 6f365b9d1a19d..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/test_now_mode/test_result_header.tsx +++ /dev/null @@ -1,122 +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 { - EuiBadge, - EuiFlexGroup, - EuiFlexItem, - EuiLink, - EuiLoadingSpinner, - EuiText, - EuiTitle, -} from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; -import * as React from 'react'; -import { formatDuration } from '../../monitor/ping_list/ping_list'; -import { JourneyStep, Ping } from '../../../../../common/runtime_types'; -import { useUptimeSettingsContext } from '../../../contexts/uptime_settings_context'; - -interface Props { - checkGroupId?: string; - summaryDocs?: Ping[] | JourneyStep[] | null; - journeyStarted?: boolean; - title?: string; - isCompleted: boolean; -} - -export function TestResultHeader({ - checkGroupId, - title, - summaryDocs, - journeyStarted, - isCompleted, -}: Props) { - const { basePath } = useUptimeSettingsContext(); - let duration = 0; - if (summaryDocs && summaryDocs.length > 0) { - summaryDocs.forEach((sDoc) => { - duration += sDoc.monitor.duration?.us ?? 0; - }); - } - - const summaryDoc = summaryDocs?.[0] as Ping; - - return ( - - - -

{title ?? TEST_RESULT}

-
-
- - {isCompleted ? ( - - - 0 ? 'danger' : 'success'}> - {summaryDoc?.summary?.down! > 0 ? FAILED_LABEL : COMPLETED_LABEL} - - - - - {i18n.translate('xpack.synthetics.monitorManagement.timeTaken', { - defaultMessage: 'Took {timeTaken}', - values: { timeTaken: formatDuration(duration) }, - })} - - - - ) : ( - - - - {journeyStarted ? IN_PROGRESS_LABEL : PENDING_LABEL} - - - - - - - )} - - {checkGroupId && ( - - - {VIEW_DETAILS} - - - )} -
- ); -} - -export const PENDING_LABEL = i18n.translate('xpack.synthetics.monitorManagement.pending', { - defaultMessage: 'PENDING', -}); - -const TEST_RESULT = i18n.translate('xpack.synthetics.monitorManagement.testResult', { - defaultMessage: 'Test result', -}); - -const COMPLETED_LABEL = i18n.translate('xpack.synthetics.monitorManagement.completed', { - defaultMessage: 'COMPLETED', -}); - -const FAILED_LABEL = i18n.translate('xpack.synthetics.monitorManagement.failed', { - defaultMessage: 'FAILED', -}); - -export const IN_PROGRESS_LABEL = i18n.translate('xpack.synthetics.monitorManagement.inProgress', { - defaultMessage: 'IN PROGRESS', -}); - -const VIEW_DETAILS = i18n.translate('xpack.synthetics.monitorManagement.viewTestRunDetails', { - defaultMessage: 'View test result details', -}); diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/test_now_mode/test_run_results.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/test_now_mode/test_run_results.tsx deleted file mode 100644 index 6e9ef292ad6b2..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/test_now_mode/test_run_results.tsx +++ /dev/null @@ -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 * as React from 'react'; -import { SyntheticsMonitor } from '../../../../../common/runtime_types'; -import { BrowserTestRunResult } from './browser/browser_test_results'; -import { SimpleTestResults } from './simple/simple_test_results'; - -interface Props { - monitorId: string; - monitor: SyntheticsMonitor; - isMonitorSaved: boolean; - expectPings: number; - onDone: () => void; -} -export const TestRunResult = ({ - monitorId, - monitor, - isMonitorSaved, - expectPings, - onDone, -}: Props) => { - return monitor.type === 'browser' ? ( - - ) : ( - - ); -}; diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/test_now_mode/use_tick_tick.ts b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/test_now_mode/use_tick_tick.ts deleted file mode 100644 index ce6d88806144c..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/test_now_mode/use_tick_tick.ts +++ /dev/null @@ -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 { useEffect, useState, useContext } from 'react'; -import { UptimeRefreshContext } from '../../../contexts'; - -export function useTickTick(interval?: number, refresh = true) { - const { refreshApp } = useContext(UptimeRefreshContext); - - const [nextTick, setNextTick] = useState(Date.now()); - - const [tickTick] = useState(() => - setInterval(() => { - if (refresh) { - refreshApp(); - } - setNextTick(Date.now()); - }, interval ?? 5 * 1000) - ); - - useEffect(() => { - return () => { - clearInterval(tickTick); - }; - }, [tickTick]); - - return { refreshTimer: tickTick, lastRefresh: nextTick }; -} diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/validation.test.ts b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/validation.test.ts deleted file mode 100644 index 616041ad1bd99..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/validation.test.ts +++ /dev/null @@ -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 { - ConfigKey, - DataStream, - HTTPFields, - MonitorFields, - ScheduleUnit, - ServiceLocations, - SyntheticsMonitor, -} from '../../../../common/runtime_types'; -import { validate, validateCommon } from './validation'; - -describe('[Monitor Management] validation', () => { - const commonPropsValid: Partial = { - [ConfigKey.SCHEDULE]: { number: '5', unit: ScheduleUnit.MINUTES }, - [ConfigKey.TIMEOUT]: '3m', - [ConfigKey.LOCATIONS]: [ - { - id: 'test-service-location', - isServiceManaged: true, - url: 'https:test-url.com', - geo: { lat: 33.33432323, lon: 73.23424221 }, - label: 'EU West', - }, - ] as ServiceLocations, - [ConfigKey.NAME]: 'test-name', - [ConfigKey.NAMESPACE]: 'namespace', - }; - - describe('Common monitor fields', () => { - it('should return false for all valid props', () => { - const result = Object.values(validateCommon).map((validator) => { - return validator ? validator(commonPropsValid) : true; - }); - - expect(result.reduce((previous, current) => previous || current)).toBeFalsy(); - }); - - it('should invalidate on invalid namespace', () => { - const validatorFn = validateCommon[ConfigKey.NAMESPACE]; - const result = [undefined, null, '', '*/&<>:', 'A', 'a'.repeat(101)].map((testValue) => - validatorFn?.({ [ConfigKey.NAMESPACE]: testValue } as Partial) - ); - - expect(result.reduce((previous, current) => previous && current)).toBeTruthy(); - }); - }); - - describe('HTTP', () => { - const httpPropsValid: Partial = { - ...commonPropsValid, - [ConfigKey.RESPONSE_STATUS_CHECK]: ['200', '204'], - [ConfigKey.RESPONSE_HEADERS_CHECK]: { 'Content-Type': 'application/json' }, - [ConfigKey.REQUEST_HEADERS_CHECK]: { 'Accept-Language': 'en-GB,en-US;q=0.9,en;q=0.8' }, - [ConfigKey.MAX_REDIRECTS]: '3', - [ConfigKey.URLS]: 'https:// example-url.com', - }; - - it('should return false for all valid props', () => { - const validators = validate[DataStream.HTTP]; - const keysToValidate = [ - ConfigKey.SCHEDULE, - ConfigKey.TIMEOUT, - ConfigKey.LOCATIONS, - ConfigKey.RESPONSE_STATUS_CHECK, - ConfigKey.RESPONSE_HEADERS_CHECK, - ConfigKey.REQUEST_HEADERS_CHECK, - ConfigKey.MAX_REDIRECTS, - ConfigKey.URLS, - ]; - const validatorFns = keysToValidate.map((key) => validators[key]); - const result = validatorFns.map((fn) => fn?.(httpPropsValid) ?? true); - - expect(result).not.toEqual(expect.arrayContaining([true])); - }); - - it('should invalidate when locations is empty', () => { - const validators = validate[DataStream.HTTP]; - const validatorFn = validators[ConfigKey.LOCATIONS]; - const result = [undefined, null, []].map( - (testValue) => - validatorFn?.({ [ConfigKey.LOCATIONS]: testValue } as Partial) ?? false - ); - - expect(result).toEqual([true, true, true]); - }); - }); - - describe.each([[ConfigKey.SOURCE_INLINE, 'step(() => {});']])('Browser', (configKey, value) => { - const browserProps = { - ...commonPropsValid, - [ConfigKey.MONITOR_TYPE]: DataStream.BROWSER, - [ConfigKey.TIMEOUT]: undefined, - [configKey]: value, - } as SyntheticsMonitor; - - it('should return false for all valid props', () => { - const validators = validate[DataStream.BROWSER]; - const keysToValidate = [ConfigKey.SCHEDULE, ConfigKey.TIMEOUT, configKey]; - const validatorFns = keysToValidate.map((key) => validators[key]); - const result = validatorFns.map((fn) => fn?.(browserProps as Partial) ?? true); - - expect(result).not.toEqual(expect.arrayContaining([true])); - }); - - it('should invalidate when locations is empty', () => { - const validators = validate[DataStream.BROWSER]; - const validatorFn = validators[ConfigKey.LOCATIONS]; - const result = [undefined, null, []].map( - (testValue) => - validatorFn?.({ [ConfigKey.LOCATIONS]: testValue } as Partial) ?? false - ); - - expect(result).toEqual([true, true, true]); - }); - }); - - // TODO: Add test for other monitor types if needed -}); diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/validation.ts b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/validation.ts deleted file mode 100644 index d4abfbd62d6fb..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/validation.ts +++ /dev/null @@ -1,171 +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 { isValidNamespace } from '@kbn/fleet-plugin/common'; -import { - ConfigKey, - DataStream, - ScheduleUnit, - MonitorFields, - isServiceLocationInvalid, -} from '../../../../common/runtime_types'; -import { Validation } from '../../../../common/types'; - -export const digitsOnly = /^[0-9]*$/g; -export const includesValidPort = /[^\:]+:[0-9]{1,5}$/g; - -// returns true if invalid -function validateHeaders(headers: T): boolean { - return Object.keys(headers).some((key) => { - if (key) { - const whiteSpaceRegEx = /[\s]/g; - return whiteSpaceRegEx.test(key); - } else { - return false; - } - }); -} - -export const validateParamsValue = (params?: string) => { - try { - if (params) { - JSON.parse(params ?? ''); - } - } catch (e) { - return true; - } - return false; -}; - -// returns true if invalid -const validateTimeout = ({ - scheduleNumber, - scheduleUnit, - timeout, -}: { - scheduleNumber: string; - scheduleUnit: ScheduleUnit; - timeout: string; -}): boolean => { - let schedule: number; - switch (scheduleUnit) { - case ScheduleUnit.SECONDS: - schedule = parseFloat(scheduleNumber); - break; - case ScheduleUnit.MINUTES: - schedule = parseFloat(scheduleNumber) * 60; - break; - default: - schedule = parseFloat(scheduleNumber); - } - - return parseFloat(timeout) > schedule; -}; - -// validation functions return true when invalid -export const validateCommon: Validation = { - [ConfigKey.NAME]: ({ [ConfigKey.NAME]: value }) => { - return !value || typeof value !== 'string'; - }, - [ConfigKey.SCHEDULE]: ({ [ConfigKey.SCHEDULE]: value }) => { - const { number, unit } = value as MonitorFields[ConfigKey.SCHEDULE]; - const parsedFloat = parseFloat(number); - return !parsedFloat || !unit || parsedFloat < 1; - }, - [ConfigKey.TIMEOUT]: ({ - [ConfigKey.MONITOR_TYPE]: monitorType, - [ConfigKey.TIMEOUT]: timeout, - [ConfigKey.SCHEDULE]: schedule, - }) => { - const { number, unit } = schedule as MonitorFields[ConfigKey.SCHEDULE]; - - // Timeout is not currently supported by browser monitors - if (monitorType === DataStream.BROWSER) { - return false; - } - - return ( - !timeout || - parseFloat(timeout) < 0 || - validateTimeout({ - timeout, - scheduleNumber: number, - scheduleUnit: unit, - }) - ); - }, - [ConfigKey.LOCATIONS]: ({ [ConfigKey.LOCATIONS]: locations }) => { - return ( - !Array.isArray(locations) || locations.length < 1 || locations.some(isServiceLocationInvalid) - ); - }, - [ConfigKey.NAMESPACE]: ({ [ConfigKey.NAMESPACE]: value }) => { - const { error = '', valid } = isValidNamespace(value ?? ''); - return valid ? false : error; - }, -}; - -const validateHTTP: Validation = { - [ConfigKey.RESPONSE_STATUS_CHECK]: ({ [ConfigKey.RESPONSE_STATUS_CHECK]: value }) => { - const statusCodes = value as MonitorFields[ConfigKey.RESPONSE_STATUS_CHECK]; - return statusCodes.length ? statusCodes.some((code) => !`${code}`.match(digitsOnly)) : false; - }, - [ConfigKey.RESPONSE_HEADERS_CHECK]: ({ [ConfigKey.RESPONSE_HEADERS_CHECK]: value }) => { - const headers = value as MonitorFields[ConfigKey.RESPONSE_HEADERS_CHECK]; - return validateHeaders(headers); - }, - [ConfigKey.REQUEST_HEADERS_CHECK]: ({ [ConfigKey.REQUEST_HEADERS_CHECK]: value }) => { - const headers = value as MonitorFields[ConfigKey.REQUEST_HEADERS_CHECK]; - return validateHeaders(headers); - }, - [ConfigKey.MAX_REDIRECTS]: ({ [ConfigKey.MAX_REDIRECTS]: value }) => - (!!value && !`${value}`.match(digitsOnly)) || - parseFloat(value as MonitorFields[ConfigKey.MAX_REDIRECTS]) < 0, - [ConfigKey.URLS]: ({ [ConfigKey.URLS]: value }) => !value, - ...validateCommon, -}; - -const validateTCP: Validation = { - [ConfigKey.HOSTS]: ({ [ConfigKey.HOSTS]: value }) => { - return !value || !`${value}`.match(includesValidPort); - }, - ...validateCommon, -}; - -const validateICMP: Validation = { - [ConfigKey.HOSTS]: ({ [ConfigKey.HOSTS]: value }) => !value, - [ConfigKey.WAIT]: ({ [ConfigKey.WAIT]: value }) => - !!value && - !digitsOnly.test(`${value}`) && - parseFloat(value as MonitorFields[ConfigKey.WAIT]) < 0, - ...validateCommon, -}; - -const validateThrottleValue = (speed: string | undefined, allowZero?: boolean) => { - if (speed === undefined || speed === '') return false; - const throttleValue = parseFloat(speed); - return isNaN(throttleValue) || (allowZero ? throttleValue < 0 : throttleValue <= 0); -}; - -const validateBrowser: Validation = { - ...validateCommon, - [ConfigKey.SOURCE_INLINE]: ({ [ConfigKey.SOURCE_INLINE]: inlineScript }) => !inlineScript, - [ConfigKey.DOWNLOAD_SPEED]: ({ [ConfigKey.DOWNLOAD_SPEED]: downloadSpeed }) => - validateThrottleValue(downloadSpeed), - [ConfigKey.UPLOAD_SPEED]: ({ [ConfigKey.UPLOAD_SPEED]: uploadSpeed }) => - validateThrottleValue(uploadSpeed), - [ConfigKey.LATENCY]: ({ [ConfigKey.LATENCY]: latency }) => validateThrottleValue(latency, true), - [ConfigKey.PARAMS]: ({ [ConfigKey.PARAMS]: params }) => validateParamsValue(params), -}; - -export type ValidateDictionary = Record; - -export const validate: ValidateDictionary = { - [DataStream.HTTP]: validateHTTP, - [DataStream.TCP]: validateTCP, - [DataStream.ICMP]: validateICMP, - [DataStream.BROWSER]: validateBrowser, -}; diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/overview/monitor_list/columns/monitor_status_column.test.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/overview/monitor_list/columns/monitor_status_column.test.tsx index 7869125014b02..f15c716827724 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/overview/monitor_list/columns/monitor_status_column.test.tsx +++ b/x-pack/plugins/synthetics/public/legacy_uptime/components/overview/monitor_list/columns/monitor_status_column.test.tsx @@ -238,12 +238,7 @@ describe('MonitorListStatusColumn', () => { it('provides expected tooltip and display times', async () => { const { getByText } = render( - + ); @@ -258,12 +253,7 @@ describe('MonitorListStatusColumn', () => { it('can handle a non-numeric timestamp value', () => { const { getByText } = render( - + ); @@ -278,7 +268,6 @@ describe('MonitorListStatusColumn', () => { ping.observer!.geo!.name! === 'Islamabad')} /> @@ -298,7 +287,6 @@ describe('MonitorListStatusColumn', () => { status="up" timestamp={new Date().toString()} summaryPings={summaryPings} - monitorType="http" /> ); @@ -316,7 +304,6 @@ describe('MonitorListStatusColumn', () => { diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/overview/monitor_list/columns/monitor_status_column.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/overview/monitor_list/columns/monitor_status_column.tsx index fe235eece5bad..6b04eb5a7f594 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/overview/monitor_list/columns/monitor_status_column.tsx +++ b/x-pack/plugins/synthetics/public/legacy_uptime/components/overview/monitor_list/columns/monitor_status_column.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { useCallback, useEffect } from 'react'; +import React from 'react'; import moment, { Moment } from 'moment'; import { i18n } from '@kbn/i18n'; import styled from 'styled-components'; @@ -15,15 +15,11 @@ import { EuiText, EuiToolTip, EuiSpacer, - EuiHighlight, EuiHorizontalRule, } from '@elastic/eui'; -import { useDispatch, useSelector } from 'react-redux'; import { euiStyled } from '@kbn/kibana-react-plugin/common'; -import { kibanaService } from '../../../../state/kibana_service'; -import { useRunOnceErrors } from '../../../monitor_management/hooks/use_run_once_errors'; import { parseTimestamp } from '../parse_timestamp'; -import { DataStream, Ping, PingError } from '../../../../../../common/runtime_types'; +import { Ping, PingError } from '../../../../../../common/runtime_types'; import { STATUS, SHORT_TIMESPAN_LOCALE, @@ -35,20 +31,11 @@ import { STATUS_DOWN_LABEL, STATUS_UP_LABEL, } from '../../../../../../common/translations/translations'; -import { MonitorProgress } from './progress/monitor_progress'; -import { refreshedMonitorSelector } from '../../../../state/reducers/monitor_list'; -import { testNowRunSelector } from '../../../../state/reducers/test_now_runs'; -import { clearTestNowMonitorAction } from '../../../../state/actions'; import { StatusBadge } from './status_badge'; interface MonitorListStatusColumnProps { - configId?: string; - monitorId?: string; - checkGroup?: string; status: string; - monitorType: string; timestamp: string; - duration?: number; summaryPings: Ping[]; summaryError?: PingError; } @@ -168,12 +155,7 @@ export const getLocationStatus = (summaryPings: Ping[], status: string) => { }; export const MonitorListStatusColumn = ({ - monitorType, - configId, - monitorId, status, - duration, - checkGroup, summaryError, summaryPings = [], timestamp: tsString, @@ -182,60 +164,11 @@ export const MonitorListStatusColumn = ({ const { statusMessage, locTooltip } = getLocationStatus(summaryPings, status); - const dispatch = useDispatch(); - - const stopProgressTrack = useCallback(() => { - if (configId) { - dispatch(clearTestNowMonitorAction(configId)); - } - }, [configId, dispatch]); - - const refreshedMonitorIds = useSelector(refreshedMonitorSelector); - - const testNowRun = useSelector(testNowRunSelector(configId)); - - const { expectPings, errorMessages, hasBlockingError } = useRunOnceErrors({ - testRunId: testNowRun?.monitorId ?? '', - serviceError: (testNowRun?.fetchError as Error) ?? null, - locations: testNowRun?.locations ?? [], - errors: testNowRun?.errors ?? [], - }); - - useEffect(() => { - errorMessages.forEach( - ({ name, message, title }: { name: string; message: string; title: string }) => { - kibanaService.toasts.addError({ name, message }, { title }); - } - ); - - if (hasBlockingError) { - stopProgressTrack(); - } - }, [errorMessages, hasBlockingError, stopProgressTrack]); - return (
- {testNowRun && configId && testNowRun?.testRunId ? ( - - ) : ( - - )} + @@ -264,15 +197,9 @@ export const MonitorListStatusColumn = ({ } > - {monitorId && refreshedMonitorIds?.includes(monitorId) ? ( - - {getCheckedLabel(timestamp)} - - ) : ( - - {getCheckedLabel(timestamp)} - - )} + + {getCheckedLabel(timestamp)} +
diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/overview/monitor_list/columns/progress/browser_monitor_progress.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/overview/monitor_list/columns/progress/browser_monitor_progress.tsx deleted file mode 100644 index 0692af54b9bf4..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/overview/monitor_list/columns/progress/browser_monitor_progress.tsx +++ /dev/null @@ -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 { EuiBadge, EuiProgress } from '@elastic/eui'; -import React, { useEffect, useRef, useState } from 'react'; -import { scheduleToMilli } from '../../../../../../../common/lib/schedule_to_time'; -import { SyntheticsMonitorSchedule } from '../../../../../../../common/runtime_types'; -import { useBrowserRunOnceMonitors } from '../../../../monitor_management/test_now_mode/browser/use_browser_run_once_monitors'; -import { - IN_PROGRESS_LABEL, - PENDING_LABEL, -} from '../../../../monitor_management/test_now_mode/test_result_header'; - -export const BrowserMonitorProgress = ({ - configId, - testRunId, - duration, - schedule, - isUpdating, - expectPings, - updateMonitorStatus, - stopProgressTrack, -}: { - configId: string; - testRunId: string; - duration: number; - schedule: SyntheticsMonitorSchedule; - isUpdating: boolean; - expectPings: number; - updateMonitorStatus: () => void; - stopProgressTrack: () => void; -}) => { - const { data, checkGroupResults, lastUpdated, expectedSummariesLoaded } = - useBrowserRunOnceMonitors({ - configId, - testRunId, - refresh: false, - skipDetails: true, - expectSummaryDocs: expectPings, - }); - - const journeyStarted = checkGroupResults.some((result) => result.journeyStarted); - const [passedTime, setPassedTime] = useState(0); - - const startTime = useRef(Date.now()); - - useEffect(() => { - if (expectedSummariesLoaded) { - updateMonitorStatus(); - } - }, [updateMonitorStatus, expectedSummariesLoaded]); - - useEffect(() => { - setPassedTime((Date.now() - startTime.current) * 1000); - - // Stop waiting for docs if time elapsed is more than monitor frequency - const timeSinceLastDoc = Date.now() - lastUpdated; - const usualDurationMilli = duration / 1000; - const maxTimeout = scheduleToMilli(schedule) - usualDurationMilli; - if (timeSinceLastDoc >= maxTimeout) { - stopProgressTrack(); - } - }, [data, checkGroupResults, lastUpdated, duration, schedule, stopProgressTrack]); - - if (journeyStarted && (isUpdating || passedTime > duration)) { - return ( - <> - {IN_PROGRESS_LABEL} - - - ); - } - - return ( - <> - {journeyStarted ? ( - <> - {IN_PROGRESS_LABEL} - - - ) : ( - <> - {PENDING_LABEL} - - - )} - - ); -}; diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/overview/monitor_list/columns/progress/monitor_progress.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/overview/monitor_list/columns/progress/monitor_progress.tsx deleted file mode 100644 index ebc28310930ad..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/overview/monitor_list/columns/progress/monitor_progress.tsx +++ /dev/null @@ -1,71 +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 } from 'react'; -import { useSelector } from 'react-redux'; -import { SimpleMonitorProgress } from './simple_monitor_progress'; -import { BrowserMonitorProgress } from './browser_monitor_progress'; -import { DataStream, SyntheticsMonitorSchedule } from '../../../../../../../common/runtime_types'; -import { useUpdatedMonitor } from './use_updated_monitor'; -import { refreshedMonitorSelector } from '../../../../../state/reducers/monitor_list'; - -export const MonitorProgress = ({ - monitorId, - configId, - testRunId, - duration, - monitorType, - schedule, - expectPings, - stopProgressTrack, -}: { - monitorId: string; - configId: string; - testRunId: string; - duration: number; - monitorType: DataStream; - schedule: SyntheticsMonitorSchedule; - expectPings: number; - stopProgressTrack: () => void; -}) => { - const { updateMonitorStatus, isUpdating } = useUpdatedMonitor({ - testRunId, - monitorId, - }); - - const refreshedMonitorId = useSelector(refreshedMonitorSelector); - - useEffect(() => { - if (refreshedMonitorId.includes(monitorId)) { - stopProgressTrack(); - } - }, [isUpdating, monitorId, refreshedMonitorId, stopProgressTrack]); - - return monitorType === 'browser' ? ( - - ) : ( - - ); -}; diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/overview/monitor_list/columns/progress/simple_monitor_progress.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/overview/monitor_list/columns/progress/simple_monitor_progress.tsx deleted file mode 100644 index bbc96eeda397b..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/overview/monitor_list/columns/progress/simple_monitor_progress.tsx +++ /dev/null @@ -1,79 +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 { EuiBadge, EuiProgress } from '@elastic/eui'; -import React, { useEffect, useRef, useState } from 'react'; -import { scheduleToMilli } from '../../../../../../../common/lib/schedule_to_time'; -import { SyntheticsMonitorSchedule } from '../../../../../../../common/runtime_types'; -import { useSimpleRunOnceMonitors } from '../../../../monitor_management/test_now_mode/simple/use_simple_run_once_monitors'; -import { IN_PROGRESS_LABEL } from '../../../../monitor_management/test_now_mode/test_result_header'; - -export const SimpleMonitorProgress = ({ - monitorId, - testRunId, - duration, - schedule, - expectPings, - isUpdating, - updateMonitorStatus, - stopProgressTrack, -}: { - monitorId: string; - testRunId: string; - duration: number; - schedule: SyntheticsMonitorSchedule; - expectPings: number; - isUpdating: boolean; - updateMonitorStatus: () => void; - stopProgressTrack: () => void; -}) => { - const { summaryDocs, data, lastUpdated } = useSimpleRunOnceMonitors({ - configId: monitorId, - testRunId, - expectSummaryDocs: expectPings, - }); - - const startTime = useRef(Date.now()); - - const [passedTime, setPassedTime] = useState(Date.now()); - - useEffect(() => { - if (summaryDocs?.length) { - updateMonitorStatus(); - } - }, [updateMonitorStatus, summaryDocs]); - - useEffect(() => { - setPassedTime(Date.now() - startTime.current); - - // Stop waiting for docs if time elapsed is more than monitor frequency - const timeSinceLastDoc = Date.now() - lastUpdated; - const usualDurationMilli = duration / 1000; - const maxTimeout = scheduleToMilli(schedule) - usualDurationMilli; - if (timeSinceLastDoc >= maxTimeout) { - stopProgressTrack(); - } - }, [data, lastUpdated, duration, schedule, stopProgressTrack]); - - const passedTimeMicro = passedTime * 1000; - - if (isUpdating || passedTimeMicro > duration) { - return ( - <> - {IN_PROGRESS_LABEL} - - - ); - } - - return ( - <> - {IN_PROGRESS_LABEL} - - - ); -}; diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/overview/monitor_list/columns/progress/use_updated_monitor.ts b/x-pack/plugins/synthetics/public/legacy_uptime/components/overview/monitor_list/columns/progress/use_updated_monitor.ts deleted file mode 100644 index b76971d4079e3..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/overview/monitor_list/columns/progress/use_updated_monitor.ts +++ /dev/null @@ -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 { useCallback } from 'react'; -import { useDispatch, useSelector } from 'react-redux'; -import { getUpdatedMonitor, setUpdatingMonitorId } from '../../../../../state/actions'; -import { isUpdatingMonitorSelector } from '../../../../../state/reducers/monitor_list'; - -export const useUpdatedMonitor = ({ - testRunId, - monitorId, -}: { - testRunId: string; - monitorId: string; -}) => { - const dispatch = useDispatch(); - - const isUpdatingMonitors = useSelector(isUpdatingMonitorSelector); - - const updateMonitorStatus = useCallback(() => { - if (testRunId) { - dispatch( - getUpdatedMonitor.get({ - dateRangeStart: 'now-10m', - dateRangeEnd: 'now', - filters: JSON.stringify({ - bool: { - should: [{ match_phrase: { test_run_id: testRunId } }], - minimum_should_match: 1, - }, - }), - pageSize: 1, - }) - ); - dispatch(setUpdatingMonitorId(monitorId)); - } - }, [dispatch, monitorId, testRunId]); - - return { updateMonitorStatus, isUpdating: isUpdatingMonitors.includes(monitorId) }; -}; diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/overview/monitor_list/columns/status_badge.test.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/overview/monitor_list/columns/status_badge.test.tsx index 992defffc5552..c0a3ce6e714f5 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/overview/monitor_list/columns/status_badge.test.tsx +++ b/x-pack/plugins/synthetics/public/legacy_uptime/components/overview/monitor_list/columns/status_badge.test.tsx @@ -12,36 +12,20 @@ import { render } from '../../../../lib/helper/rtl_helpers'; describe('', () => { it('render no error for up status', () => { - render(); + render(); expect(screen.getByText('Up')).toBeInTheDocument(); }); it('renders errors for downs state', () => { - render( - - ); + render(); expect(screen.getByText('Down')).toBeInTheDocument(); - expect( - screen.getByLabelText('journey did not run. Click for more details.') - ).toBeInTheDocument(); }); it('renders errors for downs state for http monitor', () => { - render( - - ); + render(); expect(screen.getByText('Down')).toBeInTheDocument(); - expect(screen.getByLabelText('journey did not run')).toBeInTheDocument(); }); }); diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/overview/monitor_list/columns/status_badge.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/overview/monitor_list/columns/status_badge.tsx index 0328242ffc036..04311ff66aa8f 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/overview/monitor_list/columns/status_badge.tsx +++ b/x-pack/plugins/synthetics/public/legacy_uptime/components/overview/monitor_list/columns/status_badge.tsx @@ -6,29 +6,22 @@ */ import { EuiBadge, EuiToolTip } from '@elastic/eui'; -import React, { useContext, useState } from 'react'; +import React, { useContext } from 'react'; import { STATUS } from '../../../../../../common/constants'; import { getHealthMessage } from './monitor_status_column'; import { UptimeThemeContext } from '../../../../contexts'; import { PingError } from '../../../../../../common/runtime_types'; -import { getInlineErrorLabel } from '../../../monitor_management/monitor_list/inline_error'; -import { StdErrorPopover } from '../../../monitor_management/monitor_list/stderr_logs_popover'; export const StatusBadge = ({ status, - checkGroup, summaryError, - monitorType, }: { status: string; - monitorType: string; - checkGroup?: string; summaryError?: PingError; }) => { const { colors: { dangerBehindText }, } = useContext(UptimeThemeContext); - const [isOpen, setIsOpen] = useState(false); if (status === STATUS.UP) { return ( @@ -38,33 +31,13 @@ export const StatusBadge = ({ ); } - const errorMessage = - monitorType !== 'browser' ? summaryError?.message : getInlineErrorLabel(summaryError?.message); + const errorMessage = summaryError?.message; - const button = ( + return ( - setIsOpen(true)} - onClickAriaLabel={errorMessage} - > + {getHealthMessage(status)} ); - - if (monitorType !== 'browser') { - return button; - } - - return ( - - ); }; diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/overview/monitor_list/columns/test_now_col.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/overview/monitor_list/columns/test_now_col.tsx deleted file mode 100644 index 390994646d08a..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/overview/monitor_list/columns/test_now_col.tsx +++ /dev/null @@ -1,70 +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 } from 'react'; -import { EuiButtonIcon, EuiLoadingSpinner, EuiToolTip } from '@elastic/eui'; -import { useDispatch, useSelector } from 'react-redux'; -import { Ping } from '../../../../../../common/runtime_types'; -import { testNowMonitorAction } from '../../../../state/actions'; -import { testNowRunSelector, TestRunStats } from '../../../../state/reducers/test_now_runs'; -import * as labels from '../translations'; - -export const TestNowColumn = ({ - monitorId, - configId, - summaryPings, -}: { - monitorId: string; - configId?: string; - summaryPings: Ping[]; -}) => { - const dispatch = useDispatch(); - - const testNowRun = useSelector(testNowRunSelector(configId)); - - const isOnFleetManaged = useMemo(() => { - return summaryPings.every((ping) => !!ping.monitor.fleet_managed); - }, [summaryPings]); - - if (isOnFleetManaged) { - return ( - - <>-- - - ); - } - - if (!configId) { - return ( - - <>-- - - ); - } - - const testNowClick = () => { - dispatch(testNowMonitorAction.get(configId)); - }; - - const isTestNowLoading = testNowRun && testNowRun.status === TestRunStats.LOADING; - const isTestNowCompleted = !testNowRun || testNowRun.status === TestRunStats.COMPLETED; - - if (isTestNowLoading) { - return ; - } - - return ( - - testNowClick()} - isDisabled={!isTestNowCompleted} - aria-label={labels.TEST_NOW_ARIA_LABEL} - /> - - ); -}; diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/overview/monitor_list/monitor_list.test.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/overview/monitor_list/monitor_list.test.tsx index c9416263cbc60..03bf3cbf09c4b 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/overview/monitor_list/monitor_list.test.tsx +++ b/x-pack/plugins/synthetics/public/legacy_uptime/components/overview/monitor_list/monitor_list.test.tsx @@ -149,7 +149,6 @@ describe('MonitorList component', () => { }} pageSize={10} setPageSize={jest.fn()} - refreshedMonitorIds={[]} /> ); expect(await findByText(NO_DATA_MESSAGE)).toBeInTheDocument(); @@ -164,7 +163,6 @@ describe('MonitorList component', () => { }} pageSize={10} setPageSize={jest.fn()} - refreshedMonitorIds={[]} /> ); @@ -185,7 +183,6 @@ describe('MonitorList component', () => { }} pageSize={10} setPageSize={jest.fn()} - refreshedMonitorIds={[]} /> ); @@ -222,7 +219,6 @@ describe('MonitorList component', () => { }} pageSize={10} setPageSize={jest.fn()} - refreshedMonitorIds={[]} /> ); @@ -251,7 +247,6 @@ describe('MonitorList component', () => { }} pageSize={10} setPageSize={jest.fn()} - refreshedMonitorIds={[]} /> ); @@ -281,7 +276,6 @@ describe('MonitorList component', () => { }} pageSize={10} setPageSize={jest.fn()} - refreshedMonitorIds={[]} /> ); diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/overview/monitor_list/monitor_list.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/overview/monitor_list/monitor_list.tsx index a306a97fc5851..ce4e6eda9caa2 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/overview/monitor_list/monitor_list.tsx +++ b/x-pack/plugins/synthetics/public/legacy_uptime/components/overview/monitor_list/monitor_list.tsx @@ -32,18 +32,16 @@ import { CertStatusColumn } from './columns/cert_status_column'; import { MonitorListHeader } from './monitor_list_header'; import { TAGS_LABEL, URL_LABEL } from '../../../../../common/translations/translations'; import { EnableMonitorAlert } from './columns/enable_alert'; -import { STATUS_ALERT_COLUMN, TEST_NOW_COLUMN } from './translations'; +import { STATUS_ALERT_COLUMN } from './translations'; import { MonitorNameColumn } from './columns/monitor_name_col'; import { MonitorTags } from '../../common/monitor_tags'; import { useMonitorHistogram } from './use_monitor_histogram'; -import { TestNowColumn } from './columns/test_now_col'; import { NoItemsMessage } from './no_items_message'; interface Props extends MonitorListProps { pageSize: number; setPageSize: (val: number) => void; monitorList: MonitorList; - refreshedMonitorIds: string[]; isPending?: boolean; } @@ -51,12 +49,10 @@ export const MonitorListComponent: ({ filters, monitorList: { list, error, loading }, pageSize, - refreshedMonitorIds, setPageSize, isPending, }: Props) => any = ({ filters, - refreshedMonitorIds = [], monitorList: { list, error, loading }, pageSize, setPageSize, @@ -115,27 +111,13 @@ export const MonitorListComponent: ({ }, render: ( status: string, - { - monitor_id: monitorId, - state: { - timestamp, - summaryPings, - monitor: { type, duration, checkGroup }, - error: summaryError, - }, - configId, - }: MonitorSummary + { state: { timestamp, summaryPings, error: summaryError } }: MonitorSummary ) => { return ( ); @@ -212,19 +194,6 @@ export const MonitorListComponent: ({ /> ), }, - { - align: 'center' as const, - field: '', - name: TEST_NOW_COLUMN, - width: '100px', - render: (item: MonitorSummary) => ( - - ), - }, ...(!hideExtraColumns ? [ { @@ -274,7 +243,7 @@ export const MonitorListComponent: ({ 'aria-label': labels.getExpandDrawerLabel(monitorId), }) : ({ monitor_id: monitorId }) => ({ - className: refreshedMonitorIds.includes(monitorId) ? 'refresh-row' : undefined, + className: undefined, }) } /> diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/overview/monitor_list/monitor_list_container.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/overview/monitor_list/monitor_list_container.tsx index ab4c165cd478a..8447f1c9d3e71 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/overview/monitor_list/monitor_list_container.tsx +++ b/x-pack/plugins/synthetics/public/legacy_uptime/components/overview/monitor_list/monitor_list_container.tsx @@ -7,7 +7,7 @@ import React, { useContext, useEffect, useState } from 'react'; import { useSelector, useDispatch } from 'react-redux'; -import { clearRefreshedMonitorId, getMonitorList } from '../../../state/actions'; +import { getMonitorList } from '../../../state/actions'; import { esKuerySelector, monitorListSelector } from '../../../state/selectors'; import { MonitorListComponent } from './monitor_list'; import { useUrlParams } from '../../../hooks'; @@ -15,7 +15,6 @@ import { UptimeRefreshContext } from '../../../contexts'; import { getConnectorsAction, getMonitorAlertsAction } from '../../../state/alerts/alerts'; import { useMappingCheck } from '../../../hooks/use_mapping_check'; import { useOverviewFilterCheck } from '../../../hooks/use_overview_filter_check'; -import { refreshedMonitorSelector } from '../../../state/reducers/monitor_list'; export interface MonitorListProps { filters?: string; @@ -44,8 +43,6 @@ export const MonitorList: React.FC = (props) => { const { lastRefresh } = useContext(UptimeRefreshContext); - const refreshedMonitorIds = useSelector(refreshedMonitorSelector); - const monitorList = useSelector(monitorListSelector); useMappingCheck(monitorList.error); @@ -84,23 +81,12 @@ export const MonitorList: React.FC = (props) => { dispatch(getConnectorsAction.get()); }, [dispatch]); - useEffect(() => { - if (refreshedMonitorIds) { - refreshedMonitorIds.forEach((id) => { - setTimeout(() => { - dispatch(clearRefreshedMonitorId(id)); - }, 5 * 1000); - }); - } - }, [dispatch, refreshedMonitorIds]); - return ( ); diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/overview/monitor_list/monitor_list_drawer/__snapshots__/most_recent_error.test.tsx.snap b/x-pack/plugins/synthetics/public/legacy_uptime/components/overview/monitor_list/monitor_list_drawer/__snapshots__/most_recent_error.test.tsx.snap index c30c707203033..619096189b084 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/overview/monitor_list/monitor_list_drawer/__snapshots__/most_recent_error.test.tsx.snap +++ b/x-pack/plugins/synthetics/public/legacy_uptime/components/overview/monitor_list/monitor_list_drawer/__snapshots__/most_recent_error.test.tsx.snap @@ -13,18 +13,14 @@ exports[`MostRecentError component renders properly with mock data 1`] = `
- + Get https://expired.badssl.com: x509: certificate has expired or is not yet valid +
`; diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/overview/monitor_list/translations.ts b/x-pack/plugins/synthetics/public/legacy_uptime/components/overview/monitor_list/translations.ts index 2f3bac51ce887..d40a491143014 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/overview/monitor_list/translations.ts +++ b/x-pack/plugins/synthetics/public/legacy_uptime/components/overview/monitor_list/translations.ts @@ -66,52 +66,9 @@ export const NO_DATA_MESSAGE = i18n.translate('xpack.synthetics.monitorList.noIt description: 'This message is shown if the monitors table is rendered but has no items.', }); -export const RESPONSE_ANOMALY_SCORE = i18n.translate( - 'xpack.synthetics.monitorList.anomalyColumn.label', - { - defaultMessage: 'Response Anomaly Score', - } -); - export const STATUS_ALERT_COLUMN = i18n.translate( 'xpack.synthetics.monitorList.statusAlert.label', { defaultMessage: 'Status alert', } ); - -export const TEST_NOW_COLUMN = i18n.translate('xpack.synthetics.monitorList.testNow.label', { - defaultMessage: 'Test now', -}); - -export const TEST_NOW_AVAILABLE_LABEL = i18n.translate( - 'xpack.synthetics.monitorList.testNow.available', - { - defaultMessage: 'Test now is only available for monitors added via Monitor Management.', - } -); - -export const TEST_SCHEDULED_LABEL = i18n.translate( - 'xpack.synthetics.monitorList.testNow.scheduled', - { - defaultMessage: 'Test is already scheduled', - } -); - -export const PRIVATE_AVAILABLE_LABEL = i18n.translate( - 'xpack.synthetics.monitorList.testNow.available.private', - { - defaultMessage: `You can't currently test monitors running on private locations on demand.`, - } -); - -export const TEST_NOW_ARIA_LABEL = i18n.translate( - 'xpack.synthetics.monitorList.testNow.AriaLabel', - { - defaultMessage: 'Click to run test now', - } -); - -export const TEST_NOW_LABEL = i18n.translate('xpack.synthetics.monitorList.testNow.label', { - defaultMessage: 'Test now', -}); diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/lib/__mocks__/uptime_store.mock.ts b/x-pack/plugins/synthetics/public/legacy_uptime/lib/__mocks__/uptime_store.mock.ts index 2d8b6aeeead04..f463fc8e84f42 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/lib/__mocks__/uptime_store.mock.ts +++ b/x-pack/plugins/synthetics/public/legacy_uptime/lib/__mocks__/uptime_store.mock.ts @@ -6,7 +6,6 @@ */ import { DYNAMIC_SETTINGS_DEFAULTS } from '../../../../common/constants'; -import { DEFAULT_THROTTLING } from '../../../../common/runtime_types'; import { AppState } from '../../state'; /** @@ -60,34 +59,6 @@ export const mockState: AppState = { summaries: [], }, loading: false, - refreshedMonitorIds: [], - }, - monitorManagementList: { - throttling: DEFAULT_THROTTLING, - list: { - page: 1, - perPage: 10, - total: null, - monitors: [], - syncErrors: null, - absoluteTotal: 0, - }, - locations: [], - loading: { - monitorList: false, - serviceLocations: false, - enablement: false, - }, - error: { - monitorList: null, - serviceLocations: null, - enablement: null, - }, - enablement: null, - syntheticsService: { - loading: false, - signupUrl: null, - }, }, ml: { mlJob: { @@ -127,7 +98,4 @@ export const mockState: AppState = { cacheSize: 0, hitCount: [], }, - testNowRuns: {}, - agentPolicies: { loading: false, data: null, error: null }, - deleteMonitor: {}, }; diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/pages/certificates.test.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/pages/certificates.test.tsx index d61fde13663a7..252ea7dea1312 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/pages/certificates.test.tsx +++ b/x-pack/plugins/synthetics/public/legacy_uptime/pages/certificates.test.tsx @@ -14,10 +14,6 @@ describe('CertificatesPage', () => { const { findByText } = render(); expect(await findByText('This table contains 0 rows; Page 1 of 0.')).toBeInTheDocument(); - expect( - await findByText( - 'No Certificates found. Note: Certificates are only visible for Heartbeat 7.8+' - ) - ).toBeInTheDocument(); + expect(await findByText('No Certificates found.')).toBeInTheDocument(); }); }); diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/pages/index.ts b/x-pack/plugins/synthetics/public/legacy_uptime/pages/index.ts index bbd41b87f54a0..352ceb39123e8 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/pages/index.ts +++ b/x-pack/plugins/synthetics/public/legacy_uptime/pages/index.ts @@ -10,8 +10,3 @@ export { MonitorPage } from './monitor'; export { StepDetailPage } from './synthetics/step_detail_page'; export { SettingsPage } from './settings'; export { NotFoundPage } from './not_found'; -export { AddMonitorPage } from './monitor_management/add_monitor'; -export { EditMonitorPage } from './monitor_management/edit_monitor'; -export { MonitorManagementPage } from './monitor_management/monitor_management'; -export { MonitorManagementBottomBar } from './monitor_management/bottom_bar'; -export { APIKeysButton } from './monitor_management/api_keys_btn'; diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/pages/monitor_management/add_monitor.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/pages/monitor_management/add_monitor.tsx deleted file mode 100644 index 16910019fa32e..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/pages/monitor_management/add_monitor.tsx +++ /dev/null @@ -1,71 +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 } from 'react'; -import { i18n } from '@kbn/i18n'; -import { useTrackPageview } from '@kbn/observability-plugin/public'; -import { useDispatch } from 'react-redux'; -import { ScheduleUnit } from '../../../../common/runtime_types'; -import { SyntheticsProviders } from '../../components/fleet_package/contexts'; -import { Loader } from '../../components/monitor_management/loader/loader'; -import { MonitorConfig } from '../../components/monitor_management/monitor_config/monitor_config'; -import { useLocations } from '../../components/monitor_management/hooks/use_locations'; -import { useMonitorManagementBreadcrumbs } from './use_monitor_management_breadcrumbs'; -import { getAgentPoliciesAction } from '../../state/private_locations'; - -export const AddMonitorPage: React.FC = () => { - useTrackPageview({ app: 'uptime', path: 'add-monitor' }); - useTrackPageview({ app: 'uptime', path: 'add-monitor', delay: 15000 }); - - const { error, loading, locations, throttling } = useLocations(); - - const dispatch = useDispatch(); - - useMonitorManagementBreadcrumbs({ isAddMonitor: true }); - - useEffect(() => { - dispatch(getAgentPoliciesAction.get()); - }, [dispatch]); - - return ( - - - - - - ); -}; - -const LOADING_LABEL = i18n.translate('xpack.synthetics.monitorManagement.addMonitorLoadingLabel', { - defaultMessage: 'Loading Monitor Management', -}); - -const ERROR_HEADING_LABEL = i18n.translate( - 'xpack.synthetics.monitorManagement.addMonitorLoadingError', - { - defaultMessage: 'Error loading Monitor Management', - } -); - -const ERROR_BODY_LABEL = i18n.translate( - 'xpack.synthetics.monitorManagement.addMonitorServiceLocationsLoadingError', - { - defaultMessage: 'Service locations were not able to be loaded. Please try again later.', - } -); diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/pages/monitor_management/api_keys_btn.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/pages/monitor_management/api_keys_btn.tsx deleted file mode 100644 index 9a2d41e184e36..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/pages/monitor_management/api_keys_btn.tsx +++ /dev/null @@ -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 React from 'react'; -import { OutPortal } from 'react-reverse-portal'; -import { APIKeysPortalNode } from './portals'; - -export const APIKeysButton = () => { - return ( -
- -
- ); -}; diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/pages/monitor_management/bottom_bar.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/pages/monitor_management/bottom_bar.tsx deleted file mode 100644 index 93109bc7682f8..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/pages/monitor_management/bottom_bar.tsx +++ /dev/null @@ -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 React from 'react'; -import { OutPortal } from 'react-reverse-portal'; -import { ActionBarPortalNode } from './portals'; - -export const MonitorManagementBottomBar = () => { - return ( -
- -
- ); -}; diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/pages/monitor_management/content.ts b/x-pack/plugins/synthetics/public/legacy_uptime/pages/monitor_management/content.ts deleted file mode 100644 index e82f5f2eac062..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/pages/monitor_management/content.ts +++ /dev/null @@ -1,36 +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 { i18n } from '@kbn/i18n'; - -export const LOADING_LABEL = i18n.translate( - 'xpack.synthetics.monitorManagement.editMonitorLoadingLabel', - { - defaultMessage: 'Loading monitor', - } -); - -export const ERROR_HEADING_LABEL = i18n.translate( - 'xpack.synthetics.monitorManagement.editMonitorError', - { - defaultMessage: 'Error loading Monitor Management', - } -); - -export const SERVICE_LOCATIONS_ERROR_LABEL = i18n.translate( - 'xpack.synthetics.monitorManagement.addMonitorError', - { - defaultMessage: 'Service locations were not able to be loaded. Please try again later.', - } -); - -export const MONITOR_LOADING_ERROR_LABEL = i18n.translate( - 'xpack.synthetics.monitorManagement.editMonitorErrorBody', - { - defaultMessage: 'Monitor configuration was not able to be loaded. Please try again later.', - } -); diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/pages/monitor_management/disabled_callout.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/pages/monitor_management/disabled_callout.tsx deleted file mode 100644 index fd0a0feba5a15..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/pages/monitor_management/disabled_callout.tsx +++ /dev/null @@ -1,88 +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 { EuiButton, EuiCallOut, EuiLink, EuiSpacer } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; -import { useSelector } from 'react-redux'; -import { monitorManagementListSelector } from '../../state/selectors'; -import { useEnablement } from '../../components/monitor_management/hooks/use_enablement'; - -export const DisabledCallout = () => { - const { enablement, enableSynthetics } = useEnablement(); - const { list: monitorList } = useSelector(monitorManagementListSelector); - - const showDisableCallout = !enablement.isEnabled && monitorList.total && monitorList.total > 0; - - if (!showDisableCallout) { - return null; - } - - return ( - <> - -

{CALLOUT_MANAGEMENT_DESCRIPTION}

- {enablement.canEnable ? ( - { - enableSynthetics(); - }} - > - {SYNTHETICS_ENABLE_LABEL} - - ) : ( -

- {CALLOUT_MANAGEMENT_CONTACT_ADMIN}{' '} - - {LEARN_MORE_LABEL} - -

- )} -
- - - ); -}; - -const LEARN_MORE_LABEL = i18n.translate( - 'xpack.synthetics.monitorManagement.manageMonitorLoadingLabel.disabledCallout.learnMore', - { - defaultMessage: 'Learn more', - } -); - -const CALLOUT_MANAGEMENT_DISABLED = i18n.translate( - 'xpack.synthetics.monitorManagement.callout.disabled', - { - defaultMessage: 'Monitor Management is disabled', - } -); - -const CALLOUT_MANAGEMENT_CONTACT_ADMIN = i18n.translate( - 'xpack.synthetics.monitorManagement.disabledCallout.adminContact', - { - defaultMessage: 'Contact your administrator to enable Monitor Management.', - } -); - -const CALLOUT_MANAGEMENT_DESCRIPTION = i18n.translate( - 'xpack.synthetics.monitorManagement.disabledCallout.description.disabled', - { - defaultMessage: - 'Monitor Management is currently disabled and your existing monitors are paused. You can enable Monitor Management to run your monitors.', - } -); - -const SYNTHETICS_ENABLE_LABEL = i18n.translate( - 'xpack.synthetics.monitorManagement.syntheticsEnableLabel.management', - { - defaultMessage: 'Enable Monitor Management', - } -); diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/pages/monitor_management/edit_monitor.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/pages/monitor_management/edit_monitor.tsx deleted file mode 100644 index d0397de8be960..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/pages/monitor_management/edit_monitor.tsx +++ /dev/null @@ -1,51 +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 { useParams } from 'react-router-dom'; -import { useTrackPageview, FETCH_STATUS, useFetcher } from '@kbn/observability-plugin/public'; -import { MonitorFields } from '../../../../common/runtime_types'; -import { EditMonitorConfig } from '../../components/monitor_management/edit_monitor_config'; -import { Loader } from '../../components/monitor_management/loader/loader'; -import { getMonitor } from '../../state/api'; -import { DecryptedSyntheticsMonitorSavedObject } from '../../../../common/types'; -import { useLocations } from '../../components/monitor_management/hooks/use_locations'; -import { useMonitorManagementBreadcrumbs } from './use_monitor_management_breadcrumbs'; -import { - LOADING_LABEL, - ERROR_HEADING_LABEL, - MONITOR_LOADING_ERROR_LABEL, - SERVICE_LOCATIONS_ERROR_LABEL, -} from './content'; - -export const EditMonitorPage: React.FC = () => { - useTrackPageview({ app: 'uptime', path: 'edit-monitor' }); - useTrackPageview({ app: 'uptime', path: 'edit-monitor', delay: 15000 }); - useMonitorManagementBreadcrumbs({ isEditMonitor: true }); - const { monitorId } = useParams<{ monitorId: string }>(); - - const { data, status } = useFetcher< - Promise - >(() => { - return getMonitor({ id: monitorId }); - }, [monitorId]); - - const monitor = data?.attributes as MonitorFields; - const { error: locationsError, loading: locationsLoading, throttling } = useLocations(); - - return ( - - {monitor && } - - ); -}; diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/pages/monitor_management/manage_locations.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/pages/monitor_management/manage_locations.tsx deleted file mode 100644 index d4a1dc05617e4..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/pages/monitor_management/manage_locations.tsx +++ /dev/null @@ -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 React from 'react'; -import { OutPortal } from 'react-reverse-portal'; -import { ManageLocationsPortalNode } from './portals'; - -export const ManageLocations = () => { - return ( -
- -
- ); -}; diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/pages/monitor_management/monitor_management.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/pages/monitor_management/monitor_management.tsx deleted file mode 100644 index c7ff463cca95a..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/pages/monitor_management/monitor_management.tsx +++ /dev/null @@ -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 React, { useEffect, useRef, useState } from 'react'; -import { i18n } from '@kbn/i18n'; -import { useSelector } from 'react-redux'; -import { useTrackPageview } from '@kbn/observability-plugin/public'; -import { InvalidApiKeyCalloutCallout } from './invalid_api_key_callout'; -import { DisabledCallout } from './disabled_callout'; -import { ManageLocationsPortal } from '../../components/monitor_management/manage_locations/manage_locations'; -import { monitorManagementListSelector } from '../../state/selectors'; -import { useMonitorManagementBreadcrumbs } from './use_monitor_management_breadcrumbs'; -import { MonitorListContainer } from '../../components/monitor_management/monitor_list/monitor_list_container'; -import { EnablementEmptyState } from '../../components/monitor_management/monitor_list/enablement_empty_state'; -import { useEnablement } from '../../components/monitor_management/hooks/use_enablement'; -import { useLocations } from '../../components/monitor_management/hooks/use_locations'; -import { Loader } from '../../components/monitor_management/loader/loader'; -import { ERROR_HEADING_LABEL } from './content'; -import { useMonitorList } from '../../components/monitor_management/hooks/use_monitor_list'; - -export const MonitorManagementPage: React.FC = () => { - useTrackPageview({ app: 'uptime', path: 'manage-monitors' }); - useTrackPageview({ app: 'uptime', path: 'manage-monitors', delay: 15000 }); - useMonitorManagementBreadcrumbs(); - const [shouldFocusEnablementButton, setShouldFocusEnablementButton] = useState(false); - - const { error: enablementError, enablement, loading: enablementLoading } = useEnablement(); - const { loading: locationsLoading } = useLocations(); - const { list: monitorList } = useSelector(monitorManagementListSelector); - const { isEnabled } = enablement; - - const isEnabledRef = useRef(isEnabled); - - useEffect(() => { - if (!isEnabled && isEnabledRef.current === true) { - /* shift focus to enable button when enable toggle disappears. Prevent - * focus loss on the page */ - setShouldFocusEnablementButton(true); - } - isEnabledRef.current = Boolean(isEnabled); - }, [isEnabled]); - - const { pageState, dispatchPageAction } = useMonitorList(); - - const showEmptyState = isEnabled !== undefined && monitorList.total === 0; - - return ( - <> - - - - - - - {showEmptyState && } - - ); -}; - -const LOADING_LABEL = i18n.translate( - 'xpack.synthetics.monitorManagement.manageMonitorLoadingLabel', - { - defaultMessage: 'Loading Monitor Management', - } -); - -const ERROR_HEADING_BODY = i18n.translate( - 'xpack.synthetics.monitorManagement.editMonitorError.description', - { - defaultMessage: 'Monitor Management settings could not be loaded. Please contact Support.', - } -); diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/pages/monitor_management/service_allowed_wrapper.test.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/pages/monitor_management/service_allowed_wrapper.test.tsx deleted file mode 100644 index 35f4f33e2cfbf..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/pages/monitor_management/service_allowed_wrapper.test.tsx +++ /dev/null @@ -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 { render, forNearestButton, forNearestAnchor } from '../../lib/helper/rtl_helpers'; - -import * as allowedHook from '../../components/monitor_management/hooks/use_service_allowed'; -import { ServiceAllowedWrapper } from './service_allowed_wrapper'; - -describe('ServiceAllowedWrapper', () => { - it('renders expected elements for valid props', async () => { - const { findByText } = render( - -
Test text
-
- ); - - expect(await findByText('Test text')).toBeInTheDocument(); - }); - - it('renders loading state when allowed state is loading', async () => { - jest - .spyOn(allowedHook, 'useSyntheticsServiceAllowed') - .mockReturnValue({ loading: true, signupUrl: null }); - - const { findByText } = render( - -
Test text
-
- ); - - expect(await findByText('Loading Monitor Management')).toBeInTheDocument(); - }); - - it('renders children when allowed state is true', async () => { - jest - .spyOn(allowedHook, 'useSyntheticsServiceAllowed') - .mockReturnValue({ loading: false, isAllowed: true, signupUrl: 'https://example.com' }); - - const { findByText, queryByText } = render( - -
Test text
-
- ); - - expect(await findByText('Test text')).toBeInTheDocument(); - expect(await queryByText('Monitor management')).not.toBeInTheDocument(); - }); - - describe('when enabled state is false', () => { - it('renders an enabled button if there is a form URL', async () => { - jest - .spyOn(allowedHook, 'useSyntheticsServiceAllowed') - .mockReturnValue({ loading: false, isAllowed: false, signupUrl: 'https://example.com' }); - - const { findByText, getByText } = render( - -
Test text
-
- ); - - expect(await findByText('Monitor Management')).toBeInTheDocument(); - expect(forNearestAnchor(getByText)('Request access')).toBeEnabled(); - expect(forNearestAnchor(getByText)('Request access')).toHaveAttribute( - 'href', - 'https://example.com' - ); - }); - - it('renders a disabled button if there is no form URL', async () => { - jest - .spyOn(allowedHook, 'useSyntheticsServiceAllowed') - .mockReturnValue({ loading: false, isAllowed: false, signupUrl: null }); - - const { findByText, getByText } = render( - -
Test text
-
- ); - - expect(await findByText('Monitor Management')).toBeInTheDocument(); - expect(forNearestButton(getByText)('Request access')).toBeDisabled(); - }); - - it('renders when enabled state is false', async () => { - jest - .spyOn(allowedHook, 'useSyntheticsServiceAllowed') - .mockReturnValue({ loading: false, isAllowed: false, signupUrl: 'https://example.com' }); - - const { findByText } = render( - -
Test text
-
- ); - - expect(await findByText('Monitor Management')).toBeInTheDocument(); - }); - }); -}); diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/pages/monitor_management/service_allowed_wrapper.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/pages/monitor_management/service_allowed_wrapper.tsx deleted file mode 100644 index 1418380bfd6a0..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/pages/monitor_management/service_allowed_wrapper.tsx +++ /dev/null @@ -1,71 +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 { i18n } from '@kbn/i18n'; -import { EuiButton, EuiEmptyPrompt, EuiLoadingLogo } from '@elastic/eui'; -import { useSyntheticsServiceAllowed } from '../../components/monitor_management/hooks/use_service_allowed'; - -export const ServiceAllowedWrapper: React.FC = ({ children }) => { - const { isAllowed, signupUrl, loading } = useSyntheticsServiceAllowed(); - - // checking for explicit false - if (isAllowed === false) { - return ( - {MONITOR_MANAGEMENT_LABEL}} - body={

{PUBLIC_BETA_DESCRIPTION}

} - actions={[ - - {REQUEST_ACCESS_LABEL} - , - ]} - /> - ); - } - - return ( - <> - {loading && ( - } - title={

{LOADING_MONITOR_MANAGEMENT_LABEL}

} - /> - )} -
{children}
- - ); -}; - -const REQUEST_ACCESS_LABEL = i18n.translate('xpack.synthetics.monitorManagement.requestAccess', { - defaultMessage: 'Request access', -}); - -export const MONITOR_MANAGEMENT_LABEL = i18n.translate('xpack.synthetics.monitorManagement.label', { - defaultMessage: 'Monitor Management', -}); - -const LOADING_MONITOR_MANAGEMENT_LABEL = i18n.translate( - 'xpack.synthetics.monitorManagement.loading.label', - { - defaultMessage: 'Loading Monitor Management', - } -); - -export const PUBLIC_BETA_DESCRIPTION = i18n.translate( - 'xpack.synthetics.monitorManagement.publicBetaDescription', - { - defaultMessage: - "We've got a brand new app on the way. In the meantime, we're excited to give you early access to our globally managed testing infrastructure. This will allow you to upload synthetic monitors using our new point and click script recorder and manage your monitors with a new UI.", - } -); diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/pages/monitor_management/use_monitor_management_breadcrumbs.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/pages/monitor_management/use_monitor_management_breadcrumbs.tsx deleted file mode 100644 index 2c7a4fe7a8259..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/pages/monitor_management/use_monitor_management_breadcrumbs.tsx +++ /dev/null @@ -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 { i18n } from '@kbn/i18n'; -import { useKibana } from '@kbn/kibana-react-plugin/public'; -import { useBreadcrumbs } from '../../hooks/use_breadcrumbs'; -import { MONITOR_MANAGEMENT_ROUTE } from '../../../../common/constants'; -import { PLUGIN } from '../../../../common/constants/plugin'; - -export const useMonitorManagementBreadcrumbs = ({ - isAddMonitor, - isEditMonitor, - monitorId, -}: { - isAddMonitor?: boolean; - isEditMonitor?: boolean; - monitorId?: string; -} = {}) => { - const kibana = useKibana(); - const appPath = kibana.services.application?.getUrlForApp(PLUGIN.ID) ?? ''; - - useBreadcrumbs([ - { - text: MONITOR_MANAGEMENT_CRUMB, - href: - isAddMonitor || isEditMonitor ? `${appPath}/${MONITOR_MANAGEMENT_ROUTE}/all` : undefined, - }, - ...(isAddMonitor - ? [ - { - text: ADD_MONITOR_CRUMB, - }, - ] - : []), - ...(isEditMonitor - ? [ - { - text: EDIT_MONITOR_CRUMB, - }, - ] - : []), - ]); -}; - -export const MONITOR_MANAGEMENT_CRUMB = i18n.translate( - 'xpack.synthetics.monitorManagement.monitorManagementCrumb', - { - defaultMessage: 'Monitor Management', - } -); - -export const ADD_MONITOR_CRUMB = i18n.translate( - 'xpack.synthetics.monitorManagement.addMonitorCrumb', - { - defaultMessage: 'Add monitor', - } -); - -export const EDIT_MONITOR_CRUMB = i18n.translate( - 'xpack.synthetics.monitorManagement.editMonitorCrumb', - { - defaultMessage: 'Edit monitor', - } -); diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/routes.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/routes.tsx index dda5953f6e8e9..c4032142c55d9 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/routes.tsx +++ b/x-pack/plugins/synthetics/public/legacy_uptime/routes.tsx @@ -6,7 +6,6 @@ */ import React, { FC, useEffect } from 'react'; -import { EuiBetaBadge, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { Switch } from 'react-router-dom'; import { Route } from '@kbn/shared-ux-router'; @@ -15,31 +14,16 @@ import { i18n } from '@kbn/i18n'; import { APP_WRAPPER_CLASS } from '@kbn/core/public'; import { useInspectorContext } from '@kbn/observability-plugin/public'; import type { LazyObservabilityPageTemplateProps } from '@kbn/observability-plugin/public'; -import { ManageLocations } from './pages/monitor_management/manage_locations'; import { CERTIFICATES_ROUTE, MAPPING_ERROR_ROUTE, MONITOR_ROUTE, - MONITOR_ADD_ROUTE, - MONITOR_EDIT_ROUTE, - MONITOR_MANAGEMENT_ROUTE, OVERVIEW_ROUTE, SETTINGS_ROUTE, STEP_DETAIL_ROUTE, SYNTHETIC_CHECK_STEPS_ROUTE, } from '../../common/constants'; -import { - MappingErrorPage, - MonitorPage, - AddMonitorPage, - EditMonitorPage, - MonitorManagementPage, - StepDetailPage, - NotFoundPage, - SettingsPage, - MonitorManagementBottomBar, - APIKeysButton, -} from './pages'; +import { MappingErrorPage, MonitorPage, StepDetailPage, NotFoundPage, SettingsPage } from './pages'; import { CertificatesPage } from './pages/certificates'; import { UptimePage, useUptimeTelemetry } from './hooks'; import { OverviewPageComponent } from './pages/overview'; @@ -59,9 +43,7 @@ import { } from './pages/synthetics/step_detail_page'; import { UptimePageTemplateComponent } from './app/uptime_page_template'; import { apiService } from './state/api/utils'; -import { AddMonitorBtn } from './components/monitor_management/add_monitor_btn'; import { SettingsBottomBar } from './components/settings/settings_bottom_bar'; -import { ServiceAllowedWrapper } from './pages/monitor_management/service_allowed_wrapper'; type RouteProps = LazyObservabilityPageTemplateProps & { path: string; @@ -187,95 +169,6 @@ const getRoutes = (): RouteProps[] => { rightSideItems: [], }, }, - { - title: i18n.translate('xpack.synthetics.addMonitorRoute.title', { - defaultMessage: 'Add Monitor | {baseTitle}', - values: { baseTitle }, - }), - path: MONITOR_ADD_ROUTE, - component: () => ( - - - - ), - dataTestSubj: 'uptimeMonitorAddPage', - telemetryId: UptimePage.MonitorAdd, - pageHeader: { - pageTitle: ( - - ), - rightSideItems: [, ], - }, - bottomBar: , - bottomBarProps: { paddingSize: 'm' as const }, - }, - { - title: i18n.translate('xpack.synthetics.editMonitorRoute.title', { - defaultMessage: 'Edit Monitor | {baseTitle}', - values: { baseTitle }, - }), - path: MONITOR_EDIT_ROUTE, - component: () => ( - - - - ), - dataTestSubj: 'uptimeMonitorEditPage', - telemetryId: UptimePage.MonitorEdit, - pageHeader: { - pageTitle: ( - - ), - rightSideItems: [, ], - }, - bottomBar: , - bottomBarProps: { paddingSize: 'm' as const }, - }, - { - title: i18n.translate('xpack.synthetics.monitorManagementRoute.title', { - defaultMessage: 'Monitor Management | {baseTitle}', - values: { baseTitle }, - }), - path: MONITOR_MANAGEMENT_ROUTE + '/:type?', - component: () => ( - - - - ), - dataTestSubj: 'uptimeMonitorManagementListPage', - telemetryId: UptimePage.MonitorManagement, - pageHeader: { - pageTitle: ( - - - - - - - - - ), - rightSideItems: [, , ], - }, - }, ]; }; diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/state/actions/index.ts b/x-pack/plugins/synthetics/public/legacy_uptime/state/actions/index.ts index 657564160271b..370d201097d8f 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/state/actions/index.ts +++ b/x-pack/plugins/synthetics/public/legacy_uptime/state/actions/index.ts @@ -13,4 +13,3 @@ export * from './ping'; export * from './ml_anomaly'; export * from './monitor_duration'; export * from './index_status'; -export * from './monitor_management'; diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/state/actions/monitor_list.ts b/x-pack/plugins/synthetics/public/legacy_uptime/state/actions/monitor_list.ts index 8f7946dbd9891..dbf7fc097e152 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/state/actions/monitor_list.ts +++ b/x-pack/plugins/synthetics/public/legacy_uptime/state/actions/monitor_list.ts @@ -6,29 +6,13 @@ */ import { createAction } from 'redux-actions'; -import { TestNowResponse } from '../../../../common/types'; import { FetchMonitorStatesQueryArgs, MonitorSummariesResult, } from '../../../../common/runtime_types'; -import { createAsyncAction } from './utils'; export const getMonitorList = createAction('GET_MONITOR_LIST'); export const getMonitorListSuccess = createAction( 'GET_MONITOR_LIST_SUCCESS' ); export const getMonitorListFailure = createAction('GET_MONITOR_LIST_FAIL'); - -export const setUpdatingMonitorId = createAction('SET_UPDATING_MONITOR_ID'); -export const clearRefreshedMonitorId = createAction('CLEAR_REFRESH_MONITOR_ID'); - -export const testNowMonitorAction = createAsyncAction( - 'TEST_NOW_MONITOR_ACTION' -); - -export const clearTestNowMonitorAction = createAction('CLEAR_TEST_NOW_MONITOR_ACTION'); - -export const getUpdatedMonitor = createAsyncAction< - FetchMonitorStatesQueryArgs, - MonitorSummariesResult ->('GET_UPDATED_MONITOR'); diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/state/actions/monitor_management.ts b/x-pack/plugins/synthetics/public/legacy_uptime/state/actions/monitor_management.ts deleted file mode 100644 index 79bec6f3e1b65..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/state/actions/monitor_management.ts +++ /dev/null @@ -1,51 +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 { createAction } from '@reduxjs/toolkit'; -import { - MonitorManagementListResult, - ServiceLocations, - ThrottlingOptions, - FetchMonitorManagementListQueryArgs, -} from '../../../../common/runtime_types'; -import { createAsyncAction } from './utils'; -import { SyntheticsServiceAllowed } from '../../../../common/types'; - -export const getMonitors = createAction( - 'GET_MONITOR_MANAGEMENT_LIST' -); -export const getMonitorsSuccess = createAction( - 'GET_MONITOR_MANAGEMENT_LIST_SUCCESS' -); -export const getMonitorsFailure = createAction('GET_MONITOR_MANAGEMENT_LIST_FAILURE'); - -export const getServiceLocations = createAction('GET_SERVICE_LOCATIONS_LIST'); -export const getServiceLocationsSuccess = createAction<{ - throttling: ThrottlingOptions | undefined; - locations: ServiceLocations; -}>('GET_SERVICE_LOCATIONS_LIST_SUCCESS'); -export const getServiceLocationsFailure = createAction('GET_SERVICE_LOCATIONS_LIST_FAILURE'); - -export const getSyntheticsEnablement = createAction('GET_SYNTHETICS_ENABLEMENT'); -export const getSyntheticsEnablementSuccess = createAction( - 'GET_SYNTHETICS_ENABLEMENT_SUCCESS' -); -export const getSyntheticsEnablementFailure = createAction( - 'GET_SYNTHETICS_ENABLEMENT_FAILURE' -); - -export const disableSynthetics = createAction('DISABLE_SYNTHETICS'); -export const disableSyntheticsSuccess = createAction('DISABLE_SYNTEHTICS_SUCCESS'); -export const disableSyntheticsFailure = createAction('DISABLE_SYNTHETICS_FAILURE'); - -export const enableSynthetics = createAction('ENABLE_SYNTHETICS'); -export const enableSyntheticsSuccess = createAction('ENABLE_SYNTEHTICS_SUCCESS'); -export const enableSyntheticsFailure = createAction('ENABLE_SYNTHETICS_FAILURE'); - -export const getSyntheticsServiceAllowed = createAsyncAction( - 'GET_SYNTHETICS_SERVICE_ALLOWED' -); diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/state/api/index.ts b/x-pack/plugins/synthetics/public/legacy_uptime/state/api/index.ts index 6045ba7e5a11b..f3e29e2c02d2f 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/state/api/index.ts +++ b/x-pack/plugins/synthetics/public/legacy_uptime/state/api/index.ts @@ -13,4 +13,3 @@ export * from './dynamic_settings'; export * from './index_status'; export * from './ping'; export * from './monitor_duration'; -export * from './monitor_management'; diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/state/api/monitor_management.ts b/x-pack/plugins/synthetics/public/legacy_uptime/state/api/monitor_management.ts deleted file mode 100644 index 4e844602d6d5c..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/state/api/monitor_management.ts +++ /dev/null @@ -1,120 +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 { API_URLS } from '../../../../common/constants'; -import { - FetchMonitorManagementListQueryArgs, - MonitorManagementListResultCodec, - MonitorManagementListResult, - MonitorManagementEnablementResultCodec, - MonitorManagementEnablementResult, - ServiceLocations, - SyntheticsMonitor, - EncryptedSyntheticsMonitor, - ServiceLocationsApiResponseCodec, - ServiceLocationErrors, - ThrottlingOptions, -} from '../../../../common/runtime_types'; -import { - DecryptedSyntheticsMonitorSavedObject, - SyntheticsServiceAllowed, - TestNowResponse, -} from '../../../../common/types'; -import { apiService } from './utils'; - -export const setMonitor = async ({ - monitor, - id, -}: { - monitor: SyntheticsMonitor | EncryptedSyntheticsMonitor; - id?: string; -}): Promise<{ attributes: { errors: ServiceLocationErrors } } | SyntheticsMonitor> => { - if (id) { - return await apiService.put(`${API_URLS.SYNTHETICS_MONITORS}/${id}`, monitor); - } else { - return await apiService.post(API_URLS.SYNTHETICS_MONITORS, monitor, undefined, { - preserve_namespace: true, - }); - } -}; - -export const getMonitor = async ({ - id, -}: { - id: string; -}): Promise => { - return await apiService.get(`${API_URLS.SYNTHETICS_MONITORS}/${id}`); -}; - -export const deleteMonitor = async ({ id }: { id: string }): Promise => { - return await apiService.delete(`${API_URLS.SYNTHETICS_MONITORS}/${id}`); -}; - -export const fetchMonitorManagementList = async ( - params: FetchMonitorManagementListQueryArgs -): Promise => { - return await apiService.get( - API_URLS.SYNTHETICS_MONITORS, - params, - MonitorManagementListResultCodec - ); -}; - -export const fetchServiceLocations = async (): Promise<{ - throttling: ThrottlingOptions | undefined; - locations: ServiceLocations; -}> => { - const { throttling, locations } = await apiService.get( - API_URLS.SERVICE_LOCATIONS, - undefined, - ServiceLocationsApiResponseCodec - ); - return { throttling, locations }; -}; - -export const runOnceMonitor = async ({ - monitor, - id, -}: { - monitor: SyntheticsMonitor; - id: string; -}): Promise<{ errors: Array<{ error: Error }> }> => { - return await apiService.post(API_URLS.RUN_ONCE_MONITOR + `/${id}`, monitor); -}; - -export const triggerTestNowMonitor = async ( - configId: string -): Promise => { - return await apiService.get(API_URLS.TRIGGER_MONITOR + `/${configId}`); -}; - -export const fetchGetSyntheticsEnablement = - async (): Promise => { - return await apiService.get( - API_URLS.SYNTHETICS_ENABLEMENT, - undefined, - MonitorManagementEnablementResultCodec - ); - }; - -export const fetchDisableSynthetics = async (): Promise => { - return await apiService.delete(API_URLS.SYNTHETICS_ENABLEMENT); -}; - -export const fetchEnableSynthetics = async (): Promise => { - return await apiService.post(API_URLS.SYNTHETICS_ENABLEMENT); -}; - -export const fetchServiceAllowed = async (): Promise => { - return await apiService.get(API_URLS.SERVICE_ALLOWED); -}; - -export const fetchServiceAPIKey = async (): Promise<{ - apiKey: { encoded: string }; -}> => { - return await apiService.get(API_URLS.SYNTHETICS_APIKEY); -}; diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/state/effects/delete_monitor.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/state/effects/delete_monitor.tsx deleted file mode 100644 index fc4a68c974620..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/state/effects/delete_monitor.tsx +++ /dev/null @@ -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 { toMountPoint } from '@kbn/kibana-react-plugin/public'; -import { put, call, takeEvery } from 'redux-saga/effects'; -import { Action } from 'redux-actions'; -import { i18n } from '@kbn/i18n'; -import { deleteMonitorAction } from '../actions/delete_monitor'; -import { deleteMonitor } from '../api'; -import { kibanaService } from '../kibana_service'; - -export function* deleteMonitorEffect() { - yield takeEvery( - String(deleteMonitorAction.get), - function* (action: Action<{ id: string; name: string }>) { - try { - const { id, name } = action.payload; - yield call(deleteMonitor, { id }); - yield put(deleteMonitorAction.success(id)); - kibanaService.core.notifications.toasts.addSuccess({ - title: toMountPoint( -

- {i18n.translate( - 'xpack.synthetics.monitorManagement.monitorDeleteSuccessMessage.name', - { - defaultMessage: 'Deleted "{name}"', - values: { name }, - } - )} -

- ), - }); - } catch (err) { - kibanaService.core.notifications.toasts.addError(err, { - title: MONITOR_DELETE_FAILURE_LABEL, - }); - yield put(deleteMonitorAction.fail({ id: action.payload.id, error: err })); - } - } - ); -} - -const MONITOR_DELETE_FAILURE_LABEL = i18n.translate( - 'xpack.synthetics.monitorManagement.monitorDeleteFailureMessage', - { - defaultMessage: 'Monitor was unable to be deleted. Please try again later.', - } -); diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/state/effects/index.ts b/x-pack/plugins/synthetics/public/legacy_uptime/state/effects/index.ts index 3fade3e59257e..7935b003e5113 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/state/effects/index.ts +++ b/x-pack/plugins/synthetics/public/legacy_uptime/state/effects/index.ts @@ -6,16 +6,9 @@ */ import { fork } from 'redux-saga/effects'; -import { deleteMonitorEffect } from './delete_monitor'; -import { fetchAgentPoliciesEffect } from '../private_locations'; import { fetchMonitorDetailsEffect } from './monitor'; -import { fetchMonitorListEffect, fetchUpdatedMonitorEffect } from './monitor_list'; -import { - fetchMonitorManagementEffect, - fetchSyntheticsServiceAllowedEffect, -} from './monitor_management'; +import { fetchMonitorListEffect } from './monitor_list'; import { fetchMonitorStatusEffect } from './monitor_status'; -import { fetchTestNowMonitorEffect } from './test_now_runs'; import { fetchDynamicSettingsEffect, setDynamicSettingsEffect } from './dynamic_settings'; import { fetchPingsEffect, fetchPingHistogramEffect } from './ping'; import { fetchMonitorDurationEffect } from './monitor_duration'; @@ -33,10 +26,7 @@ import { export function* rootEffect() { yield fork(fetchMonitorDetailsEffect); yield fork(fetchMonitorListEffect); - yield fork(fetchUpdatedMonitorEffect); - yield fork(fetchMonitorManagementEffect); yield fork(fetchMonitorStatusEffect); - yield fork(fetchTestNowMonitorEffect); yield fork(fetchDynamicSettingsEffect); yield fork(setDynamicSettingsEffect); yield fork(fetchPingsEffect); @@ -50,7 +40,4 @@ export function* rootEffect() { yield fork(fetchScreenshotBlocks); yield fork(generateBlockStatsOnPut); yield fork(pruneBlockCache); - yield fork(fetchSyntheticsServiceAllowedEffect); - yield fork(fetchAgentPoliciesEffect); - yield fork(deleteMonitorEffect); } diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/state/effects/monitor_list.ts b/x-pack/plugins/synthetics/public/legacy_uptime/state/effects/monitor_list.ts index 3e4d0f67ffa18..154d32ca47a7e 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/state/effects/monitor_list.ts +++ b/x-pack/plugins/synthetics/public/legacy_uptime/state/effects/monitor_list.ts @@ -6,12 +6,7 @@ */ import { takeLatest } from 'redux-saga/effects'; -import { - getMonitorList, - getMonitorListSuccess, - getMonitorListFailure, - getUpdatedMonitor, -} from '../actions'; +import { getMonitorList, getMonitorListSuccess, getMonitorListFailure } from '../actions'; import { fetchMonitorList } from '../api'; import { fetchEffectFactory } from './fetch_effect'; @@ -21,10 +16,3 @@ export function* fetchMonitorListEffect() { fetchEffectFactory(fetchMonitorList, getMonitorListSuccess, getMonitorListFailure) ); } - -export function* fetchUpdatedMonitorEffect() { - yield takeLatest( - getUpdatedMonitor.get, - fetchEffectFactory(fetchMonitorList, getUpdatedMonitor.success, getUpdatedMonitor.fail) - ); -} diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/state/effects/monitor_management.ts b/x-pack/plugins/synthetics/public/legacy_uptime/state/effects/monitor_management.ts deleted file mode 100644 index b5ee599b0a4f9..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/state/effects/monitor_management.ts +++ /dev/null @@ -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 { takeLatest, takeLeading } from 'redux-saga/effects'; -import { - getMonitors, - getMonitorsSuccess, - getMonitorsFailure, - getServiceLocations, - getServiceLocationsSuccess, - getServiceLocationsFailure, - getSyntheticsEnablement, - getSyntheticsEnablementSuccess, - getSyntheticsEnablementFailure, - disableSynthetics, - disableSyntheticsSuccess, - disableSyntheticsFailure, - enableSynthetics, - enableSyntheticsSuccess, - enableSyntheticsFailure, - getSyntheticsServiceAllowed, -} from '../actions'; -import { - fetchMonitorManagementList, - fetchServiceLocations, - fetchServiceAllowed, - fetchGetSyntheticsEnablement, - fetchDisableSynthetics, - fetchEnableSynthetics, -} from '../api'; -import { fetchEffectFactory } from './fetch_effect'; - -export function* fetchMonitorManagementEffect() { - yield takeLeading( - getMonitors, - fetchEffectFactory(fetchMonitorManagementList, getMonitorsSuccess, getMonitorsFailure) - ); - yield takeLeading( - getServiceLocations, - fetchEffectFactory( - fetchServiceLocations, - getServiceLocationsSuccess, - getServiceLocationsFailure - ) - ); - yield takeLeading( - getSyntheticsEnablement, - fetchEffectFactory( - fetchGetSyntheticsEnablement, - getSyntheticsEnablementSuccess, - getSyntheticsEnablementFailure - ) - ); - yield takeLatest( - disableSynthetics, - fetchEffectFactory(fetchDisableSynthetics, disableSyntheticsSuccess, disableSyntheticsFailure) - ); - yield takeLatest( - enableSynthetics, - fetchEffectFactory(fetchEnableSynthetics, enableSyntheticsSuccess, enableSyntheticsFailure) - ); -} - -export function* fetchSyntheticsServiceAllowedEffect() { - yield takeLeading( - getSyntheticsServiceAllowed.get, - fetchEffectFactory( - fetchServiceAllowed, - getSyntheticsServiceAllowed.success, - getSyntheticsServiceAllowed.fail - ) - ); -} diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/state/effects/test_now_runs.ts b/x-pack/plugins/synthetics/public/legacy_uptime/state/effects/test_now_runs.ts deleted file mode 100644 index 8b4e6718ae7b5..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/state/effects/test_now_runs.ts +++ /dev/null @@ -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 { takeEvery } from 'redux-saga/effects'; -import type { IHttpFetchError } from '@kbn/core-http-browser'; -import { TestNowResponse } from '../../../../common/types'; -import { testNowMonitorAction } from '../actions'; -import { triggerTestNowMonitor } from '../api'; -import { fetchEffectFactory } from './fetch_effect'; - -export function* fetchTestNowMonitorEffect() { - yield takeEvery( - testNowMonitorAction.get, - fetchEffectFactory(triggerTestNowMonitor, testNowMonitorSuccessAction, testNowMonitorFailAction) - ); -} - -function testNowMonitorSuccessAction(payload: TestNowResponse | undefined) { - return testNowMonitorAction.success(payload); -} - -function testNowMonitorFailAction(payload: IHttpFetchError) { - return testNowMonitorAction.fail(payload); -} diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/state/private_locations/actions.ts b/x-pack/plugins/synthetics/public/legacy_uptime/state/private_locations/actions.ts deleted file mode 100644 index 73870f3fd1935..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/state/private_locations/actions.ts +++ /dev/null @@ -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 { createAction } from '@reduxjs/toolkit'; -import { AgentPoliciesList } from '.'; -import { createAsyncAction } from '../../../apps/synthetics/state/utils/actions'; - -export const getAgentPoliciesAction = createAsyncAction( - '[AGENT POLICIES] GET' -); - -export const setManageFlyoutOpen = createAction('SET MANAGE FLYOUT OPEN'); - -export const setAddingNewPrivateLocation = createAction('SET MANAGE FLYOUT ADDING NEW'); diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/state/private_locations/api.ts b/x-pack/plugins/synthetics/public/legacy_uptime/state/private_locations/api.ts deleted file mode 100644 index 2f898e6dbdb4f..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/state/private_locations/api.ts +++ /dev/null @@ -1,58 +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 { SavedObjectsClientContract } from '@kbn/core/public'; -import { AgentPoliciesList } from '.'; -import { - privateLocationsSavedObjectId, - privateLocationsSavedObjectName, -} from '../../../../common/saved_objects/private_locations'; -import { apiService } from '../api/utils'; -import { SyntheticsPrivateLocations } from '../../../../common/runtime_types'; - -const FLEET_URLS = { - AGENT_POLICIES: '/api/fleet/agent_policies', -}; - -export const fetchAgentPolicies = async (): Promise => { - return await apiService.get( - FLEET_URLS.AGENT_POLICIES, - { - page: 1, - perPage: 10000, - sortField: 'name', - sortOrder: 'asc', - full: true, - kuery: 'ingest-agent-policies.is_managed : false', - }, - null - ); -}; - -export const setSyntheticsPrivateLocations = async ( - client: SavedObjectsClientContract, - privateLocations: SyntheticsPrivateLocations -) => { - const result = await client.create(privateLocationsSavedObjectName, privateLocations, { - id: privateLocationsSavedObjectId, - overwrite: true, - }); - - return result.attributes; -}; - -export const getSyntheticsPrivateLocations = async (client: SavedObjectsClientContract) => { - try { - const obj = await client.get( - privateLocationsSavedObjectName, - privateLocationsSavedObjectId - ); - return obj?.attributes.locations ?? []; - } catch (getErr) { - return []; - } -}; diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/state/private_locations/effects.ts b/x-pack/plugins/synthetics/public/legacy_uptime/state/private_locations/effects.ts deleted file mode 100644 index c731beade6682..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/state/private_locations/effects.ts +++ /dev/null @@ -1,22 +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 { takeLeading } from 'redux-saga/effects'; -import { fetchAgentPolicies } from './api'; -import { getAgentPoliciesAction } from './actions'; -import { fetchEffectFactory } from '../../../apps/synthetics/state/utils/fetch_effect'; - -export function* fetchAgentPoliciesEffect() { - yield takeLeading( - getAgentPoliciesAction.get, - fetchEffectFactory( - fetchAgentPolicies, - getAgentPoliciesAction.success, - getAgentPoliciesAction.fail - ) - ); -} diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/state/private_locations/index.ts b/x-pack/plugins/synthetics/public/legacy_uptime/state/private_locations/index.ts deleted file mode 100644 index 831f8a9cbf6bb..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/state/private_locations/index.ts +++ /dev/null @@ -1,63 +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 { createReducer } from '@reduxjs/toolkit'; -import { AgentPolicy } from '@kbn/fleet-plugin/common'; -import { IHttpSerializedFetchError } from '../../../apps/synthetics/state'; -import { - getAgentPoliciesAction, - setAddingNewPrivateLocation, - setManageFlyoutOpen, -} from './actions'; - -export interface AgentPoliciesList { - items: AgentPolicy[]; - total: number; - page: number; - perPage: number; -} - -export interface AgentPoliciesState { - data: AgentPoliciesList | null; - loading: boolean; - error: IHttpSerializedFetchError | null; - isManageFlyoutOpen?: boolean; - isAddingNewPrivateLocation?: boolean; -} - -const initialState: AgentPoliciesState = { - data: null, - loading: false, - error: null, - isManageFlyoutOpen: false, - isAddingNewPrivateLocation: false, -}; - -export const agentPoliciesReducer = createReducer(initialState, (builder) => { - builder - .addCase(getAgentPoliciesAction.get, (state) => { - state.loading = true; - }) - .addCase(getAgentPoliciesAction.success, (state, action) => { - state.data = action.payload; - state.loading = false; - }) - .addCase(getAgentPoliciesAction.fail, (state, action) => { - state.error = action.payload; - state.loading = false; - }) - .addCase(setManageFlyoutOpen, (state, action) => { - state.isManageFlyoutOpen = action.payload; - }) - .addCase(setAddingNewPrivateLocation, (state, action) => { - state.isAddingNewPrivateLocation = action.payload; - }); -}); - -export * from './actions'; -export * from './effects'; -export * from './selectors'; diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/state/private_locations/selectors.ts b/x-pack/plugins/synthetics/public/legacy_uptime/state/private_locations/selectors.ts deleted file mode 100644 index 2bf51e2836cd5..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/state/private_locations/selectors.ts +++ /dev/null @@ -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 { createSelector } from 'reselect'; -import { AppState } from '..'; - -const getState = (appState: AppState) => appState.agentPolicies; -export const selectAgentPolicies = createSelector(getState, (state) => state); -export const selectManageFlyoutOpen = createSelector(getState, (state) => - Boolean(state.isManageFlyoutOpen) -); - -export const selectAddingNewPrivateLocation = createSelector(getState, (state) => - Boolean(state.isAddingNewPrivateLocation) -); diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/state/reducers/delete_monitor.ts b/x-pack/plugins/synthetics/public/legacy_uptime/state/reducers/delete_monitor.ts deleted file mode 100644 index b28cc214f0e8b..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/state/reducers/delete_monitor.ts +++ /dev/null @@ -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 { createReducer, PayloadAction } from '@reduxjs/toolkit'; -import { WritableDraft } from 'immer/dist/types/types-external'; -import { deleteMonitorAction } from '../actions/delete_monitor'; - -export interface DeleteMonitorState { - error?: Record; - loading?: string[]; - deletedMonitorIds?: string[]; -} - -export const initialState: DeleteMonitorState = { - error: {}, - loading: [], - deletedMonitorIds: [], -}; - -export const deleteMonitorReducer = createReducer(initialState, (builder) => { - builder - .addCase( - String(deleteMonitorAction.get), - ( - state: WritableDraft, - action: PayloadAction<{ id: string; name: string }> - ) => ({ - ...state, - loading: [...(state.loading ?? []), action.payload.id], - error: { ...state.error, [action.payload.id]: undefined }, - }) - ) - .addCase( - String(deleteMonitorAction.success), - (state: WritableDraft, action: PayloadAction) => ({ - ...state, - loading: state.loading?.filter((id) => id !== action.payload), - deletedMonitorIds: [...(state.deletedMonitorIds ?? []), action.payload], - }) - ) - .addCase( - String(deleteMonitorAction.fail), - ( - state: WritableDraft, - action: PayloadAction<{ id: string; error: Error }> - ) => ({ - ...state, - loading: state.loading?.filter((id) => id !== action.payload.id), - error: { ...state.error, [action.payload.id]: action.payload.error }, - }) - ); -}); diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/state/reducers/index.ts b/x-pack/plugins/synthetics/public/legacy_uptime/state/reducers/index.ts index 554d8f1a44cd8..7e79ea8abeaa2 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/state/reducers/index.ts +++ b/x-pack/plugins/synthetics/public/legacy_uptime/state/reducers/index.ts @@ -6,8 +6,6 @@ */ import { combineReducers } from 'redux'; -import { deleteMonitorReducer, DeleteMonitorState } from './delete_monitor'; -import { agentPoliciesReducer, AgentPoliciesState } from '../private_locations'; import { monitorReducer, MonitorState } from './monitor'; import { uiReducer, UiState } from './ui'; import { monitorStatusReducer, MonitorStatusState } from './monitor_status'; @@ -25,14 +23,11 @@ import { alertsReducer, AlertState } from '../alerts/alerts'; import { JourneyKVP, journeyReducer } from './journey'; import { networkEventsReducer, NetworkEventsState } from './network_events'; import { syntheticsReducer, SyntheticsReducerState } from './synthetics'; -import { monitorManagementListReducer, MonitorManagementList } from './monitor_management'; -import { testNowRunsReducer, TestNowRunsState } from './test_now_runs'; export interface RootState { monitor: MonitorState; ui: UiState; monitorList: MonitorList; - monitorManagementList: MonitorManagementList; monitorStatus: MonitorStatusState; dynamicSettings: DynamicSettingsState; ping: PingState; @@ -46,16 +41,12 @@ export interface RootState { journeys: JourneyKVP; networkEvents: NetworkEventsState; synthetics: SyntheticsReducerState; - testNowRuns: TestNowRunsState; - agentPolicies: AgentPoliciesState; - deleteMonitor: DeleteMonitorState; } export const rootReducer = combineReducers({ monitor: monitorReducer, ui: uiReducer, monitorList: monitorListReducer, - monitorManagementList: monitorManagementListReducer, monitorStatus: monitorStatusReducer, dynamicSettings: dynamicSettingsReducer, ping: pingReducer, @@ -69,7 +60,4 @@ export const rootReducer = combineReducers({ journeys: journeyReducer, networkEvents: networkEventsReducer, synthetics: syntheticsReducer, - testNowRuns: testNowRunsReducer, - agentPolicies: agentPoliciesReducer, - deleteMonitor: deleteMonitorReducer, }); diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/state/reducers/monitor_list.ts b/x-pack/plugins/synthetics/public/legacy_uptime/state/reducers/monitor_list.ts index 4b37af47d6781..7076858ba6357 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/state/reducers/monitor_list.ts +++ b/x-pack/plugins/synthetics/public/legacy_uptime/state/reducers/monitor_list.ts @@ -7,23 +7,12 @@ import { handleActions, type Action } from 'redux-actions'; import type { IHttpFetchError, ResponseErrorBody } from '@kbn/core-http-browser'; -import { TestNowResponse } from '../../../../common/types'; -import { - getMonitorList, - getMonitorListSuccess, - getMonitorListFailure, - getUpdatedMonitor, - clearRefreshedMonitorId, - setUpdatingMonitorId, -} from '../actions'; +import { getMonitorList, getMonitorListSuccess, getMonitorListFailure } from '../actions'; import type { MonitorSummariesResult } from '../../../../common/runtime_types'; -import type { AppState } from '..'; export interface MonitorList { loading: boolean; isLoaded?: boolean; - refreshedMonitorIds?: string[]; - isUpdating?: string[]; list: MonitorSummariesResult; error?: IHttpFetchError; } @@ -36,13 +25,9 @@ export const initialState: MonitorList = { }, loading: false, isLoaded: false, - refreshedMonitorIds: [], }; -type Payload = MonitorSummariesResult & - IHttpFetchError & - string & - TestNowResponse; +type Payload = MonitorSummariesResult & IHttpFetchError & string; export const monitorListReducer = handleActions( { @@ -69,64 +54,6 @@ export const monitorListReducer = handleActions( loading: false, isLoaded: true, }), - [String(setUpdatingMonitorId)]: (state: MonitorList, action: Action) => ({ - ...state, - isUpdating: [...(state.isUpdating ?? []), action.payload], - }), - [String(getUpdatedMonitor.get)]: (state: MonitorList) => ({ - ...state, - }), - [String(getUpdatedMonitor.success)]: ( - state: MonitorList, - action: Action - ) => { - const summaries = state.list.summaries; - - const newSummary = action.payload.summaries?.[0]; - - if (!newSummary) { - return { ...state, isUpdating: [] }; - } - - return { - ...state, - loading: false, - error: undefined, - isUpdating: state.isUpdating?.filter((item) => item !== newSummary.monitor_id), - refreshedMonitorIds: [...(state.refreshedMonitorIds ?? []), newSummary.monitor_id], - list: { - ...state.list, - summaries: summaries.map((summary) => { - if (summary.monitor_id === newSummary.monitor_id) { - return newSummary; - } - return summary; - }), - }, - }; - }, - [String(getUpdatedMonitor.fail)]: ( - state: MonitorList, - action: Action> - ) => ({ - ...state, - error: action.payload, - loading: false, - isUpdating: [], - }), - [String(clearRefreshedMonitorId)]: (state: MonitorList, action: Action) => ({ - ...state, - refreshedMonitorIds: (state.refreshedMonitorIds ?? []).filter( - (item) => item !== action.payload - ), - }), }, initialState ); - -export const refreshedMonitorSelector = ({ monitorList }: AppState) => { - return monitorList.refreshedMonitorIds ?? []; -}; - -export const isUpdatingMonitorSelector = ({ monitorList }: AppState) => - monitorList.isUpdating ?? []; diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/state/reducers/monitor_management.ts b/x-pack/plugins/synthetics/public/legacy_uptime/state/reducers/monitor_management.ts deleted file mode 100644 index ef9f3239625ff..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/state/reducers/monitor_management.ts +++ /dev/null @@ -1,309 +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 { createReducer, PayloadAction } from '@reduxjs/toolkit'; -import { WritableDraft } from 'immer/dist/types/types-external'; -import { - getMonitors, - getMonitorsSuccess, - getMonitorsFailure, - getServiceLocations, - getServiceLocationsSuccess, - getServiceLocationsFailure, - getSyntheticsEnablement, - getSyntheticsEnablementSuccess, - getSyntheticsEnablementFailure, - disableSynthetics, - disableSyntheticsSuccess, - disableSyntheticsFailure, - enableSynthetics, - enableSyntheticsSuccess, - enableSyntheticsFailure, - getSyntheticsServiceAllowed, -} from '../actions'; -import { - MonitorManagementEnablementResult, - MonitorManagementListResult, - ServiceLocations, - ThrottlingOptions, - DEFAULT_THROTTLING, -} from '../../../../common/runtime_types'; -import { SyntheticsServiceAllowed } from '../../../../common/types'; - -export interface MonitorManagementList { - error: Record<'monitorList' | 'serviceLocations' | 'enablement', Error | null>; - loading: Record<'monitorList' | 'serviceLocations' | 'enablement', boolean>; - list: MonitorManagementListResult; - locations: ServiceLocations; - enablement: MonitorManagementEnablementResult | null; - syntheticsService: { isAllowed?: boolean; signupUrl: string | null; loading: boolean }; - throttling: ThrottlingOptions; - locationsLoaded?: boolean; -} - -export const initialState: MonitorManagementList = { - list: { - page: 1, - perPage: 10, - total: null, - monitors: [], - syncErrors: [], - absoluteTotal: 0, - }, - locations: [], - enablement: null, - loading: { - monitorList: false, - serviceLocations: false, - enablement: false, - }, - error: { - monitorList: null, - serviceLocations: null, - enablement: null, - }, - syntheticsService: { - signupUrl: null, - loading: false, - }, - throttling: DEFAULT_THROTTLING, - locationsLoaded: false, -}; - -export const monitorManagementListReducer = createReducer(initialState, (builder) => { - builder - .addCase(getMonitors, (state: WritableDraft) => ({ - ...state, - loading: { - ...state.loading, - monitorList: true, - }, - })) - .addCase( - getMonitorsSuccess, - ( - state: WritableDraft, - action: PayloadAction - ) => ({ - ...state, - loading: { - ...state.loading, - monitorList: false, - }, - error: { - ...state.error, - monitorList: null, - }, - list: { ...action.payload }, - }) - ) - .addCase( - getMonitorsFailure, - (state: WritableDraft, action: PayloadAction) => ({ - ...state, - loading: { - ...state.loading, - monitorList: false, - }, - error: { - ...state.error, - monitorList: action.payload, - }, - }) - ) - .addCase(getServiceLocations, (state: WritableDraft) => ({ - ...state, - loading: { - ...state.loading, - serviceLocations: true, - }, - locationsLoaded: true, - })) - .addCase( - getServiceLocationsSuccess, - ( - state: WritableDraft, - action: PayloadAction<{ - throttling: ThrottlingOptions | undefined; - locations: ServiceLocations; - }> - ) => ({ - ...state, - loading: { - ...state.loading, - serviceLocations: false, - }, - error: { - ...state.error, - serviceLocations: null, - }, - locations: action.payload.locations, - throttling: action.payload.throttling || DEFAULT_THROTTLING, - }) - ) - .addCase( - getServiceLocationsFailure, - (state: WritableDraft, action: PayloadAction) => ({ - ...state, - loading: { - ...state.loading, - serviceLocations: false, - }, - error: { - ...state.error, - serviceLocations: action.payload, - }, - }) - ) - .addCase(getSyntheticsEnablement, (state: WritableDraft) => ({ - ...state, - loading: { - ...state.loading, - enablement: true, - }, - })) - .addCase( - getSyntheticsEnablementSuccess, - (state: WritableDraft, action: PayloadAction) => ({ - ...state, - loading: { - ...state.loading, - enablement: false, - }, - error: { - ...state.error, - enablement: null, - }, - enablement: action.payload, - }) - ) - .addCase( - getSyntheticsEnablementFailure, - (state: WritableDraft, action: PayloadAction) => ({ - ...state, - loading: { - ...state.loading, - enablement: false, - }, - error: { - ...state.error, - enablement: action.payload, - }, - }) - ) - .addCase(disableSynthetics, (state: WritableDraft) => ({ - ...state, - loading: { - ...state.loading, - enablement: true, - }, - })) - .addCase(disableSyntheticsSuccess, (state: WritableDraft) => ({ - ...state, - loading: { - ...state.loading, - enablement: false, - }, - error: { - ...state.error, - enablement: null, - }, - enablement: { - canManageApiKeys: state.enablement?.canManageApiKeys || false, - canEnable: state.enablement?.canEnable || false, - areApiKeysEnabled: state.enablement?.areApiKeysEnabled || false, - isEnabled: false, - isValidApiKey: state.enablement?.isValidApiKey || false, - }, - })) - .addCase( - disableSyntheticsFailure, - (state: WritableDraft, action: PayloadAction) => ({ - ...state, - loading: { - ...state.loading, - enablement: false, - }, - error: { - ...state.error, - enablement: action.payload, - }, - }) - ) - .addCase(enableSynthetics, (state: WritableDraft) => ({ - ...state, - loading: { - ...state.loading, - enablement: true, - }, - })) - .addCase( - enableSyntheticsSuccess, - (state: WritableDraft, action: PayloadAction) => ({ - ...state, - loading: { - ...state.loading, - enablement: false, - }, - error: { - ...state.error, - enablement: null, - }, - enablement: action.payload, - }) - ) - .addCase( - enableSyntheticsFailure, - (state: WritableDraft, action: PayloadAction) => ({ - ...state, - loading: { - ...state.loading, - enablement: false, - }, - error: { - ...state.error, - enablement: action.payload, - }, - }) - ) - .addCase( - String(getSyntheticsServiceAllowed.get), - (state: WritableDraft) => ({ - ...state, - syntheticsService: { - isAllowed: state.syntheticsService?.isAllowed, - signupUrl: state.syntheticsService?.signupUrl, - loading: true, - }, - }) - ) - .addCase( - String(getSyntheticsServiceAllowed.success), - ( - state: WritableDraft, - action: PayloadAction - ) => ({ - ...state, - syntheticsService: { - isAllowed: action.payload.serviceAllowed, - signupUrl: action.payload.signupUrl, - loading: false, - }, - }) - ) - .addCase( - String(getSyntheticsServiceAllowed.fail), - (state: WritableDraft, action: PayloadAction) => ({ - ...state, - syntheticsService: { - isAllowed: false, - signupUrl: null, - loading: false, - }, - }) - ); -}); diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/state/reducers/test_now_runs.ts b/x-pack/plugins/synthetics/public/legacy_uptime/state/reducers/test_now_runs.ts deleted file mode 100644 index e809fded6ece2..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/state/reducers/test_now_runs.ts +++ /dev/null @@ -1,126 +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 { createReducer, type PayloadAction } from '@reduxjs/toolkit'; -import type { WritableDraft } from 'immer/dist/types/types-external'; -import type { IHttpFetchError } from '@kbn/core-http-browser'; -import { TestNowResponse } from '../../../../common/types'; -import { - type Locations, - ScheduleUnit, - type ServiceLocationErrors, - type SyntheticsMonitorSchedule, -} from '../../../../common/runtime_types'; -import { clearTestNowMonitorAction, testNowMonitorAction } from '../actions'; -import type { AppState } from '..'; - -export enum TestRunStats { - LOADING = 'loading', - IN_PROGRESS = 'in-progress', - COMPLETED = 'completed', -} - -interface TestNowRun { - monitorId: string; - testRunId?: string; - status: TestRunStats; - schedule: SyntheticsMonitorSchedule; - locations: Locations; - errors?: ServiceLocationErrors; - fetchError?: { name: string; message: string }; -} - -export interface TestNowRunsState { - [monitorId: string]: TestNowRun; -} - -export const initialState: TestNowRunsState = {}; - -export const testNowRunsReducer = createReducer(initialState, (builder) => { - builder - .addCase( - String(testNowMonitorAction.get), - (state: WritableDraft, action: PayloadAction) => ({ - ...state, - [action.payload]: { - monitorId: action.payload, - status: TestRunStats.LOADING, - schedule: { unit: ScheduleUnit.MINUTES, number: '3' }, - locations: [], - }, - }) - ) - .addCase( - String(testNowMonitorAction.success), - (state: WritableDraft, { payload }: PayloadAction) => ({ - ...state, - [payload.configId]: { - monitorId: payload.configId, - testRunId: payload.testRunId, - status: TestRunStats.IN_PROGRESS, - errors: payload.errors, - schedule: payload.schedule, - locations: payload.locations, - }, - }) - ) - .addCase( - String(testNowMonitorAction.fail), - (state: WritableDraft, action: PayloadAction) => { - const fetchError = action.payload as unknown as IHttpFetchError; - if (fetchError?.request.url) { - const { name, message } = fetchError; - - const [, errorMonitor] = - Object.entries(state).find( - ([key]) => fetchError.request.url.indexOf(key) > -1 ?? false - ) ?? []; - - if (errorMonitor) { - return { - ...state, - [errorMonitor.monitorId]: { - ...state[errorMonitor.monitorId], - status: TestRunStats.COMPLETED, - errors: undefined, - fetchError: { name, message }, - }, - }; - } - } - - if (action.payload.configId) { - return { - ...state, - [action.payload.configId]: { - ...state[action.payload.configId], - status: TestRunStats.COMPLETED, - errors: action.payload.errors, - fetchError: undefined, - }, - }; - } - - return state; - } - ) - .addCase( - String(clearTestNowMonitorAction), - (state: WritableDraft, action: PayloadAction) => { - const { [action.payload]: payloadTestRun, ...rest } = state; - - return rest; - } - ); -}); - -export const testNowRunsSelector = ({ testNowRuns }: AppState) => testNowRuns.testNowRuns; - -export const testNowRunSelector = - (monitorId?: string) => - ({ testNowRuns }: AppState) => - monitorId ? testNowRuns[monitorId] : undefined; diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/state/selectors/index.ts b/x-pack/plugins/synthetics/public/legacy_uptime/state/selectors/index.ts index 8a91c017635ae..2f0570022d0dd 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/state/selectors/index.ts +++ b/x-pack/plugins/synthetics/public/legacy_uptime/state/selectors/index.ts @@ -19,15 +19,6 @@ export const monitorDetailsSelector = (state: AppState, summary: any) => { return state.monitor.monitorDetailsList[summary.monitor_id]; }; -export const deleteMonitorLoadingSelector = (state: AppState, id?: string) => { - if (!id) return (state.deleteMonitor.loading ?? []).length > 0; - return state.deleteMonitor.loading?.includes(id) ?? false; -}; - -export const deleteMonitorSuccessSelector = (state: AppState, id: string) => { - return state.deleteMonitor.deletedMonitorIds?.includes(id) ?? false; -}; - export const monitorDetailsLoadingSelector = (state: AppState) => state.monitor.loading; export const monitorLocationsSelector = (state: AppState, monitorId: string) => { @@ -67,8 +58,6 @@ export const hasNewMLJobSelector = ({ ml }: AppState) => ml.createJob; export const isMLJobCreatingSelector = ({ ml }: AppState) => ml.createJob.loading; export const isMLJobDeletingSelector = ({ ml }: AppState) => ml.deleteJob.loading; -export const isAnomalyAlertDeletingSelector = ({ alerts }: AppState) => - alerts.alertDeletion.loading; export const isMLJobDeletedSelector = ({ ml }: AppState) => ml.deleteJob; @@ -85,9 +74,6 @@ export const indexStatusSelector = ({ indexStatus }: AppState) => indexStatus.in export const monitorListSelector = ({ monitorList }: AppState) => monitorList; -export const monitorManagementListSelector = ({ monitorManagementList }: AppState) => - monitorManagementList; - export const esKuerySelector = ({ ui: { esKuery } }: AppState) => esKuery; export const searchTextSelector = ({ ui: { searchText } }: AppState) => searchText; @@ -101,8 +87,3 @@ export const journeySelector = ({ journeys }: AppState) => journeys; export const networkEventsSelector = ({ networkEvents }: AppState) => networkEvents; export const syntheticsSelector = ({ synthetics }: AppState) => synthetics; - -export const uptimeWriteSelector = (state: AppState) => state; - -export const syntheticsServiceAllowedSelector = (state: AppState) => - state.monitorManagementList.syntheticsService; diff --git a/x-pack/plugins/synthetics/server/legacy_uptime/lib/requests/get_certs.test.ts b/x-pack/plugins/synthetics/server/legacy_uptime/lib/requests/get_certs.test.ts index e70a8fc49da05..b33c41a7238a5 100644 --- a/x-pack/plugins/synthetics/server/legacy_uptime/lib/requests/get_certs.test.ts +++ b/x-pack/plugins/synthetics/server/legacy_uptime/lib/requests/get_certs.test.ts @@ -114,6 +114,7 @@ describe('getCerts', () => { "issuer": "GlobalSign CloudSSL CA - SHA256 - G3", "monitors": Array [ Object { + "configId": undefined, "id": "real-world-test", "name": "Real World Test", "url": undefined, @@ -158,6 +159,7 @@ describe('getCerts', () => { "monitor.id", "monitor.name", "url.full", + "config_id", ], }, "collapse": Object { diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index 89ec0645e3a7b..87b1e4e65f9c3 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -34253,7 +34253,6 @@ "xpack.stackConnectors.xmatters.title": "xMatters", "xpack.stackConnectors.xmatters.unexpectedNullResponseErrorMessage": "réponse nulle inattendue de xmatters", "xpack.synthetics.addMonitor.pageHeader.description": "Pour plus d'informations sur les types de moniteur disponibles et les autres options, consultez notre {docs}.", - "xpack.synthetics.addMonitorRoute.title": "Ajouter un moniteur | {baseTitle}", "xpack.synthetics.alertRules.monitorStatus.reasonMessage": "Le moniteur \"{name}\" de {location} est {status}. Vérifié à {checkedAt}.", "xpack.synthetics.alerts.durationAnomaly.defaultActionMessage": "Temps de réponse anormal (niveau {severity}) détecté sur le {monitor} avec l'URL {monitorUrl} à {anomalyStartTimestamp}. La note de sévérité d'anomalie est {severityScore}.\nDes temps de réponse aussi élevés que {slowestAnomalyResponse} ont été détectés à partir de l'emplacement {observerLocation}. Le temps de réponse attendu est {expectedResponseTime}.", "xpack.synthetics.alerts.durationAnomaly.defaultRecoveryMessage": "L'alerte pour temps de réponse anormal (niveau {severity}) détecté sur le moniteur {monitor} possédant l'URL {monitorUrl} depuis l'emplacement {observerLocation} à {anomalyStartTimestamp} a été résolue", @@ -34284,9 +34283,6 @@ "xpack.synthetics.charts.mlAnnotation.severity": "Sévérité : {severity}", "xpack.synthetics.controls.selectSeverity.scoreDetailsDescription": "score {value} et supérieur", "xpack.synthetics.createMonitorRoute.title": "Créer le moniteur | {baseTitle}", - "xpack.synthetics.createPackagePolicy.stepConfigure.browserAdvancedSettings.throttling.throttling_exceeded.message": "Vous avez dépassé la limite de {throttlingField} pour les nœuds synthétiques. La valeur {throttlingField} ne peut pas être supérieure à {limit} Mbits/s.", - "xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.monitorType.browser.warning.content": "Pour créer un moniteur \"Navigateur\", veuillez vous assurer que vous utilisez le conteneur Docker {agent}, qui inclut les dépendances permettant d'exécuter ces moniteurs. Pour en savoir plus, visitez notre {link}.", - "xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.params.helpText": "Utilisez JSON pour définir les paramètres qui peuvent être référencés dans votre script avec {code}", "xpack.synthetics.deprecateNoticeModal.forMoreInformation": "Pour en savoir plus, {docsLink}", "xpack.synthetics.durationChart.emptyPrompt.description": "Ce moniteur n'a jamais été {emphasizedText} au cours de la plage temporelle sélectionnée.", "xpack.synthetics.editMonitorRoute.title": "Modifier le moniteur | {baseTitle}", @@ -34343,8 +34339,6 @@ "xpack.synthetics.monitorList.redirects.description": "Heartbeat a suivi {number} redirections lors de l'exécution du ping.", "xpack.synthetics.monitorList.redirects.title.number": "{number}", "xpack.synthetics.monitorList.statusColumn.checkedTimestamp": "Vérifié à {timestamp}", - "xpack.synthetics.monitorList.statusColumn.error.message": "{message}. Cliquez pour en savoir plus.", - "xpack.synthetics.monitorList.statusColumn.error.messageLabel": "{message}. Cliquez pour en savoir plus.", "xpack.synthetics.monitorList.statusColumn.locStatusMessage": "dans {noLoc} emplacement", "xpack.synthetics.monitorList.statusColumn.locStatusMessage.multiple": "dans {noLoc} emplacements", "xpack.synthetics.monitorList.statusColumn.locStatusMessage.tooltip.down": "Arrêté dans {locs}", @@ -34353,17 +34347,11 @@ "xpack.synthetics.monitorList.tags.filter": "Filtrer tous les moniteurs avec la balise {tag}", "xpack.synthetics.monitorManagement.agentCallout.content": "Si vous avez l'intention d'exécuter les moniteurs \"Navigateur\" dans cet emplacement privé, assurez-vous d'utiliser le conteneur Docker {code}, qui contient les dépendances permettant de les exécuter. Pour en savoir plus, {link}.", "xpack.synthetics.monitorManagement.anotherPrivateLocation": "Cette politique d'agent est déjà associée à l'emplacement {locationName}.", - "xpack.synthetics.monitorManagement.cannotDelete": "Cet emplacement ne peut pas être détecté, car il a {monCount} moniteurs en cours d'exécution. Retirez cet emplacement de vos moniteurs avant de le supprimer.", "xpack.synthetics.monitorManagement.cannotDelete.description": "Cet emplacement ne peut pas être détecté, car il a {monCount, number} {monCount, plural, one {moniteur} other {moniteurs}} en cours d'exécution.\n Retirez cet emplacement de vos moniteurs avant de le supprimer.", "xpack.synthetics.monitorManagement.deleteLocationName": "Supprimer \"{location}\"", "xpack.synthetics.monitorManagement.deleteMonitorNameLabel": "Supprimer le moniteur \"{name}\" ?", - "xpack.synthetics.monitorManagement.disclaimer": "En utilisant cette fonctionnalité, le client reconnaît avoir lu et accepté {link}. ", "xpack.synthetics.monitorManagement.lastXDays": "{count, number} {count, plural, one {dernier jour} other {derniers jours}}", - "xpack.synthetics.monitorManagement.monitorAdvancedOptions.namespaceHelpLabel": "Modifiez l'espace de nom par défaut. Ce paramètre modifie le nom du flux de données du moniteur. {learnMore}.", "xpack.synthetics.monitorManagement.monitorDeleteSuccessMessage.name": "\"{name}\" supprimé", - "xpack.synthetics.monitorManagement.monitorDisabledSuccessMessage": "Moniteur {name} désactivé.", - "xpack.synthetics.monitorManagement.monitorEnabledSuccessMessage": "Moniteur {name} activé.", - "xpack.synthetics.monitorManagement.monitorEnabledUpdateFailureMessage": "Impossible de mettre à jour le moniteur {name}.", "xpack.synthetics.monitorManagement.monitorList.disclaimer.label": "Pour le supprimer complètement et empêcher qu'il soit de nouveau transmis à l'avenir, supprimez-le de la source du projet. {docsLink}.", "xpack.synthetics.monitorManagement.service.error.message": "Votre moniteur a été enregistré, mais un problème est survenu lors de la synchronisation de la configuration pour {location}. Nous réessaierons plus tard de façon automatique. Si le problème persiste, vos moniteurs arrêteront de fonctionner dans {location}. Veuillez contacter le support technique pour obtenir de l'aide.", "xpack.synthetics.monitorManagement.service.error.reason": "Raison : {reason}.", @@ -34451,7 +34439,6 @@ "xpack.synthetics.addEditMonitor.scriptEditor.label": "Éditeur de script", "xpack.synthetics.addEditMonitor.scriptEditor.placeholder": "// Collez votre script Playwright ici...", "xpack.synthetics.addMonitor.pageHeader.docsLink": "documentation", - "xpack.synthetics.addMonitor.pageHeader.title": "Ajouter un moniteur", "xpack.synthetics.alertDropdown.noWritePermissions": "Vous devez disposer d'un accès en lecture-écriture à Uptime pour créer des alertes dans cette application.", "xpack.synthetics.alertRule.monitorStatus.description": "Gérez les actions de règle de statut du moniteur Synthetics.", "xpack.synthetics.alertRules.actionGroups.monitorStatus": "Statut du moniteur Synthetics", @@ -34615,18 +34602,6 @@ "xpack.synthetics.breadcrumbs.legacyOverviewBreadcrumbText": "Uptime", "xpack.synthetics.breadcrumbs.observabilityText": "Observabilité", "xpack.synthetics.breadcrumbs.overviewBreadcrumbText": "Synthetics", - "xpack.synthetics.browser.project.browserAdvancedSettings.description": "Fournissez une configuration précise pour l'agent synthétique.", - "xpack.synthetics.browser.project.browserAdvancedSettings.screenshots.helpText": "Définissez cette option pour gérer les captures d'écran effectuées par l'agent synthétique.", - "xpack.synthetics.browser.project.browserAdvancedSettings.screenshots.label": "Options de capture d'écran", - "xpack.synthetics.browser.project.browserAdvancedSettings.title": "Options de l'agent synthétique", - "xpack.synthetics.browser.project.monitorIntegrationSettingsSection.monitorInterval": "Fréquence", - "xpack.synthetics.browser.project.monitorIntegrationSettingsSection.monitorInterval.error": "La fréquence du moniteur est requise", - "xpack.synthetics.browser.project.monitorIntegrationSettingsSection.tags.helpText": "Liste de balises qui seront envoyées avec l'événement de monitoring. Appuyez sur Entrée pour ajouter une nouvelle balise. Affiché dans Uptime et permet la recherche par balise.", - "xpack.synthetics.browser.project.monitorIntegrationSettingsSection.tags.label": "Balises", - "xpack.synthetics.browser.project.monitorIntegrationSettingsSectionDescription": "Configurez votre moniteur avec les options suivantes.", - "xpack.synthetics.browser.project.monitorIntegrationSettingsSectionTitle": "Paramètres du moniteur", - "xpack.synthetics.browser.project.readOnly.callout.content": "Ce moniteur a été ajouté depuis un projet externe. La configuration est en lecture seule.", - "xpack.synthetics.browser.project.readOnly.callout.title": "Cette configuration est en lecture seule", "xpack.synthetics.certificates.loading": "Chargement des certificats...", "xpack.synthetics.certificates.refresh": "Actualiser", "xpack.synthetics.certificatesPage.heading": "Certificats TLS", @@ -34637,7 +34612,6 @@ "xpack.synthetics.certs.list.commonName": "Nom courant", "xpack.synthetics.certs.list.copyFingerprint": "Cliquez pour copier la valeur d'empreinte", "xpack.synthetics.certs.list.days": "jours", - "xpack.synthetics.certs.list.empty": "Aucun certificat n'a été trouvé. Remarque : les certificats sont visibles uniquement pour Heartbeat 7.8+", "xpack.synthetics.certs.list.expirationDate": "Empreintes", "xpack.synthetics.certs.list.issuedBy": "Émis par", "xpack.synthetics.certs.list.monitors": "Moniteurs", @@ -34660,137 +34634,13 @@ "xpack.synthetics.createMonitor.pageHeader.title": "Créer le moniteur", "xpack.synthetics.createPackagePolicy.stepConfigure.browser.scriptRecorder.experimentalLabel": "Préversion technique", "xpack.synthetics.createPackagePolicy.stepConfigure.browser.scriptRecorder.experimentalTooltip": "Prévisualisez la méthode la plus rapide permettant de créer des scripts de monitoring Elastic Synthetics avec notre enregistreur Elastic Synthetics", - "xpack.synthetics.createPackagePolicy.stepConfigure.browser.scriptRecorder.label": "Enregistreur de scripts", - "xpack.synthetics.createPackagePolicy.stepConfigure.browserAdvancedSettings.description": "Fournissez une configuration précise pour l'agent synthétique.", - "xpack.synthetics.createPackagePolicy.stepConfigure.browserAdvancedSettings.ignoreHttpsErrors.helpText": "Définissez cette option sur \"true\" pour désactiver la validation TLS/SSL dans le navigateur synthétique. Cette opération est utile pour tester les sites qui utilisent des certificats autosignés.", - "xpack.synthetics.createPackagePolicy.stepConfigure.browserAdvancedSettings.ignoreHttpsErrors.label": "Ignorer les erreurs HTTPS", - "xpack.synthetics.createPackagePolicy.stepConfigure.browserAdvancedSettings.screenshots.helpText": "Définissez cette option pour gérer les captures d'écran effectuées par l'agent synthétique.", - "xpack.synthetics.createPackagePolicy.stepConfigure.browserAdvancedSettings.screenshots.label": "Options de capture d'écran", - "xpack.synthetics.createPackagePolicy.stepConfigure.browserAdvancedSettings.syntheticsArgs.helpText": "Arguments supplémentaires à transmettre au package de l'agent synthétique. Accepte une liste de chaînes. Cela est utile dans des scénarios rares et ne devrait normalement pas avoir besoin d'être défini.", - "xpack.synthetics.createPackagePolicy.stepConfigure.browserAdvancedSettings.syntheticsArgs.label": "Arguments synthétiques", - "xpack.synthetics.createPackagePolicy.stepConfigure.browserAdvancedSettings.throttling.automatic_node_cap.message": "Lorsque vous désactivez la régulation, la bande passante de votre moniteur sera toujours limitée par les configurations des nœuds synthétiques dans lesquels votre moniteur est en cours d'exécution.", - "xpack.synthetics.createPackagePolicy.stepConfigure.browserAdvancedSettings.throttling.automatic_node_cap.title": "Limite automatique", - "xpack.synthetics.createPackagePolicy.stepConfigure.browserAdvancedSettings.throttling.description": "Contrôlez les vitesses de téléchargement et chargement du moniteur, ainsi que sa latence pour simuler le comportement de votre application sur des réseaux plus lents.", - "xpack.synthetics.createPackagePolicy.stepConfigure.browserAdvancedSettings.throttling.download.error": "La vitesse de téléchargement doit être supérieure à zéro.", - "xpack.synthetics.createPackagePolicy.stepConfigure.browserAdvancedSettings.throttling.download.label": "Vitesse de téléchargement", - "xpack.synthetics.createPackagePolicy.stepConfigure.browserAdvancedSettings.throttling.exceeded_throttling.message": "Lors de l'utilisation de valeurs de régulation plus élevées que la limite de la bande passante d'un nœud synthétique, la bande passante de votre moniteur sera toujours limitée.", - "xpack.synthetics.createPackagePolicy.stepConfigure.browserAdvancedSettings.throttling.exceeded_throttling.title": "Vous avez dépassé les limites de bande passante du nœud synthétique", - "xpack.synthetics.createPackagePolicy.stepConfigure.browserAdvancedSettings.throttling.latency.error": "La latence ne doit pas être négative.", - "xpack.synthetics.createPackagePolicy.stepConfigure.browserAdvancedSettings.throttling.latency.label": "Latence", - "xpack.synthetics.createPackagePolicy.stepConfigure.browserAdvancedSettings.throttling.switch.description": "Activer la régulation", - "xpack.synthetics.createPackagePolicy.stepConfigure.browserAdvancedSettings.throttling.title": "Options de régulation", - "xpack.synthetics.createPackagePolicy.stepConfigure.browserAdvancedSettings.throttling.upload.error": "La vitesse de chargement doit être supérieure à zéro.", - "xpack.synthetics.createPackagePolicy.stepConfigure.browserAdvancedSettings.throttling.upload.label": "Vitesse de chargement", - "xpack.synthetics.createPackagePolicy.stepConfigure.browserAdvancedSettings.title": "Options de l'agent synthétique", - "xpack.synthetics.createPackagePolicy.stepConfigure.certificateSettings.enableSSLSettings.label": "Activer la configuration TLS", - "xpack.synthetics.createPackagePolicy.stepConfigure.certsField.certificate.helpText": "Certificat formaté PEM pour l'authentification du client TLS.", - "xpack.synthetics.createPackagePolicy.stepConfigure.certsField.certificate.label": "certificat", - "xpack.synthetics.createPackagePolicy.stepConfigure.certsField.certificateAuthorities.helpText": "Autorités de certificats personnalisés formatés PEM.", - "xpack.synthetics.createPackagePolicy.stepConfigure.certsField.certificateAuthorities.label": "Autorités de certificats", - "xpack.synthetics.createPackagePolicy.stepConfigure.certsField.certificateKey.helpText": "Clé de certificat formaté PEM pour l'authentification du client TLS.", - "xpack.synthetics.createPackagePolicy.stepConfigure.certsField.certificateKey.label": "clé", - "xpack.synthetics.createPackagePolicy.stepConfigure.certsField.certificateKeyPassphrase.helpText": "Phrase secrète de la clé de certificat pour l'authentification du client TLS.", - "xpack.synthetics.createPackagePolicy.stepConfigure.certsField.certificateKeyPassphrase.label": "phrase secrète de la clé", - "xpack.synthetics.createPackagePolicy.stepConfigure.certsField.legend": "Paramètres de certificat", - "xpack.synthetics.createPackagePolicy.stepConfigure.certsField.tlsRole.client": "Client", - "xpack.synthetics.createPackagePolicy.stepConfigure.certsField.tlsRole.server": "Serveur", - "xpack.synthetics.createPackagePolicy.stepConfigure.certsField.verificationMode.certificate.description": "Vérifie que le certificat fourni est signé par une autorité reconnue (CA), mais n'effectue aucune vérification du nom d'hôte.", - "xpack.synthetics.createPackagePolicy.stepConfigure.certsField.verificationMode.certificate.label": "Certificat", - "xpack.synthetics.createPackagePolicy.stepConfigure.certsField.verificationMode.full.description": "Vérifie que le certificat fourni est signé par une autorité reconnue (CA), mais également que le nom d'hôte du serveur (ou l'adresse IP) correspond aux noms identifiés dans le certificat.", - "xpack.synthetics.createPackagePolicy.stepConfigure.certsField.verificationMode.full.label": "Pleine", - "xpack.synthetics.createPackagePolicy.stepConfigure.certsField.verificationMode.label": "Mode de vérification", - "xpack.synthetics.createPackagePolicy.stepConfigure.certsField.verificationMode.none.description": "N'effectue aucune vérification du certificat du serveur. Il est principalement destiné à servir de mécanisme de diagnostic temporaire lors de tentatives de résolution des erreurs TLS ; son utilisation dans des environnements de production est fortement déconseillée.", - "xpack.synthetics.createPackagePolicy.stepConfigure.certsField.verificationMode.none.label": "Aucun", - "xpack.synthetics.createPackagePolicy.stepConfigure.certsField.verificationMode.strict.description": "Vérifie que le certificat fourni est signé par une autorité reconnue (CA), mais également que le nom d'hôte du serveur (ou l'adresse IP) correspond aux noms identifiés dans le certificat. Si le nom alternatif du sujet n'est pas renseigné, il renvoie une erreur.", - "xpack.synthetics.createPackagePolicy.stepConfigure.certsField.verificationMode.strict.label": "Strict", - "xpack.synthetics.createPackagePolicy.stepConfigure.certsField.verificationMode.warning.description": "Ce mode désactive de nombreux avantages de sécurité de SSL/TLS et ne doit être utilisé qu'après un examen approfondi.", - "xpack.synthetics.createPackagePolicy.stepConfigure.certsField.verificationMode.warning.title": "Désactivation de TLS", - "xpack.synthetics.createPackagePolicy.stepConfigure.certsField.version.label": "Protocoles TLS pris en charge", - "xpack.synthetics.createPackagePolicy.stepConfigure.certsField.version.placeholder": "Sélectionnez un ou plusieurs protocoles TLS.", "xpack.synthetics.createPackagePolicy.stepConfigure.headerField.addHeader.label": "Ajouter un en-tête", - "xpack.synthetics.createPackagePolicy.stepConfigure.httpAdvancedOptions": "Options HTTP avancées", - "xpack.synthetics.createPackagePolicy.stepConfigure.httpAdvancedOptions.indexResponseBody.helpText": "Contrôle l'indexation du contenu du corps de la réponse HTTP en fonction du ", - "xpack.synthetics.createPackagePolicy.stepConfigure.httpAdvancedOptions.indexResponseHeaders.helpText": "Contrôle l'indexation des en-têtes de la réponse HTTP en fonction du ", - "xpack.synthetics.createPackagePolicy.stepConfigure.httpAdvancedOptions.requestBody.helpText": "Contenu du corps de la requête.", - "xpack.synthetics.createPackagePolicy.stepConfigure.httpAdvancedOptions.requestConfiguration.description": "Configurez une requête facultative à envoyer à l'hôte distant, comprenant la méthode, le corps et les en-têtes.", - "xpack.synthetics.createPackagePolicy.stepConfigure.httpAdvancedOptions.requestConfiguration.requestBody": "Corps de la requête", - "xpack.synthetics.createPackagePolicy.stepConfigure.httpAdvancedOptions.requestConfiguration.requestHeaders": "En-têtes de la requête", - "xpack.synthetics.createPackagePolicy.stepConfigure.httpAdvancedOptions.requestConfiguration.requestMethod.label": "Méthode de la requête", - "xpack.synthetics.createPackagePolicy.stepConfigure.httpAdvancedOptions.requestConfiguration.title": "Configuration de la requête", - "xpack.synthetics.createPackagePolicy.stepConfigure.httpAdvancedOptions.requestHeadersField.error": "La clé d'en-tête doit être un token HTTP valide.", - "xpack.synthetics.createPackagePolicy.stepConfigure.httpAdvancedOptions.requestHeadersField.helpText": "Dictionnaire d'en-têtes HTTP supplémentaires à envoyer. Par défaut, le client définira l'en-tête User-Agent (utilisateur-agent) de façon à ce qu'il s'identifie lui-même.", - "xpack.synthetics.createPackagePolicy.stepConfigure.httpAdvancedOptions.requestMethod.helpText": "Méthode HTTP à utiliser.", - "xpack.synthetics.createPackagePolicy.stepConfigure.httpAdvancedOptions.responseBodyCheckNegative.helpText": "Liste d'expressions régulières pour une correspondance négative à la sortie du corps. Appuyez sur Entrée pour ajouter une nouvelle expression. Renvoie un échec de correspondance si l'expression unique correspond.", - "xpack.synthetics.createPackagePolicy.stepConfigure.httpAdvancedOptions.responseBodyCheckPositive.helpText": "Liste d'expressions régulières pour une correspondance au corps. Appuyez sur Entrée pour ajouter une nouvelle expression. Seule une expression unique a besoin de correspondre.", - "xpack.synthetics.createPackagePolicy.stepConfigure.httpAdvancedOptions.responseCheckNegative.label": "Le corps de la réponse de vérification ne contient pas", - "xpack.synthetics.createPackagePolicy.stepConfigure.httpAdvancedOptions.responseCheckPositive.label": "Le corps de la réponse de vérification contient", - "xpack.synthetics.createPackagePolicy.stepConfigure.httpAdvancedOptions.responseChecks.checkResponseHeadersContain": "Les en-têtes de la réponse de vérification contiennent", - "xpack.synthetics.createPackagePolicy.stepConfigure.httpAdvancedOptions.responseChecks.description": "Configurez la réponse HTTP attendue.", - "xpack.synthetics.createPackagePolicy.stepConfigure.httpAdvancedOptions.responseChecks.responseStatusCheck.error": "Le code de statut doit contenir uniquement des chiffres.", - "xpack.synthetics.createPackagePolicy.stepConfigure.httpAdvancedOptions.responseChecks.responseStatusCheck.helpText": "Liste de codes de statut attendus. Appuyez sur Entrée pour ajouter un nouveau code. Les codes 4xx et 5xx sont considérés comme \"Arrêté\" par défaut. Les autres codes sont considérés comme \"Opérationnel\"", - "xpack.synthetics.createPackagePolicy.stepConfigure.httpAdvancedOptions.responseChecks.responseStatusCheck.label": "Le statut de la réponse de vérification est égal à", - "xpack.synthetics.createPackagePolicy.stepConfigure.httpAdvancedOptions.responseChecks.title": "Vérifications des réponses", - "xpack.synthetics.createPackagePolicy.stepConfigure.httpAdvancedOptions.responseConfig.indexResponseBody": "Indexer le corps de réponse", - "xpack.synthetics.createPackagePolicy.stepConfigure.httpAdvancedOptions.responseConfig.indexResponseHeaders": "Indexer les en-têtes de réponse", - "xpack.synthetics.createPackagePolicy.stepConfigure.httpAdvancedOptions.responseConfig.responseBodyIndexPolicy": "Politique d'indexation du corps de réponse", - "xpack.synthetics.createPackagePolicy.stepConfigure.httpAdvancedOptions.responseConfiguration.description": "Contrôlez l'indexation des contenus de réponses HTTP.", - "xpack.synthetics.createPackagePolicy.stepConfigure.httpAdvancedOptions.responseConfiguration.title": "Configuration des réponses", - "xpack.synthetics.createPackagePolicy.stepConfigure.httpAdvancedOptions.responseHeadersField.error": "La clé d'en-tête doit être un token HTTP valide.", - "xpack.synthetics.createPackagePolicy.stepConfigure.httpAdvancedOptions.responseHeadersField.helpText": "Liste d'en-têtes de réponse attendus.", - "xpack.synthetics.createPackagePolicy.stepConfigure.icmpAdvancedOptions": "Options ICMP avancées", "xpack.synthetics.createPackagePolicy.stepConfigure.inputVarFieldOptionalLabel": "Facultatif", - "xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.APMServiceName.helpText": "Nom de service APM pour ce monitoring. Correspond au champ ECS service.name. Définissez-le lors du monitoring d'une application qui utilise également APM pour activer les intégrations entre les données Uptime et APM dans Kibana.", - "xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.APMServiceName.label": "Nom de service APM", - "xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.browser.inlineScript.error": "Script obligatoire", - "xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.browser.inlineScript.helpText": "Exécute des scripts de tests synthétiques définis en ligne.", - "xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.browser.inlineScript.label": "Script en ligne", "xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.browser.scriptRecorder.closeButtonLabel": "Fermer le menu volant du script", "xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.browser.scriptRecorder.mockFileName": "test_script.js", - "xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.browser.sourceType.label": "Type de source", "xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.browser.uploader.fieldLabel": "Script de test", "xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.browser.uploader.invalidFileError": "Type de fichier non valide. Veuillez charger un fichier .js généré par l'enregistreur Elastic Synthetics.", - "xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.browser.uploader.label": "Sélectionner un fichier .js généré par l'enregistreur", "xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.browser.uploader.parsingError": "Erreur lors du chargement du fichier. Veuillez charger un fichier .js généré par l'enregistreur Elastic Synthetics au format de script en ligne.", - "xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.browserLabel": "Navigateur (version bêta)", - "xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.enabled.helpText": "Désactivez cette configuration pour désactiver le moniteur.", - "xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.enabled.label": "Activé", - "xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.icmp.hosts": "Hôte", - "xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.icmp.hosts.error": "L'hôte est requis", - "xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.maxRedirects": "Nb maxi de redirections", - "xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.maxRedirects.error": "Le nb maxi de redirections doit être supérieur ou égal à 0", - "xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.maxRedirects.helpText": "Nombre total de redirections à suivre.", - "xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.monitorInterval": "Fréquence", - "xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.monitorInterval.error": "La fréquence du moniteur est requise", - "xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.monitorType": "Type de moniteur", - "xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.monitorType.browser.warning.link": "documentation Synthetics", - "xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.monitorType.browser.warning.title": "Exigence", - "xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.monitorType.error": "Le type de moniteur est requis", - "xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.params.label": "Paramètres", - "xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.password.helpText": "Mot de passe pour l'authentification avec le serveur.", - "xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.password.label": "Mot de passe", - "xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.proxyUrl.http.helpText": "URL du proxy HTTP.", - "xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.proxyURL.http.label": "URL du proxy", - "xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.proxyURL.label": "URL du proxy", - "xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.proxyUrl.tcp.helpText": "URL du proxy SOCKS5 à utiliser lors de la connexion au serveur. La valeur doit être une URL avec un schéma de socks5://.", - "xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.resolveHostnamesLocally": "Résout les noms d'hôte localement", - "xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.tags.helpText": "Liste de balises qui seront envoyées avec l'événement de monitoring. Appuyez sur Entrée pour ajouter une nouvelle balise. Affiché dans Uptime et permet la recherche par balise.", - "xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.tags.label": "Balises", - "xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.tcp.hosts": "Host:Port", - "xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.tcp.hosts.error": "L'hôte et le port sont requis", - "xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.timeout.helpText": "Temps total autorisé pour tester la connexion et l'échange de données.", - "xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.timeout.label": "Délai d'expiration en secondes", - "xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.timeout.lessThanIntervalError": "Le délai d'expiration doit être inférieur à la fréquence du moniteur", - "xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.timeout.moreThanZeroError": "Le délai d'expiration doit être supérieur ou égal à 0", - "xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.URL": "URL", - "xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.URL.error": "L'URL est requise", - "xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.username.helpText": "Nom d'utilisateur pour l'authentification avec le serveur.", - "xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.username.label": "Nom d'utilisateur", - "xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.wait.error": "L'attente doit être supérieure ou égale à 0", - "xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.wait.helpText": "Durée d'attente avant l'émission d'une autre requête d'écho ICMP si aucune réponse n'est reçue.", - "xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.wait.label": "Attente en secondes", - "xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSectionDescription": "Configurez votre moniteur avec les options suivantes.", - "xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSectionTitle": "Paramètres du moniteur", - "xpack.synthetics.createPackagePolicy.stepConfigure.requestBody.codeEditor.javascript.ariaLabel": "Éditeur de code JavaScript", "xpack.synthetics.createPackagePolicy.stepConfigure.requestBody.codeEditor.json.ariaLabel": "Éditeur de code JSON", "xpack.synthetics.createPackagePolicy.stepConfigure.requestBody.codeEditor.text.ariaLabel": "Éditeur de code texte", "xpack.synthetics.createPackagePolicy.stepConfigure.requestBody.codeEditor.xml.ariaLabel": "Éditeur de code XML", @@ -34801,20 +34651,6 @@ "xpack.synthetics.createPackagePolicy.stepConfigure.requestBodyType.XML": "XML", "xpack.synthetics.createPackagePolicy.stepConfigure.responseBodyIndex.always": "Toujours", "xpack.synthetics.createPackagePolicy.stepConfigure.responseBodyIndex.onError": "En cas d'erreur", - "xpack.synthetics.createPackagePolicy.stepConfigure.scheduleField.minutes": "Minutes", - "xpack.synthetics.createPackagePolicy.stepConfigure.scheduleField.number": "Nombre", - "xpack.synthetics.createPackagePolicy.stepConfigure.scheduleField.seconds": "Secondes", - "xpack.synthetics.createPackagePolicy.stepConfigure.scheduleField.unit": "Unité", - "xpack.synthetics.createPackagePolicy.stepConfigure.tcpAdvacnedSettings.requestConfiguration.description": "Configurez les données utiles envoyées à l'hôte distant.", - "xpack.synthetics.createPackagePolicy.stepConfigure.tcpAdvacnedSettings.requestConfiguration.requestPayload.helpText": "Chaîne de données utiles à envoyer à l'hôte distant.", - "xpack.synthetics.createPackagePolicy.stepConfigure.tcpAdvacnedSettings.requestConfiguration.requestPayload.label": "Charge utile de la requête", - "xpack.synthetics.createPackagePolicy.stepConfigure.tcpAdvacnedSettings.requestConfiguration.title": "Configuration de la requête", - "xpack.synthetics.createPackagePolicy.stepConfigure.tcpAdvacnedSettings.responseConfiguration.responseContains.helpText": "Réponse attendue de l'hôte distant.", - "xpack.synthetics.createPackagePolicy.stepConfigure.tcpAdvacnedSettings.responseConfiguration.responseContains.label": "La réponse de vérification contient", - "xpack.synthetics.createPackagePolicy.stepConfigure.tcpAdvancedOptions.responseConfiguration.description": "Configurez la réponse attendue à partir de l'hôte distant.", - "xpack.synthetics.createPackagePolicy.stepConfigure.tcpAdvancedOptions.responseConfiguration.title": "Vérifications des réponses", - "xpack.synthetics.createPackagePolicy.stepConfigure.tlsSettings.description": "Configurez les options TLS, y compris le mode de vérification, les autorités de certification et les certificats clients.", - "xpack.synthetics.createPackagePolicy.stepConfigure.tlsSettings.label": "Paramètres TLS", "xpack.synthetics.dcl.label": "DCL", "xpack.synthetics.deprecateNoticeModal.addPrivateLocations": "Ajouter des emplacements privés pour vos politiques Fleet", "xpack.synthetics.deprecateNoticeModal.automateMonitors": "Automatiser la création de vos moniteurs à l'aide de moniteurs de projet", @@ -35187,8 +35023,6 @@ "xpack.synthetics.monitorErrorsTab.title": "Erreurs", "xpack.synthetics.monitorHistoryTab.title": "Historique", "xpack.synthetics.monitorLastRun.lastRunLabel": "Dernière exécution", - "xpack.synthetics.monitorList.allMonitors": "Tous les moniteurs", - "xpack.synthetics.monitorList.anomalyColumn.label": "Score d'anomalies de réponse", "xpack.synthetics.monitorList.closeFlyoutText": "Fermer", "xpack.synthetics.monitorList.defineConnector.popover.description": "pour recevoir les alertes de statut.", "xpack.synthetics.monitorList.disableDownAlert": "Désactiver les alertes de statut", @@ -35212,7 +35046,6 @@ "xpack.synthetics.monitorList.infraIntegrationAction.kubernetes.description": "Vérifier l'interface utilisateur de l'infrastructure pour cet UID de pod du monitoring", "xpack.synthetics.monitorList.infraIntegrationAction.kubernetes.message": "Afficher les indicateurs de pod", "xpack.synthetics.monitorList.integrationGroup.emptyMessage": "Aucune application intégrée n'est disponible", - "xpack.synthetics.monitorList.invalidMonitors": "Moniteurs non valides", "xpack.synthetics.monitorList.lastModified": "Dernière modification", "xpack.synthetics.monitorList.lastRunHeaderText": "Dernière exécution", "xpack.synthetics.monitorList.loading": "Chargement...", @@ -35234,12 +35067,10 @@ "xpack.synthetics.monitorList.projectIdHeaderText": "ID de projet", "xpack.synthetics.monitorList.redirects.openWindow": "Le lien s'ouvrira dans une nouvelle fenêtre.", "xpack.synthetics.monitorList.redirects.title": "Redirections", - "xpack.synthetics.monitorList.refresh": "Actualiser", "xpack.synthetics.monitorList.runTest.label": "Exécuter le test", "xpack.synthetics.monitorList.statusAlert.label": "Alerte de statut", "xpack.synthetics.monitorList.statusColumn.completeLabel": "Terminé", "xpack.synthetics.monitorList.statusColumn.downLabel": "Arrêté", - "xpack.synthetics.monitorList.statusColumn.error.logs": "Logs d'erreurs", "xpack.synthetics.monitorList.statusColumn.failedLabel": "Échoué", "xpack.synthetics.monitorList.statusColumn.upLabel": "Opérationnel", "xpack.synthetics.monitorList.statusColumnLabel": "Statut", @@ -35248,9 +35079,6 @@ "xpack.synthetics.monitorList.table.url.name": "Url", "xpack.synthetics.monitorList.tags.expand": "Cliquer pour afficher les balises restantes", "xpack.synthetics.monitorList.testNow.AriaLabel": "Cliquer pour exécuter le test maintenant", - "xpack.synthetics.monitorList.testNow.available": "L'option Tester maintenant est uniquement disponible pour les moniteurs ajoutés via Gestion des moniteurs.", - "xpack.synthetics.monitorList.testNow.available.private": "Vous ne pouvez pas tester actuellement les moniteurs qui s'exécutent dans des emplacements privés à la demande.", - "xpack.synthetics.monitorList.testNow.label": "Tester maintenant", "xpack.synthetics.monitorList.testNow.scheduled": "Le test est déjà programmé", "xpack.synthetics.monitorList.testRunLogs": "Logs d'exécution de test", "xpack.synthetics.monitorList.timestamp": "Horodatage", @@ -35265,14 +35093,7 @@ "xpack.synthetics.monitorManagement.addAgentPolicyDesc": "Les emplacements privés nécessitent une politique d'agent. Pour ajouter un emplacement privé, vous devez d'abord créer une politique d'agent dans Fleet.", "xpack.synthetics.monitorManagement.addEdit.createMonitorLabel": "Créer le moniteur", "xpack.synthetics.monitorManagement.addEdit.deleteMonitorLabel": "Supprimer le moniteur", - "xpack.synthetics.monitorManagement.addLocation": "Ajouter un emplacement", "xpack.synthetics.monitorManagement.addMonitorCrumb": "Ajouter un moniteur", - "xpack.synthetics.monitorManagement.addMonitorError": "Impossible de charger les emplacements de services. Réessayez plus tard.", - "xpack.synthetics.monitorManagement.addMonitorLabel": "Ajouter un moniteur", - "xpack.synthetics.monitorManagement.addMonitorLoadingError": "Erreur lors du chargement de la liste Gestion des moniteurs", - "xpack.synthetics.monitorManagement.addMonitorLoadingLabel": "Chargement de la liste Gestion des moniteurs", - "xpack.synthetics.monitorManagement.addMonitorServiceLocationsLoadingError": "Impossible de charger les emplacements de services. Réessayez plus tard.", - "xpack.synthetics.monitorManagement.addPrivateLocations": "Ajouter un emplacement privé", "xpack.synthetics.monitorManagement.agentCallout.link": "lire les documents", "xpack.synthetics.monitorManagement.agentCallout.title": "Exigence", "xpack.synthetics.monitorManagement.agentPolicy": "Politique d'agent", @@ -35280,7 +35101,6 @@ "xpack.synthetics.monitorManagement.agentsLabel": "Agents : ", "xpack.synthetics.monitorManagement.alreadyExists": "Le nom d’emplacement existe déjà.", "xpack.synthetics.monitorManagement.apiKey.label": "Clé d'API", - "xpack.synthetics.monitorManagement.apiKeysDisabledToolTip": "Les clés d'API sont désactivées pour ce cluster. La Gestion des moniteurs requiert l'utilisation de clés d'API pour mettre à jour votre cluster Elasticsearch. Pour activer les clés d'API, veuillez contacter un administrateur.", "xpack.synthetics.monitorManagement.apiKeyWarning.label": "Cette clé d’API ne sera affichée qu'une seule fois. Veuillez en conserver une copie pour vos propres dossiers.", "xpack.synthetics.monitorManagement.areYouSure": "Voulez-vous vraiment supprimer cet emplacement ?", "xpack.synthetics.monitorManagement.callout.apiKeyMissing": "La Gestion des moniteurs est actuellement désactivée en raison d'une clé d'API manquante", @@ -35292,7 +35112,6 @@ "xpack.synthetics.monitorManagement.cancelLabel": "Annuler", "xpack.synthetics.monitorManagement.cannotSaveIntegration": "Vous n'êtes pas autorisé à mettre à jour les intégrations. Des autorisations d'écriture pour les intégrations sont requises.", "xpack.synthetics.monitorManagement.closeButtonLabel": "Fermer", - "xpack.synthetics.monitorManagement.closeLabel": "Fermer", "xpack.synthetics.monitorManagement.completed": "TERMINÉ", "xpack.synthetics.monitorManagement.configurations.label": "Configurations", "xpack.synthetics.monitorManagement.createAgentPolicy": "Créer une stratégie d'agent", @@ -35301,24 +35120,14 @@ "xpack.synthetics.monitorManagement.createLocationMonitors": "Créer le moniteur", "xpack.synthetics.monitorManagement.createMonitorLabel": "Créer le moniteur", "xpack.synthetics.monitorManagement.createPrivateLocations": "Créer un emplacement privé", - "xpack.synthetics.monitorManagement.delete": "Supprimer l’emplacement", "xpack.synthetics.monitorManagement.deletedPolicy": "La politique est supprimée", "xpack.synthetics.monitorManagement.deleteLocation": "Supprimer l’emplacement", "xpack.synthetics.monitorManagement.deleteLocationLabel": "Supprimer l’emplacement", - "xpack.synthetics.monitorManagement.deleteMonitorLabel": "Supprimer le moniteur", "xpack.synthetics.monitorManagement.disabled.label": "Désactivé", - "xpack.synthetics.monitorManagement.disabledCallout.adminContact": "Contactez votre administrateur pour activer la Gestion des moniteurs.", - "xpack.synthetics.monitorManagement.disabledCallout.description.disabled": "La Gestion des moniteurs est actuellement désactivée, et vos moniteurs existants ont été suspendus. Vous pouvez activer la Gestion des moniteurs pour exécuter vos moniteurs.", - "xpack.synthetics.monitorManagement.disableMonitorLabel": "Désactiver le moniteur", "xpack.synthetics.monitorManagement.discardLabel": "Annuler", - "xpack.synthetics.monitorManagement.disclaimerLinkLabel": "Il n'y a aucun coût pour l'utilisation du service pour exécuter vos tests pendant la période bêta. Une politique d'utilisation équitable s'appliquera. Des coûts normaux de stockage des données s'appliquent aux résultats des tests stockés dans votre cluster Elastic.", - "xpack.synthetics.monitorManagement.duplicateNameError": "Le nom du moniteur existe déjà.", "xpack.synthetics.monitorManagement.editMonitorCrumb": "Modifier le moniteur", "xpack.synthetics.monitorManagement.editMonitorError": "Erreur lors du chargement de la liste Gestion des moniteurs", "xpack.synthetics.monitorManagement.editMonitorError.description": "Les paramètres de Gestion des moniteurs n'ont pas pu être chargés. Veuillez contacter le support technique.", - "xpack.synthetics.monitorManagement.editMonitorErrorBody": "Impossible de charger la configuration du moniteur. Réessayez plus tard.", - "xpack.synthetics.monitorManagement.editMonitorLabel": "Modifier le moniteur", - "xpack.synthetics.monitorManagement.editMonitorLoadingLabel": "Chargement du moniteur", "xpack.synthetics.monitorManagement.emptyState.enablement": "Activez la Gestion des moniteurs pour exécuter des moniteurs légers et basés sur un navigateur réel à partir d'emplacements de test hébergés dans le monde entier. L'activation de la Gestion des moniteurs générera une clé d'API pour autoriser le service Synthetics à mettre à jour votre cluster Elasticsearch.", "xpack.synthetics.monitorManagement.emptyState.enablement.disabled.title": "La Gestion des moniteurs est désactivée", "xpack.synthetics.monitorManagement.emptyState.enablement.disabledDescription": "La Gestion des moniteurs est actuellement désactivée. La Gestion des moniteurs vous permet d'exécuter des moniteurs légers et basés sur un navigateur réel à partir d'emplacements de test hébergés dans le monde entier. Pour activer la Gestion des moniteurs, veuillez contacter un administrateur.", @@ -35326,7 +35135,6 @@ "xpack.synthetics.monitorManagement.emptyState.enablement.enabled.title": "Activer la Gestion des moniteurs", "xpack.synthetics.monitorManagement.emptyState.enablement.learnMore": "Envie d'en savoir plus ?", "xpack.synthetics.monitorManagement.emptyState.enablement.title": "Activer", - "xpack.synthetics.monitorManagement.enableMonitorLabel": "Activer le moniteur", "xpack.synthetics.monitorManagement.failed": "ÉCHOUÉ", "xpack.synthetics.monitorManagement.failedRun": "Impossible d'exécuter les étapes", "xpack.synthetics.monitorManagement.filter.frequencyLabel": "Fréquence", @@ -35335,21 +35143,14 @@ "xpack.synthetics.monitorManagement.filter.projectLabel": "Projet", "xpack.synthetics.monitorManagement.filter.tagsLabel": "Balises", "xpack.synthetics.monitorManagement.filter.typeLabel": "Type", - "xpack.synthetics.monitorManagement.firstLocation": "Ajoutez votre premier emplacement privé", "xpack.synthetics.monitorManagement.firstLocationMonitor": "Pour créer un moniteur, vous devez d'abord ajouter un emplacement.", - "xpack.synthetics.monitorManagement.getApiKey.label": "Générer une clé API", "xpack.synthetics.monitorManagement.getAPIKeyLabel.description": "Utilisez une clé d’API pour transmettre des moniteurs à distance à partir d'un pipeline CLI ou CD.", "xpack.synthetics.monitorManagement.getAPIKeyLabel.disclaimer": "Remarque : pour utiliser des moniteurs push avec des emplacements de test privés, vous devez générer cette clé d’API avec un utilisateur qui dispose de droits d'écriture pour Fleet et Integrations.", - "xpack.synthetics.monitorManagement.getAPIKeyLabel.generate": "Générer une clé d’API", - "xpack.synthetics.monitorManagement.getAPIKeyLabel.label": "Clés d'API", "xpack.synthetics.monitorManagement.getAPIKeyLabel.loading": "Génération d’une clé d’API", "xpack.synthetics.monitorManagement.getAPIKeyReducedPermissions.description": "Utilisez une clé d’API pour transmettre des moniteurs à distance à partir d'un pipeline CLI ou CD. Pour générer une clé d’API, vous devez disposer des autorisations de gérer les clés d’API et d’un accès en écriture à Uptime. Veuillez contacter votre administrateur.", "xpack.synthetics.monitorManagement.getProjectApiKey.label": "Générer une clé d'API de projet", "xpack.synthetics.monitorManagement.getProjectAPIKeyLabel.generate": "Générer une clé d'API de projet", - "xpack.synthetics.monitorManagement.heading": "Gestion des moniteurs", - "xpack.synthetics.monitorManagement.hostFieldLabel": "Hôte", "xpack.synthetics.monitorManagement.inProgress": "EN COURS", - "xpack.synthetics.monitorManagement.invalidLabel": "Non valide", "xpack.synthetics.monitorManagement.label": "Gestion des moniteurs", "xpack.synthetics.monitorManagement.learnMore": "Pour en savoir plus,", "xpack.synthetics.monitorManagement.learnMore.label": "En savoir plus", @@ -35360,30 +35161,10 @@ "xpack.synthetics.monitorManagement.manageMonitorLoadingLabel": "Chargement de la liste Gestion des moniteurs", "xpack.synthetics.monitorManagement.manageMonitorLoadingLabel.callout.invalidKey": "En savoir plus", "xpack.synthetics.monitorManagement.manageMonitorLoadingLabel.callout.learnMore": "En savoir plus.", - "xpack.synthetics.monitorManagement.manageMonitorLoadingLabel.disabledCallout.learnMore": "En savoir plus", - "xpack.synthetics.monitorManagement.managePrivateLocations": "Emplacements privés", "xpack.synthetics.monitorManagement.monitorAddedSuccessMessage": "Moniteur ajouté avec succès.", - "xpack.synthetics.monitorManagement.monitorAdvancedOptions.dataStreamConfiguration.description": "Configurez les options de flux de données supplémentaires.", - "xpack.synthetics.monitorManagement.monitorAdvancedOptions.dataStreamConfiguration.title": "Paramètres de flux de données", - "xpack.synthetics.monitorManagement.monitorAdvancedOptions.monitorNamespaceFieldLabel": "Espace de nom", - "xpack.synthetics.monitorManagement.monitorAdvancedOptions.namespaceHelpLearnMoreLabel": "En savoir plus", - "xpack.synthetics.monitorManagement.monitorDeleteFailureMessage": "Impossible de supprimer le moniteur. Réessayez plus tard.", "xpack.synthetics.monitorManagement.monitorEditedSuccessMessage": "Moniteur mis à jour.", "xpack.synthetics.monitorManagement.monitorFailureMessage": "Impossible d'enregistrer le moniteur. Réessayez plus tard.", - "xpack.synthetics.monitorManagement.monitorList.actions": "Actions", "xpack.synthetics.monitorManagement.monitorList.disclaimer.title": "La suppression de ce moniteur ne le retirera pas de la source du projet", - "xpack.synthetics.monitorManagement.monitorList.enabled": "Activé", - "xpack.synthetics.monitorManagement.monitorList.locations": "Emplacements", - "xpack.synthetics.monitorManagement.monitorList.monitorName": "Nom de moniteur", - "xpack.synthetics.monitorManagement.monitorList.monitorType": "Type de moniteur", - "xpack.synthetics.monitorManagement.monitorList.schedule": "Fréquence (min)", - "xpack.synthetics.monitorManagement.monitorList.tags": "Balises", - "xpack.synthetics.monitorManagement.monitorList.title": "Liste Gestion des moniteurs", - "xpack.synthetics.monitorManagement.monitorList.URL": "URL", - "xpack.synthetics.monitorManagement.monitorLocationsLabel": "Emplacements des moniteurs", - "xpack.synthetics.monitorManagement.monitorManagementCrumb": "Gestion des moniteurs", - "xpack.synthetics.monitorManagement.monitorNameFieldError": "Le nom de moniteur est requis", - "xpack.synthetics.monitorManagement.monitorNameFieldLabel": "Nom de moniteur", "xpack.synthetics.monitorManagement.monitors": "Moniteurs", "xpack.synthetics.monitorManagement.monitorsTab.title": "Gestion", "xpack.synthetics.monitorManagement.monitorSync.failure.content": "Un problème est survenu lors de la synchronisation de vos moniteurs pour un ou plusieurs emplacements :", @@ -35393,12 +35174,9 @@ "xpack.synthetics.monitorManagement.monitorSync.failure.statusLabel": "Statut", "xpack.synthetics.monitorManagement.monitorSync.failure.title": "Impossible de synchroniser les moniteurs avec le service Synthetics", "xpack.synthetics.monitorManagement.nameRequired": "Le nom de l’emplacement est requis", - "xpack.synthetics.monitorManagement.needPermissions": "Permissions requises", "xpack.synthetics.monitorManagement.noFleetPermission": "Vous n'êtes pas autorisé à effectuer cette action. Des autorisations d'écriture pour les intégrations sont requises.", - "xpack.synthetics.monitorManagement.noLabel": "Annuler", "xpack.synthetics.monitorManagement.noSyntheticsPermissions": "Vous ne disposez pas d'autorisations suffisantes pour effectuer cette action.", "xpack.synthetics.monitorManagement.overviewTab.title": "Aperçu", - "xpack.synthetics.monitorManagement.pageHeader.title": "Gestion des moniteurs", "xpack.synthetics.monitorManagement.param.keyExists": "La clé existe déjà", "xpack.synthetics.monitorManagement.param.keyRequired": "La clé est requise", "xpack.synthetics.monitorManagement.paramForm.descriptionLabel": "Description", @@ -35407,52 +35185,38 @@ "xpack.synthetics.monitorManagement.paramForm.tagsLabel": "Balises", "xpack.synthetics.monitorManagement.pending": "EN ATTENTE", "xpack.synthetics.monitorManagement.policyHost": "Politique d'agent", - "xpack.synthetics.monitorManagement.privateLabel": "Privé", "xpack.synthetics.monitorManagement.privateLocations": "Emplacements privés", "xpack.synthetics.monitorManagement.privateLocationsNotAllowedMessage": "Vous ne disposez pas d'autorisation pour ajouter des moniteurs dans des emplacements privés. Contactez votre administrateur pour demander des droits d'accès.", "xpack.synthetics.monitorManagement.projectDelete.docsLink": "En savoir plus", "xpack.synthetics.monitorManagement.projectPush.label": "Commande push du projet", - "xpack.synthetics.monitorManagement.publicBetaDescription": "Nous avons une toute nouvelle application en préparation. En attendant, nous sommes très heureux de vous proposer un accès anticipé à notre infrastructure de test globalement gérée. Vous pourrez ainsi charger des moniteurs synthétiques à l'aide de notre nouvel enregistreur de script de type pointer-cliquer et gérer vos moniteurs avec une nouvelle interface utilisateur.", "xpack.synthetics.monitorManagement.readDocs": "lire les documents", "xpack.synthetics.monitorManagement.requestAccess": "Demander un accès", - "xpack.synthetics.monitorManagement.reRunTest": "Exécuter à nouveau le test", - "xpack.synthetics.monitorManagement.runTest": "Exécuter le test", "xpack.synthetics.monitorManagement.saveLabel": "Enregistrer", - "xpack.synthetics.monitorManagement.saveMonitorLabel": "Enregistrer le moniteur", "xpack.synthetics.monitorManagement.selectOneOrMoreLocations": "Sélectionner un ou plusieurs emplacements", "xpack.synthetics.monitorManagement.selectPolicyHost": "Sélectionner une politique d'agent", "xpack.synthetics.monitorManagement.selectPolicyHost.helpText": "Nous recommandons d'utiliser un seul agent Elastic par politique d'agent.", "xpack.synthetics.monitorManagement.service.error.title": "Impossible de synchroniser la configuration du moniteur", - "xpack.synthetics.monitorManagement.serviceLocationsValidationError": "Au moins un emplacement de service doit être spécifié", "xpack.synthetics.monitorManagement.startAddingLocationsDescription": "Les emplacements privés vous permettent d'exploiter des moniteurs depuis vos propres locaux. Ils nécessitent un agent Elastic et une politique d'agent que vous pouvez contrôler et maintenir via Fleet.", "xpack.synthetics.monitorManagement.steps": "Étapes", "xpack.synthetics.monitorManagement.summary.heading": "Résumé", - "xpack.synthetics.monitorManagement.syntheticsDisabled": "La Gestion des moniteurs est actuellement désactivée. Veuillez contacter un administrateur pour activer la Gestion des moniteurs.", "xpack.synthetics.monitorManagement.syntheticsDisabledFailure": "La Gestion des moniteurs n'a pas pu être désactivée. Veuillez contacter le support technique.", "xpack.synthetics.monitorManagement.syntheticsDisabledSuccess": "Gestion des moniteurs désactivée avec succès.", - "xpack.synthetics.monitorManagement.syntheticsDisableToolTip": "La désactivation de la Gestion des moniteurs arrêtera immédiatement l'exécution des moniteurs dans tous les emplacements de test et empêchera la création de nouveaux moniteurs.", "xpack.synthetics.monitorManagement.syntheticsEnabledFailure": "La Gestion des moniteurs n'a pas pu être activée. Veuillez contacter le support technique.", - "xpack.synthetics.monitorManagement.syntheticsEnableLabel": "Activer", "xpack.synthetics.monitorManagement.syntheticsEnableLabel.invalidKey": "Activer la Gestion des moniteurs", "xpack.synthetics.monitorManagement.syntheticsEnableLabel.management": "Activer la Gestion des moniteurs", "xpack.synthetics.monitorManagement.syntheticsEnableSuccess": "Gestion des moniteurs activée avec succès.", - "xpack.synthetics.monitorManagement.syntheticsEnableToolTip": "Activez la Gestion des moniteurs pour créer des moniteurs légers et basés sur un navigateur réel à partir d'emplacements du monde entier.", - "xpack.synthetics.monitorManagement.techPreviewLabel": "Préversion technique", "xpack.synthetics.monitorManagement.testResult": "Résultat du test", "xpack.synthetics.monitorManagement.testResults": "Résultats de test", "xpack.synthetics.monitorManagement.testRuns.label": "Exécutions de test", "xpack.synthetics.monitorManagement.updateMonitorLabel": "Mettre à jour le moniteur", - "xpack.synthetics.monitorManagement.urlFieldLabel": "Url", "xpack.synthetics.monitorManagement.urlRequiredLabel": "L'URL est requise", "xpack.synthetics.monitorManagement.useEnv.label": "Utiliser comme variable d'environnement", - "xpack.synthetics.monitorManagement.validationError": "Votre moniteur comporte des erreurs. Corrigez-les avant de l'enregistrer.", "xpack.synthetics.monitorManagement.value.required": "La valeur est requise", "xpack.synthetics.monitorManagement.viewLocationMonitors": "Afficher les moniteurs géographiques", "xpack.synthetics.monitorManagement.viewTestRunDetails": "Afficher les détails du résultat du test", "xpack.synthetics.monitorManagement.websiteUrlHelpText": "Par exemple, la page d'accueil de votre entreprise ou https://elastic.co", "xpack.synthetics.monitorManagement.websiteUrlLabel": "URL de site web", "xpack.synthetics.monitorManagement.websiteUrlPlaceholder": "Entrer l'URL d'un site web", - "xpack.synthetics.monitorManagement.yesLabel": "Supprimer", "xpack.synthetics.monitorOverviewTab.title": "Aperçu", "xpack.synthetics.monitors.management.betaLabel": "Cette fonctionnalité est en version bêta et susceptible d'être modifiée. La conception et le code sont moins matures que les fonctionnalités officielles en disponibilité générale et sont fournis tels quels sans aucune garantie. Les fonctionnalités en version bêta ne sont pas soumises à l'accord de niveau de service des fonctionnalités officielles en disponibilité générale.", "xpack.synthetics.monitors.pageHeader.createButton.label": "Créer le moniteur", @@ -35579,7 +35343,6 @@ "xpack.synthetics.page_header.analyzeData.label": "Accédez à la vue \"Explorer les données\" pour visualiser les données synthétiques/d'utilisateur", "xpack.synthetics.page_header.defineConnector.popover.defaultLink": "Définir un connecteur par défaut", "xpack.synthetics.page_header.defineConnector.settingsLink": "Paramètres", - "xpack.synthetics.page_header.manageLink.label": "Accéder à la page de gestion des moniteurs Uptime", "xpack.synthetics.page_header.manageMonitors": "Gestion des moniteurs", "xpack.synthetics.page_header.settingsLink": "Paramètres", "xpack.synthetics.page_header.settingsLink.label": "Accédez à la page de paramètres Uptime", @@ -35620,7 +35383,6 @@ "xpack.synthetics.routes.createNewMonitor": "Aller à l'accueil", "xpack.synthetics.routes.goToSynthetics": "Accéder à la page d'accueil Synthetics", "xpack.synthetics.routes.legacyBaseTitle": "Uptime - Kibana", - "xpack.synthetics.routes.monitorManagement.betaLabel": "Cette fonctionnalité est en version bêta et susceptible d'être modifiée. La conception et le code sont moins matures que les fonctionnalités officielles en disponibilité générale et sont fournis tels quels sans aucune garantie. Les fonctionnalités en version bêta ne sont pas soumises à l'accord de niveau de service des fonctionnalités officielles en disponibilité générale.", "xpack.synthetics.runTest.failure": "Impossible d'exécuter le test manuellement", "xpack.synthetics.seconds.label": "secondes", "xpack.synthetics.seconds.shortForm.label": "s", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index d5975fd3f6648..b36c94f8422d0 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -34232,7 +34232,6 @@ "xpack.stackConnectors.xmatters.title": "xMatters", "xpack.stackConnectors.xmatters.unexpectedNullResponseErrorMessage": "xmattersからの予期しないnull応答", "xpack.synthetics.addMonitor.pageHeader.description": "使用可能なモニタータイプの詳細については、{docs}を参照してください。", - "xpack.synthetics.addMonitorRoute.title": "モニターを追加 | {baseTitle}", "xpack.synthetics.alertRules.monitorStatus.reasonMessage": "{location}からのモニター\"{name}\"は{status}です。確認:{checkedAt}。", "xpack.synthetics.alerts.durationAnomaly.defaultActionMessage": "{monitor}で{anomalyStartTimestamp}で{monitorUrl}のurlで異常な({severity}レベル)応答時間を検出しました。異常重要度スコアは{severityScore}です。\n{observerLocation}の位置から{slowestAnomalyResponse}の高い応答時間が検出されています。想定応答時間は{expectedResponseTime}です。", "xpack.synthetics.alerts.durationAnomaly.defaultRecoveryMessage": "{anomalyStartTimestamp}の{observerLocation}地点から{monitorUrl}のモニター{monitor}で検出された応答時間異常({severity}レベル)のアラートが回復しました", @@ -34263,9 +34262,6 @@ "xpack.synthetics.charts.mlAnnotation.severity": "重要度:{severity}", "xpack.synthetics.controls.selectSeverity.scoreDetailsDescription": "スコア{value}以上", "xpack.synthetics.createMonitorRoute.title": "監視の作成 | {baseTitle}", - "xpack.synthetics.createPackagePolicy.stepConfigure.browserAdvancedSettings.throttling.throttling_exceeded.message": "Syntheticsノードの{throttlingField}上限を超えました。{throttlingField}値を{limit}Mbpsより大きくすることはできません。", - "xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.monitorType.browser.warning.content": "「Browser」モニターを作成するには、{agent}Dockerコンテナーを使用していることを確認します。これには、これらのモニターを実行するための依存関係が含まれています。詳細については、{link}をご覧ください。", - "xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.params.helpText": "JSONを使用して、{code}のスクリプトで参照できるパラメーターを定義します", "xpack.synthetics.deprecateNoticeModal.forMoreInformation": "詳細については、{docsLink}", "xpack.synthetics.durationChart.emptyPrompt.description": "このモニターは選択された時間範囲で一度も {emphasizedText} していません。", "xpack.synthetics.editMonitorRoute.title": "モニターを編集 | {baseTitle}", @@ -34322,8 +34318,6 @@ "xpack.synthetics.monitorList.redirects.description": "Pingの実行中にHeartbeatは{number}リダイレクトに従いました。", "xpack.synthetics.monitorList.redirects.title.number": "{number}", "xpack.synthetics.monitorList.statusColumn.checkedTimestamp": "確認:{timestamp}", - "xpack.synthetics.monitorList.statusColumn.error.message": "{message}。詳細はクリックしてください。", - "xpack.synthetics.monitorList.statusColumn.error.messageLabel": "{message}。詳細はクリックしてください。", "xpack.synthetics.monitorList.statusColumn.locStatusMessage": "{noLoc}場所", "xpack.synthetics.monitorList.statusColumn.locStatusMessage.multiple": "{noLoc}場所", "xpack.synthetics.monitorList.statusColumn.locStatusMessage.tooltip.down": "{locs}でダウン", @@ -34332,17 +34326,11 @@ "xpack.synthetics.monitorList.tags.filter": "タグ{tag}ですべての監視をフィルター", "xpack.synthetics.monitorManagement.agentCallout.content": "このプライベートロケーションで「Browser」モニターを実行する場合は、必ず{code}コンテナーを使用してください。これには、これらのモニターを実行するための依存関係が含まれています。詳細については、{link}。", "xpack.synthetics.monitorManagement.anotherPrivateLocation": "このエージェントポリシーは、すでに次の場所に関連付けられています:{locationName}。", - "xpack.synthetics.monitorManagement.cannotDelete": "この場所は、{monCount}個のモニターが実行されているため、削除できません。この場所をモニターから削除してから、この場所を削除してください。", "xpack.synthetics.monitorManagement.cannotDelete.description": "この場所は、{monCount, number}個の{monCount, plural, other {モニター}}が実行されているため、削除できません。\n この場所をモニターから削除してから、この場所を削除してください。", "xpack.synthetics.monitorManagement.deleteLocationName": "\"{location}\"の削除", "xpack.synthetics.monitorManagement.deleteMonitorNameLabel": "モニター\"{name}\"を削除しますか?", - "xpack.synthetics.monitorManagement.disclaimer": "お客様は、この機能を使用することで、{link}を読んで同意したことを確認します。", "xpack.synthetics.monitorManagement.lastXDays": "最後の{count, number} {count, plural, other {日}}", - "xpack.synthetics.monitorManagement.monitorAdvancedOptions.namespaceHelpLabel": "デフォルト名前空間を変更します。この設定により、モニターのデータストリームの名前が変更されます。{learnMore}。", "xpack.synthetics.monitorManagement.monitorDeleteSuccessMessage.name": "\"{name}\"が削除されました", - "xpack.synthetics.monitorManagement.monitorDisabledSuccessMessage": "モニター{name}は正常に無効にされました。", - "xpack.synthetics.monitorManagement.monitorEnabledSuccessMessage": "モニター{name}は正常に有効にされました。", - "xpack.synthetics.monitorManagement.monitorEnabledUpdateFailureMessage": "モニター{name}を更新できません。", "xpack.synthetics.monitorManagement.monitorList.disclaimer.label": "完全に削除し、今後再びプッシュされないようにするには、プロジェクトのソースから削除してください。{docsLink}", "xpack.synthetics.monitorManagement.service.error.message": "モニターは保存されますが、{location}の構成の同期中に問題が発生しました。しばらくたってから自動的に再試行されます。問題が解決しない場合は、{location}でのモニターの実行が停止します。ヘルプについては、サポートに問い合わせてください。", "xpack.synthetics.monitorManagement.service.error.reason": "理由:{reason}。", @@ -34430,7 +34418,6 @@ "xpack.synthetics.addEditMonitor.scriptEditor.label": "スクリプトエディター", "xpack.synthetics.addEditMonitor.scriptEditor.placeholder": "// ここにPlaywrightスクリプトを貼り付け...", "xpack.synthetics.addMonitor.pageHeader.docsLink": "ドキュメンテーション", - "xpack.synthetics.addMonitor.pageHeader.title": "モニターを追加", "xpack.synthetics.alertDropdown.noWritePermissions": "このアプリでアラートを作成するには、アップタイムへの読み書きアクセス権が必要です。", "xpack.synthetics.alertRule.monitorStatus.description": "Synthetics監視ステータスルールアクションを管理します。", "xpack.synthetics.alertRules.actionGroups.monitorStatus": "Synthetics監視ステータス", @@ -34594,18 +34581,6 @@ "xpack.synthetics.breadcrumbs.legacyOverviewBreadcrumbText": "アップタイム", "xpack.synthetics.breadcrumbs.observabilityText": "Observability", "xpack.synthetics.breadcrumbs.overviewBreadcrumbText": "Synthetics", - "xpack.synthetics.browser.project.browserAdvancedSettings.description": "Syntheticsエージェントの微調整された構成を提供します。", - "xpack.synthetics.browser.project.browserAdvancedSettings.screenshots.helpText": "このオプションを設定すると、Syntheticsエージェントでキャプチャされたスクリーンショットを管理します。", - "xpack.synthetics.browser.project.browserAdvancedSettings.screenshots.label": "スクリーンショットオプション", - "xpack.synthetics.browser.project.browserAdvancedSettings.title": "Syntheticsエージェントオプション", - "xpack.synthetics.browser.project.monitorIntegrationSettingsSection.monitorInterval": "頻度", - "xpack.synthetics.browser.project.monitorIntegrationSettingsSection.monitorInterval.error": "監視頻度は必須です", - "xpack.synthetics.browser.project.monitorIntegrationSettingsSection.tags.helpText": "モニターイベントで送信されるタグのリスト。Enterキーを押すと、新しいタグを追加します。アップタイムに表示され、タグによる検索を有効にします。", - "xpack.synthetics.browser.project.monitorIntegrationSettingsSection.tags.label": "タグ", - "xpack.synthetics.browser.project.monitorIntegrationSettingsSectionDescription": "次のオプションでモニターを構成します。", - "xpack.synthetics.browser.project.monitorIntegrationSettingsSectionTitle": "モニター設定", - "xpack.synthetics.browser.project.readOnly.callout.content": "この監視は外部プロジェクトから追加されました。構成は読み取り専用です。", - "xpack.synthetics.browser.project.readOnly.callout.title": "この構成は読み取り専用です。", "xpack.synthetics.certificates.loading": "証明書を読み込んでいます...", "xpack.synthetics.certificates.refresh": "更新", "xpack.synthetics.certificatesPage.heading": "TLS証明書", @@ -34616,7 +34591,6 @@ "xpack.synthetics.certs.list.commonName": "共通名", "xpack.synthetics.certs.list.copyFingerprint": "クリックすると、フィンガープリント値をコピーします", "xpack.synthetics.certs.list.days": "日", - "xpack.synthetics.certs.list.empty": "証明書が見つかりません。注:証明書は Heartbeat 7.8 以上でのみ表示されます。", "xpack.synthetics.certs.list.expirationDate": "指紋", "xpack.synthetics.certs.list.issuedBy": "発行者", "xpack.synthetics.certs.list.monitors": "監視", @@ -34639,137 +34613,13 @@ "xpack.synthetics.createMonitor.pageHeader.title": "監視の作成", "xpack.synthetics.createPackagePolicy.stepConfigure.browser.scriptRecorder.experimentalLabel": "テクニカルプレビュー", "xpack.synthetics.createPackagePolicy.stepConfigure.browser.scriptRecorder.experimentalTooltip": "Elastic Synthetics Recorderを使用してElastic Synthetics監視スクリプトを作成する最も簡単な方法をプレビュー", - "xpack.synthetics.createPackagePolicy.stepConfigure.browser.scriptRecorder.label": "スクリプトの記録", - "xpack.synthetics.createPackagePolicy.stepConfigure.browserAdvancedSettings.description": "Syntheticsエージェントの微調整された構成を提供します。", - "xpack.synthetics.createPackagePolicy.stepConfigure.browserAdvancedSettings.ignoreHttpsErrors.helpText": "このオプションをtrueに設定すると、SyntheticsブラウザーでTLS/SSL検証を無効にします。これは自己署名証明書を使用するサイトのテストで役立ちます。", - "xpack.synthetics.createPackagePolicy.stepConfigure.browserAdvancedSettings.ignoreHttpsErrors.label": "HTTPSエラーを無視", - "xpack.synthetics.createPackagePolicy.stepConfigure.browserAdvancedSettings.screenshots.helpText": "このオプションを設定すると、Syntheticsエージェントでキャプチャされたスクリーンショットを管理します。", - "xpack.synthetics.createPackagePolicy.stepConfigure.browserAdvancedSettings.screenshots.label": "スクリーンショットオプション", - "xpack.synthetics.createPackagePolicy.stepConfigure.browserAdvancedSettings.syntheticsArgs.helpText": "Syntheticsエージェントパッケージに渡す追加の引数。文字列のリストを取ります。これはごくまれなシナリオで有用ですが、通常は設定する必要がありません。", - "xpack.synthetics.createPackagePolicy.stepConfigure.browserAdvancedSettings.syntheticsArgs.label": "Synthetics引数", - "xpack.synthetics.createPackagePolicy.stepConfigure.browserAdvancedSettings.throttling.automatic_node_cap.message": "スロットリングを無効にするときには、モニターが実行されているSyntheticsノードの構成によって、モニターの帯域幅がまだ制限されます。", - "xpack.synthetics.createPackagePolicy.stepConfigure.browserAdvancedSettings.throttling.automatic_node_cap.title": "自動制限", - "xpack.synthetics.createPackagePolicy.stepConfigure.browserAdvancedSettings.throttling.description": "モニターのダウンロードおよびアップロードの速度、ならびにレイテンシを制御し、低速または遅延しているネットワークでアプリケーションの動作をシミュレートしてください。", - "xpack.synthetics.createPackagePolicy.stepConfigure.browserAdvancedSettings.throttling.download.error": "ダウンロード速度は0よりも大きい値でなければなりません。", - "xpack.synthetics.createPackagePolicy.stepConfigure.browserAdvancedSettings.throttling.download.label": "ダウンロード速度", - "xpack.synthetics.createPackagePolicy.stepConfigure.browserAdvancedSettings.throttling.exceeded_throttling.message": "Syntheticsノード帯域幅上限より大きいスロットリング値を使用するときには、モニターの帯域幅がまだ制限されます。", - "xpack.synthetics.createPackagePolicy.stepConfigure.browserAdvancedSettings.throttling.exceeded_throttling.title": "Syntheticsノード帯域幅上限を超えました", - "xpack.synthetics.createPackagePolicy.stepConfigure.browserAdvancedSettings.throttling.latency.error": "レイテンシは負数にできません。", - "xpack.synthetics.createPackagePolicy.stepConfigure.browserAdvancedSettings.throttling.latency.label": "レイテンシ", - "xpack.synthetics.createPackagePolicy.stepConfigure.browserAdvancedSettings.throttling.switch.description": "スロットリングを有効にする", - "xpack.synthetics.createPackagePolicy.stepConfigure.browserAdvancedSettings.throttling.title": "スロットリングオプション", - "xpack.synthetics.createPackagePolicy.stepConfigure.browserAdvancedSettings.throttling.upload.error": "アップロード速度は0よりも大きい値でなければなりません。", - "xpack.synthetics.createPackagePolicy.stepConfigure.browserAdvancedSettings.throttling.upload.label": "アップロード速度", - "xpack.synthetics.createPackagePolicy.stepConfigure.browserAdvancedSettings.title": "Syntheticsエージェントオプション", - "xpack.synthetics.createPackagePolicy.stepConfigure.certificateSettings.enableSSLSettings.label": "TLS構成を有効にする", - "xpack.synthetics.createPackagePolicy.stepConfigure.certsField.certificate.helpText": "TLSクライアント認証用のPEM形式の証明書。", - "xpack.synthetics.createPackagePolicy.stepConfigure.certsField.certificate.label": "証明書", - "xpack.synthetics.createPackagePolicy.stepConfigure.certsField.certificateAuthorities.helpText": "PEM形式のカスタム認証局。", - "xpack.synthetics.createPackagePolicy.stepConfigure.certsField.certificateAuthorities.label": "認証局", - "xpack.synthetics.createPackagePolicy.stepConfigure.certsField.certificateKey.helpText": "TLSクライアント認証用のPEM形式の証明書鍵。", - "xpack.synthetics.createPackagePolicy.stepConfigure.certsField.certificateKey.label": "キー", - "xpack.synthetics.createPackagePolicy.stepConfigure.certsField.certificateKeyPassphrase.helpText": "TLSクライアント認証用の証明書鍵パスフレーズ。", - "xpack.synthetics.createPackagePolicy.stepConfigure.certsField.certificateKeyPassphrase.label": "鍵パスフレーズ", - "xpack.synthetics.createPackagePolicy.stepConfigure.certsField.legend": "証明書設定", - "xpack.synthetics.createPackagePolicy.stepConfigure.certsField.tlsRole.client": "クライアント", - "xpack.synthetics.createPackagePolicy.stepConfigure.certsField.tlsRole.server": "サーバー", - "xpack.synthetics.createPackagePolicy.stepConfigure.certsField.verificationMode.certificate.description": "指定された証明書が信頼できる機関(CA)によって署名されていることを検証します。ホスト名検証は実行しません。", - "xpack.synthetics.createPackagePolicy.stepConfigure.certsField.verificationMode.certificate.label": "証明書", - "xpack.synthetics.createPackagePolicy.stepConfigure.certsField.verificationMode.full.description": "指定された証明書が信頼できる機関(CA)によって署名されていることを検証し、サーバーのホスト名(またはIPアドレス)が証明書で指定された名前と一致していることも検証します。", - "xpack.synthetics.createPackagePolicy.stepConfigure.certsField.verificationMode.full.label": "完全", - "xpack.synthetics.createPackagePolicy.stepConfigure.certsField.verificationMode.label": "認証モード", - "xpack.synthetics.createPackagePolicy.stepConfigure.certsField.verificationMode.none.description": "サーバーの証明書の検証は実行しません。主に、TLSエラーの解決を試みるときの一時的な診断メカニズムの目的を果たします。本番環境では使用しないことを強くお勧めします。", - "xpack.synthetics.createPackagePolicy.stepConfigure.certsField.verificationMode.none.label": "なし", - "xpack.synthetics.createPackagePolicy.stepConfigure.certsField.verificationMode.strict.description": "指定された証明書が信頼できる機関(CA)によって署名されていることを検証し、サーバーのホスト名(またはIPアドレス)が証明書で指定された名前と一致していることも検証します。サブジェクトの別名が空の場合、エラーが返されます。", - "xpack.synthetics.createPackagePolicy.stepConfigure.certsField.verificationMode.strict.label": "厳密", - "xpack.synthetics.createPackagePolicy.stepConfigure.certsField.verificationMode.warning.description": "このモードでは、SSL/TLSの多数のセキュリティの利点が無効になります。十分に検討してから使用してください。", - "xpack.synthetics.createPackagePolicy.stepConfigure.certsField.verificationMode.warning.title": "TLSを無効にしています", - "xpack.synthetics.createPackagePolicy.stepConfigure.certsField.version.label": "サポートされているTLSプロトコル", - "xpack.synthetics.createPackagePolicy.stepConfigure.certsField.version.placeholder": "1つ以上TLSプロトコルを選択します。", "xpack.synthetics.createPackagePolicy.stepConfigure.headerField.addHeader.label": "ヘッダーを追加", - "xpack.synthetics.createPackagePolicy.stepConfigure.httpAdvancedOptions": "詳細HTTPオプション", - "xpack.synthetics.createPackagePolicy.stepConfigure.httpAdvancedOptions.indexResponseBody.helpText": "HTTP応答本文コンテンツのインデックスを制御します ", - "xpack.synthetics.createPackagePolicy.stepConfigure.httpAdvancedOptions.indexResponseHeaders.helpText": "HTTP応答ヘッダーのインデックスを制御します ", - "xpack.synthetics.createPackagePolicy.stepConfigure.httpAdvancedOptions.requestBody.helpText": "リクエスト本文コンテンツ。", - "xpack.synthetics.createPackagePolicy.stepConfigure.httpAdvancedOptions.requestConfiguration.description": "方式、本文、ヘッダーを含むリモートホストに送信する任意のリクエストを構成します。", - "xpack.synthetics.createPackagePolicy.stepConfigure.httpAdvancedOptions.requestConfiguration.requestBody": "リクエスト本文", - "xpack.synthetics.createPackagePolicy.stepConfigure.httpAdvancedOptions.requestConfiguration.requestHeaders": "要求ヘッダー", - "xpack.synthetics.createPackagePolicy.stepConfigure.httpAdvancedOptions.requestConfiguration.requestMethod.label": "リクエストメソッド", - "xpack.synthetics.createPackagePolicy.stepConfigure.httpAdvancedOptions.requestConfiguration.title": "リクエスト構成", - "xpack.synthetics.createPackagePolicy.stepConfigure.httpAdvancedOptions.requestHeadersField.error": "ヘッダーキーは有効なHTTPトークンでなければなりません。", - "xpack.synthetics.createPackagePolicy.stepConfigure.httpAdvancedOptions.requestHeadersField.helpText": "送信する追加のHTTPヘッダーのディクショナリ。デフォルトでは、クライアントを特定するためにユーザーエージェントヘッダーが設定されます。", - "xpack.synthetics.createPackagePolicy.stepConfigure.httpAdvancedOptions.requestMethod.helpText": "使用するHTTP方式。", - "xpack.synthetics.createPackagePolicy.stepConfigure.httpAdvancedOptions.responseBodyCheckNegative.helpText": "本文出力と一致しない正規表現のリスト。Enterキーを押すと、新しい式を追加します。単一の式が一致する場合、一致の失敗が返されます。", - "xpack.synthetics.createPackagePolicy.stepConfigure.httpAdvancedOptions.responseBodyCheckPositive.helpText": "本文出力と一致する正規表現のリスト。Enterキーを押すと、新しい式を追加します。単一の式のみが一致する必要があります。", - "xpack.synthetics.createPackagePolicy.stepConfigure.httpAdvancedOptions.responseCheckNegative.label": "確認応答本文には含まれません", - "xpack.synthetics.createPackagePolicy.stepConfigure.httpAdvancedOptions.responseCheckPositive.label": "確認応答本文に含まれます", - "xpack.synthetics.createPackagePolicy.stepConfigure.httpAdvancedOptions.responseChecks.checkResponseHeadersContain": "確認応答ヘッダーに含まれます", - "xpack.synthetics.createPackagePolicy.stepConfigure.httpAdvancedOptions.responseChecks.description": "想定されているHTTP応答を構成します。", - "xpack.synthetics.createPackagePolicy.stepConfigure.httpAdvancedOptions.responseChecks.responseStatusCheck.error": "ステータスコードには数字のみを使用する必要があります。", - "xpack.synthetics.createPackagePolicy.stepConfigure.httpAdvancedOptions.responseChecks.responseStatusCheck.helpText": "想定されているステータスコードのリスト。Enterキーを押すと、新しいコードを追加します。デフォルトでは、4xxおよび5xxコードはダウンと見なされます。他のコードはアップと見なされます。", - "xpack.synthetics.createPackagePolicy.stepConfigure.httpAdvancedOptions.responseChecks.responseStatusCheck.label": "確認応答ステータスは等しいです", - "xpack.synthetics.createPackagePolicy.stepConfigure.httpAdvancedOptions.responseChecks.title": "応答チェック", - "xpack.synthetics.createPackagePolicy.stepConfigure.httpAdvancedOptions.responseConfig.indexResponseBody": "インデックス応答本文", - "xpack.synthetics.createPackagePolicy.stepConfigure.httpAdvancedOptions.responseConfig.indexResponseHeaders": "インデックス応答ヘッダー", - "xpack.synthetics.createPackagePolicy.stepConfigure.httpAdvancedOptions.responseConfig.responseBodyIndexPolicy": "応答本文インデックスポリシー", - "xpack.synthetics.createPackagePolicy.stepConfigure.httpAdvancedOptions.responseConfiguration.description": "HTTP応答コンテンツのインデックスを制御します。", - "xpack.synthetics.createPackagePolicy.stepConfigure.httpAdvancedOptions.responseConfiguration.title": "応答構成", - "xpack.synthetics.createPackagePolicy.stepConfigure.httpAdvancedOptions.responseHeadersField.error": "ヘッダーキーは有効なHTTPトークンでなければなりません。", - "xpack.synthetics.createPackagePolicy.stepConfigure.httpAdvancedOptions.responseHeadersField.helpText": "想定されている応答ヘッダーのリスト。", - "xpack.synthetics.createPackagePolicy.stepConfigure.icmpAdvancedOptions": "詳細ICMPオプション", "xpack.synthetics.createPackagePolicy.stepConfigure.inputVarFieldOptionalLabel": "オプション", - "xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.APMServiceName.helpText": "このモニターのAPMサービス名。service.name ECSフィールドに対応します。APMを使用して、アップタイムとKibanaのAPMデータ間の統合を有効にするアプリを監視しているときには、これを設定します。", - "xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.APMServiceName.label": "APMサービス名", - "xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.browser.inlineScript.error": "スクリプトが必要です", - "xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.browser.inlineScript.helpText": "インラインで定義されたSyntheticテストスクリプトを実行します。", - "xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.browser.inlineScript.label": "インラインスクリプト", "xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.browser.scriptRecorder.closeButtonLabel": "スクリプトフライアウトを閉じる", "xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.browser.scriptRecorder.mockFileName": "test_script.js", - "xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.browser.sourceType.label": "ソースタイプ", "xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.browser.uploader.fieldLabel": "テストスクリプト", "xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.browser.uploader.invalidFileError": "無効なファイルタイプです。Elastic Synthetics Recorderで生成された.jsファイルをアップロードしてください。", - "xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.browser.uploader.label": "レコーダーで生成された.jsファイルを選択", "xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.browser.uploader.parsingError": "ファイルのアップロードエラーです。インラインスクリプト形式でElastic Synthetics Recorderによって生成された.jsファイルをアップロードしてください。", - "xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.browserLabel": "ブラウザー(ベータ)", - "xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.enabled.helpText": "この構成をオフにすると、モニターが無効になります。", - "xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.enabled.label": "有効", - "xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.icmp.hosts": "ホスト", - "xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.icmp.hosts.error": "ホストは必須です", - "xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.maxRedirects": "最大リダイレクト数", - "xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.maxRedirects.error": "最大リダイレクト数は0以上でなければなりません", - "xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.maxRedirects.helpText": "従うリダイレクトの合計数。", - "xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.monitorInterval": "頻度", - "xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.monitorInterval.error": "監視頻度は必須です", - "xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.monitorType": "モニタータイプ", - "xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.monitorType.browser.warning.link": "Syntheticsドキュメンテーション", - "xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.monitorType.browser.warning.title": "要件", - "xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.monitorType.error": "モニタータイプは必須です", - "xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.params.label": "パラメーター", - "xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.password.helpText": "サーバーと認証するためのパスワード。", - "xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.password.label": "パスワード", - "xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.proxyUrl.http.helpText": "HTTPプロキシURL。", - "xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.proxyURL.http.label": "プロキシURL", - "xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.proxyURL.label": "プロキシURL", - "xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.proxyUrl.tcp.helpText": "サーバーに接続するときに使用するSOCKS5プロキシのURL。値はURLとスキーマsocks5://でなければなりません。", - "xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.resolveHostnamesLocally": "ローカルでホスト名を解決", - "xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.tags.helpText": "モニターイベントで送信されるタグのリスト。Enterキーを押すと、新しいタグを追加します。アップタイムに表示され、タグによる検索を有効にします。", - "xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.tags.label": "タグ", - "xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.tcp.hosts": "ホスト:ポート", - "xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.tcp.hosts.error": "ホストとポートは必須です", - "xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.timeout.helpText": "接続のテストとデータの交換に許可された合計時間。", - "xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.timeout.label": "タイムアウト(秒)", - "xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.timeout.lessThanIntervalError": "タイムアウトは監視頻度未満でなければなりません", - "xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.timeout.moreThanZeroError": "タイムアウトは0以上でなければなりません", - "xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.URL": "URL", - "xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.URL.error": "URLが必要です", - "xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.username.helpText": "サーバーと認証するためのユーザー名。", - "xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.username.label": "ユーザー名", - "xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.wait.error": "待機時間は0以上でなければなりません", - "xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.wait.helpText": "応答が受信されない場合に、他のICMPエコーリクエストを発行する前に待機する期間。", - "xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.wait.label": "待機時間(秒)", - "xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSectionDescription": "次のオプションでモニターを構成します。", - "xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSectionTitle": "モニター設定", - "xpack.synthetics.createPackagePolicy.stepConfigure.requestBody.codeEditor.javascript.ariaLabel": "JavaScriptコードエディター", "xpack.synthetics.createPackagePolicy.stepConfigure.requestBody.codeEditor.json.ariaLabel": "JSONコードエディター", "xpack.synthetics.createPackagePolicy.stepConfigure.requestBody.codeEditor.text.ariaLabel": "テキストコードエディター", "xpack.synthetics.createPackagePolicy.stepConfigure.requestBody.codeEditor.xml.ariaLabel": "XMLコードエディター", @@ -34780,20 +34630,6 @@ "xpack.synthetics.createPackagePolicy.stepConfigure.requestBodyType.XML": "XML", "xpack.synthetics.createPackagePolicy.stepConfigure.responseBodyIndex.always": "常に実行", "xpack.synthetics.createPackagePolicy.stepConfigure.responseBodyIndex.onError": "エラー時", - "xpack.synthetics.createPackagePolicy.stepConfigure.scheduleField.minutes": "分", - "xpack.synthetics.createPackagePolicy.stepConfigure.scheduleField.number": "数字", - "xpack.synthetics.createPackagePolicy.stepConfigure.scheduleField.seconds": "秒", - "xpack.synthetics.createPackagePolicy.stepConfigure.scheduleField.unit": "単位", - "xpack.synthetics.createPackagePolicy.stepConfigure.tcpAdvacnedSettings.requestConfiguration.description": "リモートホストに送信されるペイロードを構成します。", - "xpack.synthetics.createPackagePolicy.stepConfigure.tcpAdvacnedSettings.requestConfiguration.requestPayload.helpText": "リモートホストに送信するペイロード文字列。", - "xpack.synthetics.createPackagePolicy.stepConfigure.tcpAdvacnedSettings.requestConfiguration.requestPayload.label": "リクエストペイロード", - "xpack.synthetics.createPackagePolicy.stepConfigure.tcpAdvacnedSettings.requestConfiguration.title": "リクエスト構成", - "xpack.synthetics.createPackagePolicy.stepConfigure.tcpAdvacnedSettings.responseConfiguration.responseContains.helpText": "想定されたリモートホスト応答。", - "xpack.synthetics.createPackagePolicy.stepConfigure.tcpAdvacnedSettings.responseConfiguration.responseContains.label": "確認応答には含まれます", - "xpack.synthetics.createPackagePolicy.stepConfigure.tcpAdvancedOptions.responseConfiguration.description": "リモートホストから想定された応答を構成します。", - "xpack.synthetics.createPackagePolicy.stepConfigure.tcpAdvancedOptions.responseConfiguration.title": "応答チェック", - "xpack.synthetics.createPackagePolicy.stepConfigure.tlsSettings.description": "検証モード、認証局、クライアント証明書を含む、TLSオプションを構成します。", - "xpack.synthetics.createPackagePolicy.stepConfigure.tlsSettings.label": "TLS設定", "xpack.synthetics.dcl.label": "DCL", "xpack.synthetics.deprecateNoticeModal.addPrivateLocations": "Fleetポリシーに対して非公開の場所を追加", "xpack.synthetics.deprecateNoticeModal.automateMonitors": "プロジェクトモニターを使用してモニターの作成を自動化", @@ -35166,8 +35002,6 @@ "xpack.synthetics.monitorErrorsTab.title": "エラー", "xpack.synthetics.monitorHistoryTab.title": "履歴", "xpack.synthetics.monitorLastRun.lastRunLabel": "前回の実行", - "xpack.synthetics.monitorList.allMonitors": "すべてのモニター", - "xpack.synthetics.monitorList.anomalyColumn.label": "レスポンス異常スコア", "xpack.synthetics.monitorList.closeFlyoutText": "閉じる", "xpack.synthetics.monitorList.defineConnector.popover.description": "ステータスアラートを受信します。", "xpack.synthetics.monitorList.disableDownAlert": "ステータスアラートを無効にする", @@ -35191,7 +35025,6 @@ "xpack.synthetics.monitorList.infraIntegrationAction.kubernetes.description": "このモニターのポッド ID のインフラストラクチャ UI を確認します", "xpack.synthetics.monitorList.infraIntegrationAction.kubernetes.message": "ポッドメトリックを表示", "xpack.synthetics.monitorList.integrationGroup.emptyMessage": "統合されたアプリケーションがありません", - "xpack.synthetics.monitorList.invalidMonitors": "無効なモニター", "xpack.synthetics.monitorList.lastModified": "最終更新:", "xpack.synthetics.monitorList.lastRunHeaderText": "前回の実行", "xpack.synthetics.monitorList.loading": "読み込み中...", @@ -35213,12 +35046,10 @@ "xpack.synthetics.monitorList.projectIdHeaderText": "プロジェクト ID", "xpack.synthetics.monitorList.redirects.openWindow": "リンクは新しいウィンドウで開きます。", "xpack.synthetics.monitorList.redirects.title": "リダイレクト", - "xpack.synthetics.monitorList.refresh": "更新", "xpack.synthetics.monitorList.runTest.label": "テストの実行", "xpack.synthetics.monitorList.statusAlert.label": "ステータスアラート", "xpack.synthetics.monitorList.statusColumn.completeLabel": "完了", "xpack.synthetics.monitorList.statusColumn.downLabel": "ダウン", - "xpack.synthetics.monitorList.statusColumn.error.logs": "エラーログ", "xpack.synthetics.monitorList.statusColumn.failedLabel": "失敗", "xpack.synthetics.monitorList.statusColumn.upLabel": "アップ", "xpack.synthetics.monitorList.statusColumnLabel": "ステータス", @@ -35227,9 +35058,6 @@ "xpack.synthetics.monitorList.table.url.name": "Url", "xpack.synthetics.monitorList.tags.expand": "クリックすると、残りのタグが表示されます", "xpack.synthetics.monitorList.testNow.AriaLabel": "クリックすると今すぐテストを実行します", - "xpack.synthetics.monitorList.testNow.available": "現在、テストはモニター管理で追加されたモニターでのみ使用できます。", - "xpack.synthetics.monitorList.testNow.available.private": "現在、非公開の場所でオンデマンド実行されているモニターはテストできません。", - "xpack.synthetics.monitorList.testNow.label": "今すぐテストを実行", "xpack.synthetics.monitorList.testNow.scheduled": "テストはすでにスケジュールされています", "xpack.synthetics.monitorList.testRunLogs": "テスト実行ログ", "xpack.synthetics.monitorList.timestamp": "タイムスタンプ", @@ -35244,14 +35072,7 @@ "xpack.synthetics.monitorManagement.addAgentPolicyDesc": "非公開の場所にはエージェントポリシーが必要です。非公開の場所を追加するには、まず、Fleetでエージェントポリシーを作成する必要があります。", "xpack.synthetics.monitorManagement.addEdit.createMonitorLabel": "監視の作成", "xpack.synthetics.monitorManagement.addEdit.deleteMonitorLabel": "モニターの削除", - "xpack.synthetics.monitorManagement.addLocation": "場所を追加", "xpack.synthetics.monitorManagement.addMonitorCrumb": "モニターを追加", - "xpack.synthetics.monitorManagement.addMonitorError": "サービスの場所を読み込めませんでした。しばらくたってから再試行してください。", - "xpack.synthetics.monitorManagement.addMonitorLabel": "モニターを追加", - "xpack.synthetics.monitorManagement.addMonitorLoadingError": "モニター管理の読み込みエラー", - "xpack.synthetics.monitorManagement.addMonitorLoadingLabel": "モニター管理を読み込んでいます", - "xpack.synthetics.monitorManagement.addMonitorServiceLocationsLoadingError": "サービスの場所を読み込めませんでした。しばらくたってから再試行してください。", - "xpack.synthetics.monitorManagement.addPrivateLocations": "非公開の場所を追加", "xpack.synthetics.monitorManagement.agentCallout.link": "ドキュメントを読む", "xpack.synthetics.monitorManagement.agentCallout.title": "要件", "xpack.synthetics.monitorManagement.agentPolicy": "エージェントポリシー", @@ -35259,7 +35080,6 @@ "xpack.synthetics.monitorManagement.agentsLabel": "エージェント:", "xpack.synthetics.monitorManagement.alreadyExists": "場所名はすでに存在します。", "xpack.synthetics.monitorManagement.apiKey.label": "API キー", - "xpack.synthetics.monitorManagement.apiKeysDisabledToolTip": "このクラスターのAPIキーが無効です。モニター管理では、Elasticsearchクラスターに書き込むためにAPIキーを使用する必要があります。APIキーを有効にするには、管理者に連絡してください。", "xpack.synthetics.monitorManagement.apiKeyWarning.label": "このAPIキーは1回だけ表示されます。自分の記録用にコピーして保管してください。", "xpack.synthetics.monitorManagement.areYouSure": "この場所を削除しますか?", "xpack.synthetics.monitorManagement.callout.apiKeyMissing": "現在、APIキーがないため、モニター管理は無効です", @@ -35271,7 +35091,6 @@ "xpack.synthetics.monitorManagement.cancelLabel": "キャンセル", "xpack.synthetics.monitorManagement.cannotSaveIntegration": "統合を更新する権限がありません。統合書き込み権限が必要です。", "xpack.synthetics.monitorManagement.closeButtonLabel": "閉じる", - "xpack.synthetics.monitorManagement.closeLabel": "閉じる", "xpack.synthetics.monitorManagement.completed": "完了", "xpack.synthetics.monitorManagement.configurations.label": "構成", "xpack.synthetics.monitorManagement.createAgentPolicy": "エージェントポリシーを作成", @@ -35280,24 +35099,14 @@ "xpack.synthetics.monitorManagement.createLocationMonitors": "監視の作成", "xpack.synthetics.monitorManagement.createMonitorLabel": "監視の作成", "xpack.synthetics.monitorManagement.createPrivateLocations": "非公開の場所を作成", - "xpack.synthetics.monitorManagement.delete": "場所を削除", "xpack.synthetics.monitorManagement.deletedPolicy": "ポリシーが削除されました", "xpack.synthetics.monitorManagement.deleteLocation": "場所を削除", "xpack.synthetics.monitorManagement.deleteLocationLabel": "場所を削除", - "xpack.synthetics.monitorManagement.deleteMonitorLabel": "モニターの削除", "xpack.synthetics.monitorManagement.disabled.label": "無効", - "xpack.synthetics.monitorManagement.disabledCallout.adminContact": "モニター管理を有効にするには、管理者に連絡してください。", - "xpack.synthetics.monitorManagement.disabledCallout.description.disabled": "現在、モニター管理は無効です。既存のモニターが一時停止しています。モニター管理を有効にして、モニターを実行できます。", - "xpack.synthetics.monitorManagement.disableMonitorLabel": "モニターを無効にする", "xpack.synthetics.monitorManagement.discardLabel": "キャンセル", - "xpack.synthetics.monitorManagement.disclaimerLinkLabel": "ベータ期間中にテストを実行するためにサービスを使用するコストはかかりません。公正使用ポリシーが適用されます。標準データストレージコストは、Elasticクラスターに格納されるテスト結果に対して適用されます。", - "xpack.synthetics.monitorManagement.duplicateNameError": "モニター名はすでに存在します。", "xpack.synthetics.monitorManagement.editMonitorCrumb": "モニターを編集", "xpack.synthetics.monitorManagement.editMonitorError": "モニター管理の読み込みエラー", "xpack.synthetics.monitorManagement.editMonitorError.description": "モニター管理設定を読み込めませんでした。サポートに問い合わせてください。", - "xpack.synthetics.monitorManagement.editMonitorErrorBody": "モニター構成を読み込めませんでした。しばらくたってから再試行してください。", - "xpack.synthetics.monitorManagement.editMonitorLabel": "モニターを編集", - "xpack.synthetics.monitorManagement.editMonitorLoadingLabel": "モニターを読み込んでいます", "xpack.synthetics.monitorManagement.emptyState.enablement": "モニター管理を有効にすると、世界中のホスティングされたテスト場所から軽量でリアルなブラウザーモニターを実行できます。モニター管理を有効にすると、APIキーが生成され、SyntheticsサービスはElasticsearchクラスターに書き込むことができます。", "xpack.synthetics.monitorManagement.emptyState.enablement.disabled.title": "モニター管理が無効です", "xpack.synthetics.monitorManagement.emptyState.enablement.disabledDescription": "モニター管理は現在無効です。モニター管理では、世界中のホスティングされたテスト場所から軽量でリアルなブラウザーモニターを実行できます。モニター管理を有効にするには、管理者に連絡してください。", @@ -35305,7 +35114,6 @@ "xpack.synthetics.monitorManagement.emptyState.enablement.enabled.title": "モニター管理を有効にする", "xpack.synthetics.monitorManagement.emptyState.enablement.learnMore": "詳細について", "xpack.synthetics.monitorManagement.emptyState.enablement.title": "有効にする", - "xpack.synthetics.monitorManagement.enableMonitorLabel": "モニターを有効にする", "xpack.synthetics.monitorManagement.failed": "失敗", "xpack.synthetics.monitorManagement.failedRun": "ステップを実行できませんでした", "xpack.synthetics.monitorManagement.filter.frequencyLabel": "頻度", @@ -35314,21 +35122,14 @@ "xpack.synthetics.monitorManagement.filter.projectLabel": "プロジェクト", "xpack.synthetics.monitorManagement.filter.tagsLabel": "タグ", "xpack.synthetics.monitorManagement.filter.typeLabel": "型", - "xpack.synthetics.monitorManagement.firstLocation": "最初の非公開の場所を追加", "xpack.synthetics.monitorManagement.firstLocationMonitor": "モニターを作成するには、まず場所を追加する必要があります。", - "xpack.synthetics.monitorManagement.getApiKey.label": "APIキーを生成", "xpack.synthetics.monitorManagement.getAPIKeyLabel.description": "APIキーを使用して、CLIまたはCDパイプラインからリモートでモニターをプッシュします。", "xpack.synthetics.monitorManagement.getAPIKeyLabel.disclaimer": "注記:非公開のテスト場所を使用してモニターのプッシュを使用するには、Fleetおよび統合の書き込み権限があるユーザーでこのAPIを生成する必要があります。", - "xpack.synthetics.monitorManagement.getAPIKeyLabel.generate": "APIキーを生成", - "xpack.synthetics.monitorManagement.getAPIKeyLabel.label": "API キー", "xpack.synthetics.monitorManagement.getAPIKeyLabel.loading": "APIキーを生成しています", "xpack.synthetics.monitorManagement.getAPIKeyReducedPermissions.description": "APIキーを使用して、CLIまたはCDパイプラインからリモートでモニターをプッシュします。APIキーを生成するには、APIキーを管理する権限とアップタイム書き込み権限が必要です。管理者にお問い合わせください。", "xpack.synthetics.monitorManagement.getProjectApiKey.label": "プロジェクトAPIキーを生成", "xpack.synthetics.monitorManagement.getProjectAPIKeyLabel.generate": "プロジェクトAPIキーを生成", - "xpack.synthetics.monitorManagement.heading": "モニター管理", - "xpack.synthetics.monitorManagement.hostFieldLabel": "ホスト", "xpack.synthetics.monitorManagement.inProgress": "進行中", - "xpack.synthetics.monitorManagement.invalidLabel": "無効", "xpack.synthetics.monitorManagement.label": "モニター管理", "xpack.synthetics.monitorManagement.learnMore": "詳細については、", "xpack.synthetics.monitorManagement.learnMore.label": "詳細", @@ -35339,30 +35140,10 @@ "xpack.synthetics.monitorManagement.manageMonitorLoadingLabel": "モニター管理を読み込んでいます", "xpack.synthetics.monitorManagement.manageMonitorLoadingLabel.callout.invalidKey": "詳細", "xpack.synthetics.monitorManagement.manageMonitorLoadingLabel.callout.learnMore": "詳細情報", - "xpack.synthetics.monitorManagement.manageMonitorLoadingLabel.disabledCallout.learnMore": "詳細", - "xpack.synthetics.monitorManagement.managePrivateLocations": "非公開の場所", "xpack.synthetics.monitorManagement.monitorAddedSuccessMessage": "モニターが正常に追加されました。", - "xpack.synthetics.monitorManagement.monitorAdvancedOptions.dataStreamConfiguration.description": "追加のデータストリームオプションを構成します。", - "xpack.synthetics.monitorManagement.monitorAdvancedOptions.dataStreamConfiguration.title": "データストリーム設定", - "xpack.synthetics.monitorManagement.monitorAdvancedOptions.monitorNamespaceFieldLabel": "名前空間", - "xpack.synthetics.monitorManagement.monitorAdvancedOptions.namespaceHelpLearnMoreLabel": "詳細情報", - "xpack.synthetics.monitorManagement.monitorDeleteFailureMessage": "モニターを削除できませんでした。しばらくたってから再試行してください。", "xpack.synthetics.monitorManagement.monitorEditedSuccessMessage": "モニターは正常に更新されました。", "xpack.synthetics.monitorManagement.monitorFailureMessage": "モニターを保存できませんでした。しばらくたってから再試行してください。", - "xpack.synthetics.monitorManagement.monitorList.actions": "アクション", "xpack.synthetics.monitorManagement.monitorList.disclaimer.title": "このモニターを削除しても、プロジェクトのソースからは削除されません", - "xpack.synthetics.monitorManagement.monitorList.enabled": "有効", - "xpack.synthetics.monitorManagement.monitorList.locations": "場所", - "xpack.synthetics.monitorManagement.monitorList.monitorName": "モニター名", - "xpack.synthetics.monitorManagement.monitorList.monitorType": "モニタータイプ", - "xpack.synthetics.monitorManagement.monitorList.schedule": "頻度(分)", - "xpack.synthetics.monitorManagement.monitorList.tags": "タグ", - "xpack.synthetics.monitorManagement.monitorList.title": "モニター管理リスト", - "xpack.synthetics.monitorManagement.monitorList.URL": "URL", - "xpack.synthetics.monitorManagement.monitorLocationsLabel": "モニターの場所", - "xpack.synthetics.monitorManagement.monitorManagementCrumb": "モニター管理", - "xpack.synthetics.monitorManagement.monitorNameFieldError": "モニター名は必須です", - "xpack.synthetics.monitorManagement.monitorNameFieldLabel": "モニター名", "xpack.synthetics.monitorManagement.monitors": "監視", "xpack.synthetics.monitorManagement.monitorsTab.title": "管理", "xpack.synthetics.monitorManagement.monitorSync.failure.content": "1つ以上の場所でモニターを同期するときに問題が発生しました。", @@ -35372,12 +35153,9 @@ "xpack.synthetics.monitorManagement.monitorSync.failure.statusLabel": "ステータス", "xpack.synthetics.monitorManagement.monitorSync.failure.title": "モニターをSyntheticsサービスと同期できませんでした", "xpack.synthetics.monitorManagement.nameRequired": "場所名は必須です", - "xpack.synthetics.monitorManagement.needPermissions": "権限が必要です", "xpack.synthetics.monitorManagement.noFleetPermission": "このアクションを実行する権限がありません。統合書き込み権限が必要です。", - "xpack.synthetics.monitorManagement.noLabel": "キャンセル", "xpack.synthetics.monitorManagement.noSyntheticsPermissions": "このアクションを実行する十分な権限がありません。", "xpack.synthetics.monitorManagement.overviewTab.title": "概要", - "xpack.synthetics.monitorManagement.pageHeader.title": "モニター管理", "xpack.synthetics.monitorManagement.param.keyExists": "キーがすでに存在します", "xpack.synthetics.monitorManagement.param.keyRequired": "キーが必要です", "xpack.synthetics.monitorManagement.paramForm.descriptionLabel": "説明", @@ -35386,52 +35164,38 @@ "xpack.synthetics.monitorManagement.paramForm.tagsLabel": "タグ", "xpack.synthetics.monitorManagement.pending": "保留中", "xpack.synthetics.monitorManagement.policyHost": "エージェントポリシー", - "xpack.synthetics.monitorManagement.privateLabel": "非公開", "xpack.synthetics.monitorManagement.privateLocations": "非公開の場所", "xpack.synthetics.monitorManagement.privateLocationsNotAllowedMessage": "モニターを非公開の場所に追加する権限がありません。アクセスをリクエストするには、管理者に問い合わせてください。", "xpack.synthetics.monitorManagement.projectDelete.docsLink": "詳細", "xpack.synthetics.monitorManagement.projectPush.label": "プロジェクトプッシュコマンド", - "xpack.synthetics.monitorManagement.publicBetaDescription": "新しいアプリがリリース予定です。それまでの間は、グローバル管理されたテストインフラストラクチャーへのアクセスを提供しています。これにより、新しいポイントアンドクリックスクリプトレコーダーを使用して、合成モニターをアップロードしたり、新しいUIでモニターを管理したりできます。", "xpack.synthetics.monitorManagement.readDocs": "ドキュメントを読む", "xpack.synthetics.monitorManagement.requestAccess": "アクセスをリクエストする", - "xpack.synthetics.monitorManagement.reRunTest": "テストの再実行", - "xpack.synthetics.monitorManagement.runTest": "テストの実行", "xpack.synthetics.monitorManagement.saveLabel": "保存", - "xpack.synthetics.monitorManagement.saveMonitorLabel": "モニターを保存", "xpack.synthetics.monitorManagement.selectOneOrMoreLocations": "1つ以上の場所を選択", "xpack.synthetics.monitorManagement.selectPolicyHost": "エージェントポリシーを選択", "xpack.synthetics.monitorManagement.selectPolicyHost.helpText": "エージェントポリシーごとに1つのElasticエージェントを使用することをお勧めします。", "xpack.synthetics.monitorManagement.service.error.title": "モニター構成を同期できません", - "xpack.synthetics.monitorManagement.serviceLocationsValidationError": "1つ以上のサービスの場所を指定する必要があります", "xpack.synthetics.monitorManagement.startAddingLocationsDescription": "非公開の場所では、独自の施設からモニターを実行できます。Fleet経由で制御および保守できるElasticエージェントとエージェントポリシーが必要です。", "xpack.synthetics.monitorManagement.steps": "ステップ", "xpack.synthetics.monitorManagement.summary.heading": "まとめ", - "xpack.synthetics.monitorManagement.syntheticsDisabled": "モニター管理は現在無効です。モニター管理を有効にするには、管理者に連絡してください。", "xpack.synthetics.monitorManagement.syntheticsDisabledFailure": "モニター管理を無効にできませんでした。サポートに問い合わせてください。", "xpack.synthetics.monitorManagement.syntheticsDisabledSuccess": "モニター管理は正常に無効にされました。", - "xpack.synthetics.monitorManagement.syntheticsDisableToolTip": "モニター管理を無効にすると、すべてのテスト場所でモニターの実行が即時停止され、新しいモニターの作成が防止されます。", "xpack.synthetics.monitorManagement.syntheticsEnabledFailure": "モニター管理を有効にできませんでした。サポートに問い合わせてください。", - "xpack.synthetics.monitorManagement.syntheticsEnableLabel": "有効にする", "xpack.synthetics.monitorManagement.syntheticsEnableLabel.invalidKey": "モニター管理を有効にする", "xpack.synthetics.monitorManagement.syntheticsEnableLabel.management": "モニター管理を有効にする", "xpack.synthetics.monitorManagement.syntheticsEnableSuccess": "モニター管理は正常に有効にされました。", - "xpack.synthetics.monitorManagement.syntheticsEnableToolTip": "モニター管理を有効にすると、世界中の場所から軽量でリアルなブラウザーモニターを作成できます。", - "xpack.synthetics.monitorManagement.techPreviewLabel": "テクニカルプレビュー", "xpack.synthetics.monitorManagement.testResult": "テスト結果", "xpack.synthetics.monitorManagement.testResults": "テスト結果", "xpack.synthetics.monitorManagement.testRuns.label": "テスト実行", "xpack.synthetics.monitorManagement.updateMonitorLabel": "モニターの更新", - "xpack.synthetics.monitorManagement.urlFieldLabel": "Url", "xpack.synthetics.monitorManagement.urlRequiredLabel": "URLが必要です", "xpack.synthetics.monitorManagement.useEnv.label": "環境変数として使用", - "xpack.synthetics.monitorManagement.validationError": "モニターにはエラーがあります。保存前に修正してください。", "xpack.synthetics.monitorManagement.value.required": "値が必要です", "xpack.synthetics.monitorManagement.viewLocationMonitors": "場所モニターを表示", "xpack.synthetics.monitorManagement.viewTestRunDetails": "テスト結果詳細を表示", "xpack.synthetics.monitorManagement.websiteUrlHelpText": "例:会社のホームページまたはhttps://elastic.co", "xpack.synthetics.monitorManagement.websiteUrlLabel": "WebサイトのURL", "xpack.synthetics.monitorManagement.websiteUrlPlaceholder": "WebサイトURLを入力", - "xpack.synthetics.monitorManagement.yesLabel": "削除", "xpack.synthetics.monitorOverviewTab.title": "概要", "xpack.synthetics.monitors.management.betaLabel": "この機能はベータ段階で、変更される可能性があります。デザインとコードは正式に一般公開された機能より完成度が低く、現状のまま保証なしで提供されています。ベータ機能は、正式に一般公開された機能に適用されるサポートサービスレベル契約の対象外です。", "xpack.synthetics.monitors.pageHeader.createButton.label": "監視の作成", @@ -35558,7 +35322,6 @@ "xpack.synthetics.page_header.analyzeData.label": "[データの探索]ビューに移動して、合成/ユーザーデータを可視化", "xpack.synthetics.page_header.defineConnector.popover.defaultLink": "デフォルトのコネクターを定義", "xpack.synthetics.page_header.defineConnector.settingsLink": "設定", - "xpack.synthetics.page_header.manageLink.label": "アップタイムモニター管理ページに移動", "xpack.synthetics.page_header.manageMonitors": "モニター管理", "xpack.synthetics.page_header.settingsLink": "設定", "xpack.synthetics.page_header.settingsLink.label": "アップタイム設定ページに移動", @@ -35599,7 +35362,6 @@ "xpack.synthetics.routes.createNewMonitor": "ホームに移動", "xpack.synthetics.routes.goToSynthetics": "Syntheticsホームページに移動", "xpack.synthetics.routes.legacyBaseTitle": "アップタイム - Kibana", - "xpack.synthetics.routes.monitorManagement.betaLabel": "この機能はベータ段階で、変更される可能性があります。デザインとコードは正式に一般公開された機能より完成度が低く、現状のまま保証なしで提供されています。ベータ機能は、正式に一般公開された機能に適用されるサポートサービスレベル契約の対象外です。", "xpack.synthetics.runTest.failure": "手動でテストを実行できませんでした", "xpack.synthetics.seconds.label": "秒", "xpack.synthetics.seconds.shortForm.label": "秒", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 55a9c2941b8bf..edcd2a3a084fb 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -34248,7 +34248,6 @@ "xpack.stackConnectors.xmatters.title": "xMatters", "xpack.stackConnectors.xmatters.unexpectedNullResponseErrorMessage": "来自 xmatters 的异常空响应", "xpack.synthetics.addMonitor.pageHeader.description": "有关可用监测类型和其他选项的更多信息,请参阅我们的 {docs}。", - "xpack.synthetics.addMonitorRoute.title": "添加监测 | {baseTitle}", "xpack.synthetics.alertRules.monitorStatus.reasonMessage": "来自 {location} 的监测“{name}”为 {status}。已于 {checkedAt} 检查。", "xpack.synthetics.alerts.durationAnomaly.defaultActionMessage": "{anomalyStartTimestamp} 在 url {monitorUrl} 的 {monitor} 上检测到异常({severity} 级别)响应时间。异常严重性分数为 {severityScore}。\n从位置 {observerLocation} 检测到高达 {slowestAnomalyResponse} 的响应时间。预期响应时间为 {expectedResponseTime}。", "xpack.synthetics.alerts.durationAnomaly.defaultRecoveryMessage": "{anomalyStartTimestamp} 从位置 {observerLocation} 在 url {monitorUrl} 的监测 {monitor} 上检测到异常({severity} 级别)响应时间的告警已恢复", @@ -34279,9 +34278,6 @@ "xpack.synthetics.charts.mlAnnotation.severity": "严重性:{severity}", "xpack.synthetics.controls.selectSeverity.scoreDetailsDescription": "分数 {value} 及以上", "xpack.synthetics.createMonitorRoute.title": "创建监测 | {baseTitle}", - "xpack.synthetics.createPackagePolicy.stepConfigure.browserAdvancedSettings.throttling.throttling_exceeded.message": "您已超出 Synthetic 节点的 {throttlingField} 限制。{throttlingField} 值不能大于 {limit}Mbps。", - "xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.monitorType.browser.warning.content": "要创建“浏览器”监测,请确保使用 {agent} Docker 容器,其中包含运行这些监测的依赖项。有关更多信息,请访问我们的 {link}。", - "xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.params.helpText": "请使用 JSON 来定义可在您的脚本中通过 {code} 引用的参数", "xpack.synthetics.deprecateNoticeModal.forMoreInformation": "有关更多信息,{docsLink}", "xpack.synthetics.durationChart.emptyPrompt.description": "在选定时间范围内此监测从未{emphasizedText}。", "xpack.synthetics.editMonitorRoute.title": "编辑监测 | {baseTitle}", @@ -34338,8 +34334,6 @@ "xpack.synthetics.monitorList.redirects.description": "执行 ping 时,Heartbeat 在 {number} 次重定向后运行。", "xpack.synthetics.monitorList.redirects.title.number": "{number}", "xpack.synthetics.monitorList.statusColumn.checkedTimestamp": "已检查 {timestamp}", - "xpack.synthetics.monitorList.statusColumn.error.message": "{message}。单击了解更多详情。", - "xpack.synthetics.monitorList.statusColumn.error.messageLabel": "{message}。单击了解更多详情。", "xpack.synthetics.monitorList.statusColumn.locStatusMessage": "在 {noLoc} 个位置", "xpack.synthetics.monitorList.statusColumn.locStatusMessage.multiple": "在 {noLoc} 个位置", "xpack.synthetics.monitorList.statusColumn.locStatusMessage.tooltip.down": "在 {locs} 关闭", @@ -34348,17 +34342,11 @@ "xpack.synthetics.monitorList.tags.filter": "筛选带 {tag} 标签的所有监测", "xpack.synthetics.monitorManagement.agentCallout.content": "如果准备在此专用位置上运行“浏览器”监测,请确保使用 {code} Docker 容器,其中包含运行这些监测的依赖项。有关更多信息,{link}。", "xpack.synthetics.monitorManagement.anotherPrivateLocation": "此代理策略已附加到以下位置:{locationName}。", - "xpack.synthetics.monitorManagement.cannotDelete": "不能删除此位置,因为它正运行 {monCount} 个监测。请先从监测中移除该位置,再将其删除。", "xpack.synthetics.monitorManagement.cannotDelete.description": "不能删除此位置,因为它正运行 {monCount, number} 个{monCount, plural, other {监测}}。\n 请先从监测中移除该位置,再将其删除。", "xpack.synthetics.monitorManagement.deleteLocationName": "删除“{location}”", "xpack.synthetics.monitorManagement.deleteMonitorNameLabel": "删除“{name}”监测?", - "xpack.synthetics.monitorManagement.disclaimer": "通过使用此功能,客户确认已阅读并同意 {link}。", "xpack.synthetics.monitorManagement.lastXDays": "过去 {count, number} {count, plural, other {天}}", - "xpack.synthetics.monitorManagement.monitorAdvancedOptions.namespaceHelpLabel": "更改默认命名空间。此设置将更改监测的数据流的名称。{learnMore}。", "xpack.synthetics.monitorManagement.monitorDeleteSuccessMessage.name": "已删除“{name}”", - "xpack.synthetics.monitorManagement.monitorDisabledSuccessMessage": "已成功禁用监测 {name}。", - "xpack.synthetics.monitorManagement.monitorEnabledSuccessMessage": "已成功启用监测 {name}。", - "xpack.synthetics.monitorManagement.monitorEnabledUpdateFailureMessage": "无法更新监测 {name}。", "xpack.synthetics.monitorManagement.monitorList.disclaimer.label": "要将其完全删除并防止在将来再次推送,请将其从项目源中删除。{docsLink}。", "xpack.synthetics.monitorManagement.service.error.message": "已保存您的监测,但同步 {location} 的配置时遇到问题。我们会在稍后自动重试。如果此问题持续存在,您的监测将在 {location} 中停止运行。请联系支持人员获取帮助。", "xpack.synthetics.monitorManagement.service.error.reason": "原因:{reason}。", @@ -34446,7 +34434,6 @@ "xpack.synthetics.addEditMonitor.scriptEditor.label": "脚本编辑器", "xpack.synthetics.addEditMonitor.scriptEditor.placeholder": "// 在此处粘贴 Playwright 脚本......", "xpack.synthetics.addMonitor.pageHeader.docsLink": "文档", - "xpack.synthetics.addMonitor.pageHeader.title": "添加监测", "xpack.synthetics.alertDropdown.noWritePermissions": "您需要 Uptime 的读写访问权限才能在此应用中创建告警。", "xpack.synthetics.alertRule.monitorStatus.description": "管理 Synthetics 监测状态规则操作。", "xpack.synthetics.alertRules.actionGroups.monitorStatus": "Synthetics 监测状态", @@ -34610,18 +34597,6 @@ "xpack.synthetics.breadcrumbs.legacyOverviewBreadcrumbText": "运行时间", "xpack.synthetics.breadcrumbs.observabilityText": "Observability", "xpack.synthetics.breadcrumbs.overviewBreadcrumbText": "Synthetics", - "xpack.synthetics.browser.project.browserAdvancedSettings.description": "为 Synthetics 代理提供微调的配置。", - "xpack.synthetics.browser.project.browserAdvancedSettings.screenshots.helpText": "设置此选项以管理 Synthetics 代理捕获的屏幕截图。", - "xpack.synthetics.browser.project.browserAdvancedSettings.screenshots.label": "屏幕截图选项", - "xpack.synthetics.browser.project.browserAdvancedSettings.title": "Synthetics 代理选项", - "xpack.synthetics.browser.project.monitorIntegrationSettingsSection.monitorInterval": "频率", - "xpack.synthetics.browser.project.monitorIntegrationSettingsSection.monitorInterval.error": "监测频率必填", - "xpack.synthetics.browser.project.monitorIntegrationSettingsSection.tags.helpText": "将随监测事件一起发送的标签列表。按 enter 键添加新标签。显示在 Uptime 中并启用按标签搜索。", - "xpack.synthetics.browser.project.monitorIntegrationSettingsSection.tags.label": "标签", - "xpack.synthetics.browser.project.monitorIntegrationSettingsSectionDescription": "使用以下选项配置您的监测。", - "xpack.synthetics.browser.project.monitorIntegrationSettingsSectionTitle": "监测设置", - "xpack.synthetics.browser.project.readOnly.callout.content": "已从外部项目添加此监测。配置为只读状态。", - "xpack.synthetics.browser.project.readOnly.callout.title": "此配置为只读状态", "xpack.synthetics.certificates.loading": "正在加载证书......", "xpack.synthetics.certificates.refresh": "刷新", "xpack.synthetics.certificatesPage.heading": "TLS 证书", @@ -34632,7 +34607,6 @@ "xpack.synthetics.certs.list.commonName": "常见名称", "xpack.synthetics.certs.list.copyFingerprint": "单击可复制指纹值", "xpack.synthetics.certs.list.days": "天", - "xpack.synthetics.certs.list.empty": "找不到任何证书。注意:证书仅对 Heartbeat 7.8+ 可见", "xpack.synthetics.certs.list.expirationDate": "指纹", "xpack.synthetics.certs.list.issuedBy": "颁发者", "xpack.synthetics.certs.list.monitors": "监测", @@ -34655,136 +34629,13 @@ "xpack.synthetics.createMonitor.pageHeader.title": "创建监测", "xpack.synthetics.createPackagePolicy.stepConfigure.browser.scriptRecorder.experimentalLabel": "技术预览", "xpack.synthetics.createPackagePolicy.stepConfigure.browser.scriptRecorder.experimentalTooltip": "预览通过 Elastic Synthetics 记录器创建 Elastic Synthetics 监测脚本的最快方式", - "xpack.synthetics.createPackagePolicy.stepConfigure.browser.scriptRecorder.label": "脚本记录器", - "xpack.synthetics.createPackagePolicy.stepConfigure.browserAdvancedSettings.description": "为 Synthetics 代理提供微调的配置。", - "xpack.synthetics.createPackagePolicy.stepConfigure.browserAdvancedSettings.ignoreHttpsErrors.helpText": "将此选项设为 true 可在 Synthetics 浏览器中禁用 TLS/SSL 验证。这对于使用自签名证书的测试站点很有用。", - "xpack.synthetics.createPackagePolicy.stepConfigure.browserAdvancedSettings.ignoreHttpsErrors.label": "忽略 HTTPS 错误", - "xpack.synthetics.createPackagePolicy.stepConfigure.browserAdvancedSettings.screenshots.helpText": "设置此选项以管理 Synthetics 代理捕获的屏幕截图。", - "xpack.synthetics.createPackagePolicy.stepConfigure.browserAdvancedSettings.screenshots.label": "屏幕截图选项", - "xpack.synthetics.createPackagePolicy.stepConfigure.browserAdvancedSettings.syntheticsArgs.helpText": "要传递给 Synthetics 代理软件包的附加参数。取字符串列表。这在极少情况下有用,通常应不需要设置。", - "xpack.synthetics.createPackagePolicy.stepConfigure.browserAdvancedSettings.syntheticsArgs.label": "Synthetics 参数", - "xpack.synthetics.createPackagePolicy.stepConfigure.browserAdvancedSettings.throttling.automatic_node_cap.message": "禁用限制时,您的监测的带宽仍然由其中运行监测的 Synthetics 节点的配置设置上限。", - "xpack.synthetics.createPackagePolicy.stepConfigure.browserAdvancedSettings.throttling.automatic_node_cap.title": "自动上限", - "xpack.synthetics.createPackagePolicy.stepConfigure.browserAdvancedSettings.throttling.description": "控制监测的下载和上传速度及其延迟,以在更缓慢或更迟缓的网络上模拟应用程序的行为。", - "xpack.synthetics.createPackagePolicy.stepConfigure.browserAdvancedSettings.throttling.download.error": "下载速度必须大于零。", - "xpack.synthetics.createPackagePolicy.stepConfigure.browserAdvancedSettings.throttling.download.label": "下载速度", - "xpack.synthetics.createPackagePolicy.stepConfigure.browserAdvancedSettings.throttling.exceeded_throttling.message": "使用大于 Synthetics 节点带宽限制的限值时,您的监测的带宽仍然具有上限。", - "xpack.synthetics.createPackagePolicy.stepConfigure.browserAdvancedSettings.throttling.exceeded_throttling.title": "您已超出 Synthetics 节点带宽限制", - "xpack.synthetics.createPackagePolicy.stepConfigure.browserAdvancedSettings.throttling.latency.error": "延迟不得为负。", - "xpack.synthetics.createPackagePolicy.stepConfigure.browserAdvancedSettings.throttling.latency.label": "延迟", - "xpack.synthetics.createPackagePolicy.stepConfigure.browserAdvancedSettings.throttling.switch.description": "启用限制", - "xpack.synthetics.createPackagePolicy.stepConfigure.browserAdvancedSettings.throttling.title": "限制选项", - "xpack.synthetics.createPackagePolicy.stepConfigure.browserAdvancedSettings.throttling.upload.error": "上传速度必须大于零。", - "xpack.synthetics.createPackagePolicy.stepConfigure.browserAdvancedSettings.throttling.upload.label": "上传速度", - "xpack.synthetics.createPackagePolicy.stepConfigure.browserAdvancedSettings.title": "Synthetics 代理选项", - "xpack.synthetics.createPackagePolicy.stepConfigure.certificateSettings.enableSSLSettings.label": "启用 TLS 配置", - "xpack.synthetics.createPackagePolicy.stepConfigure.certsField.certificate.helpText": "用于 TLS 客户端身份验证的 PEM 格式证书。", - "xpack.synthetics.createPackagePolicy.stepConfigure.certsField.certificate.label": "证书", - "xpack.synthetics.createPackagePolicy.stepConfigure.certsField.certificateAuthorities.helpText": "PEM 格式自定义证书颁发机构。", - "xpack.synthetics.createPackagePolicy.stepConfigure.certsField.certificateAuthorities.label": "证书颁发机构", - "xpack.synthetics.createPackagePolicy.stepConfigure.certsField.certificateKey.helpText": "用于 TLS 客户端身份验证的 PEM 格式证书密钥。", - "xpack.synthetics.createPackagePolicy.stepConfigure.certsField.certificateKey.label": "密钥", - "xpack.synthetics.createPackagePolicy.stepConfigure.certsField.certificateKeyPassphrase.helpText": "用于 TLS 客户端身份验证的证书密钥密码。", - "xpack.synthetics.createPackagePolicy.stepConfigure.certsField.certificateKeyPassphrase.label": "密钥密码", - "xpack.synthetics.createPackagePolicy.stepConfigure.certsField.legend": "证书设置", - "xpack.synthetics.createPackagePolicy.stepConfigure.certsField.tlsRole.client": "客户端", - "xpack.synthetics.createPackagePolicy.stepConfigure.certsField.tlsRole.server": "服务器", - "xpack.synthetics.createPackagePolicy.stepConfigure.certsField.verificationMode.certificate.description": "验证提供的证书是否由受信任颁发机构 (CA) 签署,但不执行任何主机名验证。", - "xpack.synthetics.createPackagePolicy.stepConfigure.certsField.verificationMode.certificate.label": "证书", - "xpack.synthetics.createPackagePolicy.stepConfigure.certsField.verificationMode.full.description": "验证提供的证书是否由受信任颁发机构 (CA) 签署,同时验证服务器的主机名(或 IP 地址)是否匹配证书内识别的名称。", - "xpack.synthetics.createPackagePolicy.stepConfigure.certsField.verificationMode.full.label": "实线", - "xpack.synthetics.createPackagePolicy.stepConfigure.certsField.verificationMode.label": "验证模式", - "xpack.synthetics.createPackagePolicy.stepConfigure.certsField.verificationMode.none.description": "对服务器的证书不执行验证。主要用作尝试解决 TLS 错误时的临时诊断机制;强烈不建议在生产环境中使用它。", - "xpack.synthetics.createPackagePolicy.stepConfigure.certsField.verificationMode.none.label": "无", - "xpack.synthetics.createPackagePolicy.stepConfigure.certsField.verificationMode.strict.description": "验证提供的证书是否由受信任颁发机构 (CA) 签署,同时验证服务器的主机名(或 IP 地址)是否匹配证书内识别的名称。如果使用者备用名称为空,将返回错误。", - "xpack.synthetics.createPackagePolicy.stepConfigure.certsField.verificationMode.strict.label": "严格", - "xpack.synthetics.createPackagePolicy.stepConfigure.certsField.verificationMode.warning.description": "此模式禁用了许多 SSL/TLS 安全性功能,只能在谨慎考虑之后使用。", - "xpack.synthetics.createPackagePolicy.stepConfigure.certsField.verificationMode.warning.title": "正在禁用 TLS", - "xpack.synthetics.createPackagePolicy.stepConfigure.certsField.version.label": "支持的 TLS 协议", - "xpack.synthetics.createPackagePolicy.stepConfigure.certsField.version.placeholder": "选择一个或多个 TLS 协议。", "xpack.synthetics.createPackagePolicy.stepConfigure.headerField.addHeader.label": "添加标头", - "xpack.synthetics.createPackagePolicy.stepConfigure.httpAdvancedOptions": "高级 HTTP 选项", - "xpack.synthetics.createPackagePolicy.stepConfigure.httpAdvancedOptions.indexResponseBody.helpText": "控制将 HTTP 响应正文内容索引到 ", - "xpack.synthetics.createPackagePolicy.stepConfigure.httpAdvancedOptions.indexResponseHeaders.helpText": "控制将 HTTP 响应标头索引到 ", - "xpack.synthetics.createPackagePolicy.stepConfigure.httpAdvancedOptions.requestBody.helpText": "请求正文内容。", - "xpack.synthetics.createPackagePolicy.stepConfigure.httpAdvancedOptions.requestConfiguration.description": "配置要发送到远程主机的可选请求,包括方法、正文和标头。", - "xpack.synthetics.createPackagePolicy.stepConfigure.httpAdvancedOptions.requestConfiguration.requestBody": "请求正文", - "xpack.synthetics.createPackagePolicy.stepConfigure.httpAdvancedOptions.requestConfiguration.requestHeaders": "请求标头", - "xpack.synthetics.createPackagePolicy.stepConfigure.httpAdvancedOptions.requestConfiguration.requestMethod.label": "请求方法", - "xpack.synthetics.createPackagePolicy.stepConfigure.httpAdvancedOptions.requestConfiguration.title": "请求配置", - "xpack.synthetics.createPackagePolicy.stepConfigure.httpAdvancedOptions.requestHeadersField.error": "标头密钥必须是有效的 HTTP 令牌。", - "xpack.synthetics.createPackagePolicy.stepConfigure.httpAdvancedOptions.requestHeadersField.helpText": "要发送的额外 HTTP 标头的字典。默认情况下,客户端将设置用户代理标头以自我标识。", - "xpack.synthetics.createPackagePolicy.stepConfigure.httpAdvancedOptions.requestMethod.helpText": "要使用的 HTTP 方法。", - "xpack.synthetics.createPackagePolicy.stepConfigure.httpAdvancedOptions.responseBodyCheckNegative.helpText": "负匹配正文输出的正则表达式列表。按 enter 键添加新的表达式。如果单个表达式匹配,返回匹配失败。", - "xpack.synthetics.createPackagePolicy.stepConfigure.httpAdvancedOptions.responseBodyCheckPositive.helpText": "匹配正文输出的正则表达式列表。按 enter 键添加新的表达式。仅单个表达式需要匹配。", - "xpack.synthetics.createPackagePolicy.stepConfigure.httpAdvancedOptions.responseCheckNegative.label": "检查响应正文不包含", - "xpack.synthetics.createPackagePolicy.stepConfigure.httpAdvancedOptions.responseCheckPositive.label": "检查响应正文包含", - "xpack.synthetics.createPackagePolicy.stepConfigure.httpAdvancedOptions.responseChecks.checkResponseHeadersContain": "检查响应标头包含", - "xpack.synthetics.createPackagePolicy.stepConfigure.httpAdvancedOptions.responseChecks.description": "配置预期的 HTTP 响应。", - "xpack.synthetics.createPackagePolicy.stepConfigure.httpAdvancedOptions.responseChecks.responseStatusCheck.error": "状态代码只能包含数字。", - "xpack.synthetics.createPackagePolicy.stepConfigure.httpAdvancedOptions.responseChecks.responseStatusCheck.helpText": "预期状态代码列表。按 enter 键添加新的代码。4xx 和 5xx 代码默认情况下被视为关闭。其他代码被视为运行。", - "xpack.synthetics.createPackagePolicy.stepConfigure.httpAdvancedOptions.responseChecks.responseStatusCheck.label": "检查响应状态等于", - "xpack.synthetics.createPackagePolicy.stepConfigure.httpAdvancedOptions.responseChecks.title": "响应检查", - "xpack.synthetics.createPackagePolicy.stepConfigure.httpAdvancedOptions.responseConfig.indexResponseBody": "索引响应正文", - "xpack.synthetics.createPackagePolicy.stepConfigure.httpAdvancedOptions.responseConfig.indexResponseHeaders": "索引响应标头", - "xpack.synthetics.createPackagePolicy.stepConfigure.httpAdvancedOptions.responseConfig.responseBodyIndexPolicy": "响应正文索引策略", - "xpack.synthetics.createPackagePolicy.stepConfigure.httpAdvancedOptions.responseConfiguration.description": "控制 HTTP 响应标头的索引。", - "xpack.synthetics.createPackagePolicy.stepConfigure.httpAdvancedOptions.responseConfiguration.title": "响应配置", - "xpack.synthetics.createPackagePolicy.stepConfigure.httpAdvancedOptions.responseHeadersField.error": "标头密钥必须是有效的 HTTP 令牌。", - "xpack.synthetics.createPackagePolicy.stepConfigure.httpAdvancedOptions.responseHeadersField.helpText": "预期响应标头的列表。", - "xpack.synthetics.createPackagePolicy.stepConfigure.icmpAdvancedOptions": "高级 ICMP 选项", "xpack.synthetics.createPackagePolicy.stepConfigure.inputVarFieldOptionalLabel": "可选", - "xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.APMServiceName.helpText": "此监测的 APM 服务名称。对应于 service.name ECS 字段。监测也使用 APM 的应用时设置此选项以启用 Kibana 中的 Uptime 和 APM 数据之间的集成。", - "xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.browser.inlineScript.error": "“脚本”必填", - "xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.browser.inlineScript.helpText": "运行内联定义的 Synthetics 测试脚本。", - "xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.browser.inlineScript.label": "内联脚本", "xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.browser.scriptRecorder.closeButtonLabel": "关闭脚本浮出控件", "xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.browser.scriptRecorder.mockFileName": "test_script.js", - "xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.browser.sourceType.label": "源类型", "xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.browser.uploader.fieldLabel": "正在测试脚本", "xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.browser.uploader.invalidFileError": "文件类型无效。请上传由 Elastic Synthetics 记录器生成的 .js 文件。", - "xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.browser.uploader.label": "选择记录器生成的 .js 文件", "xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.browser.uploader.parsingError": "上传文件时出错。请上传 Elastic Synthetics 记录器以内联脚本格式生成的 .js 文件。", - "xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.browserLabel": "浏览器(公测版)", - "xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.enabled.helpText": "关闭此配置以禁用监测。", - "xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.enabled.label": "已启用", - "xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.icmp.hosts": "主机", - "xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.icmp.hosts.error": "“主机”必填", - "xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.maxRedirects": "最大重定向数", - "xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.maxRedirects.error": "“最大重定向数”必须不小于 0", - "xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.maxRedirects.helpText": "要跟随的重定向总数。", - "xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.monitorInterval": "频率", - "xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.monitorInterval.error": "监测频率必填", - "xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.monitorType": "监测类型", - "xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.monitorType.browser.warning.link": "Synthetics 文档", - "xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.monitorType.browser.warning.title": "要求", - "xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.monitorType.error": "“监测类型”必填。", - "xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.params.label": "参数", - "xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.password.helpText": "用于在服务器上进行身份验证的密码。", - "xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.password.label": "密码", - "xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.proxyUrl.http.helpText": "HTTP 代理 URL。", - "xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.proxyURL.http.label": "代理 URL", - "xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.proxyURL.label": "代理 URL", - "xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.proxyUrl.tcp.helpText": "连接到服务器时要使用的 SOCKS5 代理的 URL。该值必须是具有 socks5:// 方案的 URL。", - "xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.resolveHostnamesLocally": "本地解析主机名", - "xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.tags.helpText": "将随监测事件一起发送的标签列表。按 enter 键添加新标签。显示在 Uptime 中并启用按标签搜索。", - "xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.tags.label": "标签", - "xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.tcp.hosts": "主机:端口", - "xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.tcp.hosts.error": "主机和端口必填", - "xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.timeout.helpText": "允许用于测试连接并交换数据的总时间。", - "xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.timeout.label": "超时(秒)", - "xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.timeout.lessThanIntervalError": "超时必须小于监测频率", - "xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.timeout.moreThanZeroError": "超时必须大于或等于 0", - "xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.URL": "URL", - "xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.URL.error": "“URL”必填", - "xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.username.helpText": "用于在服务器上进行身份验证的用户名。", - "xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.username.label": "用户名", - "xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.wait.error": "“等待时间”必须不小于 0", - "xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.wait.helpText": "如果未收到响应,在发出另一个 ICMP 回显请求之前要等待的时长。", - "xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.wait.label": "等待时间(秒)", - "xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSectionDescription": "使用以下选项配置您的监测。", - "xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSectionTitle": "监测设置", - "xpack.synthetics.createPackagePolicy.stepConfigure.requestBody.codeEditor.javascript.ariaLabel": "JavaScript 代码编辑器", "xpack.synthetics.createPackagePolicy.stepConfigure.requestBody.codeEditor.json.ariaLabel": "JSON 代码编辑器", "xpack.synthetics.createPackagePolicy.stepConfigure.requestBody.codeEditor.text.ariaLabel": "文本代码编辑器", "xpack.synthetics.createPackagePolicy.stepConfigure.requestBody.codeEditor.xml.ariaLabel": "XML 代码编辑器", @@ -34795,20 +34646,6 @@ "xpack.synthetics.createPackagePolicy.stepConfigure.requestBodyType.XML": "XML", "xpack.synthetics.createPackagePolicy.stepConfigure.responseBodyIndex.always": "始终", "xpack.synthetics.createPackagePolicy.stepConfigure.responseBodyIndex.onError": "错误时", - "xpack.synthetics.createPackagePolicy.stepConfigure.scheduleField.minutes": "分钟", - "xpack.synthetics.createPackagePolicy.stepConfigure.scheduleField.number": "数字", - "xpack.synthetics.createPackagePolicy.stepConfigure.scheduleField.seconds": "秒", - "xpack.synthetics.createPackagePolicy.stepConfigure.scheduleField.unit": "单位", - "xpack.synthetics.createPackagePolicy.stepConfigure.tcpAdvacnedSettings.requestConfiguration.description": "配置向远程主机发送的有效负载。", - "xpack.synthetics.createPackagePolicy.stepConfigure.tcpAdvacnedSettings.requestConfiguration.requestPayload.helpText": "要发送给远程主机的有效负载字符串。", - "xpack.synthetics.createPackagePolicy.stepConfigure.tcpAdvacnedSettings.requestConfiguration.requestPayload.label": "请求有效负载", - "xpack.synthetics.createPackagePolicy.stepConfigure.tcpAdvacnedSettings.requestConfiguration.title": "请求配置", - "xpack.synthetics.createPackagePolicy.stepConfigure.tcpAdvacnedSettings.responseConfiguration.responseContains.helpText": "预期远程主机响应。", - "xpack.synthetics.createPackagePolicy.stepConfigure.tcpAdvacnedSettings.responseConfiguration.responseContains.label": "检查响应包含", - "xpack.synthetics.createPackagePolicy.stepConfigure.tcpAdvancedOptions.responseConfiguration.description": "配置来自远程主机的预期响应。", - "xpack.synthetics.createPackagePolicy.stepConfigure.tcpAdvancedOptions.responseConfiguration.title": "响应检查", - "xpack.synthetics.createPackagePolicy.stepConfigure.tlsSettings.description": "配置 TLS 选项,包括验证模式、证书颁发机构和客户端证书。", - "xpack.synthetics.createPackagePolicy.stepConfigure.tlsSettings.label": "TLS 设置", "xpack.synthetics.dcl.label": "DCL", "xpack.synthetics.deprecateNoticeModal.addPrivateLocations": "根据您的 Fleet 策略添加专用位置", "xpack.synthetics.deprecateNoticeModal.automateMonitors": "使用项目监测自动创建监测", @@ -35181,8 +35018,6 @@ "xpack.synthetics.monitorErrorsTab.title": "错误", "xpack.synthetics.monitorHistoryTab.title": "历史记录", "xpack.synthetics.monitorLastRun.lastRunLabel": "上次运行", - "xpack.synthetics.monitorList.allMonitors": "所有监测", - "xpack.synthetics.monitorList.anomalyColumn.label": "响应异常分数", "xpack.synthetics.monitorList.closeFlyoutText": "关闭", "xpack.synthetics.monitorList.defineConnector.popover.description": "以接收状态告警。", "xpack.synthetics.monitorList.disableDownAlert": "禁用状态告警", @@ -35206,7 +35041,6 @@ "xpack.synthetics.monitorList.infraIntegrationAction.kubernetes.description": "在 Infrastructure UI 上查找此监测的 Pod UID", "xpack.synthetics.monitorList.infraIntegrationAction.kubernetes.message": "显示 Pod 指标", "xpack.synthetics.monitorList.integrationGroup.emptyMessage": "没有可用的集成应用程序", - "xpack.synthetics.monitorList.invalidMonitors": "监测无效", "xpack.synthetics.monitorList.lastModified": "最后修改时间", "xpack.synthetics.monitorList.lastRunHeaderText": "上次运行", "xpack.synthetics.monitorList.loading": "正在加载……", @@ -35228,12 +35062,10 @@ "xpack.synthetics.monitorList.projectIdHeaderText": "项目 ID", "xpack.synthetics.monitorList.redirects.openWindow": "将在新窗口中打开链接。", "xpack.synthetics.monitorList.redirects.title": "重定向", - "xpack.synthetics.monitorList.refresh": "刷新", "xpack.synthetics.monitorList.runTest.label": "运行测试", "xpack.synthetics.monitorList.statusAlert.label": "状态告警", "xpack.synthetics.monitorList.statusColumn.completeLabel": "已完成", "xpack.synthetics.monitorList.statusColumn.downLabel": "关闭", - "xpack.synthetics.monitorList.statusColumn.error.logs": "错误日志", "xpack.synthetics.monitorList.statusColumn.failedLabel": "失败", "xpack.synthetics.monitorList.statusColumn.upLabel": "运行", "xpack.synthetics.monitorList.statusColumnLabel": "状态", @@ -35242,9 +35074,6 @@ "xpack.synthetics.monitorList.table.url.name": "URL", "xpack.synthetics.monitorList.tags.expand": "单击以查看剩余标签", "xpack.synthetics.monitorList.testNow.AriaLabel": "单击以立即运行测试", - "xpack.synthetics.monitorList.testNow.available": "立即测试仅适用于通过监测管理添加的监测。", - "xpack.synthetics.monitorList.testNow.available.private": "您当前无法按需测试在专用位置运行的监测。", - "xpack.synthetics.monitorList.testNow.label": "立即测试", "xpack.synthetics.monitorList.testNow.scheduled": "已计划测试", "xpack.synthetics.monitorList.testRunLogs": "测试运行日志", "xpack.synthetics.monitorList.timestamp": "时间戳", @@ -35259,14 +35088,7 @@ "xpack.synthetics.monitorManagement.addAgentPolicyDesc": "专用位置需要代理策略。要添加专用位置,必须首先在 Fleet 中创建代理策略。", "xpack.synthetics.monitorManagement.addEdit.createMonitorLabel": "创建监测", "xpack.synthetics.monitorManagement.addEdit.deleteMonitorLabel": "删除监测", - "xpack.synthetics.monitorManagement.addLocation": "添加位置", "xpack.synthetics.monitorManagement.addMonitorCrumb": "添加监测", - "xpack.synthetics.monitorManagement.addMonitorError": "无法加载服务位置。请稍后重试。", - "xpack.synthetics.monitorManagement.addMonitorLabel": "添加监测", - "xpack.synthetics.monitorManagement.addMonitorLoadingError": "加载监测管理时出错", - "xpack.synthetics.monitorManagement.addMonitorLoadingLabel": "正在加载监测管理", - "xpack.synthetics.monitorManagement.addMonitorServiceLocationsLoadingError": "无法加载服务位置。请稍后重试。", - "xpack.synthetics.monitorManagement.addPrivateLocations": "添加专用位置", "xpack.synthetics.monitorManagement.agentCallout.link": "阅读文档", "xpack.synthetics.monitorManagement.agentCallout.title": "要求", "xpack.synthetics.monitorManagement.agentPolicy": "代理策略", @@ -35274,7 +35096,6 @@ "xpack.synthetics.monitorManagement.agentsLabel": "代理:", "xpack.synthetics.monitorManagement.alreadyExists": "位置名称已存在。", "xpack.synthetics.monitorManagement.apiKey.label": "API 密钥", - "xpack.synthetics.monitorManagement.apiKeysDisabledToolTip": "对此集群禁用了 API 密钥。监测管理需要使用 API 密钥才能回写 Elasticsearch 集群。要启用 API 密钥,请与管理员联系。", "xpack.synthetics.monitorManagement.apiKeyWarning.label": "此 API 密钥仅显示一次。请保留副本作为您自己的记录。", "xpack.synthetics.monitorManagement.areYouSure": "是否确定要删除此位置?", "xpack.synthetics.monitorManagement.callout.apiKeyMissing": "由于缺少 API 密钥,监测管理当前已禁用", @@ -35286,7 +35107,6 @@ "xpack.synthetics.monitorManagement.cancelLabel": "取消", "xpack.synthetics.monitorManagement.cannotSaveIntegration": "您无权更新集成。需要集成写入权限。", "xpack.synthetics.monitorManagement.closeButtonLabel": "关闭", - "xpack.synthetics.monitorManagement.closeLabel": "关闭", "xpack.synthetics.monitorManagement.completed": "已完成", "xpack.synthetics.monitorManagement.configurations.label": "配置", "xpack.synthetics.monitorManagement.createAgentPolicy": "创建代理策略", @@ -35295,24 +35115,14 @@ "xpack.synthetics.monitorManagement.createLocationMonitors": "创建监测", "xpack.synthetics.monitorManagement.createMonitorLabel": "创建监测", "xpack.synthetics.monitorManagement.createPrivateLocations": "创建专用位置", - "xpack.synthetics.monitorManagement.delete": "删除位置", "xpack.synthetics.monitorManagement.deletedPolicy": "策略已策略", "xpack.synthetics.monitorManagement.deleteLocation": "删除位置", "xpack.synthetics.monitorManagement.deleteLocationLabel": "删除位置", - "xpack.synthetics.monitorManagement.deleteMonitorLabel": "删除监测", "xpack.synthetics.monitorManagement.disabled.label": "已禁用", - "xpack.synthetics.monitorManagement.disabledCallout.adminContact": "请联系管理员启用监测管理。", - "xpack.synthetics.monitorManagement.disabledCallout.description.disabled": "监测管理当前已禁用,并且您现有的监测已暂停。您可以启用监测管理来运行监测。", - "xpack.synthetics.monitorManagement.disableMonitorLabel": "禁用监测", "xpack.synthetics.monitorManagement.discardLabel": "取消", - "xpack.synthetics.monitorManagement.disclaimerLinkLabel": "在公测版期间,可以免费使用该服务来执行测试。公平使用策略适用。正常数据存储成本适用于 Elastic 集群中存储的测试结果。", - "xpack.synthetics.monitorManagement.duplicateNameError": "监测名称已存在。", "xpack.synthetics.monitorManagement.editMonitorCrumb": "编辑监测", "xpack.synthetics.monitorManagement.editMonitorError": "加载监测管理时出错", "xpack.synthetics.monitorManagement.editMonitorError.description": "无法加载监测管理设置。请联系支持人员。", - "xpack.synthetics.monitorManagement.editMonitorErrorBody": "无法加载监测配置。请稍后重试。", - "xpack.synthetics.monitorManagement.editMonitorLabel": "编辑监测", - "xpack.synthetics.monitorManagement.editMonitorLoadingLabel": "正在加载监测", "xpack.synthetics.monitorManagement.emptyState.enablement": "启用监测管理以从全球托管测试地点运行轻量级、真正的浏览器监测。启用监测管理将生成 API 密钥,以便 Synthetics 服务回写 Elasticsearch 集群。", "xpack.synthetics.monitorManagement.emptyState.enablement.disabled.title": "已禁用监测管理", "xpack.synthetics.monitorManagement.emptyState.enablement.disabledDescription": "监测管理当前处于禁用状态。通过监测管理,您可以从全球托管测试地点运行轻量级、真正的浏览器监测。要启用监测管理,请与管理员联系。", @@ -35320,7 +35130,6 @@ "xpack.synthetics.monitorManagement.emptyState.enablement.enabled.title": "启用监测管理", "xpack.synthetics.monitorManagement.emptyState.enablement.learnMore": "希望了解详情?", "xpack.synthetics.monitorManagement.emptyState.enablement.title": "启用", - "xpack.synthetics.monitorManagement.enableMonitorLabel": "启用监测", "xpack.synthetics.monitorManagement.failed": "失败", "xpack.synthetics.monitorManagement.failedRun": "无法运行步骤", "xpack.synthetics.monitorManagement.filter.frequencyLabel": "频率", @@ -35329,21 +35138,14 @@ "xpack.synthetics.monitorManagement.filter.projectLabel": "项目", "xpack.synthetics.monitorManagement.filter.tagsLabel": "标签", "xpack.synthetics.monitorManagement.filter.typeLabel": "类型", - "xpack.synthetics.monitorManagement.firstLocation": "添加首个专用位置", "xpack.synthetics.monitorManagement.firstLocationMonitor": "要创建监测,需要先添加位置。", - "xpack.synthetics.monitorManagement.getApiKey.label": "生成 API 密钥", "xpack.synthetics.monitorManagement.getAPIKeyLabel.description": "使用 API 密钥从 CLI 或 CD 管道远程推送监测。", "xpack.synthetics.monitorManagement.getAPIKeyLabel.disclaimer": "请注意:要使用专用测试位置推送监测,必须以具有 Fleet 和集成写入权限的用户身份生成此 API 密钥。", - "xpack.synthetics.monitorManagement.getAPIKeyLabel.generate": "生成 API 密钥", - "xpack.synthetics.monitorManagement.getAPIKeyLabel.label": "API 密钥", "xpack.synthetics.monitorManagement.getAPIKeyLabel.loading": "正在生成 API 密钥", "xpack.synthetics.monitorManagement.getAPIKeyReducedPermissions.description": "使用 API 密钥从 CLI 或 CD 管道远程推送监测。要生成 API 密钥,您必须有权管理 API 密钥并具有 Uptime 写入权限。请联系您的管理员。", "xpack.synthetics.monitorManagement.getProjectApiKey.label": "生成项目 API 密钥", "xpack.synthetics.monitorManagement.getProjectAPIKeyLabel.generate": "生成项目 API 密钥", - "xpack.synthetics.monitorManagement.heading": "监测管理", - "xpack.synthetics.monitorManagement.hostFieldLabel": "主机", "xpack.synthetics.monitorManagement.inProgress": "进行中", - "xpack.synthetics.monitorManagement.invalidLabel": "无效", "xpack.synthetics.monitorManagement.label": "监测管理", "xpack.synthetics.monitorManagement.learnMore": "有关更多信息,", "xpack.synthetics.monitorManagement.learnMore.label": "了解详情", @@ -35354,30 +35156,10 @@ "xpack.synthetics.monitorManagement.manageMonitorLoadingLabel": "正在加载监测管理", "xpack.synthetics.monitorManagement.manageMonitorLoadingLabel.callout.invalidKey": "了解详情", "xpack.synthetics.monitorManagement.manageMonitorLoadingLabel.callout.learnMore": "了解详情。", - "xpack.synthetics.monitorManagement.manageMonitorLoadingLabel.disabledCallout.learnMore": "了解详情", - "xpack.synthetics.monitorManagement.managePrivateLocations": "专用位置", "xpack.synthetics.monitorManagement.monitorAddedSuccessMessage": "已成功添加监测。", - "xpack.synthetics.monitorManagement.monitorAdvancedOptions.dataStreamConfiguration.description": "配置其他数据流选项。", - "xpack.synthetics.monitorManagement.monitorAdvancedOptions.dataStreamConfiguration.title": "数据流设置", - "xpack.synthetics.monitorManagement.monitorAdvancedOptions.monitorNamespaceFieldLabel": "命名空间", - "xpack.synthetics.monitorManagement.monitorAdvancedOptions.namespaceHelpLearnMoreLabel": "了解详情", - "xpack.synthetics.monitorManagement.monitorDeleteFailureMessage": "无法删除监测。请稍后重试。", "xpack.synthetics.monitorManagement.monitorEditedSuccessMessage": "已成功更新监测。", "xpack.synthetics.monitorManagement.monitorFailureMessage": "无法保存监测。请稍后重试。", - "xpack.synthetics.monitorManagement.monitorList.actions": "操作", "xpack.synthetics.monitorManagement.monitorList.disclaimer.title": "删除此监测不会将其从项目源中移除", - "xpack.synthetics.monitorManagement.monitorList.enabled": "已启用", - "xpack.synthetics.monitorManagement.monitorList.locations": "位置", - "xpack.synthetics.monitorManagement.monitorList.monitorName": "监测名称", - "xpack.synthetics.monitorManagement.monitorList.monitorType": "监测类型", - "xpack.synthetics.monitorManagement.monitorList.schedule": "频率(分钟)", - "xpack.synthetics.monitorManagement.monitorList.tags": "标签", - "xpack.synthetics.monitorManagement.monitorList.title": "监测管理列表", - "xpack.synthetics.monitorManagement.monitorList.URL": "URL", - "xpack.synthetics.monitorManagement.monitorLocationsLabel": "监测位置", - "xpack.synthetics.monitorManagement.monitorManagementCrumb": "监测管理", - "xpack.synthetics.monitorManagement.monitorNameFieldError": "监测名称必填", - "xpack.synthetics.monitorManagement.monitorNameFieldLabel": "监测名称", "xpack.synthetics.monitorManagement.monitors": "监测", "xpack.synthetics.monitorManagement.monitorsTab.title": "管理", "xpack.synthetics.monitorManagement.monitorSync.failure.content": "同步一个或多个位置的监测时遇到问题:", @@ -35387,12 +35169,9 @@ "xpack.synthetics.monitorManagement.monitorSync.failure.statusLabel": "状态", "xpack.synthetics.monitorManagement.monitorSync.failure.title": "监测无法与 Synthetics 服务同步", "xpack.synthetics.monitorManagement.nameRequired": "“位置名称”必填", - "xpack.synthetics.monitorManagement.needPermissions": "需要权限", "xpack.synthetics.monitorManagement.noFleetPermission": "您无权执行此操作。需要集成写入权限。", - "xpack.synthetics.monitorManagement.noLabel": "取消", "xpack.synthetics.monitorManagement.noSyntheticsPermissions": "您的权限不足,无法执行此操作。", "xpack.synthetics.monitorManagement.overviewTab.title": "概览", - "xpack.synthetics.monitorManagement.pageHeader.title": "监测管理", "xpack.synthetics.monitorManagement.param.keyExists": "密钥已存在", "xpack.synthetics.monitorManagement.param.keyRequired": "“密钥”必填", "xpack.synthetics.monitorManagement.paramForm.descriptionLabel": "描述", @@ -35401,52 +35180,38 @@ "xpack.synthetics.monitorManagement.paramForm.tagsLabel": "标签", "xpack.synthetics.monitorManagement.pending": "待处理", "xpack.synthetics.monitorManagement.policyHost": "代理策略", - "xpack.synthetics.monitorManagement.privateLabel": "专用", "xpack.synthetics.monitorManagement.privateLocations": "专用位置", "xpack.synthetics.monitorManagement.privateLocationsNotAllowedMessage": "您无权将监测添加到专用位置。请联系管理员请求访问权限。", "xpack.synthetics.monitorManagement.projectDelete.docsLink": "了解详情", "xpack.synthetics.monitorManagement.projectPush.label": "项目推送命令", - "xpack.synthetics.monitorManagement.publicBetaDescription": "我们获得了一个崭新的应用。同时,我们很高兴您能够尽早访问我们的全球托管测试基础架构。这将允许您使用我们的新型点击式脚本记录器上传组合监测,并通过新 UI 来管理监测。", "xpack.synthetics.monitorManagement.readDocs": "阅读文档", "xpack.synthetics.monitorManagement.requestAccess": "请求访问权限", - "xpack.synthetics.monitorManagement.reRunTest": "重新运行测试", - "xpack.synthetics.monitorManagement.runTest": "运行测试", "xpack.synthetics.monitorManagement.saveLabel": "保存", - "xpack.synthetics.monitorManagement.saveMonitorLabel": "保存监测", "xpack.synthetics.monitorManagement.selectOneOrMoreLocations": "选择一个或多个位置", "xpack.synthetics.monitorManagement.selectPolicyHost": "选择代理策略", "xpack.synthetics.monitorManagement.selectPolicyHost.helpText": "我们建议每个代理策略使用单一 Elastic 代理。", "xpack.synthetics.monitorManagement.service.error.title": "无法同步监测配置", - "xpack.synthetics.monitorManagement.serviceLocationsValidationError": "必须至少指定一个服务位置", "xpack.synthetics.monitorManagement.startAddingLocationsDescription": "专用位置供您从自己的场所运行监测。它们需要可以通过 Fleet 进行控制和维护的 Elastic 代理和代理策略。", "xpack.synthetics.monitorManagement.steps": "步长", "xpack.synthetics.monitorManagement.summary.heading": "摘要", - "xpack.synthetics.monitorManagement.syntheticsDisabled": "监测管理当前处于禁用状态。请联系管理员启用监测管理。", "xpack.synthetics.monitorManagement.syntheticsDisabledFailure": "无法禁用监测管理。请联系支持人员。", "xpack.synthetics.monitorManagement.syntheticsDisabledSuccess": "已成功禁用监测管理。", - "xpack.synthetics.monitorManagement.syntheticsDisableToolTip": "禁用监测管理会立即在所有测试地点停止执行监测并阻止创建新监测。", "xpack.synthetics.monitorManagement.syntheticsEnabledFailure": "无法启用监测管理。请联系支持人员。", - "xpack.synthetics.monitorManagement.syntheticsEnableLabel": "启用", "xpack.synthetics.monitorManagement.syntheticsEnableLabel.invalidKey": "启用监测管理", "xpack.synthetics.monitorManagement.syntheticsEnableLabel.management": "启用监测管理", "xpack.synthetics.monitorManagement.syntheticsEnableSuccess": "已成功启用监测管理。", - "xpack.synthetics.monitorManagement.syntheticsEnableToolTip": "启用监测管理以在全球各个地点创建轻量级、真正的浏览器监测。", - "xpack.synthetics.monitorManagement.techPreviewLabel": "技术预览", "xpack.synthetics.monitorManagement.testResult": "测试结果", "xpack.synthetics.monitorManagement.testResults": "测试结果", "xpack.synthetics.monitorManagement.testRuns.label": "测试运行", "xpack.synthetics.monitorManagement.updateMonitorLabel": "更新监测", - "xpack.synthetics.monitorManagement.urlFieldLabel": "URL", "xpack.synthetics.monitorManagement.urlRequiredLabel": "“URL”必填", "xpack.synthetics.monitorManagement.useEnv.label": "用作环境变量", - "xpack.synthetics.monitorManagement.validationError": "您的监测存在错误。请在保存前修复这些错误。", "xpack.synthetics.monitorManagement.value.required": "“值”必填", "xpack.synthetics.monitorManagement.viewLocationMonitors": "查看位置监测", "xpack.synthetics.monitorManagement.viewTestRunDetails": "查看测试结果详情", "xpack.synthetics.monitorManagement.websiteUrlHelpText": "例如,您公司的主页或 https://elastic.co", "xpack.synthetics.monitorManagement.websiteUrlLabel": "网站 URL", "xpack.synthetics.monitorManagement.websiteUrlPlaceholder": "输入网站 URL", - "xpack.synthetics.monitorManagement.yesLabel": "删除", "xpack.synthetics.monitorOverviewTab.title": "概览", "xpack.synthetics.monitors.management.betaLabel": "此功能为公测版,可能会进行更改。设计和代码相对于正式发行版功能还不够成熟,将按原样提供,且不提供任何保证。公测版功能不受正式发行版功能的支持服务水平协议约束。", "xpack.synthetics.monitors.pageHeader.createButton.label": "创建监测", @@ -35573,7 +35338,6 @@ "xpack.synthetics.page_header.analyzeData.label": "导航到“浏览数据”视图以可视化 Synthetics/用户数据", "xpack.synthetics.page_header.defineConnector.popover.defaultLink": "定义默认连接器", "xpack.synthetics.page_header.defineConnector.settingsLink": "设置", - "xpack.synthetics.page_header.manageLink.label": "导航到 Uptime 监测管理页面", "xpack.synthetics.page_header.manageMonitors": "监测管理", "xpack.synthetics.page_header.settingsLink": "设置", "xpack.synthetics.page_header.settingsLink.label": "导航到 Uptime 设置页面", @@ -35614,7 +35378,6 @@ "xpack.synthetics.routes.createNewMonitor": "前往主页", "xpack.synthetics.routes.goToSynthetics": "前往 Synthetics 主页", "xpack.synthetics.routes.legacyBaseTitle": "Uptime - Kibana", - "xpack.synthetics.routes.monitorManagement.betaLabel": "此功能为公测版,可能会进行更改。设计和代码相对于正式发行版功能还不够成熟,将按原样提供,且不提供任何保证。公测版功能不受正式发行版功能的支持服务水平协议约束。", "xpack.synthetics.runTest.failure": "无法手动运行测试", "xpack.synthetics.seconds.label": "秒", "xpack.synthetics.seconds.shortForm.label": "秒", diff --git a/x-pack/plugins/watcher/__jest__/client_integration/helpers/setup_environment.tsx b/x-pack/plugins/watcher/__jest__/client_integration/helpers/setup_environment.tsx index 621d0aae75dd3..39ce0108a4d41 100644 --- a/x-pack/plugins/watcher/__jest__/client_integration/helpers/setup_environment.tsx +++ b/x-pack/plugins/watcher/__jest__/client_integration/helpers/setup_environment.tsx @@ -11,13 +11,7 @@ import { HttpSetup } from '@kbn/core/public'; import { init as initHttpRequests } from './http_requests'; import { mockContextValue } from './app_context.mock'; import { AppContextProvider } from '../../../public/application/app_context'; -import { setHttpClient, setSavedObjectsClient } from '../../../public/application/lib/api'; - -const mockSavedObjectsClient = () => { - return { - find: (_params?: any) => {}, - }; -}; +import { setHttpClient } from '../../../public/application/lib/api'; export const WithAppDependencies = (Component: any, httpSetup: HttpSetup) => (props: Record) => { @@ -31,7 +25,5 @@ export const WithAppDependencies = }; export const setupEnvironment = () => { - setSavedObjectsClient(mockSavedObjectsClient() as any); - return initHttpRequests(); }; diff --git a/x-pack/plugins/watcher/public/application/index.tsx b/x-pack/plugins/watcher/public/application/index.tsx index 4b2b83c35980b..dc12832825b81 100644 --- a/x-pack/plugins/watcher/public/application/index.tsx +++ b/x-pack/plugins/watcher/public/application/index.tsx @@ -8,24 +8,22 @@ import React from 'react'; import { render, unmountComponentAtNode } from 'react-dom'; import { Observable } from 'rxjs'; -import { SavedObjectsClientContract, CoreTheme } from '@kbn/core/public'; +import { CoreTheme } from '@kbn/core/public'; import { KibanaThemeProvider } from './shared_imports'; import { App, AppDeps } from './app'; -import { setHttpClient, setSavedObjectsClient } from './lib/api'; +import { setHttpClient } from './lib/api'; interface BootDeps extends AppDeps { element: HTMLElement; - savedObjects: SavedObjectsClientContract; I18nContext: any; theme$: Observable; } export const renderApp = (bootDeps: BootDeps) => { - const { I18nContext, element, savedObjects, theme$, ...appDeps } = bootDeps; + const { I18nContext, element, theme$, ...appDeps } = bootDeps; setHttpClient(appDeps.http); - setSavedObjectsClient(savedObjects); render( diff --git a/x-pack/plugins/watcher/public/application/lib/api.ts b/x-pack/plugins/watcher/public/application/lib/api.ts index e514c2d4df5d4..4acfc980aef5f 100644 --- a/x-pack/plugins/watcher/public/application/lib/api.ts +++ b/x-pack/plugins/watcher/public/application/lib/api.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { HttpSetup, SavedObjectsClientContract } from '@kbn/core/public'; +import { HttpSetup } from '@kbn/core/public'; import { Settings } from '../models/settings'; import { Watch } from '../models/watch'; @@ -27,14 +27,6 @@ export const getHttpClient = () => { return httpClient; }; -let savedObjectsClient: SavedObjectsClientContract; - -export const setSavedObjectsClient = (aSavedObjectsClient: SavedObjectsClientContract) => { - savedObjectsClient = aSavedObjectsClient; -}; - -export const getSavedObjectsClient = () => savedObjectsClient; - const basePath = ROUTES.API_ROOT; const loadWatchesDeserializer = ({ watches = [] }: { watches: any[] }) => { diff --git a/x-pack/plugins/watcher/public/plugin.ts b/x-pack/plugins/watcher/public/plugin.ts index 4305400a9c951..ef8f36986db9f 100644 --- a/x-pack/plugins/watcher/public/plugin.ts +++ b/x-pack/plugins/watcher/public/plugin.ts @@ -48,7 +48,6 @@ export class WatcherUIPlugin implements Plugin { chrome: { docTitle }, i18n: i18nDep, docLinks, - savedObjects, application, executionContext, } = coreStart; @@ -69,7 +68,6 @@ export class WatcherUIPlugin implements Plugin { docLinks, setBreadcrumbs, theme: charts.theme, - savedObjects: savedObjects.client, I18nContext: i18nDep.Context, createTimeBuckets: () => new TimeBuckets(uiSettings, data), history, diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/group1/tests/alerting/create.ts b/x-pack/test/alerting_api_integration/security_and_spaces/group1/tests/alerting/create.ts index 5ff6a1cf55175..5f2b892a416a3 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/group1/tests/alerting/create.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/group1/tests/alerting/create.ts @@ -123,6 +123,7 @@ export default function createAlertTests({ getService }: FtrProviderContext) { notify_when: 'onThrottleInterval', updated_by: user.username, api_key_owner: user.username, + api_key_created_by_user: false, mute_all: false, muted_alert_ids: [], execution_status: response.body.execution_status, diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/group1/tests/alerting/get.ts b/x-pack/test/alerting_api_integration/security_and_spaces/group1/tests/alerting/get.ts index d0d93c0154745..d3117ed4ce209 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/group1/tests/alerting/get.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/group1/tests/alerting/get.ts @@ -79,6 +79,7 @@ const getTestUtils = ( notify_when: 'onThrottleInterval', updated_by: 'elastic', api_key_owner: 'elastic', + api_key_created_by_user: false, mute_all: false, muted_alert_ids: [], execution_status: response.body.execution_status, diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/alerting/update.ts b/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/alerting/update.ts index ba3963a00ddd1..b6972730cc780 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/alerting/update.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/alerting/update.ts @@ -119,6 +119,7 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { enabled: true, updated_by: user.username, api_key_owner: user.username, + api_key_created_by_user: false, mute_all: false, muted_alert_ids: [], actions: [ @@ -219,6 +220,7 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { enabled: true, updated_by: user.username, api_key_owner: user.username, + api_key_created_by_user: false, mute_all: false, muted_alert_ids: [], scheduled_task_id: createdAlert.scheduled_task_id, @@ -321,6 +323,7 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { enabled: true, updated_by: user.username, api_key_owner: user.username, + api_key_created_by_user: false, mute_all: false, muted_alert_ids: [], scheduled_task_id: createdAlert.scheduled_task_id, @@ -423,6 +426,7 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { enabled: true, updated_by: user.username, api_key_owner: user.username, + api_key_created_by_user: false, mute_all: false, muted_alert_ids: [], scheduled_task_id: createdAlert.scheduled_task_id, @@ -523,6 +527,7 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { enabled: true, updated_by: user.username, api_key_owner: user.username, + api_key_created_by_user: false, mute_all: false, muted_alert_ids: [], scheduled_task_id: createdAlert.scheduled_task_id, diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/group3/tests/alerting/bulk_delete.ts b/x-pack/test/alerting_api_integration/security_and_spaces/group3/tests/alerting/bulk_delete.ts index 3073fc077b5e8..4febd9b866f15 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/group3/tests/alerting/bulk_delete.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/group3/tests/alerting/bulk_delete.ts @@ -13,6 +13,7 @@ import { getUrlPrefix, getTestRuleData, ObjectRemover } from '../../../../common const getDefaultRules = (response: any) => ({ id: response.body.rules[0].id, apiKey: response.body.rules[0].apiKey, + apiKeyCreatedByUser: false, notifyWhen: 'onThrottleInterval', enabled: true, name: 'abc', @@ -46,6 +47,7 @@ const getThreeRules = (response: any) => { rules.push({ id: response.body.rules[i].id, apiKey: response.body.rules[i].apiKey, + apiKeyCreatedByUser: false, notifyWhen: 'onThrottleInterval', enabled: true, name: 'abc', diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/group3/tests/alerting/bulk_disable.ts b/x-pack/test/alerting_api_integration/security_and_spaces/group3/tests/alerting/bulk_disable.ts index 86f468534318e..13a89de8381ca 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/group3/tests/alerting/bulk_disable.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/group3/tests/alerting/bulk_disable.ts @@ -19,6 +19,7 @@ const getDefaultRules = (response: any) => ({ consumer: 'alertsFixture', throttle: '1m', alertTypeId: 'test.noop', + apiKeyCreatedByUser: false, apiKeyOwner: response.body.rules[0].apiKeyOwner, createdBy: 'elastic', updatedBy: response.body.rules[0].updatedBy, diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/group3/tests/alerting/bulk_enable.ts b/x-pack/test/alerting_api_integration/security_and_spaces/group3/tests/alerting/bulk_enable.ts index 7469773cc5705..3f1239558c5eb 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/group3/tests/alerting/bulk_enable.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/group3/tests/alerting/bulk_enable.ts @@ -83,6 +83,7 @@ export default ({ getService }: FtrProviderContext) => { consumer: 'alertsFixture', throttle: '1m', alertTypeId: 'test.noop', + apiKeyCreatedByUser: false, apiKeyOwner: response.body.rules[0].apiKeyOwner, createdBy: 'elastic', updatedBy: response.body.rules[0].updatedBy, diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/group3/tests/alerting/clone.ts b/x-pack/test/alerting_api_integration/security_and_spaces/group3/tests/alerting/clone.ts index d9fc773d5d12a..46aef808c0877 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/group3/tests/alerting/clone.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/group3/tests/alerting/clone.ts @@ -161,6 +161,7 @@ export default function createAlertTests({ getService }: FtrProviderContext) { throttle: '1m', notify_when: 'onThrottleInterval', updated_by: user.username, + api_key_created_by_user: false, api_key_owner: user.username, mute_all: false, muted_alert_ids: [], diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/create.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/create.ts index fe71eca901745..3f540927a9be7 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/create.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/create.ts @@ -90,6 +90,7 @@ export default function createAlertTests({ getService }: FtrProviderContext) { scheduled_task_id: response.body.scheduled_task_id, updated_by: null, api_key_owner: null, + api_key_created_by_user: null, throttle: '1m', notify_when: 'onThrottleInterval', mute_all: false, @@ -194,6 +195,7 @@ export default function createAlertTests({ getService }: FtrProviderContext) { scheduled_task_id: response.body.scheduled_task_id, updated_by: null, api_key_owner: null, + api_key_created_by_user: null, throttle: '1m', notify_when: 'onThrottleInterval', mute_all: false, @@ -498,6 +500,7 @@ export default function createAlertTests({ getService }: FtrProviderContext) { scheduledTaskId: response.body.scheduledTaskId, updatedBy: null, apiKeyOwner: null, + apiKeyCreatedByUser: null, throttle: '1m', notifyWhen: 'onThrottleInterval', muteAll: false, diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/find.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/find.ts index 2d85f6249db5e..295c8acebbf1a 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/find.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/find.ts @@ -109,6 +109,7 @@ const findTestUtils = ( params: {}, created_by: null, api_key_owner: null, + api_key_created_by_user: null, scheduled_task_id: match.scheduled_task_id, updated_by: null, throttle: null, @@ -361,6 +362,7 @@ export default function createFindTests({ getService }: FtrProviderContext) { params: {}, createdBy: null, apiKeyOwner: null, + apiKeyCreatedByUser: null, scheduledTaskId: match.scheduledTaskId, updatedBy: null, throttle: '1m', diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/get.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/get.ts index 43f93cccfe470..51f246e2c5609 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/get.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/get.ts @@ -49,6 +49,7 @@ const getTestUtils = ( scheduled_task_id: response.body.scheduled_task_id, updated_by: null, api_key_owner: null, + api_key_created_by_user: null, throttle: '1m', notify_when: 'onThrottleInterval', mute_all: false, @@ -149,6 +150,7 @@ export default function createGetTests({ getService }: FtrProviderContext) { scheduledTaskId: response.body.scheduledTaskId, updatedBy: null, apiKeyOwner: null, + apiKeyCreatedByUser: null, throttle: '1m', notifyWhen: 'onThrottleInterval', muteAll: false, diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group2/update.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group2/update.ts index d3ef53d5d4bd8..c7dc4e1484580 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group2/update.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group2/update.ts @@ -57,6 +57,7 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { enabled: true, updated_by: null, api_key_owner: null, + api_key_created_by_user: null, mute_all: false, muted_alert_ids: [], notify_when: 'onThrottleInterval', @@ -163,6 +164,7 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { enabled: true, updatedBy: null, apiKeyOwner: null, + apiKeyCreatedByUser: null, muteAll: false, mutedInstanceIds: [], notifyWhen: 'onThrottleInterval', diff --git a/x-pack/test/api_integration/apis/synthetics/enable_default_alerting.ts b/x-pack/test/api_integration/apis/synthetics/enable_default_alerting.ts index b1e587eedb5ea..219473ae19b7d 100644 --- a/x-pack/test/api_integration/apis/synthetics/enable_default_alerting.ts +++ b/x-pack/test/api_integration/apis/synthetics/enable_default_alerting.ts @@ -63,6 +63,7 @@ export default function ({ getService }: FtrProviderContext) { notifyWhen: 'onActionGroupChange', consumer: 'uptime', alertTypeId: 'xpack.synthetics.alerts.monitorStatus', + apiKeyCreatedByUser: false, tags: ['SYNTHETICS_DEFAULT_ALERT'], name: 'Synthetics internal alert', enabled: true, diff --git a/x-pack/test/api_integration/apis/uptime/rest/certs.ts b/x-pack/test/api_integration/apis/uptime/rest/certs.ts index ac9cc0e2be15f..f52ed57a3d39a 100644 --- a/x-pack/test/api_integration/apis/uptime/rest/certs.ts +++ b/x-pack/test/api_integration/apis/uptime/rest/certs.ts @@ -103,6 +103,7 @@ export default function ({ getService }: FtrProviderContext) { expect(Array.isArray(cert.monitors)).to.be(true); expect(cert.monitors[0]).to.eql({ name: undefined, + configId: undefined, id: monitorId, url: 'http://localhost:5678/pattern?r=200x5,500x1', }); diff --git a/x-pack/test/apm_api_integration/tests/agent_explorer/latest_agent_versions.spec.ts b/x-pack/test/apm_api_integration/tests/agent_explorer/latest_agent_versions.spec.ts new file mode 100644 index 0000000000000..00e3fedf4620c --- /dev/null +++ b/x-pack/test/apm_api_integration/tests/agent_explorer/latest_agent_versions.spec.ts @@ -0,0 +1,50 @@ +/* + * 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 { ElasticApmAgentLatestVersion } from '@kbn/apm-plugin/common/agent_explorer'; +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; + +export default function ApiTest({ getService }: FtrProviderContext) { + const registry = getService('registry'); + const apmApiClient = getService('apmApiClient'); + + const nodeAgentName = 'nodejs'; + const unlistedAgentName = 'unlistedAgent'; + + async function callApi() { + return await apmApiClient.readUser({ + endpoint: 'GET /internal/apm/get_latest_agent_versions', + }); + } + + registry.when( + 'Agent latest versions when configuration is defined', + { config: 'basic', archives: [] }, + () => { + it('returns a version when agent is listed in the file', async () => { + const { status, body } = await callApi(); + expect(status).to.be(200); + + const agents = body.data; + + const nodeAgent = agents[nodeAgentName] as ElasticApmAgentLatestVersion; + expect(nodeAgent?.latest_version).not.to.be(undefined); + }); + + it('returns undefined when agent is not listed in the file', async () => { + const { status, body } = await callApi(); + expect(status).to.be(200); + + const agents = body.data; + + // @ts-ignore + const unlistedAgent = agents[unlistedAgentName] as ElasticApmAgentLatestVersion; + expect(unlistedAgent?.latest_version).to.be(undefined); + }); + } + ); +} diff --git a/x-pack/test/apm_api_integration/tests/errors/group_id_samples.spec.ts b/x-pack/test/apm_api_integration/tests/errors/group_id_samples.spec.ts index 5d5569ef7c646..0ea2ed4f57493 100644 --- a/x-pack/test/apm_api_integration/tests/errors/group_id_samples.spec.ts +++ b/x-pack/test/apm_api_integration/tests/errors/group_id_samples.spec.ts @@ -10,6 +10,9 @@ import { APIReturnType, } from '@kbn/apm-plugin/public/services/rest/create_call_apm_api'; import { RecursivePartial } from '@kbn/apm-plugin/typings/common'; +import { timerange } from '@kbn/apm-synthtrace-client'; +import { service } from '@kbn/apm-synthtrace-client/src/lib/apm/service'; +import { orderBy } from 'lodash'; import { FtrProviderContext } from '../../common/ftr_provider_context'; import { config, generateData } from './generate_data'; @@ -138,5 +141,54 @@ export default function ApiTest({ getService }: FtrProviderContext) { }); }); }); + + describe('with sampled and unsampled transactions', () => { + let errorGroupSamplesResponse: ErrorGroupSamples; + + before(async () => { + const instance = service(serviceName, 'production', 'go').instance('a'); + + await synthtraceEsClient.index([ + timerange(start, end) + .interval('15m') + .rate(1) + .generator((timestamp) => { + return [ + instance + .transaction('GET /api/foo') + .duration(100) + .timestamp(timestamp) + .sample(false) + .errors( + instance.error({ message: `Error 1` }).timestamp(timestamp), + instance.error({ message: `Error 1` }).timestamp(timestamp + 1) + ), + instance + .transaction('GET /api/foo') + .duration(100) + .timestamp(timestamp) + .sample(true) + .errors(instance.error({ message: `Error 1` }).timestamp(timestamp)), + ]; + }), + ]); + + errorGroupSamplesResponse = ( + await callErrorGroupSamplesApi({ + path: { groupId: '0000000000000000000000000Error 1' }, + }) + ).body; + }); + + after(() => synthtraceEsClient.clean()); + + it('returns the errors in the correct order (sampled first, then unsampled)', () => { + const idsOfErrors = errorGroupSamplesResponse.errorSampleIds.map((id) => parseInt(id, 10)); + + // this checks whether the order of indexing is different from the order that is returned + // if it is not, scoring/sorting is broken + expect(idsOfErrors).to.not.eql(orderBy(idsOfErrors)); + }); + }); }); } diff --git a/x-pack/test/apm_api_integration/tests/transactions/trace_samples.spec.ts b/x-pack/test/apm_api_integration/tests/transactions/trace_samples.spec.ts index b42b6e0bfb757..5edc1f5a1abc4 100644 --- a/x-pack/test/apm_api_integration/tests/transactions/trace_samples.spec.ts +++ b/x-pack/test/apm_api_integration/tests/transactions/trace_samples.spec.ts @@ -48,6 +48,9 @@ export default function ApiTest({ getService }: FtrProviderContext) { { config: 'basic', archives: [archiveName] }, () => { it('returns the correct samples', async () => { + const traceId = '10d882b7118870015815a27c37892375'; + const transactionId = '0cf9db0b1e321239'; + const response = await apmApiClient.readUser({ endpoint: 'GET /internal/apm/services/{serviceName}/transactions/traces/samples', params: { @@ -59,6 +62,8 @@ export default function ApiTest({ getService }: FtrProviderContext) { environment: 'ENVIRONMENT_ALL', transactionName: 'APIRestController#stats', kuery: '', + traceId, + transactionId, }, }, }); @@ -66,101 +71,13 @@ export default function ApiTest({ getService }: FtrProviderContext) { const { traceSamples } = response.body; expect(response.status).to.be(200); - expectSnapshot(response.body.traceSamples.length).toMatchInline(`15`); - expectSnapshot(traceSamples).toMatchInline(` - Array [ - Object { - "score": 0, - "timestamp": "2021-08-03T07:19:11.880Z", - "traceId": "6d85d8f1bc4bbbfdb19cdba59d2fc164", - "transactionId": "d0a16f0f52f25d6b", - }, - Object { - "score": 0, - "timestamp": "2021-08-03T07:19:10.914Z", - "traceId": "10d882b7118870015815a27c37892375", - "transactionId": "0cf9db0b1e321239", - }, - Object { - "score": 0, - "timestamp": "2021-08-03T07:17:50.702Z", - "traceId": "45b3d1a86003938687a55e49bf3610b8", - "transactionId": "a707456bda99ee98", - }, - Object { - "score": 0, - "timestamp": "2021-08-03T07:17:47.588Z", - "traceId": "2ca82e99453c58584c4b8de9a8ba4ec3", - "transactionId": "8fa2ca73976ce1e7", - }, - Object { - "score": 0, - "timestamp": "2021-08-03T07:17:09.819Z", - "traceId": "a21ea39b41349a4614a86321d965c957", - "transactionId": "338bd7908cbf7f2d", - }, - Object { - "score": 0, - "timestamp": "2021-08-03T07:15:15.804Z", - "traceId": "ca7a2072e7974ae84b5096706c6b6255", - "transactionId": "92ab7f2ef11685dd", - }, - Object { - "score": 0, - "timestamp": "2021-08-03T07:15:00.171Z", - "traceId": "d250e2a1bad40f78653d8858db65326b", - "transactionId": "6fcd12599c1b57fa", - }, - Object { - "score": 0, - "timestamp": "2021-08-03T07:14:34.640Z", - "traceId": "66bd97c457f5675665397ac9201cc050", - "transactionId": "592b60cc9ddabb15", - }, - Object { - "score": 0, - "timestamp": "2021-08-03T07:11:55.249Z", - "traceId": "d9415d102c0634e1e8fa53ceef07be70", - "transactionId": "fab91c68c9b1c42b", - }, - Object { - "score": 0, - "timestamp": "2021-08-03T07:03:29.734Z", - "traceId": "0996b09e42ad4dbfaaa6a069326c6e66", - "transactionId": "5721364b179716d0", - }, - Object { - "score": 0, - "timestamp": "2021-08-03T07:03:05.825Z", - "traceId": "7483bd52150d1c93a858c60bfdd0c138", - "transactionId": "e20e701ff93bdb55", - }, - Object { - "score": 0, - "timestamp": "2021-08-03T06:58:34.565Z", - "traceId": "4943691f87b7eb97d442d1ef33ca65c7", - "transactionId": "f6f4677d731e57c5", - }, - Object { - "score": 0, - "timestamp": "2021-08-03T06:55:02.016Z", - "traceId": "9a84d15e5a0e32098d569948474e8e2f", - "transactionId": "b85db78a9824107b", - }, - Object { - "score": 0, - "timestamp": "2021-08-03T06:54:37.915Z", - "traceId": "e123f0466fa092f345d047399db65aa2", - "transactionId": "c0af16286229d811", - }, - Object { - "score": 0, - "timestamp": "2021-08-03T06:53:18.507Z", - "traceId": "5267685738bf75b68b16bf3426ba858c", - "transactionId": "5223f43bc3154c5a", - }, - ] - `); + + expect(traceSamples.some((sample) => sample.score! > 0)).to.be(true); + + expect(traceSamples[0].traceId).to.eql(traceId); + expect(traceSamples[0].transactionId).to.eql(transactionId); + + expect(response.body.traceSamples.length).to.eql(15); }); } ); diff --git a/x-pack/test/cases_api_integration/security_and_spaces/tests/common/comments/post_comment.ts b/x-pack/test/cases_api_integration/security_and_spaces/tests/common/comments/post_comment.ts index b04ee89675472..af3bf769a7489 100644 --- a/x-pack/test/cases_api_integration/security_and_spaces/tests/common/comments/post_comment.ts +++ b/x-pack/test/cases_api_integration/security_and_spaces/tests/common/comments/post_comment.ts @@ -39,7 +39,6 @@ import { updateCase, getCaseUserActions, removeServerGeneratedPropertiesFromUserAction, - bulkCreateAttachments, } from '../../../../common/lib/api'; import { createSignalsIndex, @@ -180,13 +179,17 @@ export default ({ getService }: FtrProviderContext): void => { expect(caseWithAttachments.totalComment).to.be(1); expect(fileAttachment.externalReferenceMetadata).to.eql(fileAttachmentMetadata); }); + }); + }); - it('should create a single file attachment with multiple file objects within it', async () => { + describe('unhappy path', () => { + describe('files', () => { + it('400s when attempting to create a single file attachment with multiple file objects within it', async () => { const postedCase = await createCase(supertest, getPostCaseRequest()); const files = [fileMetadata(), fileMetadata()]; - const caseWithAttachments = await createComment({ + await createComment({ supertest, caseId: postedCase.id, params: getFilesAttachmentReq({ @@ -194,38 +197,10 @@ export default ({ getService }: FtrProviderContext): void => { files, }, }), + expectedHttpCode: 400, }); - - const firstFileAttachment = - caseWithAttachments.comments![0] as CommentRequestExternalReferenceSOType; - - expect(caseWithAttachments.totalComment).to.be(1); - expect(firstFileAttachment.externalReferenceMetadata).to.eql({ files }); - }); - - it('should create a file attachments when there are 99 attachments already within the case', async () => { - const fileRequests = [...Array(99).keys()].map(() => getFilesAttachmentReq()); - - const postedCase = await createCase(supertest, postCaseReq); - await bulkCreateAttachments({ - supertest, - caseId: postedCase.id, - params: fileRequests, - }); - - const caseWith100Files = await createComment({ - supertest, - caseId: postedCase.id, - params: getFilesAttachmentReq(), - }); - - expect(caseWith100Files.comments?.length).to.be(100); }); - }); - }); - describe('unhappy path', () => { - describe('files', () => { it('should return a 400 when attaching a file with metadata that is missing the file field', async () => { const postedCase = await createCase(supertest, getPostCaseRequest()); @@ -254,24 +229,6 @@ export default ({ getService }: FtrProviderContext): void => { expectedHttpCode: 400, }); }); - - it('should return a 400 when attempting to create a file attachment when the case already has 100 files attached', async () => { - const fileRequests = [...Array(100).keys()].map(() => getFilesAttachmentReq()); - - const postedCase = await createCase(supertest, postCaseReq); - await bulkCreateAttachments({ - supertest, - caseId: postedCase.id, - params: fileRequests, - }); - - await createComment({ - supertest, - caseId: postedCase.id, - params: getFilesAttachmentReq(), - expectedHttpCode: 400, - }); - }); }); it('400s when attempting to create a comment with a different owner than the case', async () => { diff --git a/x-pack/test/cases_api_integration/security_and_spaces/tests/common/internal/bulk_create_attachments.ts b/x-pack/test/cases_api_integration/security_and_spaces/tests/common/internal/bulk_create_attachments.ts index 859f1922e375f..d1c56417acd50 100644 --- a/x-pack/test/cases_api_integration/security_and_spaces/tests/common/internal/bulk_create_attachments.ts +++ b/x-pack/test/cases_api_integration/security_and_spaces/tests/common/internal/bulk_create_attachments.ts @@ -38,6 +38,8 @@ import { updateCase, getCaseUserActions, removeServerGeneratedPropertiesFromUserAction, + createAndUploadFile, + deleteAllFiles, } from '../../../../common/lib/api'; import { createSignalsIndex, @@ -64,6 +66,7 @@ import { getAlertById, } from '../../../../common/lib/alerts'; import { User } from '../../../../common/lib/authentication/types'; +import { SECURITY_SOLUTION_FILE_KIND } from '../../../../common/lib/constants'; // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext): void => { @@ -173,30 +176,6 @@ export default ({ getService }: FtrProviderContext): void => { expect(secondFileAttachment.externalReferenceMetadata).to.eql(fileAttachmentMetadata); }); - it('should create a single file attachment with multiple file objects within it', async () => { - const postedCase = await createCase(supertest, getPostCaseRequest()); - - const files = [fileMetadata(), fileMetadata()]; - - const caseWithAttachments = await bulkCreateAttachments({ - supertest, - caseId: postedCase.id, - params: [ - getFilesAttachmentReq({ - externalReferenceMetadata: { - files, - }, - }), - ], - }); - - const firstFileAttachment = - caseWithAttachments.comments![0] as CommentRequestExternalReferenceSOType; - - expect(caseWithAttachments.totalComment).to.be(1); - expect(firstFileAttachment.externalReferenceMetadata).to.eql({ files }); - }); - it('should bulk create 100 file attachments', async () => { const fileRequests = [...Array(100).keys()].map(() => getFilesAttachmentReq()); @@ -235,23 +214,101 @@ export default ({ getService }: FtrProviderContext): void => { params: [postExternalReferenceSOReq, ...fileRequests], }); }); + + it('should bulk create 99 file attachments when the case has a file associated to it', async () => { + const postedCase = await createCase( + supertestWithoutAuth, + getPostCaseRequest({ owner: 'securitySolution' }), + 200, + { user: superUser, space: null } + ); + + await createAndUploadFile({ + supertest: supertestWithoutAuth, + createFileParams: { + name: 'testfile', + kind: SECURITY_SOLUTION_FILE_KIND, + mimeType: 'text/plain', + meta: { + caseIds: [postedCase.id], + owner: [postedCase.owner], + }, + }, + data: 'abc', + auth: { user: superUser, space: null }, + }); + + const fileRequests = [...Array(99).keys()].map(() => + getFilesAttachmentReq({ owner: 'securitySolution' }) + ); + + await bulkCreateAttachments({ + supertest: supertestWithoutAuth, + caseId: postedCase.id, + params: fileRequests, + auth: { user: superUser, space: null }, + expectedHttpCode: 200, + }); + }); }); }); describe('errors', () => { describe('files', () => { - it('400s when attaching a file with metadata that is missing the file field', async () => { + afterEach(async () => { + await deleteAllFiles({ + supertest, + }); + }); + + it('should return a 400 when attempting to create 100 file attachments when a file associated to the case exists', async () => { + const postedCase = await createCase( + supertestWithoutAuth, + getPostCaseRequest({ owner: 'securitySolution' }), + 200, + { user: superUser, space: null } + ); + + await createAndUploadFile({ + supertest: supertestWithoutAuth, + createFileParams: { + name: 'testfile', + kind: SECURITY_SOLUTION_FILE_KIND, + mimeType: 'text/plain', + meta: { + caseIds: [postedCase.id], + owner: [postedCase.owner], + }, + }, + data: 'abc', + auth: { user: superUser, space: null }, + }); + + const fileRequests = [...Array(100).keys()].map(() => + getFilesAttachmentReq({ owner: 'securitySolution' }) + ); + + await bulkCreateAttachments({ + supertest: supertestWithoutAuth, + caseId: postedCase.id, + params: fileRequests, + auth: { user: superUser, space: null }, + expectedHttpCode: 400, + }); + }); + + it('400s when attempting to create a single file attachment with multiple file objects within it', async () => { const postedCase = await createCase(supertest, getPostCaseRequest()); + const files = [fileMetadata(), fileMetadata()]; + await bulkCreateAttachments({ supertest, caseId: postedCase.id, params: [ - postCommentUserReq, getFilesAttachmentReq({ externalReferenceMetadata: { - // intentionally structure the data in a way that is invalid - food: fileAttachmentMetadata.files, + files, }, }), ], @@ -259,59 +316,43 @@ export default ({ getService }: FtrProviderContext): void => { }); }); - it('should return a 400 when attaching a file with an empty metadata', async () => { + it('400s when attaching a file with metadata that is missing the file field', async () => { const postedCase = await createCase(supertest, getPostCaseRequest()); await bulkCreateAttachments({ supertest, caseId: postedCase.id, params: [ + postCommentUserReq, getFilesAttachmentReq({ - externalReferenceMetadata: {}, + externalReferenceMetadata: { + // intentionally structure the data in a way that is invalid + food: fileAttachmentMetadata.files, + }, }), ], expectedHttpCode: 400, }); }); - it('400s when attempting to add more than 100 files to a case', async () => { - const fileRequests = [...Array(101).keys()].map(() => getFilesAttachmentReq()); - const postedCase = await createCase(supertest, postCaseReq); - await bulkCreateAttachments({ - supertest, - caseId: postedCase.id, - params: fileRequests, - expectedHttpCode: 400, - }); - }); - - it('400s when attempting to add a file to a case that already has 100 files', async () => { - const fileRequests = [...Array(100).keys()].map(() => getFilesAttachmentReq()); - - const postedCase = await createCase(supertest, postCaseReq); - await bulkCreateAttachments({ - supertest, - caseId: postedCase.id, - params: fileRequests, - }); + it('should return a 400 when attaching a file with an empty metadata', async () => { + const postedCase = await createCase(supertest, getPostCaseRequest()); await bulkCreateAttachments({ supertest, caseId: postedCase.id, - params: [getFilesAttachmentReq()], + params: [ + getFilesAttachmentReq({ + externalReferenceMetadata: {}, + }), + ], expectedHttpCode: 400, }); }); - it('400s when the case already has files and the sum of existing and new files exceed 100', async () => { - const fileRequests = [...Array(51).keys()].map(() => getFilesAttachmentReq()); + it('400s when attempting to add more than 100 files to a case', async () => { + const fileRequests = [...Array(101).keys()].map(() => getFilesAttachmentReq()); const postedCase = await createCase(supertest, postCaseReq); - await bulkCreateAttachments({ - supertest, - caseId: postedCase.id, - params: fileRequests, - }); - await bulkCreateAttachments({ supertest, caseId: postedCase.id, diff --git a/x-pack/test/fleet_api_integration/apis/index.js b/x-pack/test/fleet_api_integration/apis/index.js index 99be7518fd882..902818c92c242 100644 --- a/x-pack/test/fleet_api_integration/apis/index.js +++ b/x-pack/test/fleet_api_integration/apis/index.js @@ -14,10 +14,12 @@ export default function ({ loadTestFile, getService }) { }); // EPM - loadTestFile(require.resolve('./epm')); + // FAILING ES PROMOTION: https://github.com/elastic/kibana/issues/154739 + // loadTestFile(require.resolve('./epm')); // Fleet setup - loadTestFile(require.resolve('./fleet_setup')); + // FAILING ES PROMOTION: https://github.com/elastic/kibana/issues/154739 + // loadTestFile(require.resolve('./fleet_setup')); // Agents loadTestFile(require.resolve('./agents')); @@ -26,9 +28,12 @@ export default function ({ loadTestFile, getService }) { loadTestFile(require.resolve('./enrollment_api_keys/crud')); // Package policies - loadTestFile(require.resolve('./package_policy/create')); - loadTestFile(require.resolve('./package_policy/update')); - loadTestFile(require.resolve('./package_policy/get')); + // FAILING ES PROMOTION: https://github.com/elastic/kibana/issues/154739 + // + // loadTestFile(require.resolve('./package_policy/create')); + // loadTestFile(require.resolve('./package_policy/update')); + // loadTestFile(require.resolve('./package_policy/get')); + // loadTestFile(require.resolve('./package_policy/delete')); loadTestFile(require.resolve('./package_policy/upgrade')); loadTestFile(require.resolve('./package_policy/input_package_create_upgrade')); diff --git a/x-pack/test/functional/services/uptime/certificates.ts b/x-pack/test/functional/services/uptime/certificates.ts index 3a560acee52d8..830423109509b 100644 --- a/x-pack/test/functional/services/uptime/certificates.ts +++ b/x-pack/test/functional/services/uptime/certificates.ts @@ -64,9 +64,7 @@ export function UptimeCertProvider({ getService, getPageObjects }: FtrProviderCo async displaysEmptyMessage() { await testSubjects.existOrFail('uptimeCertsEmptyMessage'); const emptyText = await testSubjects.getVisibleText('uptimeCertsEmptyMessage'); - expect(emptyText).to.eql( - 'No Certificates found. Note: Certificates are only visible for Heartbeat 7.8+' - ); + expect(emptyText).to.eql('No Certificates found.'); }, }; } diff --git a/x-pack/test/security_solution_endpoint/apps/endpoint/index.ts b/x-pack/test/security_solution_endpoint/apps/endpoint/index.ts index 6aab2457c5278..7950e429f2165 100644 --- a/x-pack/test/security_solution_endpoint/apps/endpoint/index.ts +++ b/x-pack/test/security_solution_endpoint/apps/endpoint/index.ts @@ -15,7 +15,8 @@ import { export default function (providerContext: FtrProviderContext) { const { loadTestFile, getService } = providerContext; - describe('endpoint', function () { + // FAILING ES PROMOTION: https://github.com/elastic/kibana/issues/154741 + describe.skip('endpoint', function () { const ingestManager = getService('ingestManager'); const log = getService('log'); const endpointTestResources = getService('endpointTestResources'); diff --git a/x-pack/test/security_solution_endpoint_api_int/apis/index.ts b/x-pack/test/security_solution_endpoint_api_int/apis/index.ts index fbd33d38d1a94..de58478d5962d 100644 --- a/x-pack/test/security_solution_endpoint_api_int/apis/index.ts +++ b/x-pack/test/security_solution_endpoint_api_int/apis/index.ts @@ -43,11 +43,14 @@ export default function endpointAPIIntegrationTests(providerContext: FtrProvider }); loadTestFile(require.resolve('./resolver')); - loadTestFile(require.resolve('./metadata')); + // FAILING ES PROMOTION: https://github.com/elastic/kibana/issues/154740 + // loadTestFile(require.resolve('./metadata')); loadTestFile(require.resolve('./policy')); loadTestFile(require.resolve('./package')); - loadTestFile(require.resolve('./endpoint_authz')); - loadTestFile(require.resolve('./endpoint_response_actions/execute')); + // FAILING ES PROMOTION: https://github.com/elastic/kibana/issues/154740 + // loadTestFile(require.resolve('./endpoint_authz')); + // FAILING ES PROMOTION: https://github.com/elastic/kibana/issues/154740 + // loadTestFile(require.resolve('./endpoint_response_actions/execute')); loadTestFile(require.resolve('./file_upload_index')); loadTestFile(require.resolve('./endpoint_artifacts/trusted_apps')); loadTestFile(require.resolve('./endpoint_artifacts/event_filters')); diff --git a/yarn.lock b/yarn.lock index ba61f9481e6b6..90493ed9ea652 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4561,6 +4561,10 @@ version "0.0.0" uid "" +"@kbn/ml-random-sampler-utils@link:x-pack/packages/ml/random_sampler_utils": + version "0.0.0" + uid "" + "@kbn/ml-route-utils@link:x-pack/packages/ml/route_utils": version "0.0.0" uid ""