diff --git a/docs/api/saved-objects/import.asciidoc b/docs/api/saved-objects/import.asciidoc index 923482954aa2e..a214598af31af 100644 --- a/docs/api/saved-objects/import.asciidoc +++ b/docs/api/saved-objects/import.asciidoc @@ -11,11 +11,13 @@ Saved objects can only be imported into the same version, a newer minor on the s |======= | Exporting version | Importing version | Compatible? -| 6.7.0 | 6.8.1 | Yes -| 6.8.1 | 7.3.0 | Yes -| 7.3.0 | 7.11.1 | Yes -| 7.11.1 | 7.6.0 | No -| 6.8.1 | 8.0.0 | No +| 6.7.x | 6.8.x | Yes +| 6.x.x | 7.x.x | Yes +| 7.x.x | 8.x.x | Yes +| 7.1.x | 7.15.x | Yes +| 7.x.x | 6.x.x | No +| 7.15.x | 7.1.x | No +| 6.x.x | 8.x.x | No |======= [[saved-objects-api-import-request]] diff --git a/docs/concepts/data-views.asciidoc b/docs/concepts/data-views.asciidoc index 7eb95405db6bc..954581faa2460 100644 --- a/docs/concepts/data-views.asciidoc +++ b/docs/concepts/data-views.asciidoc @@ -1,7 +1,7 @@ [[data-views]] === Create a data view -{kib} requires a data view to access the {es} data that you want to explore. +{kib} requires a data view to access the {es} data that you want to explore. A data view selects the data to use and allows you to define properties of the fields. A data view can point to one or more indices, {ref}/data-streams.html[data stream], or {ref}/alias.html[index aliases]. @@ -37,7 +37,7 @@ If you loaded your own data, follow these steps to create a data view. . Click *Create data view*. [role="screenshot"] -image:management/index-patterns/images/create-index-pattern.png["Create data view"] +image:management/index-patterns/images/create-data-view.png["Create data view"] . Start typing in the *name* field, and {kib} looks for the names of indices, data streams, and aliases that match your input. @@ -87,11 +87,11 @@ For an example, refer to <: +: ``` To query {ls} indices across two {es} clusters diff --git a/docs/concepts/index.asciidoc b/docs/concepts/index.asciidoc index eac26beee1f9b..457251e62ae8d 100644 --- a/docs/concepts/index.asciidoc +++ b/docs/concepts/index.asciidoc @@ -40,8 +40,6 @@ image:concepts/images/global-search.png["Global search showing matches to apps a {kib} requires a data view to tell it which {es} data you want to access, and whether the data is time-based. A data view can point to one or more {es} data streams, indices, or index aliases by name. -For example, `logs-elasticsearch-prod-*` is an index pattern, -and it is time-based with a time field of `@timestamp`. The time field is not editable. Data views are typically created by an administrator when sending data to {es}. You can <> in *Stack Management*, or by using a script @@ -129,8 +127,7 @@ Previously, {kib} used the {ref}/search-aggregations-bucket-terms-aggregation.ht Structured filters are a more interactive way to create {es} queries, and are commonly used when building dashboards that are shared by multiple analysts. Each filter can be disabled, inverted, or pinned across all apps. -The structured filters are the only way to use the {es} Query DSL in JSON form, -or to target a specific index pattern for filtering. Each of the structured +Each of the structured filters is combined with AND logic on the rest of the query. [role="screenshot"] diff --git a/docs/concepts/save-query.asciidoc b/docs/concepts/save-query.asciidoc index 61113b5491c29..54137d1f9f2cf 100644 --- a/docs/concepts/save-query.asciidoc +++ b/docs/concepts/save-query.asciidoc @@ -17,7 +17,7 @@ image:concepts/images/saved-query.png["Example of the saved query management pop Saved queries are different than <>, which include the *Discover* configuration—selected columns in the document table, sort order, and -index pattern—in addition to the query. +{data-source}—in addition to the query. Saved searches are primarily used for adding search results to a dashboard. [role="xpack"] diff --git a/docs/concepts/set-time-filter.asciidoc b/docs/concepts/set-time-filter.asciidoc index 116bcd6f91f77..b379c0ac279e5 100644 --- a/docs/concepts/set-time-filter.asciidoc +++ b/docs/concepts/set-time-filter.asciidoc @@ -2,7 +2,7 @@ === Set the time range Display data within a specified time range when your index contains time-based events, and a time-field is configured for the -selected <>. +selected <>. The default time range is 15 minutes, but you can customize it in <>. diff --git a/docs/development/core/public/kibana-plugin-core-public.doclinksstart.links.md b/docs/development/core/public/kibana-plugin-core-public.doclinksstart.links.md index 5c2c1d5317543..7669b9b644916 100644 --- a/docs/development/core/public/kibana-plugin-core-public.doclinksstart.links.md +++ b/docs/development/core/public/kibana-plugin-core-public.doclinksstart.links.md @@ -24,6 +24,9 @@ readonly links: { readonly canvas: { readonly guide: string; }; + readonly cloud: { + readonly indexManagement: string; + }; readonly dashboard: { readonly guide: string; readonly drilldowns: string; @@ -55,10 +58,64 @@ readonly links: { readonly install: string; readonly start: string; }; + readonly appSearch: { + readonly apiRef: string; + readonly apiClients: string; + readonly apiKeys: string; + readonly authentication: string; + readonly crawlRules: string; + readonly curations: string; + readonly duplicateDocuments: string; + readonly entryPoints: string; + readonly guide: string; + readonly indexingDocuments: string; + readonly indexingDocumentsSchema: string; + readonly logSettings: string; + readonly metaEngines: string; + readonly nativeAuth: string; + readonly precisionTuning: string; + readonly relevanceTuning: string; + readonly resultSettings: string; + readonly searchUI: string; + readonly security: string; + readonly standardAuth: string; + readonly synonyms: string; + readonly webCrawler: string; + readonly webCrawlerEventLogs: string; + }; readonly enterpriseSearch: { - readonly base: string; - readonly appSearchBase: string; - readonly workplaceSearchBase: string; + readonly configuration: string; + readonly licenseManagement: string; + readonly mailService: string; + readonly usersAccess: string; + }; + readonly workplaceSearch: { + readonly box: string; + readonly confluenceCloud: string; + readonly confluenceServer: string; + readonly customSources: string; + readonly customSourcePermissions: string; + readonly documentPermissions: string; + readonly dropbox: string; + readonly externalIdentities: string; + readonly gitHub: string; + readonly gettingStarted: string; + readonly gmail: string; + readonly googleDrive: string; + readonly indexingSchedule: string; + readonly jiraCloud: string; + readonly jiraServer: string; + readonly nativeAuth: string; + readonly oneDrive: string; + readonly permissions: string; + readonly salesforce: string; + readonly security: string; + readonly serviceNow: string; + readonly sharePoint: string; + readonly slack: string; + readonly standardAuth: string; + readonly synch: string; + readonly zendesk: string; }; readonly heartbeat: { readonly base: string; diff --git a/docs/development/core/public/kibana-plugin-core-public.doclinksstart.md b/docs/development/core/public/kibana-plugin-core-public.doclinksstart.md index cbfe53d3eaea0..6aa528d4f04d1 100644 --- a/docs/development/core/public/kibana-plugin-core-public.doclinksstart.md +++ b/docs/development/core/public/kibana-plugin-core-public.doclinksstart.md @@ -17,5 +17,5 @@ export interface DocLinksStart | --- | --- | --- | | [DOC\_LINK\_VERSION](./kibana-plugin-core-public.doclinksstart.doc_link_version.md) | string | | | [ELASTIC\_WEBSITE\_URL](./kibana-plugin-core-public.doclinksstart.elastic_website_url.md) | string | | -| [links](./kibana-plugin-core-public.doclinksstart.links.md) | { readonly settings: string; readonly elasticStackGetStarted: string; readonly upgrade: { readonly upgradingElasticStack: string; }; readonly apm: { readonly kibanaSettings: string; readonly supportedServiceMaps: string; readonly customLinks: string; readonly droppedTransactionSpans: string; readonly upgrading: string; readonly metaData: string; }; readonly canvas: { readonly guide: string; }; readonly dashboard: { readonly guide: string; readonly drilldowns: string; readonly drilldownsTriggerPicker: string; readonly urlDrilldownTemplateSyntax: string; readonly urlDrilldownVariables: string; }; readonly discover: Record<string, string>; readonly filebeat: { readonly base: string; readonly installation: string; readonly configuration: string; readonly elasticsearchOutput: string; readonly elasticsearchModule: string; readonly startup: string; readonly exportedFields: string; readonly suricataModule: string; readonly zeekModule: string; }; readonly auditbeat: { readonly base: string; readonly auditdModule: string; readonly systemModule: string; }; readonly metricbeat: { readonly base: string; readonly configure: string; readonly httpEndpoint: string; readonly install: string; readonly start: string; }; readonly enterpriseSearch: { readonly base: string; readonly appSearchBase: string; readonly workplaceSearchBase: string; }; readonly heartbeat: { readonly base: string; }; readonly libbeat: { readonly getStarted: string; }; readonly logstash: { readonly base: string; }; readonly functionbeat: { readonly base: string; }; readonly winlogbeat: { readonly base: string; }; readonly aggs: { readonly composite: string; readonly composite\_missing\_bucket: string; readonly date\_histogram: string; readonly date\_range: string; readonly date\_format\_pattern: string; readonly filter: string; readonly filters: string; readonly geohash\_grid: string; readonly histogram: string; readonly ip\_range: string; readonly range: string; readonly significant\_terms: string; readonly terms: string; readonly terms\_doc\_count\_error: string; readonly avg: string; readonly avg\_bucket: string; readonly max\_bucket: string; readonly min\_bucket: string; readonly sum\_bucket: string; readonly cardinality: string; readonly count: string; readonly cumulative\_sum: string; readonly derivative: string; readonly geo\_bounds: string; readonly geo\_centroid: string; readonly max: string; readonly median: string; readonly min: string; readonly moving\_avg: string; readonly percentile\_ranks: string; readonly serial\_diff: string; readonly std\_dev: string; readonly sum: string; readonly top\_hits: string; }; readonly runtimeFields: { readonly overview: string; readonly mapping: string; }; readonly scriptedFields: { readonly scriptFields: string; readonly scriptAggs: string; readonly painless: string; readonly painlessApi: string; readonly painlessLangSpec: string; readonly painlessSyntax: string; readonly painlessWalkthrough: string; readonly luceneExpressions: string; }; readonly search: { readonly sessions: string; readonly sessionLimits: string; }; readonly indexPatterns: { readonly introduction: string; readonly fieldFormattersNumber: string; readonly fieldFormattersString: string; readonly runtimeFields: string; }; readonly addData: string; readonly kibana: string; readonly upgradeAssistant: { readonly overview: string; readonly batchReindex: string; readonly remoteReindex: string; }; readonly rollupJobs: string; readonly elasticsearch: Record<string, string>; readonly siem: { readonly privileges: string; readonly guide: string; readonly gettingStarted: string; readonly ml: string; readonly ruleChangeLog: string; readonly detectionsReq: string; readonly networkMap: string; readonly troubleshootGaps: string; }; readonly securitySolution: { readonly trustedApps: string; }; readonly query: { readonly eql: string; readonly kueryQuerySyntax: string; readonly luceneQuerySyntax: string; readonly percolate: string; readonly queryDsl: string; }; readonly date: { readonly dateMath: string; readonly dateMathIndexNames: string; }; readonly management: Record<string, string>; readonly ml: Record<string, string>; readonly transforms: Record<string, string>; readonly visualize: Record<string, string>; readonly apis: Readonly<{ bulkIndexAlias: string; byteSizeUnits: string; createAutoFollowPattern: string; createFollower: string; createIndex: string; createSnapshotLifecyclePolicy: string; createRoleMapping: string; createRoleMappingTemplates: string; createRollupJobsRequest: string; createApiKey: string; createPipeline: string; createTransformRequest: string; cronExpressions: string; executeWatchActionModes: string; indexExists: string; openIndex: string; putComponentTemplate: string; painlessExecute: string; painlessExecuteAPIContexts: string; putComponentTemplateMetadata: string; putSnapshotLifecyclePolicy: string; putIndexTemplateV1: string; putWatch: string; simulatePipeline: string; timeUnits: string; updateTransform: string; }>; readonly observability: Readonly<{ guide: string; infrastructureThreshold: string; logsThreshold: string; metricsThreshold: string; monitorStatus: string; monitorUptime: string; tlsCertificate: string; uptimeDurationAnomaly: string; }>; readonly alerting: Record<string, string>; readonly maps: Readonly<{ guide: string; importGeospatialPrivileges: string; gdalTutorial: string; }>; readonly monitoring: Record<string, string>; readonly security: Readonly<{ apiKeyServiceSettings: string; clusterPrivileges: string; elasticsearchSettings: string; elasticsearchEnableSecurity: string; elasticsearchEnableApiKeys: string; indicesPrivileges: string; kibanaTLS: string; kibanaPrivileges: string; mappingRoles: string; mappingRolesFieldRules: string; runAsPrivilege: string; }>; readonly spaces: Readonly<{ kibanaLegacyUrlAliases: string; kibanaDisableLegacyUrlAliasesApi: string; }>; readonly watcher: Record<string, string>; readonly ccs: Record<string, string>; readonly plugins: Record<string, string>; readonly snapshotRestore: Record<string, string>; readonly ingest: Record<string, string>; readonly fleet: Readonly<{ beatsAgentComparison: string; guide: string; fleetServer: string; fleetServerAddFleetServer: string; settings: string; settingsFleetServerHostSettings: string; settingsFleetServerProxySettings: string; troubleshooting: string; elasticAgent: string; datastreams: string; datastreamsNamingScheme: string; installElasticAgent: string; installElasticAgentStandalone: string; upgradeElasticAgent: string; upgradeElasticAgent712lower: string; learnMoreBlog: string; apiKeysLearnMore: string; onPremRegistry: string; }>; readonly ecs: { readonly guide: string; }; readonly clients: { readonly guide: string; readonly goOverview: string; readonly javaIndex: string; readonly jsIntro: string; readonly netGuide: string; readonly perlGuide: string; readonly phpGuide: string; readonly pythonGuide: string; readonly rubyOverview: string; readonly rustGuide: string; }; readonly endpoints: { readonly troubleshooting: string; }; } | | +| [links](./kibana-plugin-core-public.doclinksstart.links.md) | { readonly settings: string; readonly elasticStackGetStarted: string; readonly upgrade: { readonly upgradingElasticStack: string; }; readonly apm: { readonly kibanaSettings: string; readonly supportedServiceMaps: string; readonly customLinks: string; readonly droppedTransactionSpans: string; readonly upgrading: string; readonly metaData: string; }; readonly canvas: { readonly guide: string; }; readonly cloud: { readonly indexManagement: string; }; readonly dashboard: { readonly guide: string; readonly drilldowns: string; readonly drilldownsTriggerPicker: string; readonly urlDrilldownTemplateSyntax: string; readonly urlDrilldownVariables: string; }; readonly discover: Record<string, string>; readonly filebeat: { readonly base: string; readonly installation: string; readonly configuration: string; readonly elasticsearchOutput: string; readonly elasticsearchModule: string; readonly startup: string; readonly exportedFields: string; readonly suricataModule: string; readonly zeekModule: string; }; readonly auditbeat: { readonly base: string; readonly auditdModule: string; readonly systemModule: string; }; readonly metricbeat: { readonly base: string; readonly configure: string; readonly httpEndpoint: string; readonly install: string; readonly start: string; }; readonly appSearch: { readonly apiRef: string; readonly apiClients: string; readonly apiKeys: string; readonly authentication: string; readonly crawlRules: string; readonly curations: string; readonly duplicateDocuments: string; readonly entryPoints: string; readonly guide: string; readonly indexingDocuments: string; readonly indexingDocumentsSchema: string; readonly logSettings: string; readonly metaEngines: string; readonly nativeAuth: string; readonly precisionTuning: string; readonly relevanceTuning: string; readonly resultSettings: string; readonly searchUI: string; readonly security: string; readonly standardAuth: string; readonly synonyms: string; readonly webCrawler: string; readonly webCrawlerEventLogs: string; }; readonly enterpriseSearch: { readonly configuration: string; readonly licenseManagement: string; readonly mailService: string; readonly usersAccess: string; }; readonly workplaceSearch: { readonly box: string; readonly confluenceCloud: string; readonly confluenceServer: string; readonly customSources: string; readonly customSourcePermissions: string; readonly documentPermissions: string; readonly dropbox: string; readonly externalIdentities: string; readonly gitHub: string; readonly gettingStarted: string; readonly gmail: string; readonly googleDrive: string; readonly indexingSchedule: string; readonly jiraCloud: string; readonly jiraServer: string; readonly nativeAuth: string; readonly oneDrive: string; readonly permissions: string; readonly salesforce: string; readonly security: string; readonly serviceNow: string; readonly sharePoint: string; readonly slack: string; readonly standardAuth: string; readonly synch: string; readonly zendesk: string; }; readonly heartbeat: { readonly base: string; }; readonly libbeat: { readonly getStarted: string; }; readonly logstash: { readonly base: string; }; readonly functionbeat: { readonly base: string; }; readonly winlogbeat: { readonly base: string; }; readonly aggs: { readonly composite: string; readonly composite\_missing\_bucket: string; readonly date\_histogram: string; readonly date\_range: string; readonly date\_format\_pattern: string; readonly filter: string; readonly filters: string; readonly geohash\_grid: string; readonly histogram: string; readonly ip\_range: string; readonly range: string; readonly significant\_terms: string; readonly terms: string; readonly terms\_doc\_count\_error: string; readonly avg: string; readonly avg\_bucket: string; readonly max\_bucket: string; readonly min\_bucket: string; readonly sum\_bucket: string; readonly cardinality: string; readonly count: string; readonly cumulative\_sum: string; readonly derivative: string; readonly geo\_bounds: string; readonly geo\_centroid: string; readonly max: string; readonly median: string; readonly min: string; readonly moving\_avg: string; readonly percentile\_ranks: string; readonly serial\_diff: string; readonly std\_dev: string; readonly sum: string; readonly top\_hits: string; }; readonly runtimeFields: { readonly overview: string; readonly mapping: string; }; readonly scriptedFields: { readonly scriptFields: string; readonly scriptAggs: string; readonly painless: string; readonly painlessApi: string; readonly painlessLangSpec: string; readonly painlessSyntax: string; readonly painlessWalkthrough: string; readonly luceneExpressions: string; }; readonly search: { readonly sessions: string; readonly sessionLimits: string; }; readonly indexPatterns: { readonly introduction: string; readonly fieldFormattersNumber: string; readonly fieldFormattersString: string; readonly runtimeFields: string; }; readonly addData: string; readonly kibana: string; readonly upgradeAssistant: { readonly overview: string; readonly batchReindex: string; readonly remoteReindex: string; }; readonly rollupJobs: string; readonly elasticsearch: Record<string, string>; readonly siem: { readonly privileges: string; readonly guide: string; readonly gettingStarted: string; readonly ml: string; readonly ruleChangeLog: string; readonly detectionsReq: string; readonly networkMap: string; readonly troubleshootGaps: string; }; readonly securitySolution: { readonly trustedApps: string; }; readonly query: { readonly eql: string; readonly kueryQuerySyntax: string; readonly luceneQuerySyntax: string; readonly percolate: string; readonly queryDsl: string; }; readonly date: { readonly dateMath: string; readonly dateMathIndexNames: string; }; readonly management: Record<string, string>; readonly ml: Record<string, string>; readonly transforms: Record<string, string>; readonly visualize: Record<string, string>; readonly apis: Readonly<{ bulkIndexAlias: string; byteSizeUnits: string; createAutoFollowPattern: string; createFollower: string; createIndex: string; createSnapshotLifecyclePolicy: string; createRoleMapping: string; createRoleMappingTemplates: string; createRollupJobsRequest: string; createApiKey: string; createPipeline: string; createTransformRequest: string; cronExpressions: string; executeWatchActionModes: string; indexExists: string; openIndex: string; putComponentTemplate: string; painlessExecute: string; painlessExecuteAPIContexts: string; putComponentTemplateMetadata: string; putSnapshotLifecyclePolicy: string; putIndexTemplateV1: string; putWatch: string; simulatePipeline: string; timeUnits: string; updateTransform: string; }>; readonly observability: Readonly<{ guide: string; infrastructureThreshold: string; logsThreshold: string; metricsThreshold: string; monitorStatus: string; monitorUptime: string; tlsCertificate: string; uptimeDurationAnomaly: string; }>; readonly alerting: Record<string, string>; readonly maps: Readonly<{ guide: string; importGeospatialPrivileges: string; gdalTutorial: string; }>; readonly monitoring: Record<string, string>; readonly security: Readonly<{ apiKeyServiceSettings: string; clusterPrivileges: string; elasticsearchSettings: string; elasticsearchEnableSecurity: string; elasticsearchEnableApiKeys: string; indicesPrivileges: string; kibanaTLS: string; kibanaPrivileges: string; mappingRoles: string; mappingRolesFieldRules: string; runAsPrivilege: string; }>; readonly spaces: Readonly<{ kibanaLegacyUrlAliases: string; kibanaDisableLegacyUrlAliasesApi: string; }>; readonly watcher: Record<string, string>; readonly ccs: Record<string, string>; readonly plugins: Record<string, string>; readonly snapshotRestore: Record<string, string>; readonly ingest: Record<string, string>; readonly fleet: Readonly<{ beatsAgentComparison: string; guide: string; fleetServer: string; fleetServerAddFleetServer: string; settings: string; settingsFleetServerHostSettings: string; settingsFleetServerProxySettings: string; troubleshooting: string; elasticAgent: string; datastreams: string; datastreamsNamingScheme: string; installElasticAgent: string; installElasticAgentStandalone: string; upgradeElasticAgent: string; upgradeElasticAgent712lower: string; learnMoreBlog: string; apiKeysLearnMore: string; onPremRegistry: string; }>; readonly ecs: { readonly guide: string; }; readonly clients: { readonly guide: string; readonly goOverview: string; readonly javaIndex: string; readonly jsIntro: string; readonly netGuide: string; readonly perlGuide: string; readonly phpGuide: string; readonly pythonGuide: string; readonly rubyOverview: string; readonly rustGuide: string; }; readonly endpoints: { readonly troubleshooting: string; }; } | | diff --git a/docs/getting-started/quick-start-guide.asciidoc b/docs/getting-started/quick-start-guide.asciidoc index 03e40c7cc6cef..2667729f4b854 100644 --- a/docs/getting-started/quick-start-guide.asciidoc +++ b/docs/getting-started/quick-start-guide.asciidoc @@ -11,7 +11,7 @@ When you've finished, you'll know how to: [float] === Required privileges -You must have `read`, `write`, and `manage` privileges on the `kibana_sample_data_*` indices. +You must have `read`, `write`, and `manage` privileges on the `kibana_sample_data_*` indices. Learn how to <>, or refer to {ref}/security-privileges.html[Security privileges] for more information. [float] @@ -37,7 +37,7 @@ image::images/addData_sampleDataCards_7.15.0.png[Add data UI for the sample data [[explore-the-data]] == Explore the data -*Discover* displays the data in an interactive histogram that shows the distribution of data, or documents, over time, and a table that lists the fields for each document that matches the index pattern. To view a subset of the documents, you can apply filters to the data, and customize the table to display only the fields you want to explore. +*Discover* displays the data in an interactive histogram that shows the distribution of data, or documents, over time, and a table that lists the fields for each document that matches the {data-source}. To view a subset of the documents, you can apply filters to the data, and customize the table to display only the fields you want to explore. . Open the main menu, then click *Discover*. @@ -65,7 +65,7 @@ image::images/tutorial-discover-3.png[Discover table that displays only the prod A dashboard is a collection of panels that you can use to view and analyze the data. Panels contain visualizations, interactive controls, text, and more. -. Open the main menu, then click *Dashboard*. +. Open the main menu, then click *Dashboard*. . Click *[eCommerce] Revenue Dashboard*. + @@ -104,7 +104,7 @@ The treemap appears as the last visualization panel on the dashboard. [[interact-with-the-data]] === Interact with the data -You can interact with the dashboard data using controls that allow you to apply dashboard-level filters. Interact with the *[eCommerce] Controls* panel to view the women's clothing data from the Gnomehouse manufacturer. +You can interact with the dashboard data using controls that allow you to apply dashboard-level filters. Interact with the *[eCommerce] Controls* panel to view the women's clothing data from the Gnomehouse manufacturer. . From the *Manufacturer* dropdown, select *Gnomehouse*. diff --git a/docs/management/index-patterns/images/create-data-view.png b/docs/management/index-patterns/images/create-data-view.png new file mode 100644 index 0000000000000..229ed0f490b41 Binary files /dev/null and b/docs/management/index-patterns/images/create-data-view.png differ diff --git a/docs/management/index-patterns/images/create-index-pattern.png b/docs/management/index-patterns/images/create-index-pattern.png deleted file mode 100644 index c1b673f1ab886..0000000000000 Binary files a/docs/management/index-patterns/images/create-index-pattern.png and /dev/null differ diff --git a/docs/migration/migrate_8_0.asciidoc b/docs/migration/migrate_8_0.asciidoc index 59dd36a4fa5ef..8936e41762c69 100644 --- a/docs/migration/migrate_8_0.asciidoc +++ b/docs/migration/migrate_8_0.asciidoc @@ -65,7 +65,7 @@ If you are currently using one of these settings in your Kibana config, please r ==== Default logging timezone is now the system's timezone *Details:* In prior releases the timezone used in logs defaulted to UTC. We now use the host machine's timezone by default. -*Impact:* To restore the previous behavior, in kibana.yml use the pattern layout, with a date modifier: +*Impact:* To restore the previous behavior, in kibana.yml use the pattern layout, with a {kibana-ref}/logging-configuration.html#date-format[date modifier]: [source,yaml] ------------------- logging: @@ -100,7 +100,7 @@ See https://github.com/elastic/kibana/pull/87939 for more details. [float] ==== Logging destination is specified by the appender -*Details:* Previously log destination would be `stdout` and could be changed to `file` using `logging.dest`. With the new logging configuration, you can specify the destination using appenders. +*Details:* Previously log destination would be `stdout` and could be changed to `file` using `logging.dest`. With the new logging configuration, you can specify the destination using {kibana-ref}/logging-configuration.html#logging-appenders[appenders]. *Impact:* To restore the previous behavior and log records to *stdout*, in `kibana.yml` use an appender with `type: console`. [source,yaml] @@ -131,7 +131,7 @@ logging: [float] ==== Set log verbosity with root -*Details:* Previously logging output would be specified by `logging.silent` (none), `logging.quiet` (error messages only) and `logging.verbose` (all). With the new logging configuration, set the minimum required log level. +*Details:* Previously logging output would be specified by `logging.silent` (none), `logging.quiet` (error messages only) and `logging.verbose` (all). With the new logging configuration, set the minimum required {kibana-ref}/logging-configuration.html#log-level[log level]. *Impact:* To restore the previous behavior, in `kibana.yml` specify `logging.root.level`: [source,yaml] @@ -188,7 +188,7 @@ logging: ==== Configure log rotation with the rolling-file appender *Details:* Previously log rotation would be enabled when `logging.rotate.enabled` was true. -*Impact:* To restore the previous behavior, in `kibana.yml` use the `rolling-file` appender. +*Impact:* To restore the previous behavior, in `kibana.yml` use the {kibana-ref}/logging-configuration.html#rolling-file-appender[`rolling-file`] appender. [source,yaml] ------------------- diff --git a/docs/settings/logging-settings.asciidoc b/docs/settings/logging-settings.asciidoc index cb8237c5aa384..a9053b90ce722 100644 --- a/docs/settings/logging-settings.asciidoc +++ b/docs/settings/logging-settings.asciidoc @@ -30,7 +30,7 @@ The following table serves as a quick reference for different logging configurat | Allows you to specify a fileName to write log records to disk. To write <>, add the file appender to `root.appenders`. If configured, you also need to specify <>. | `logging.appenders[].rolling-file:` -| Similar to Log4j's `RollingFileAppender`, this appender will log to a file and rotate if following a rolling strategy when the configured policy triggers. There are currently two policies supported: `size-limit` and `time-interval`. +| Similar to https://logging.apache.org/log4j/2.x/[Log4j's] `RollingFileAppender`, this appender will log to a file and rotate if following a rolling strategy when the configured policy triggers. There are currently two policies supported: <> and <>. | `logging.appenders[]..type` | The appender type determines where the log messages are sent. Options are `console`, `file`, `rewrite`, `rolling-file`. Required. diff --git a/docs/user/dashboard/lens.asciidoc b/docs/user/dashboard/lens.asciidoc index 23a6d1fbcfd3d..1b0bbf866b852 100644 --- a/docs/user/dashboard/lens.asciidoc +++ b/docs/user/dashboard/lens.asciidoc @@ -249,7 +249,7 @@ In the legend, click the field, then choose one of the following options: [[configure-the-visualization-components]] ==== Configure the visualization components -Each visualiztion type comes with a set of components that you access from the editor toolbar. +Each visualization type comes with a set of components that you access from the editor toolbar. The following component menus are available: diff --git a/package.json b/package.json index 983dddda5d4fb..b0c7e4659a559 100644 --- a/package.json +++ b/package.json @@ -397,12 +397,12 @@ "usng.js": "^0.4.5", "utility-types": "^3.10.0", "uuid": "3.3.2", - "vega": "^5.19.1", + "vega": "^5.21.0", "vega-interpreter": "^1.0.4", - "vega-lite": "^5.0.0", - "vega-schema-url-parser": "^2.1.0", + "vega-lite": "^5.2.0", + "vega-schema-url-parser": "^2.2.0", "vega-spec-injector": "^0.0.2", - "vega-tooltip": "^0.25.1", + "vega-tooltip": "^0.27.0", "venn.js": "0.2.20", "vinyl": "^2.2.0", "vt-pbf": "^3.1.1", @@ -556,6 +556,7 @@ "@types/json-stable-stringify": "^1.0.32", "@types/json5": "^0.0.30", "@types/kbn__ace": "link:bazel-bin/packages/kbn-ace/npm_module_types", + "@types/kbn__alerts": "link:bazel-bin/packages/kbn-alerts/npm_module_types", "@types/kbn__i18n": "link:bazel-bin/packages/kbn-i18n/npm_module_types", "@types/kbn__i18n-react": "link:bazel-bin/packages/kbn-i18n-react/npm_module_types", "@types/license-checker": "15.0.0", diff --git a/packages/BUILD.bazel b/packages/BUILD.bazel index 07513ac94c85d..c9a0f6a759b2a 100644 --- a/packages/BUILD.bazel +++ b/packages/BUILD.bazel @@ -78,6 +78,7 @@ filegroup( "//packages/elastic-apm-synthtrace:build_types", "//packages/elastic-datemath:build_types", "//packages/kbn-ace:build_types", + "//packages/kbn-alerts:build_types", "//packages/kbn-i18n:build_types", "//packages/kbn-i18n-react:build_types", ], diff --git a/packages/elastic-apm-synthtrace/src/index.ts b/packages/elastic-apm-synthtrace/src/index.ts index 931215c75fde4..381222ee10efc 100644 --- a/packages/elastic-apm-synthtrace/src/index.ts +++ b/packages/elastic-apm-synthtrace/src/index.ts @@ -6,17 +6,11 @@ * Side Public License, v 1. */ -export type { Exception } from './lib/entity'; -export { service } from './lib/service'; -export { browser } from './lib/browser'; export { timerange } from './lib/timerange'; -export { getTransactionMetrics } from './lib/utils/get_transaction_metrics'; -export { getSpanDestinationMetrics } from './lib/utils/get_span_destination_metrics'; -export { getObserverDefaults } from './lib/defaults/get_observer_defaults'; -export { getChromeUserAgentDefaults } from './lib/defaults/get_chrome_user_agent_defaults'; -export { toElasticsearchOutput } from './lib/output/to_elasticsearch_output'; -export { getBreakdownMetrics } from './lib/utils/get_breakdown_metrics'; +export { apm } from './lib/apm'; +export { stackMonitoring } from './lib/stack_monitoring'; export { cleanWriteTargets } from './lib/utils/clean_write_targets'; -export { getWriteTargets } from './lib/utils/get_write_targets'; -export { SynthtraceEsClient } from './lib/client/synthtrace_es_client'; export { createLogger, LogLevel } from './lib/utils/create_logger'; + +export type { Fields } from './lib/entity'; +export type { ApmException, ApmSynthtraceEsClient } from './lib/apm'; diff --git a/packages/elastic-apm-synthtrace/src/lib/apm_error.ts b/packages/elastic-apm-synthtrace/src/lib/apm/apm_error.ts similarity index 74% rename from packages/elastic-apm-synthtrace/src/lib/apm_error.ts rename to packages/elastic-apm-synthtrace/src/lib/apm/apm_error.ts index 5a48093a26db2..334c0f296851d 100644 --- a/packages/elastic-apm-synthtrace/src/lib/apm_error.ts +++ b/packages/elastic-apm-synthtrace/src/lib/apm/apm_error.ts @@ -6,12 +6,12 @@ * Side Public License, v 1. */ -import { Fields } from './entity'; -import { Serializable } from './serializable'; -import { generateLongId, generateShortId } from './utils/generate_id'; +import { ApmFields } from './apm_fields'; +import { Serializable } from '../serializable'; +import { generateLongId, generateShortId } from '../utils/generate_id'; -export class ApmError extends Serializable { - constructor(fields: Fields) { +export class ApmError extends Serializable { + constructor(fields: ApmFields) { super({ ...fields, 'processor.event': 'error', diff --git a/packages/elastic-apm-synthtrace/src/lib/apm/apm_fields.ts b/packages/elastic-apm-synthtrace/src/lib/apm/apm_fields.ts new file mode 100644 index 0000000000000..a7a826d144d0e --- /dev/null +++ b/packages/elastic-apm-synthtrace/src/lib/apm/apm_fields.ts @@ -0,0 +1,78 @@ +/* + * Copyright 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 { Fields } from '../entity'; + +export type ApmApplicationMetricFields = Partial<{ + 'system.process.memory.size': number; + 'system.memory.actual.free': number; + 'system.memory.total': number; + 'system.cpu.total.norm.pct': number; + 'system.process.memory.rss.bytes': number; + 'system.process.cpu.total.norm.pct': number; +}>; + +export type ApmUserAgentFields = Partial<{ + 'user_agent.original': string; + 'user_agent.os.name': string; + 'user_agent.name': string; + 'user_agent.device.name': string; + 'user_agent.version': number; +}>; + +export interface ApmException { + message: string; +} + +export type ApmFields = Fields & + Partial<{ + 'agent.name': string; + 'agent.version': string; + 'container.id': string; + 'ecs.version': string; + 'event.outcome': string; + 'event.ingested': number; + 'error.id': string; + 'error.exception': ApmException[]; + 'error.grouping_name': string; + 'error.grouping_key': string; + 'host.name': string; + 'kubernetes.pod.uid': string; + 'metricset.name': string; + 'observer.version': string; + 'observer.version_major': number; + 'parent.id': string; + 'processor.event': string; + 'processor.name': string; + 'trace.id': string; + 'transaction.name': string; + 'transaction.type': string; + 'transaction.id': string; + 'transaction.duration.us': number; + 'transaction.duration.histogram': { + values: number[]; + counts: number[]; + }; + 'transaction.sampled': true; + 'service.name': string; + 'service.environment': string; + 'service.node.name': string; + 'span.id': string; + 'span.name': string; + 'span.type': string; + 'span.subtype': string; + 'span.duration.us': number; + 'span.destination.service.name': string; + 'span.destination.service.resource': string; + 'span.destination.service.type': string; + 'span.destination.service.response_time.sum.us': number; + 'span.destination.service.response_time.count': number; + 'span.self_time.count': number; + 'span.self_time.sum.us': number; + }> & + ApmApplicationMetricFields; diff --git a/packages/elastic-apm-synthtrace/src/lib/base_span.ts b/packages/elastic-apm-synthtrace/src/lib/apm/base_span.ts similarity index 86% rename from packages/elastic-apm-synthtrace/src/lib/base_span.ts rename to packages/elastic-apm-synthtrace/src/lib/apm/base_span.ts index f762bf730a717..ba2af8ce9ee53 100644 --- a/packages/elastic-apm-synthtrace/src/lib/base_span.ts +++ b/packages/elastic-apm-synthtrace/src/lib/apm/base_span.ts @@ -6,16 +6,16 @@ * Side Public License, v 1. */ -import { Fields } from './entity'; -import { Serializable } from './serializable'; +import { Serializable } from '../serializable'; import { Span } from './span'; import { Transaction } from './transaction'; -import { generateLongId } from './utils/generate_id'; +import { generateLongId } from '../utils/generate_id'; +import { ApmFields } from './apm_fields'; -export class BaseSpan extends Serializable { +export class BaseSpan extends Serializable { private readonly _children: BaseSpan[] = []; - constructor(fields: Fields) { + constructor(fields: ApmFields) { super({ ...fields, 'event.outcome': 'unknown', @@ -60,7 +60,7 @@ export class BaseSpan extends Serializable { return this; } - serialize(): Fields[] { + serialize(): ApmFields[] { return [this.fields, ...this._children.flatMap((child) => child.serialize())]; } diff --git a/packages/elastic-apm-synthtrace/src/lib/browser.ts b/packages/elastic-apm-synthtrace/src/lib/apm/browser.ts similarity index 86% rename from packages/elastic-apm-synthtrace/src/lib/browser.ts rename to packages/elastic-apm-synthtrace/src/lib/apm/browser.ts index 0fd8b44b69851..ebba6a0e89a69 100644 --- a/packages/elastic-apm-synthtrace/src/lib/browser.ts +++ b/packages/elastic-apm-synthtrace/src/lib/apm/browser.ts @@ -6,11 +6,12 @@ * Side Public License, v 1. */ -import { Entity, UserAgentFields } from './entity'; +import { ApmFields, ApmUserAgentFields } from './apm_fields'; +import { Entity } from '../entity'; import { RumSpan } from './rum_span'; import { RumTransaction } from './rum_transaction'; -export class Browser extends Entity { +export class Browser extends Entity { transaction(transactionName: string, transactionType: string = 'page-load') { return new RumTransaction({ ...this.fields, @@ -29,7 +30,7 @@ export class Browser extends Entity { } } -export function browser(serviceName: string, production: string, userAgent: UserAgentFields) { +export function browser(serviceName: string, production: string, userAgent: ApmUserAgentFields) { return new Browser({ 'agent.name': 'rum-js', 'service.name': serviceName, diff --git a/packages/elastic-apm-synthtrace/src/lib/client/synthtrace_es_client.ts b/packages/elastic-apm-synthtrace/src/lib/apm/client/apm_synthtrace_es_client.ts similarity index 58% rename from packages/elastic-apm-synthtrace/src/lib/client/synthtrace_es_client.ts rename to packages/elastic-apm-synthtrace/src/lib/apm/client/apm_synthtrace_es_client.ts index 546214f83c364..4a25d7009ad01 100644 --- a/packages/elastic-apm-synthtrace/src/lib/client/synthtrace_es_client.ts +++ b/packages/elastic-apm-synthtrace/src/lib/apm/client/apm_synthtrace_es_client.ts @@ -7,45 +7,52 @@ */ import { Client } from '@elastic/elasticsearch'; -import { uploadEvents } from '../../scripts/utils/upload_events'; -import { Fields } from '../entity'; -import { cleanWriteTargets } from '../utils/clean_write_targets'; +import { uploadEvents } from '../../../scripts/utils/upload_events'; +import { Fields } from '../../entity'; +import { cleanWriteTargets } from '../../utils/clean_write_targets'; import { getBreakdownMetrics } from '../utils/get_breakdown_metrics'; import { getSpanDestinationMetrics } from '../utils/get_span_destination_metrics'; import { getTransactionMetrics } from '../utils/get_transaction_metrics'; -import { getWriteTargets } from '../utils/get_write_targets'; -import { Logger } from '../utils/logger'; +import { getApmWriteTargets } from '../utils/get_apm_write_targets'; +import { Logger } from '../../utils/create_logger'; +import { apmEventsToElasticsearchOutput } from '../utils/apm_events_to_elasticsearch_output'; -export class SynthtraceEsClient { +export class ApmSynthtraceEsClient { constructor(private readonly client: Client, private readonly logger: Logger) {} private getWriteTargets() { - return getWriteTargets({ client: this.client }); + return getApmWriteTargets({ client: this.client }); } clean() { return this.getWriteTargets().then((writeTargets) => - cleanWriteTargets({ client: this.client, writeTargets, logger: this.logger }) + cleanWriteTargets({ + client: this.client, + targets: Object.values(writeTargets), + logger: this.logger, + }) ); } async index(events: Fields[]) { - const eventsToIndex = [ - ...events, - ...getTransactionMetrics(events), - ...getSpanDestinationMetrics(events), - ...getBreakdownMetrics(events), - ]; - const writeTargets = await this.getWriteTargets(); + const eventsToIndex = apmEventsToElasticsearchOutput({ + events: [ + ...events, + ...getTransactionMetrics(events), + ...getSpanDestinationMetrics(events), + ...getBreakdownMetrics(events), + ], + writeTargets, + }); + await uploadEvents({ batchSize: 1000, client: this.client, clientWorkers: 5, events: eventsToIndex, logger: this.logger, - writeTargets, }); return this.client.indices.refresh({ diff --git a/packages/elastic-apm-synthtrace/src/lib/defaults/get_chrome_user_agent_defaults.ts b/packages/elastic-apm-synthtrace/src/lib/apm/defaults/get_chrome_user_agent_defaults.ts similarity index 84% rename from packages/elastic-apm-synthtrace/src/lib/defaults/get_chrome_user_agent_defaults.ts rename to packages/elastic-apm-synthtrace/src/lib/apm/defaults/get_chrome_user_agent_defaults.ts index 0031456248c1a..9a919e505185c 100644 --- a/packages/elastic-apm-synthtrace/src/lib/defaults/get_chrome_user_agent_defaults.ts +++ b/packages/elastic-apm-synthtrace/src/lib/apm/defaults/get_chrome_user_agent_defaults.ts @@ -6,9 +6,9 @@ * Side Public License, v 1. */ -import { UserAgentFields } from '../entity'; +import { ApmUserAgentFields } from '../../apm/apm_fields'; -export function getChromeUserAgentDefaults(): UserAgentFields { +export function getChromeUserAgentDefaults(): ApmUserAgentFields { return { 'user_agent.original': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.54 Safari/537.36', diff --git a/packages/elastic-apm-synthtrace/src/lib/defaults/get_observer_defaults.ts b/packages/elastic-apm-synthtrace/src/lib/apm/defaults/get_observer_defaults.ts similarity index 82% rename from packages/elastic-apm-synthtrace/src/lib/defaults/get_observer_defaults.ts rename to packages/elastic-apm-synthtrace/src/lib/apm/defaults/get_observer_defaults.ts index 67a4d5773b937..882029a50e47f 100644 --- a/packages/elastic-apm-synthtrace/src/lib/defaults/get_observer_defaults.ts +++ b/packages/elastic-apm-synthtrace/src/lib/apm/defaults/get_observer_defaults.ts @@ -6,9 +6,9 @@ * Side Public License, v 1. */ -import { Fields } from '../entity'; +import { ApmFields } from '../apm_fields'; -export function getObserverDefaults(): Fields { +export function getObserverDefaults(): ApmFields { return { 'observer.version': '7.16.0', 'observer.version_major': 7, diff --git a/packages/elastic-apm-synthtrace/src/lib/apm/index.ts b/packages/elastic-apm-synthtrace/src/lib/apm/index.ts new file mode 100644 index 0000000000000..f020d9a1282e8 --- /dev/null +++ b/packages/elastic-apm-synthtrace/src/lib/apm/index.ts @@ -0,0 +1,34 @@ +/* + * Copyright 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 { service } from './service'; +import { browser } from './browser'; +import { getTransactionMetrics } from './utils/get_transaction_metrics'; +import { getSpanDestinationMetrics } from './utils/get_span_destination_metrics'; +import { getObserverDefaults } from './defaults/get_observer_defaults'; +import { getChromeUserAgentDefaults } from './defaults/get_chrome_user_agent_defaults'; +import { apmEventsToElasticsearchOutput } from './utils/apm_events_to_elasticsearch_output'; +import { getBreakdownMetrics } from './utils/get_breakdown_metrics'; +import { getApmWriteTargets } from './utils/get_apm_write_targets'; +import { ApmSynthtraceEsClient } from './client/apm_synthtrace_es_client'; + +import type { ApmException } from './apm_fields'; + +export const apm = { + service, + browser, + getTransactionMetrics, + getSpanDestinationMetrics, + getObserverDefaults, + getChromeUserAgentDefaults, + apmEventsToElasticsearchOutput, + getBreakdownMetrics, + getApmWriteTargets, + ApmSynthtraceEsClient, +}; + +export type { ApmSynthtraceEsClient, ApmException }; diff --git a/packages/elastic-apm-synthtrace/src/lib/instance.ts b/packages/elastic-apm-synthtrace/src/lib/apm/instance.ts similarity index 87% rename from packages/elastic-apm-synthtrace/src/lib/instance.ts rename to packages/elastic-apm-synthtrace/src/lib/apm/instance.ts index 08444fde48ba6..4051d7e8241da 100644 --- a/packages/elastic-apm-synthtrace/src/lib/instance.ts +++ b/packages/elastic-apm-synthtrace/src/lib/apm/instance.ts @@ -7,12 +7,13 @@ */ import { ApmError } from './apm_error'; -import { ApplicationMetricFields, Entity } from './entity'; +import { Entity } from '../entity'; import { Metricset } from './metricset'; import { Span } from './span'; import { Transaction } from './transaction'; +import { ApmApplicationMetricFields, ApmFields } from './apm_fields'; -export class Instance extends Entity { +export class Instance extends Entity { transaction(transactionName: string, transactionType = 'request') { return new Transaction({ ...this.fields, @@ -43,7 +44,7 @@ export class Instance extends Entity { return this; } - appMetrics(metrics: ApplicationMetricFields) { + appMetrics(metrics: ApmApplicationMetricFields) { return new Metricset({ ...this.fields, 'metricset.name': 'app', diff --git a/packages/elastic-apm-synthtrace/src/lib/metricset.ts b/packages/elastic-apm-synthtrace/src/lib/apm/metricset.ts similarity index 72% rename from packages/elastic-apm-synthtrace/src/lib/metricset.ts rename to packages/elastic-apm-synthtrace/src/lib/apm/metricset.ts index c1ebbea313123..88177e816a852 100644 --- a/packages/elastic-apm-synthtrace/src/lib/metricset.ts +++ b/packages/elastic-apm-synthtrace/src/lib/apm/metricset.ts @@ -6,11 +6,11 @@ * Side Public License, v 1. */ -import { Fields } from './entity'; -import { Serializable } from './serializable'; +import { Serializable } from '../serializable'; +import { ApmFields } from './apm_fields'; -export class Metricset extends Serializable { - constructor(fields: Fields) { +export class Metricset extends Serializable { + constructor(fields: ApmFields) { super({ 'processor.event': 'metric', 'processor.name': 'metric', diff --git a/packages/elastic-apm-synthtrace/src/lib/rum_span.ts b/packages/elastic-apm-synthtrace/src/lib/apm/rum_span.ts similarity index 100% rename from packages/elastic-apm-synthtrace/src/lib/rum_span.ts rename to packages/elastic-apm-synthtrace/src/lib/apm/rum_span.ts diff --git a/packages/elastic-apm-synthtrace/src/lib/rum_transaction.ts b/packages/elastic-apm-synthtrace/src/lib/apm/rum_transaction.ts similarity index 100% rename from packages/elastic-apm-synthtrace/src/lib/rum_transaction.ts rename to packages/elastic-apm-synthtrace/src/lib/apm/rum_transaction.ts diff --git a/packages/elastic-apm-synthtrace/src/lib/service.ts b/packages/elastic-apm-synthtrace/src/lib/apm/service.ts similarity index 85% rename from packages/elastic-apm-synthtrace/src/lib/service.ts rename to packages/elastic-apm-synthtrace/src/lib/apm/service.ts index 859afa18aab03..16917821c7ee4 100644 --- a/packages/elastic-apm-synthtrace/src/lib/service.ts +++ b/packages/elastic-apm-synthtrace/src/lib/apm/service.ts @@ -6,10 +6,11 @@ * Side Public License, v 1. */ -import { Entity } from './entity'; +import { Entity } from '../entity'; +import { ApmFields } from './apm_fields'; import { Instance } from './instance'; -export class Service extends Entity { +export class Service extends Entity { instance(instanceName: string) { return new Instance({ ...this.fields, diff --git a/packages/elastic-apm-synthtrace/src/lib/span.ts b/packages/elastic-apm-synthtrace/src/lib/apm/span.ts similarity index 88% rename from packages/elastic-apm-synthtrace/src/lib/span.ts rename to packages/elastic-apm-synthtrace/src/lib/apm/span.ts index 3c8d90f56b78e..91cbacadf59cc 100644 --- a/packages/elastic-apm-synthtrace/src/lib/span.ts +++ b/packages/elastic-apm-synthtrace/src/lib/apm/span.ts @@ -7,11 +7,11 @@ */ import { BaseSpan } from './base_span'; -import { Fields } from './entity'; -import { generateShortId } from './utils/generate_id'; +import { generateShortId } from '../utils/generate_id'; +import { ApmFields } from './apm_fields'; export class Span extends BaseSpan { - constructor(fields: Fields) { + constructor(fields: ApmFields) { super({ ...fields, 'processor.event': 'span', diff --git a/packages/elastic-apm-synthtrace/src/lib/transaction.ts b/packages/elastic-apm-synthtrace/src/lib/apm/transaction.ts similarity index 93% rename from packages/elastic-apm-synthtrace/src/lib/transaction.ts rename to packages/elastic-apm-synthtrace/src/lib/apm/transaction.ts index 3a8d32e1843f8..47924e49e9b84 100644 --- a/packages/elastic-apm-synthtrace/src/lib/transaction.ts +++ b/packages/elastic-apm-synthtrace/src/lib/apm/transaction.ts @@ -8,14 +8,14 @@ import { ApmError } from './apm_error'; import { BaseSpan } from './base_span'; -import { Fields } from './entity'; -import { generateShortId } from './utils/generate_id'; +import { generateShortId } from '../utils/generate_id'; +import { ApmFields } from './apm_fields'; export class Transaction extends BaseSpan { private _sampled: boolean = true; private readonly _errors: ApmError[] = []; - constructor(fields: Fields) { + constructor(fields: ApmFields) { super({ ...fields, 'processor.event': 'transaction', diff --git a/packages/elastic-apm-synthtrace/src/lib/utils/aggregate.ts b/packages/elastic-apm-synthtrace/src/lib/apm/utils/aggregate.ts similarity index 82% rename from packages/elastic-apm-synthtrace/src/lib/utils/aggregate.ts rename to packages/elastic-apm-synthtrace/src/lib/apm/utils/aggregate.ts index 81b72f6fa01e9..505f7452fe5d9 100644 --- a/packages/elastic-apm-synthtrace/src/lib/utils/aggregate.ts +++ b/packages/elastic-apm-synthtrace/src/lib/apm/utils/aggregate.ts @@ -8,15 +8,15 @@ import moment from 'moment'; import { pickBy } from 'lodash'; import objectHash from 'object-hash'; -import { Fields } from '../entity'; +import { ApmFields } from '../apm_fields'; import { createPicker } from './create_picker'; -export function aggregate(events: Fields[], fields: string[]) { +export function aggregate(events: ApmFields[], fields: string[]) { const picker = createPicker(fields); - const metricsets = new Map(); + const metricsets = new Map(); - function getMetricsetKey(span: Fields) { + function getMetricsetKey(span: ApmFields) { const timestamp = moment(span['@timestamp']).valueOf(); return { '@timestamp': timestamp - (timestamp % (60 * 1000)), diff --git a/packages/elastic-apm-synthtrace/src/lib/output/to_elasticsearch_output.ts b/packages/elastic-apm-synthtrace/src/lib/apm/utils/apm_events_to_elasticsearch_output.ts similarity index 57% rename from packages/elastic-apm-synthtrace/src/lib/output/to_elasticsearch_output.ts rename to packages/elastic-apm-synthtrace/src/lib/apm/utils/apm_events_to_elasticsearch_output.ts index 016f1c5362fb4..46456098df4a0 100644 --- a/packages/elastic-apm-synthtrace/src/lib/output/to_elasticsearch_output.ts +++ b/packages/elastic-apm-synthtrace/src/lib/apm/utils/apm_events_to_elasticsearch_output.ts @@ -6,16 +6,12 @@ * Side Public License, v 1. */ -import { set } from 'lodash'; -import { getObserverDefaults } from '../..'; -import { Fields } from '../entity'; +import { getObserverDefaults } from '../defaults/get_observer_defaults'; +import { ApmFields } from '../apm_fields'; +import { dedot } from '../../utils/dedot'; +import { ElasticsearchOutput } from '../../utils/to_elasticsearch_output'; -export interface ElasticsearchOutput { - _index: string; - _source: unknown; -} - -export interface ElasticsearchOutputWriteTargets { +export interface ApmElasticsearchOutputWriteTargets { transaction: string; span: string; error: string; @@ -30,16 +26,14 @@ const esDocumentDefaults = { }, }; -// eslint-disable-next-line guard-for-in -for (const key in observerDefaults) { - set(esDocumentDefaults, key, observerDefaults[key as keyof typeof observerDefaults]); -} -export function toElasticsearchOutput({ +dedot(observerDefaults, esDocumentDefaults); + +export function apmEventsToElasticsearchOutput({ events, writeTargets, }: { - events: Fields[]; - writeTargets: ElasticsearchOutputWriteTargets; + events: ApmFields[]; + writeTargets: ApmElasticsearchOutputWriteTargets; }): ElasticsearchOutput[] { return events.map((event) => { const values = {}; @@ -55,15 +49,12 @@ export function toElasticsearchOutput({ Object.assign(document, esDocumentDefaults); - // eslint-disable-next-line guard-for-in - for (const key in values) { - const val = values[key as keyof typeof values]; - set(document, key, val); - } + dedot(values, document); return { - _index: writeTargets[event['processor.event'] as keyof ElasticsearchOutputWriteTargets], + _index: writeTargets[event['processor.event'] as keyof ApmElasticsearchOutputWriteTargets], _source: document, + timestamp: event['@timestamp']!, }; }); } diff --git a/packages/elastic-apm-synthtrace/src/lib/utils/create_picker.ts b/packages/elastic-apm-synthtrace/src/lib/apm/utils/create_picker.ts similarity index 100% rename from packages/elastic-apm-synthtrace/src/lib/utils/create_picker.ts rename to packages/elastic-apm-synthtrace/src/lib/apm/utils/create_picker.ts diff --git a/packages/elastic-apm-synthtrace/src/lib/utils/get_write_targets.ts b/packages/elastic-apm-synthtrace/src/lib/apm/utils/get_apm_write_targets.ts similarity index 90% rename from packages/elastic-apm-synthtrace/src/lib/utils/get_write_targets.ts rename to packages/elastic-apm-synthtrace/src/lib/apm/utils/get_apm_write_targets.ts index fbe11d295e099..f040ca46a9db9 100644 --- a/packages/elastic-apm-synthtrace/src/lib/utils/get_write_targets.ts +++ b/packages/elastic-apm-synthtrace/src/lib/apm/utils/get_apm_write_targets.ts @@ -7,13 +7,13 @@ */ import { Client } from '@elastic/elasticsearch'; -import { ElasticsearchOutputWriteTargets } from '../../lib/output/to_elasticsearch_output'; +import { ApmElasticsearchOutputWriteTargets } from './apm_events_to_elasticsearch_output'; -export async function getWriteTargets({ +export async function getApmWriteTargets({ client, }: { client: Client; -}): Promise { +}): Promise { const [indicesResponse, datastreamsResponse] = await Promise.all([ client.indices.getAlias({ index: 'apm-*', diff --git a/packages/elastic-apm-synthtrace/src/lib/utils/get_breakdown_metrics.ts b/packages/elastic-apm-synthtrace/src/lib/apm/utils/get_breakdown_metrics.ts similarity index 95% rename from packages/elastic-apm-synthtrace/src/lib/utils/get_breakdown_metrics.ts rename to packages/elastic-apm-synthtrace/src/lib/apm/utils/get_breakdown_metrics.ts index 8eae0941c6bdd..4f29a31d5d278 100644 --- a/packages/elastic-apm-synthtrace/src/lib/utils/get_breakdown_metrics.ts +++ b/packages/elastic-apm-synthtrace/src/lib/apm/utils/get_breakdown_metrics.ts @@ -7,7 +7,7 @@ */ import objectHash from 'object-hash'; import { groupBy, pickBy } from 'lodash'; -import { Fields } from '../entity'; +import { ApmFields } from '../apm_fields'; import { createPicker } from './create_picker'; const instanceFields = [ @@ -29,7 +29,7 @@ const metricsetPicker = createPicker([ 'span.subtype', ]); -export function getBreakdownMetrics(events: Fields[]) { +export function getBreakdownMetrics(events: ApmFields[]) { const txWithSpans = groupBy( events.filter( (event) => event['processor.event'] === 'span' || event['processor.event'] === 'transaction' @@ -37,13 +37,13 @@ export function getBreakdownMetrics(events: Fields[]) { (event) => event['transaction.id'] ); - const metricsets: Map = new Map(); + const metricsets: Map = new Map(); Object.keys(txWithSpans).forEach((transactionId) => { const txEvents = txWithSpans[transactionId]; const transaction = txEvents.find((event) => event['processor.event'] === 'transaction')!; - const eventsById: Record = {}; + const eventsById: Record = {}; const activityByParentId: Record> = {}; for (const event of txEvents) { const id = diff --git a/packages/elastic-apm-synthtrace/src/lib/utils/get_span_destination_metrics.ts b/packages/elastic-apm-synthtrace/src/lib/apm/utils/get_span_destination_metrics.ts similarity index 90% rename from packages/elastic-apm-synthtrace/src/lib/utils/get_span_destination_metrics.ts rename to packages/elastic-apm-synthtrace/src/lib/apm/utils/get_span_destination_metrics.ts index decf2f71a9be4..7adcdaa6ff940 100644 --- a/packages/elastic-apm-synthtrace/src/lib/utils/get_span_destination_metrics.ts +++ b/packages/elastic-apm-synthtrace/src/lib/apm/utils/get_span_destination_metrics.ts @@ -6,10 +6,10 @@ * Side Public License, v 1. */ -import { Fields } from '../entity'; +import { ApmFields } from '../apm_fields'; import { aggregate } from './aggregate'; -export function getSpanDestinationMetrics(events: Fields[]) { +export function getSpanDestinationMetrics(events: ApmFields[]) { const exitSpans = events.filter((event) => !!event['span.destination.service.resource']); const metricsets = aggregate(exitSpans, [ diff --git a/packages/elastic-apm-synthtrace/src/lib/utils/get_transaction_metrics.ts b/packages/elastic-apm-synthtrace/src/lib/apm/utils/get_transaction_metrics.ts similarity index 94% rename from packages/elastic-apm-synthtrace/src/lib/utils/get_transaction_metrics.ts rename to packages/elastic-apm-synthtrace/src/lib/apm/utils/get_transaction_metrics.ts index 4d46461c6dcc9..1595e58957223 100644 --- a/packages/elastic-apm-synthtrace/src/lib/utils/get_transaction_metrics.ts +++ b/packages/elastic-apm-synthtrace/src/lib/apm/utils/get_transaction_metrics.ts @@ -7,7 +7,7 @@ */ import { sortBy } from 'lodash'; -import { Fields } from '../entity'; +import { ApmFields } from '../apm_fields'; import { aggregate } from './aggregate'; function sortAndCompressHistogram(histogram?: { values: number[]; counts: number[] }) { @@ -28,7 +28,7 @@ function sortAndCompressHistogram(histogram?: { values: number[]; counts: number ); } -export function getTransactionMetrics(events: Fields[]) { +export function getTransactionMetrics(events: ApmFields[]) { const transactions = events .filter((event) => event['processor.event'] === 'transaction') .map((transaction) => { diff --git a/packages/elastic-apm-synthtrace/src/lib/entity.ts b/packages/elastic-apm-synthtrace/src/lib/entity.ts index c6e0c7193f8ba..f1b11a3905df7 100644 --- a/packages/elastic-apm-synthtrace/src/lib/entity.ts +++ b/packages/elastic-apm-synthtrace/src/lib/entity.ts @@ -6,83 +6,18 @@ * Side Public License, v 1. */ -export type ApplicationMetricFields = Partial<{ - 'system.process.memory.size': number; - 'system.memory.actual.free': number; - 'system.memory.total': number; - 'system.cpu.total.norm.pct': number; - 'system.process.memory.rss.bytes': number; - 'system.process.cpu.total.norm.pct': number; -}>; - -export type UserAgentFields = Partial<{ - 'user_agent.original': string; - 'user_agent.os.name': string; - 'user_agent.name': string; - 'user_agent.device.name': string; - 'user_agent.version': number; -}>; - -export interface Exception { - message: string; +export interface Fields { + '@timestamp'?: number; } -export type Fields = Partial<{ - '@timestamp': number; - 'agent.name': string; - 'agent.version': string; - 'container.id': string; - 'ecs.version': string; - 'event.outcome': string; - 'event.ingested': number; - 'error.id': string; - 'error.exception': Exception[]; - 'error.grouping_name': string; - 'error.grouping_key': string; - 'host.name': string; - 'kubernetes.pod.uid': string; - 'metricset.name': string; - 'observer.version': string; - 'observer.version_major': number; - 'parent.id': string; - 'processor.event': string; - 'processor.name': string; - 'trace.id': string; - 'transaction.name': string; - 'transaction.type': string; - 'transaction.id': string; - 'transaction.duration.us': number; - 'transaction.duration.histogram': { - values: number[]; - counts: number[]; - }; - 'transaction.sampled': true; - 'service.name': string; - 'service.environment': string; - 'service.node.name': string; - 'span.id': string; - 'span.name': string; - 'span.type': string; - 'span.subtype': string; - 'span.duration.us': number; - 'span.destination.service.name': string; - 'span.destination.service.resource': string; - 'span.destination.service.type': string; - 'span.destination.service.response_time.sum.us': number; - 'span.destination.service.response_time.count': number; - 'span.self_time.count': number; - 'span.self_time.sum.us': number; -}> & - ApplicationMetricFields; - -export class Entity { - constructor(public readonly fields: Fields) { +export class Entity { + constructor(public readonly fields: TFields) { this.fields = fields; } - defaults(defaults: Fields) { + defaults(defaults: TFields) { Object.keys(defaults).forEach((key) => { - const fieldName: keyof Fields = key as any; + const fieldName: keyof TFields = key as any; if (!(fieldName in this.fields)) { this.fields[fieldName] = defaults[fieldName] as any; diff --git a/packages/elastic-apm-synthtrace/src/lib/serializable.ts b/packages/elastic-apm-synthtrace/src/lib/serializable.ts index 3a92dc539855a..e9ffe3ae96994 100644 --- a/packages/elastic-apm-synthtrace/src/lib/serializable.ts +++ b/packages/elastic-apm-synthtrace/src/lib/serializable.ts @@ -8,8 +8,8 @@ import { Entity, Fields } from './entity'; -export class Serializable extends Entity { - constructor(fields: Fields) { +export class Serializable extends Entity { + constructor(fields: TFields) { super({ ...fields, }); @@ -19,7 +19,7 @@ export class Serializable extends Entity { this.fields['@timestamp'] = time; return this; } - serialize(): Fields[] { + serialize(): TFields[] { return [this.fields]; } } diff --git a/packages/elastic-apm-synthtrace/src/lib/stack_monitoring/cluster.ts b/packages/elastic-apm-synthtrace/src/lib/stack_monitoring/cluster.ts new file mode 100644 index 0000000000000..7a665522abba4 --- /dev/null +++ b/packages/elastic-apm-synthtrace/src/lib/stack_monitoring/cluster.ts @@ -0,0 +1,38 @@ +/* + * Copyright 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 { Entity } from '../entity'; +import { generateShortId } from '../utils/generate_id'; +import { Kibana } from './kibana'; +import { StackMonitoringFields } from './stack_monitoring_fields'; +import { ClusterStats } from './cluster_stats'; + +export class Cluster extends Entity { + kibana(name: string, index: string = '.kibana') { + return new Kibana({ + cluster_uuid: this.fields.cluster_uuid, + 'kibana_stats.kibana.name': name, + 'kibana_stats.kibana.uuid': generateShortId(), + 'kibana_stats.kibana.index': index, + type: 'kibana_stats', + }); + } + + stats() { + return new ClusterStats({ + ...this.fields, + }); + } +} + +export function cluster(name: string) { + return new Cluster({ + cluster_name: name, + cluster_uuid: generateShortId(), + }); +} diff --git a/packages/elastic-apm-synthtrace/src/lib/stack_monitoring/cluster_stats.ts b/packages/elastic-apm-synthtrace/src/lib/stack_monitoring/cluster_stats.ts new file mode 100644 index 0000000000000..0995013cbcbb7 --- /dev/null +++ b/packages/elastic-apm-synthtrace/src/lib/stack_monitoring/cluster_stats.ts @@ -0,0 +1,30 @@ +/* + * Copyright 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 { Serializable } from '../serializable'; +import { StackMonitoringFields } from './stack_monitoring_fields'; + +export class ClusterStats extends Serializable { + constructor(fields: StackMonitoringFields) { + super(fields); + + this.fields.type = 'cluster_stats'; + this.fields['license.status'] = 'active'; + } + + timestamp(timestamp: number) { + super.timestamp(timestamp); + this.fields['cluster_stats.timestamp'] = new Date(timestamp).toISOString(); + return this; + } + + indices(count: number) { + this.fields['cluster_stats.indices.count'] = count; + return this; + } +} diff --git a/packages/elastic-apm-synthtrace/src/lib/stack_monitoring/index.ts b/packages/elastic-apm-synthtrace/src/lib/stack_monitoring/index.ts new file mode 100644 index 0000000000000..ee926269ea36b --- /dev/null +++ b/packages/elastic-apm-synthtrace/src/lib/stack_monitoring/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 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 { cluster } from './cluster'; + +export const stackMonitoring = { + cluster, +}; diff --git a/packages/elastic-apm-synthtrace/src/lib/stack_monitoring/kibana.ts b/packages/elastic-apm-synthtrace/src/lib/stack_monitoring/kibana.ts new file mode 100644 index 0000000000000..fec244bc19dc1 --- /dev/null +++ b/packages/elastic-apm-synthtrace/src/lib/stack_monitoring/kibana.ts @@ -0,0 +1,19 @@ +/* + * Copyright 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 { Serializable } from '../serializable'; +import { StackMonitoringFields } from './stack_monitoring_fields'; +import { KibanaStats } from './kibana_stats'; + +export class Kibana extends Serializable { + stats() { + return new KibanaStats({ + ...this.fields, + }); + } +} diff --git a/packages/elastic-apm-synthtrace/src/lib/stack_monitoring/kibana_stats.ts b/packages/elastic-apm-synthtrace/src/lib/stack_monitoring/kibana_stats.ts new file mode 100644 index 0000000000000..495e5f013600e --- /dev/null +++ b/packages/elastic-apm-synthtrace/src/lib/stack_monitoring/kibana_stats.ts @@ -0,0 +1,26 @@ +/* + * Copyright 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 { Serializable } from '../serializable'; +import { StackMonitoringFields } from './stack_monitoring_fields'; + +export class KibanaStats extends Serializable { + timestamp(timestamp: number) { + super.timestamp(timestamp); + this.fields['kibana_stats.timestamp'] = new Date(timestamp).toISOString(); + this.fields['kibana_stats.response_times.max'] = 250; + this.fields['kibana_stats.kibana.status'] = 'green'; + return this; + } + + requests(disconnects: number, total: number) { + this.fields['kibana_stats.requests.disconnects'] = disconnects; + this.fields['kibana_stats.requests.total'] = total; + return this; + } +} diff --git a/packages/elastic-apm-synthtrace/src/lib/stack_monitoring/stack_monitoring_fields.ts b/packages/elastic-apm-synthtrace/src/lib/stack_monitoring/stack_monitoring_fields.ts new file mode 100644 index 0000000000000..3e80d1e9f733f --- /dev/null +++ b/packages/elastic-apm-synthtrace/src/lib/stack_monitoring/stack_monitoring_fields.ts @@ -0,0 +1,29 @@ +/* + * Copyright 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 { Fields } from '../entity'; + +export type StackMonitoringFields = Fields & + Partial<{ + cluster_name: string; + cluster_uuid: string; + type: string; + + 'cluster_stats.timestamp': string; + 'cluster_stats.indices.count': number; + 'license.status': string; + + 'kibana_stats.kibana.name': string; + 'kibana_stats.kibana.uuid': string; + 'kibana_stats.kibana.status': string; + 'kibana_stats.kibana.index': string; + 'kibana_stats.requests.disconnects': number; + 'kibana_stats.requests.total': number; + 'kibana_stats.timestamp': string; + 'kibana_stats.response_times.max': number; + }>; diff --git a/packages/elastic-apm-synthtrace/src/lib/utils/clean_write_targets.ts b/packages/elastic-apm-synthtrace/src/lib/utils/clean_write_targets.ts index 4a2ab281a2849..91b8e0084b27f 100644 --- a/packages/elastic-apm-synthtrace/src/lib/utils/clean_write_targets.ts +++ b/packages/elastic-apm-synthtrace/src/lib/utils/clean_write_targets.ts @@ -7,20 +7,17 @@ */ import { Client } from '@elastic/elasticsearch'; -import { ElasticsearchOutputWriteTargets } from '../../lib/output/to_elasticsearch_output'; -import { Logger } from './logger'; +import { Logger } from './create_logger'; export async function cleanWriteTargets({ - writeTargets, + targets, client, logger, }: { - writeTargets: ElasticsearchOutputWriteTargets; + targets: string[]; client: Client; logger: Logger; }) { - const targets = Object.values(writeTargets); - logger.info(`Cleaning indices: ${targets.join(', ')}`); const response = await client.deleteByQuery({ diff --git a/packages/elastic-apm-synthtrace/src/lib/utils/dedot.ts b/packages/elastic-apm-synthtrace/src/lib/utils/dedot.ts new file mode 100644 index 0000000000000..4f38a7025f3b5 --- /dev/null +++ b/packages/elastic-apm-synthtrace/src/lib/utils/dedot.ts @@ -0,0 +1,16 @@ +/* + * Copyright 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 { set } from 'lodash'; + +export function dedot(source: Record, target: Record) { + // eslint-disable-next-line guard-for-in + for (const key in source) { + const val = source[key as keyof typeof source]; + set(target, key, val); + } +} diff --git a/packages/elastic-apm-synthtrace/src/lib/utils/logger.ts b/packages/elastic-apm-synthtrace/src/lib/utils/logger.ts deleted file mode 100644 index 4afdda74105cf..0000000000000 --- a/packages/elastic-apm-synthtrace/src/lib/utils/logger.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 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 { isPromise } from 'util/types'; - -export enum LogLevel { - trace = 0, - debug = 1, - info = 2, - error = 3, -} - -function getTimeString() { - return `[${new Date().toLocaleTimeString()}]`; -} - -export function createLogger(logLevel: LogLevel) { - function logPerf(name: string, start: bigint) { - // eslint-disable-next-line no-console - console.debug( - getTimeString(), - `${name}: ${Number(process.hrtime.bigint() - start) / 1000000}ms` - ); - } - return { - perf: (name: string, cb: () => T): T => { - if (logLevel <= LogLevel.trace) { - const start = process.hrtime.bigint(); - const val = cb(); - if (isPromise(val)) { - val.then(() => { - logPerf(name, start); - }); - } else { - logPerf(name, start); - } - return val; - } - return cb(); - }, - debug: (...args: any[]) => { - if (logLevel <= LogLevel.debug) { - // eslint-disable-next-line no-console - console.debug(getTimeString(), ...args); - } - }, - info: (...args: any[]) => { - if (logLevel <= LogLevel.info) { - // eslint-disable-next-line no-console - console.log(getTimeString(), ...args); - } - }, - error: (...args: any[]) => { - if (logLevel <= LogLevel.error) { - // eslint-disable-next-line no-console - console.log(getTimeString(), ...args); - } - }, - }; -} - -export type Logger = ReturnType; diff --git a/packages/elastic-apm-synthtrace/src/lib/utils/to_elasticsearch_output.ts b/packages/elastic-apm-synthtrace/src/lib/utils/to_elasticsearch_output.ts new file mode 100644 index 0000000000000..58bafffaff692 --- /dev/null +++ b/packages/elastic-apm-synthtrace/src/lib/utils/to_elasticsearch_output.ts @@ -0,0 +1,44 @@ +/* + * Copyright 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 { Fields } from '../entity'; +import { dedot } from './dedot'; + +export interface ElasticsearchOutput { + _index: string; + _source: unknown; + timestamp: number; +} + +export function eventsToElasticsearchOutput({ + events, + writeTarget, +}: { + events: Fields[]; + writeTarget: string; +}): ElasticsearchOutput[] { + return events.map((event) => { + const values = {}; + + const timestamp = event['@timestamp']!; + + Object.assign(values, event, { + '@timestamp': new Date(timestamp).toISOString(), + }); + + const document = {}; + + dedot(values, document); + + return { + _index: writeTarget, + _source: document, + timestamp, + }; + }); +} diff --git a/packages/elastic-apm-synthtrace/src/scripts/examples/01_simple_trace.ts b/packages/elastic-apm-synthtrace/src/scripts/examples/01_simple_trace.ts index 8c1f24bd5e64f..559c636cfaeec 100644 --- a/packages/elastic-apm-synthtrace/src/scripts/examples/01_simple_trace.ts +++ b/packages/elastic-apm-synthtrace/src/scripts/examples/01_simple_trace.ts @@ -6,75 +6,103 @@ * Side Public License, v 1. */ -import { service, timerange, getTransactionMetrics, getSpanDestinationMetrics } from '../..'; -import { getBreakdownMetrics } from '../../lib/utils/get_breakdown_metrics'; +import { apm, timerange } from '../../index'; +import { apmEventsToElasticsearchOutput } from '../../lib/apm/utils/apm_events_to_elasticsearch_output'; +import { getApmWriteTargets } from '../../lib/apm/utils/get_apm_write_targets'; +import { Scenario } from '../scenario'; +import { getCommonServices } from '../utils/get_common_services'; -export default function ({ from, to }: { from: number; to: number }) { - const numServices = 3; +const scenario: Scenario = async ({ target, logLevel }) => { + const { client, logger } = getCommonServices({ target, logLevel }); + const writeTargets = await getApmWriteTargets({ client }); - const range = timerange(from, to); + return { + generate: ({ from, to }) => { + const numServices = 3; - const transactionName = '240rpm/75% 1000ms'; + const range = timerange(from, to); - const successfulTimestamps = range.interval('1s').rate(3); + const transactionName = '240rpm/75% 1000ms'; - const failedTimestamps = range.interval('1s').rate(1); + const successfulTimestamps = range.interval('1s').rate(3); - return new Array(numServices).fill(undefined).flatMap((_, index) => { - const instance = service(`opbeans-go-${index}`, 'production', 'go').instance('instance'); + const failedTimestamps = range.interval('1s').rate(1); - const successfulTraceEvents = successfulTimestamps.flatMap((timestamp) => - instance - .transaction(transactionName) - .timestamp(timestamp) - .duration(1000) - .success() - .children( - instance - .span('GET apm-*/_search', 'db', 'elasticsearch') - .duration(1000) - .success() - .destination('elasticsearch') - .timestamp(timestamp), - instance.span('custom_operation', 'custom').duration(100).success().timestamp(timestamp) - ) - .serialize() - ); + return new Array(numServices).fill(undefined).flatMap((_, index) => { + const events = logger.perf('generating_apm_events', () => { + const instance = apm + .service(`opbeans-go-${index}`, 'production', 'go') + .instance('instance'); - const failedTraceEvents = failedTimestamps.flatMap((timestamp) => - instance - .transaction(transactionName) - .timestamp(timestamp) - .duration(1000) - .failure() - .errors( - instance.error('[ResponseError] index_not_found_exception').timestamp(timestamp + 50) - ) - .serialize() - ); + const successfulTraceEvents = successfulTimestamps.flatMap((timestamp) => + instance + .transaction(transactionName) + .timestamp(timestamp) + .duration(1000) + .success() + .children( + instance + .span('GET apm-*/_search', 'db', 'elasticsearch') + .duration(1000) + .success() + .destination('elasticsearch') + .timestamp(timestamp), + instance + .span('custom_operation', 'custom') + .duration(100) + .success() + .timestamp(timestamp) + ) + .serialize() + ); - const metricsets = range - .interval('30s') - .rate(1) - .flatMap((timestamp) => - instance - .appMetrics({ - 'system.memory.actual.free': 800, - 'system.memory.total': 1000, - 'system.cpu.total.norm.pct': 0.6, - 'system.process.cpu.total.norm.pct': 0.7, + const failedTraceEvents = failedTimestamps.flatMap((timestamp) => + instance + .transaction(transactionName) + .timestamp(timestamp) + .duration(1000) + .failure() + .errors( + instance + .error('[ResponseError] index_not_found_exception') + .timestamp(timestamp + 50) + ) + .serialize() + ); + + const metricsets = range + .interval('30s') + .rate(1) + .flatMap((timestamp) => + instance + .appMetrics({ + 'system.memory.actual.free': 800, + 'system.memory.total': 1000, + 'system.cpu.total.norm.pct': 0.6, + 'system.process.cpu.total.norm.pct': 0.7, + }) + .timestamp(timestamp) + .serialize() + ); + return [...successfulTraceEvents, ...failedTraceEvents, ...metricsets]; + }); + + return logger.perf('apm_events_to_es_output', () => + apmEventsToElasticsearchOutput({ + events: [ + ...events, + ...logger.perf('get_transaction_metrics', () => apm.getTransactionMetrics(events)), + ...logger.perf('get_span_destination_metrics', () => + apm.getSpanDestinationMetrics(events) + ), + ...logger.perf('get_breakdown_metrics', () => apm.getBreakdownMetrics(events)), + ], + writeTargets, }) - .timestamp(timestamp) - .serialize() - ); - const events = successfulTraceEvents.concat(failedTraceEvents); + ); + }); + }, + }; +}; - return [ - ...events, - ...metricsets, - ...getTransactionMetrics(events), - ...getSpanDestinationMetrics(events), - ...getBreakdownMetrics(events), - ]; - }); -} +export default scenario; diff --git a/packages/elastic-apm-synthtrace/src/scripts/examples/02_kibana_stats.ts b/packages/elastic-apm-synthtrace/src/scripts/examples/02_kibana_stats.ts new file mode 100644 index 0000000000000..2ba3c4a29c52b --- /dev/null +++ b/packages/elastic-apm-synthtrace/src/scripts/examples/02_kibana_stats.ts @@ -0,0 +1,48 @@ +/* + * Copyright 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 { stackMonitoring, timerange } from '../../index'; +import { eventsToElasticsearchOutput } from '../../lib/utils/to_elasticsearch_output'; +import { Scenario } from '../scenario'; +import { getCommonServices } from '../utils/get_common_services'; + +const scenario: Scenario = async ({ target, writeTarget, logLevel }) => { + const { logger } = getCommonServices({ target, logLevel }); + + if (!writeTarget) { + throw new Error('Write target is not defined'); + } + + return { + generate: ({ from, to }) => { + const kibanaStats = stackMonitoring.cluster('cluster-01').kibana('kibana-01').stats(); + + const range = timerange(from, to); + return range + .interval('30s') + .rate(1) + .flatMap((timestamp) => { + const events = logger.perf('generating_sm_events', () => { + return kibanaStats.timestamp(timestamp).requests(10, 20).serialize(); + }); + + return logger.perf('sm_events_to_es_output', () => { + const smEvents = eventsToElasticsearchOutput({ events, writeTarget }); + smEvents.forEach((event: any) => { + const ts = event._source['@timestamp']; + delete event._source['@timestamp']; + event._source.timestamp = ts; + }); + return smEvents; + }); + }); + }, + }; +}; + +export default scenario; diff --git a/packages/elastic-apm-synthtrace/src/scripts/examples/03_monitoring.ts b/packages/elastic-apm-synthtrace/src/scripts/examples/03_monitoring.ts new file mode 100644 index 0000000000000..53dcd820f5519 --- /dev/null +++ b/packages/elastic-apm-synthtrace/src/scripts/examples/03_monitoring.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 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. + */ + +// Run with: node ./src/scripts/run ./src/scripts/examples/03_monitoring.ts --target=http://elastic:changeme@localhost:9200 + +import { stackMonitoring, timerange } from '../../index'; +import { + ElasticsearchOutput, + eventsToElasticsearchOutput, +} from '../../lib/utils/to_elasticsearch_output'; +import { Scenario } from '../scenario'; +import { getCommonServices } from '../utils/get_common_services'; +import { StackMonitoringFields } from '../../lib/stack_monitoring/stack_monitoring_fields'; + +// TODO (mat): move this into a function like utils/apm_events_to_elasticsearch_output.ts +function smEventsToElasticsearchOutput( + events: StackMonitoringFields[], + writeTarget: string +): ElasticsearchOutput[] { + const smEvents = eventsToElasticsearchOutput({ events, writeTarget }); + smEvents.forEach((event: any) => { + const ts = event._source['@timestamp']; + delete event._source['@timestamp']; + event._source.timestamp = ts; + }); + return smEvents; +} + +const scenario: Scenario = async ({ target, logLevel }) => { + const { logger } = getCommonServices({ target, logLevel }); + + return { + generate: ({ from, to }) => { + const cluster = stackMonitoring.cluster('test-cluster'); + const clusterStats = cluster.stats(); + const kibanaStats = cluster.kibana('kibana-01').stats(); + + const range = timerange(from, to); + return range + .interval('10s') + .rate(1) + .flatMap((timestamp) => { + const clusterEvents = logger.perf('generating_es_events', () => { + return clusterStats.timestamp(timestamp).indices(115).serialize(); + }); + const clusterOutputs = smEventsToElasticsearchOutput( + clusterEvents, + '.monitoring-es-7-synthtrace' + ); + + const kibanaEvents = logger.perf('generating_kb_events', () => { + return kibanaStats.timestamp(timestamp).requests(10, 20).serialize(); + }); + const kibanaOutputs = smEventsToElasticsearchOutput( + kibanaEvents, + '.monitoring-kibana-7-synthtrace' + ); + + return [...clusterOutputs, ...kibanaOutputs]; + }); + }, + }; +}; + +export default scenario; diff --git a/packages/elastic-apm-synthtrace/src/scripts/run.ts b/packages/elastic-apm-synthtrace/src/scripts/run.ts index aa427d8e211ae..4078c848aa480 100644 --- a/packages/elastic-apm-synthtrace/src/scripts/run.ts +++ b/packages/elastic-apm-synthtrace/src/scripts/run.ts @@ -7,136 +7,109 @@ */ import datemath from '@elastic/datemath'; import yargs from 'yargs/yargs'; -import { cleanWriteTargets } from '../lib/utils/clean_write_targets'; +import { Argv } from 'yargs'; import { intervalToMs } from './utils/interval_to_ms'; -import { getCommonResources } from './utils/get_common_resources'; import { startHistoricalDataUpload } from './utils/start_historical_data_upload'; import { startLiveDataUpload } from './utils/start_live_data_upload'; +import { parseRunCliFlags } from './utils/parse_run_cli_flags'; +import { getCommonServices } from './utils/get_common_services'; -yargs(process.argv.slice(2)) - .command( - '*', - 'Generate data and index into Elasticsearch', - (y) => { - return y - .positional('file', { - describe: 'File that contains the trace scenario', - demandOption: true, - string: true, - }) - .option('target', { - describe: 'Elasticsearch target, including username/password', - demandOption: true, - string: true, - }) - .option('from', { - description: 'The start of the time window', - }) - .option('to', { - description: 'The end of the time window', - }) - .option('live', { - description: 'Generate and index data continuously', - boolean: true, - }) - .option('clean', { - describe: 'Clean APM indices before indexing new data', - default: false, - boolean: true, - }) - .option('workers', { - describe: 'Amount of Node.js worker threads', - default: 5, - }) - .option('bucketSize', { - describe: 'Size of bucket for which to generate data', - default: '15m', - }) - .option('interval', { - describe: 'The interval at which to index data', - default: '10s', - }) - .option('clientWorkers', { - describe: 'Number of concurrently connected ES clients', - default: 5, - }) - .option('batchSize', { - describe: 'Number of documents per bulk index request', - default: 1000, - }) - .option('logLevel', { - describe: 'Log level', - default: 'info', - }) - .conflicts('to', 'live'); - }, - async (argv) => { - const file = String(argv.file || argv._[0]); +function options(y: Argv) { + return y + .positional('file', { + describe: 'File that contains the trace scenario', + demandOption: true, + string: true, + }) + .option('target', { + describe: 'Elasticsearch target, including username/password', + demandOption: true, + string: true, + }) + .option('from', { + description: 'The start of the time window', + }) + .option('to', { + description: 'The end of the time window', + }) + .option('live', { + description: 'Generate and index data continuously', + boolean: true, + }) + .option('clean', { + describe: 'Clean APM indices before indexing new data', + default: false, + boolean: true, + }) + .option('workers', { + describe: 'Amount of Node.js worker threads', + default: 5, + }) + .option('bucketSize', { + describe: 'Size of bucket for which to generate data', + default: '15m', + }) + .option('interval', { + describe: 'The interval at which to index data', + default: '10s', + }) + .option('clientWorkers', { + describe: 'Number of concurrently connected ES clients', + default: 5, + }) + .option('batchSize', { + describe: 'Number of documents per bulk index request', + default: 1000, + }) + .option('logLevel', { + describe: 'Log level', + default: 'info', + }) + .option('writeTarget', { + describe: 'Target to index', + string: true, + }) + .conflicts('to', 'live'); +} + +export type RunCliFlags = ReturnType['argv']; - const { target, workers, clean, clientWorkers, batchSize } = argv; +yargs(process.argv.slice(2)) + .command('*', 'Generate data and index into Elasticsearch', options, async (argv) => { + const runOptions = parseRunCliFlags(argv); - const { scenario, intervalInMs, bucketSizeInMs, logger, writeTargets, client, logLevel } = - await getCommonResources({ - ...argv, - file, - }); + const { logger } = getCommonServices(runOptions); - if (clean) { - await cleanWriteTargets({ writeTargets, client, logger }); - } + const to = datemath.parse(String(argv.to ?? 'now'))!.valueOf(); + const from = argv.from + ? datemath.parse(String(argv.from))!.valueOf() + : to - intervalToMs('15m'); - const to = datemath.parse(String(argv.to ?? 'now'))!.valueOf(); - const from = argv.from - ? datemath.parse(String(argv.from))!.valueOf() - : to - intervalToMs('15m'); + const live = argv.live; - const live = argv.live; + logger.info( + `Starting data generation\n: ${JSON.stringify( + { + ...runOptions, + from: new Date(from).toISOString(), + to: new Date(to).toISOString(), + }, + null, + 2 + )}` + ); - logger.info( - `Starting data generation\n: ${JSON.stringify( - { - intervalInMs, - bucketSizeInMs, - workers, - target, - writeTargets, - from: new Date(from).toISOString(), - to: new Date(to).toISOString(), - live, - }, - null, - 2 - )}` - ); + startHistoricalDataUpload({ + ...runOptions, + from, + to, + }); - startHistoricalDataUpload({ - from, - to, - file, - bucketSizeInMs, - client, - workers, - clientWorkers, - batchSize, - writeTargets, - logger, - logLevel, - target, + if (live) { + startLiveDataUpload({ + ...runOptions, + start: to, }); - - if (live) { - startLiveDataUpload({ - bucketSizeInMs, - client, - intervalInMs, - logger, - scenario, - start: to, - clientWorkers, - batchSize, - writeTargets, - }); - } } - ) + }) .parse(); diff --git a/packages/elastic-apm-synthtrace/src/scripts/scenario.ts b/packages/elastic-apm-synthtrace/src/scripts/scenario.ts new file mode 100644 index 0000000000000..c134c08cd8354 --- /dev/null +++ b/packages/elastic-apm-synthtrace/src/scripts/scenario.ts @@ -0,0 +1,13 @@ +/* + * Copyright 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 { ElasticsearchOutput } from '../lib/utils/to_elasticsearch_output'; +import { RunOptions } from './utils/parse_run_cli_flags'; + +type Generate = (range: { from: number; to: number }) => ElasticsearchOutput[]; +export type Scenario = (options: RunOptions) => Promise<{ generate: Generate }>; diff --git a/packages/elastic-apm-synthtrace/src/scripts/utils/get_common_services.ts b/packages/elastic-apm-synthtrace/src/scripts/utils/get_common_services.ts new file mode 100644 index 0000000000000..0dee6dbc951eb --- /dev/null +++ b/packages/elastic-apm-synthtrace/src/scripts/utils/get_common_services.ts @@ -0,0 +1,25 @@ +/* + * Copyright 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 { Client } from '@elastic/elasticsearch'; +import { createLogger, LogLevel } from '../../lib/utils/create_logger'; + +export function getCommonServices({ target, logLevel }: { target: string; logLevel: LogLevel }) { + const client = new Client({ + node: target, + }); + + const logger = createLogger(logLevel); + + return { + logger, + client, + }; +} + +export type RunServices = ReturnType; diff --git a/packages/elastic-apm-synthtrace/src/scripts/utils/get_scenario.ts b/packages/elastic-apm-synthtrace/src/scripts/utils/get_scenario.ts index f8c59cff4febc..43f9e4f5e9988 100644 --- a/packages/elastic-apm-synthtrace/src/scripts/utils/get_scenario.ts +++ b/packages/elastic-apm-synthtrace/src/scripts/utils/get_scenario.ts @@ -6,10 +6,8 @@ * Side Public License, v 1. */ import Path from 'path'; -import { Fields } from '../../lib/entity'; import { Logger } from '../../lib/utils/create_logger'; - -export type Scenario = (options: { from: number; to: number }) => Fields[]; +import { Scenario } from '../scenario'; export function getScenario({ file, logger }: { file: unknown; logger: Logger }) { const location = Path.join(process.cwd(), String(file)); diff --git a/packages/elastic-apm-synthtrace/src/scripts/utils/get_common_resources.ts b/packages/elastic-apm-synthtrace/src/scripts/utils/parse_run_cli_flags.ts similarity index 59% rename from packages/elastic-apm-synthtrace/src/scripts/utils/get_common_resources.ts rename to packages/elastic-apm-synthtrace/src/scripts/utils/parse_run_cli_flags.ts index baa1d8758c3c4..5c081707bb75c 100644 --- a/packages/elastic-apm-synthtrace/src/scripts/utils/get_common_resources.ts +++ b/packages/elastic-apm-synthtrace/src/scripts/utils/parse_run_cli_flags.ts @@ -6,25 +6,16 @@ * Side Public License, v 1. */ -import { Client } from '@elastic/elasticsearch'; -import { getScenario } from './get_scenario'; -import { getWriteTargets } from '../../lib/utils/get_write_targets'; +import { pick } from 'lodash'; +import { LogLevel } from '../../lib/utils/create_logger'; +import { RunCliFlags } from '../run'; import { intervalToMs } from './interval_to_ms'; -import { createLogger, LogLevel } from '../../lib/utils/create_logger'; -export async function getCommonResources({ - file, - interval, - bucketSize, - target, - logLevel, -}: { - file: string; - interval: string; - bucketSize: string; - target: string; - logLevel: string; -}) { +export function parseRunCliFlags(flags: RunCliFlags) { + const { file, _, logLevel, interval, bucketSize } = flags; + + const parsedFile = String(file || _[0]); + let parsedLogLevel = LogLevel.info; switch (logLevel) { case 'trace': @@ -44,8 +35,6 @@ export async function getCommonResources({ break; } - const logger = createLogger(parsedLogLevel); - const intervalInMs = intervalToMs(interval); if (!intervalInMs) { throw new Error('Invalid interval'); @@ -57,22 +46,13 @@ export async function getCommonResources({ throw new Error('Invalid bucket size'); } - const client = new Client({ - node: target, - }); - - const [scenario, writeTargets] = await Promise.all([ - getScenario({ file, logger }), - getWriteTargets({ client }), - ]); - return { - scenario, - writeTargets, - logger, - client, + ...pick(flags, 'target', 'workers', 'clientWorkers', 'batchSize', 'writeTarget'), intervalInMs, bucketSizeInMs, logLevel: parsedLogLevel, + file: parsedFile, }; } + +export type RunOptions = ReturnType; diff --git a/packages/elastic-apm-synthtrace/src/scripts/utils/start_historical_data_upload.ts b/packages/elastic-apm-synthtrace/src/scripts/utils/start_historical_data_upload.ts index dc568170a9744..dd848d9f66c63 100644 --- a/packages/elastic-apm-synthtrace/src/scripts/utils/start_historical_data_upload.ts +++ b/packages/elastic-apm-synthtrace/src/scripts/utils/start_historical_data_upload.ts @@ -5,41 +5,30 @@ * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ -import { Client } from '@elastic/elasticsearch'; import pLimit from 'p-limit'; import Path from 'path'; import { Worker } from 'worker_threads'; -import { ElasticsearchOutputWriteTargets } from '../../lib/output/to_elasticsearch_output'; -import { Logger, LogLevel } from '../../lib/utils/create_logger'; +import { getCommonServices } from './get_common_services'; +import { RunOptions } from './parse_run_cli_flags'; +import { WorkerData } from './upload_next_batch'; export async function startHistoricalDataUpload({ from, to, + intervalInMs, bucketSizeInMs, workers, clientWorkers, batchSize, - writeTargets, logLevel, - logger, target, file, -}: { - from: number; - to: number; - bucketSizeInMs: number; - client: Client; - workers: number; - clientWorkers: number; - batchSize: number; - writeTargets: ElasticsearchOutputWriteTargets; - logger: Logger; - logLevel: LogLevel; - target: string; - file: string; -}) { + writeTarget, +}: RunOptions & { from: number; to: number }) { let requestedUntil: number = from; + const { logger } = getCommonServices({ target, logLevel }); + function processNextBatch() { const bucketFrom = requestedUntil; const bucketTo = Math.min(to, bucketFrom + bucketSizeInMs); @@ -56,17 +45,22 @@ export async function startHistoricalDataUpload({ ).toISOString()}` ); + const workerData: WorkerData = { + bucketFrom, + bucketTo, + file, + logLevel, + batchSize, + bucketSizeInMs, + clientWorkers, + intervalInMs, + target, + workers, + writeTarget, + }; + const worker = new Worker(Path.join(__dirname, './upload_next_batch.js'), { - workerData: { - bucketFrom, - bucketTo, - logLevel, - writeTargets, - target, - file, - clientWorkers, - batchSize, - }, + workerData, }); logger.perf('created_worker', () => { diff --git a/packages/elastic-apm-synthtrace/src/scripts/utils/start_live_data_upload.ts b/packages/elastic-apm-synthtrace/src/scripts/utils/start_live_data_upload.ts index cec0970420d16..3610ffae3c7e6 100644 --- a/packages/elastic-apm-synthtrace/src/scripts/utils/start_live_data_upload.ts +++ b/packages/elastic-apm-synthtrace/src/scripts/utils/start_live_data_upload.ts @@ -6,44 +6,49 @@ * Side Public License, v 1. */ -import { Client } from '@elastic/elasticsearch'; import { partition } from 'lodash'; -import { Fields } from '../../lib/entity'; -import { ElasticsearchOutputWriteTargets } from '../../lib/output/to_elasticsearch_output'; -import { Scenario } from './get_scenario'; -import { Logger } from '../../lib/utils/create_logger'; +import { getScenario } from './get_scenario'; import { uploadEvents } from './upload_events'; +import { RunOptions } from './parse_run_cli_flags'; +import { getCommonServices } from './get_common_services'; +import { ElasticsearchOutput } from '../../lib/utils/to_elasticsearch_output'; -export function startLiveDataUpload({ +export async function startLiveDataUpload({ + file, start, bucketSizeInMs, intervalInMs, clientWorkers, batchSize, - writeTargets, - scenario, - client, - logger, -}: { - start: number; - bucketSizeInMs: number; - intervalInMs: number; - clientWorkers: number; - batchSize: number; - writeTargets: ElasticsearchOutputWriteTargets; - scenario: Scenario; - client: Client; - logger: Logger; -}) { - let queuedEvents: Fields[] = []; + target, + logLevel, + workers, + writeTarget, +}: RunOptions & { start: number }) { + let queuedEvents: ElasticsearchOutput[] = []; let requestedUntil: number = start; + const { logger, client } = getCommonServices({ target, logLevel }); + + const scenario = await getScenario({ file, logger }); + const { generate } = await scenario({ + batchSize, + bucketSizeInMs, + clientWorkers, + file, + intervalInMs, + logLevel, + target, + workers, + writeTarget, + }); + function uploadNextBatch() { const end = new Date().getTime(); if (end > requestedUntil) { const bucketFrom = requestedUntil; const bucketTo = requestedUntil + bucketSizeInMs; - const nextEvents = scenario({ from: bucketFrom, to: bucketTo }); + const nextEvents = generate({ from: bucketFrom, to: bucketTo }); logger.debug( `Requesting ${new Date(bucketFrom).toISOString()} to ${new Date( bucketTo @@ -55,7 +60,7 @@ export function startLiveDataUpload({ const [eventsToUpload, eventsToRemainInQueue] = partition( queuedEvents, - (event) => event['@timestamp']! <= end + (event) => event.timestamp <= end ); logger.info(`Uploading until ${new Date(end).toISOString()}, events: ${eventsToUpload.length}`); @@ -64,11 +69,10 @@ export function startLiveDataUpload({ uploadEvents({ events: eventsToUpload, - client, clientWorkers, batchSize, - writeTargets, logger, + client, }); } diff --git a/packages/elastic-apm-synthtrace/src/scripts/utils/upload_events.ts b/packages/elastic-apm-synthtrace/src/scripts/utils/upload_events.ts index 7382948525986..d68a1b88132bb 100644 --- a/packages/elastic-apm-synthtrace/src/scripts/utils/upload_events.ts +++ b/packages/elastic-apm-synthtrace/src/scripts/utils/upload_events.ts @@ -9,11 +9,7 @@ import { Client } from '@elastic/elasticsearch'; import { chunk } from 'lodash'; import pLimit from 'p-limit'; import { inspect } from 'util'; -import { Fields } from '../../lib/entity'; -import { - ElasticsearchOutputWriteTargets, - toElasticsearchOutput, -} from '../../lib/output/to_elasticsearch_output'; +import { ElasticsearchOutput } from '../../lib/utils/to_elasticsearch_output'; import { Logger } from '../../lib/utils/create_logger'; export function uploadEvents({ @@ -21,24 +17,23 @@ export function uploadEvents({ client, clientWorkers, batchSize, - writeTargets, logger, }: { - events: Fields[]; + events: ElasticsearchOutput[]; client: Client; clientWorkers: number; batchSize: number; - writeTargets: ElasticsearchOutputWriteTargets; logger: Logger; }) { - const esDocuments = logger.perf('to_elasticsearch_output', () => { - return toElasticsearchOutput({ events, writeTargets }); - }); const fn = pLimit(clientWorkers); - const batches = chunk(esDocuments, batchSize); + const batches = chunk(events, batchSize); + + if (!batches.length) { + return; + } - logger.debug(`Uploading ${esDocuments.length} in ${batches.length} batches`); + logger.debug(`Uploading ${events.length} in ${batches.length} batches`); const time = new Date().getTime(); @@ -47,7 +42,6 @@ export function uploadEvents({ fn(() => { return logger.perf('bulk_upload', () => client.bulk({ - require_alias: true, refresh: false, body: batch.flatMap((doc) => { return [{ index: { _index: doc._index } }, doc._source]; diff --git a/packages/elastic-apm-synthtrace/src/scripts/utils/upload_next_batch.ts b/packages/elastic-apm-synthtrace/src/scripts/utils/upload_next_batch.ts index 2fe5f9b6a6d61..c25fc7ca9f1c2 100644 --- a/packages/elastic-apm-synthtrace/src/scripts/utils/upload_next_batch.ts +++ b/packages/elastic-apm-synthtrace/src/scripts/utils/upload_next_batch.ts @@ -9,22 +9,37 @@ // add this to workerExample.js file. import { Client } from '@elastic/elasticsearch'; import { workerData } from 'worker_threads'; -import { ElasticsearchOutputWriteTargets } from '../../lib/output/to_elasticsearch_output'; import { getScenario } from './get_scenario'; import { createLogger, LogLevel } from '../../lib/utils/create_logger'; import { uploadEvents } from './upload_events'; -const { bucketFrom, bucketTo, file, logLevel, target, writeTargets, clientWorkers, batchSize } = - workerData as { - bucketFrom: number; - bucketTo: number; - file: string; - logLevel: LogLevel; - target: string; - writeTargets: ElasticsearchOutputWriteTargets; - clientWorkers: number; - batchSize: number; - }; +export interface WorkerData { + bucketFrom: number; + bucketTo: number; + file: string; + logLevel: LogLevel; + clientWorkers: number; + batchSize: number; + intervalInMs: number; + bucketSizeInMs: number; + target: string; + workers: number; + writeTarget?: string; +} + +const { + bucketFrom, + bucketTo, + file, + logLevel, + clientWorkers, + batchSize, + intervalInMs, + bucketSizeInMs, + workers, + target, + writeTarget, +} = workerData as WorkerData; async function uploadNextBatch() { if (bucketFrom === bucketTo) { @@ -38,8 +53,20 @@ async function uploadNextBatch() { const scenario = await logger.perf('get_scenario', () => getScenario({ file, logger })); + const { generate } = await scenario({ + intervalInMs, + bucketSizeInMs, + logLevel, + file, + clientWorkers, + batchSize, + target, + workers, + writeTarget, + }); + const events = logger.perf('execute_scenario', () => - scenario({ from: bucketFrom, to: bucketTo }) + generate({ from: bucketFrom, to: bucketTo }) ); return uploadEvents({ @@ -47,7 +74,6 @@ async function uploadNextBatch() { client, clientWorkers, batchSize, - writeTargets, logger, }); } @@ -56,6 +82,11 @@ uploadNextBatch() .then(() => { process.exit(0); }) - .catch(() => { - process.exit(1); + .catch((error) => { + // eslint-disable-next-line + console.log(error); + // make sure error shows up in console before process is killed + setTimeout(() => { + process.exit(1); + }, 100); }); diff --git a/packages/elastic-apm-synthtrace/src/test/to_elasticsearch_output.test.ts b/packages/elastic-apm-synthtrace/src/test/apm_events_to_elasticsearch_output.test.ts similarity index 76% rename from packages/elastic-apm-synthtrace/src/test/to_elasticsearch_output.test.ts rename to packages/elastic-apm-synthtrace/src/test/apm_events_to_elasticsearch_output.test.ts index 02d17f6b561ae..b8d0302558925 100644 --- a/packages/elastic-apm-synthtrace/src/test/to_elasticsearch_output.test.ts +++ b/packages/elastic-apm-synthtrace/src/test/apm_events_to_elasticsearch_output.test.ts @@ -6,8 +6,8 @@ * Side Public License, v 1. */ -import { Fields } from '../lib/entity'; -import { toElasticsearchOutput } from '../lib/output/to_elasticsearch_output'; +import { apmEventsToElasticsearchOutput } from '../lib/apm/utils/apm_events_to_elasticsearch_output'; +import { ApmFields } from '../lib/apm/apm_fields'; const writeTargets = { transaction: 'apm-8.0.0-transaction', @@ -16,8 +16,8 @@ const writeTargets = { error: 'apm-8.0.0-error', }; -describe('output to elasticsearch', () => { - let event: Fields; +describe('output apm events to elasticsearch', () => { + let event: ApmFields; beforeEach(() => { event = { @@ -29,13 +29,13 @@ describe('output to elasticsearch', () => { }); it('properly formats @timestamp', () => { - const doc = toElasticsearchOutput({ events: [event], writeTargets })[0] as any; + const doc = apmEventsToElasticsearchOutput({ events: [event], writeTargets })[0] as any; expect(doc._source['@timestamp']).toEqual('2020-12-31T23:00:00.000Z'); }); it('formats a nested object', () => { - const doc = toElasticsearchOutput({ events: [event], writeTargets })[0] as any; + const doc = apmEventsToElasticsearchOutput({ events: [event], writeTargets })[0] as any; expect(doc._source.processor).toEqual({ event: 'transaction', @@ -44,7 +44,7 @@ describe('output to elasticsearch', () => { }); it('formats all fields consistently', () => { - const doc = toElasticsearchOutput({ events: [event], writeTargets })[0] as any; + const doc = apmEventsToElasticsearchOutput({ events: [event], writeTargets })[0] as any; expect(doc._source).toMatchInlineSnapshot(` Object { diff --git a/packages/elastic-apm-synthtrace/src/test/scenarios/01_simple_trace.test.ts b/packages/elastic-apm-synthtrace/src/test/scenarios/01_simple_trace.test.ts index fc20202e210ff..b38d34266f3ac 100644 --- a/packages/elastic-apm-synthtrace/src/test/scenarios/01_simple_trace.test.ts +++ b/packages/elastic-apm-synthtrace/src/test/scenarios/01_simple_trace.test.ts @@ -6,14 +6,14 @@ * Side Public License, v 1. */ -import { service } from '../../lib/service'; +import { apm } from '../../lib/apm'; import { timerange } from '../../lib/timerange'; describe('simple trace', () => { let events: Array>; beforeEach(() => { - const javaService = service('opbeans-java', 'production', 'java'); + const javaService = apm.service('opbeans-java', 'production', 'java'); const javaInstance = javaService.instance('instance-1'); const range = timerange( diff --git a/packages/elastic-apm-synthtrace/src/test/scenarios/02_transaction_metrics.test.ts b/packages/elastic-apm-synthtrace/src/test/scenarios/02_transaction_metrics.test.ts index 58b28f71b9afc..d074bcbf6c1fd 100644 --- a/packages/elastic-apm-synthtrace/src/test/scenarios/02_transaction_metrics.test.ts +++ b/packages/elastic-apm-synthtrace/src/test/scenarios/02_transaction_metrics.test.ts @@ -6,15 +6,15 @@ * Side Public License, v 1. */ -import { service } from '../../lib/service'; +import { apm } from '../../lib/apm'; import { timerange } from '../../lib/timerange'; -import { getTransactionMetrics } from '../../lib/utils/get_transaction_metrics'; +import { getTransactionMetrics } from '../../lib/apm/utils/get_transaction_metrics'; describe('transaction metrics', () => { let events: Array>; beforeEach(() => { - const javaService = service('opbeans-java', 'production', 'java'); + const javaService = apm.service('opbeans-java', 'production', 'java'); const javaInstance = javaService.instance('instance-1'); const range = timerange( diff --git a/packages/elastic-apm-synthtrace/src/test/scenarios/03_span_destination_metrics.test.ts b/packages/elastic-apm-synthtrace/src/test/scenarios/03_span_destination_metrics.test.ts index 0bf59f044bf03..fe4734c65739c 100644 --- a/packages/elastic-apm-synthtrace/src/test/scenarios/03_span_destination_metrics.test.ts +++ b/packages/elastic-apm-synthtrace/src/test/scenarios/03_span_destination_metrics.test.ts @@ -6,15 +6,15 @@ * Side Public License, v 1. */ -import { service } from '../../lib/service'; +import { apm } from '../../lib/apm'; import { timerange } from '../../lib/timerange'; -import { getSpanDestinationMetrics } from '../../lib/utils/get_span_destination_metrics'; +import { getSpanDestinationMetrics } from '../../lib/apm/utils/get_span_destination_metrics'; describe('span destination metrics', () => { let events: Array>; beforeEach(() => { - const javaService = service('opbeans-java', 'production', 'java'); + const javaService = apm.service('opbeans-java', 'production', 'java'); const javaInstance = javaService.instance('instance-1'); const range = timerange( diff --git a/packages/elastic-apm-synthtrace/src/test/scenarios/04_breakdown_metrics.test.ts b/packages/elastic-apm-synthtrace/src/test/scenarios/04_breakdown_metrics.test.ts index 469f56b99c5f2..817f0aad9f5e4 100644 --- a/packages/elastic-apm-synthtrace/src/test/scenarios/04_breakdown_metrics.test.ts +++ b/packages/elastic-apm-synthtrace/src/test/scenarios/04_breakdown_metrics.test.ts @@ -6,13 +6,13 @@ * Side Public License, v 1. */ import { sumBy } from 'lodash'; -import { Fields } from '../../lib/entity'; -import { service } from '../../lib/service'; +import { apm } from '../../lib/apm'; import { timerange } from '../../lib/timerange'; -import { getBreakdownMetrics } from '../../lib/utils/get_breakdown_metrics'; +import { getBreakdownMetrics } from '../../lib/apm/utils/get_breakdown_metrics'; +import { ApmFields } from '../../lib/apm/apm_fields'; describe('breakdown metrics', () => { - let events: Fields[]; + let events: ApmFields[]; const LIST_RATE = 2; const LIST_SPANS = 2; @@ -21,7 +21,7 @@ describe('breakdown metrics', () => { const INTERVALS = 6; beforeEach(() => { - const javaService = service('opbeans-java', 'production', 'java'); + const javaService = apm.service('opbeans-java', 'production', 'java'); const javaInstance = javaService.instance('instance-1'); const start = new Date('2021-01-01T00:00:00.000Z').getTime(); diff --git a/packages/elastic-apm-synthtrace/src/test/scenarios/05_transactions_with_errors.test.ts b/packages/elastic-apm-synthtrace/src/test/scenarios/05_transactions_with_errors.test.ts index 63fdb691e8e5c..b9b12aeab0754 100644 --- a/packages/elastic-apm-synthtrace/src/test/scenarios/05_transactions_with_errors.test.ts +++ b/packages/elastic-apm-synthtrace/src/test/scenarios/05_transactions_with_errors.test.ts @@ -6,15 +6,15 @@ * Side Public License, v 1. */ import { pick } from 'lodash'; -import { service } from '../../index'; -import { Instance } from '../../lib/instance'; +import { apm } from '../../lib/apm'; +import { Instance } from '../../lib/apm/instance'; describe('transactions with errors', () => { let instance: Instance; const timestamp = new Date('2021-01-01T00:00:00.000Z').getTime(); beforeEach(() => { - instance = service('opbeans-java', 'production', 'java').instance('instance'); + instance = apm.service('opbeans-java', 'production', 'java').instance('instance'); }); it('generates error events', () => { const events = instance diff --git a/packages/elastic-apm-synthtrace/src/test/scenarios/06_application_metrics.test.ts b/packages/elastic-apm-synthtrace/src/test/scenarios/06_application_metrics.test.ts index 59ca8f0edbe88..7bae1e51f1ab3 100644 --- a/packages/elastic-apm-synthtrace/src/test/scenarios/06_application_metrics.test.ts +++ b/packages/elastic-apm-synthtrace/src/test/scenarios/06_application_metrics.test.ts @@ -6,15 +6,15 @@ * Side Public License, v 1. */ import { pick } from 'lodash'; -import { service } from '../../index'; -import { Instance } from '../../lib/instance'; +import { apm } from '../../lib/apm'; +import { Instance } from '../../lib/apm/instance'; describe('application metrics', () => { let instance: Instance; const timestamp = new Date('2021-01-01T00:00:00.000Z').getTime(); beforeEach(() => { - instance = service('opbeans-java', 'production', 'java').instance('instance'); + instance = apm.service('opbeans-java', 'production', 'java').instance('instance'); }); it('generates application metricsets', () => { const events = instance diff --git a/packages/kbn-alerts/BUILD.bazel b/packages/kbn-alerts/BUILD.bazel index e567c18807dfc..15dbc163cd288 100644 --- a/packages/kbn-alerts/BUILD.bazel +++ b/packages/kbn-alerts/BUILD.bazel @@ -1,10 +1,10 @@ -load("@npm//@bazel/typescript:index.bzl", "ts_config", "ts_project") -load("@build_bazel_rules_nodejs//:index.bzl", "js_library", "pkg_npm") -load("//src/dev/bazel:index.bzl", "jsts_transpiler") +load("@npm//@bazel/typescript:index.bzl", "ts_config") +load("@build_bazel_rules_nodejs//:index.bzl", "js_library") +load("//src/dev/bazel:index.bzl", "jsts_transpiler", "pkg_npm", "pkg_npm_types", "ts_project") PKG_BASE_NAME = "kbn-alerts" - PKG_REQUIRE_NAME = "@kbn/alerts" +TYPES_PKG_REQUIRE_NAME = "@types/kbn__alerts" SOURCE_FILES = glob( [ @@ -87,7 +87,7 @@ ts_project( js_library( name = PKG_BASE_NAME, srcs = NPM_MODULE_EXTRA_FILES, - deps = RUNTIME_DEPS + [":target_node", ":target_web", ":tsc_types"], + deps = RUNTIME_DEPS + [":target_node", ":target_web"], package_name = PKG_REQUIRE_NAME, visibility = ["//visibility:public"], ) @@ -106,3 +106,20 @@ filegroup( ], visibility = ["//visibility:public"], ) + +pkg_npm_types( + name = "npm_module_types", + srcs = SRCS, + deps = [":tsc_types"], + package_name = TYPES_PKG_REQUIRE_NAME, + tsconfig = ":tsconfig", + visibility = ["//visibility:public"], +) + +filegroup( + name = "build_types", + srcs = [ + ":npm_module_types", + ], + visibility = ["//visibility:public"], +) diff --git a/packages/kbn-alerts/package.json b/packages/kbn-alerts/package.json index b52a6efc35139..13b60ad9fa072 100644 --- a/packages/kbn-alerts/package.json +++ b/packages/kbn-alerts/package.json @@ -5,6 +5,5 @@ "license": "SSPL-1.0 OR Elastic License 2.0", "browser": "./target_web/index.js", "main": "./target_node/index.js", - "types": "./target_types/index.d.ts", "private": true } diff --git a/packages/kbn-babel-preset/common_preset.js b/packages/kbn-babel-preset/common_preset.js index 3a3763693db9a..824a73f9b2611 100644 --- a/packages/kbn-babel-preset/common_preset.js +++ b/packages/kbn-babel-preset/common_preset.js @@ -6,46 +6,57 @@ * Side Public License, v 1. */ -const plugins = [ - require.resolve('babel-plugin-add-module-exports'), - - // The class properties proposal was merged with the private fields proposal - // into the "class fields" proposal. Babel doesn't support this combined - // proposal yet, which includes private field, so this transform is - // TECHNICALLY stage 2, but for all intents and purposes it's stage 3 - // - // See https://github.com/babel/proposals/issues/12 for progress - require.resolve('@babel/plugin-proposal-class-properties'), - - // Optional Chaining proposal is stage 4 (https://github.com/tc39/proposal-optional-chaining) - // Need this since we are using TypeScript 3.7+ - require.resolve('@babel/plugin-proposal-optional-chaining'), - - // Nullish coalescing proposal is stage 4 (https://github.com/tc39/proposal-nullish-coalescing) - // Need this since we are using TypeScript 3.7+ - require.resolve('@babel/plugin-proposal-nullish-coalescing-operator'), - - // Proposal is on stage 4, and included in ECMA-262 (https://github.com/tc39/proposal-export-ns-from) - // Need this since we are using TypeScript 3.8+ - require.resolve('@babel/plugin-proposal-export-namespace-from'), - - // Proposal is on stage 4, and included in ECMA-262 (https://github.com/tc39/proposal-export-ns-from) - // Need this since we are using TypeScript 3.9+ - require.resolve('@babel/plugin-proposal-private-methods'), - - // It enables the @babel/runtime so we can decrease the bundle sizes of the produced outputs - [ - require.resolve('@babel/plugin-transform-runtime'), +module.exports = { + presets: [ + // plugins always run before presets, but in this case we need the + // @babel/preset-typescript preset to run first so we have to move + // our explicit plugin configs to a sub-preset { - version: '^7.12.5', + plugins: [ + require.resolve('babel-plugin-add-module-exports'), + + // The class properties proposal was merged with the private fields proposal + // into the "class fields" proposal. Babel doesn't support this combined + // proposal yet, which includes private field, so this transform is + // TECHNICALLY stage 2, but for all intents and purposes it's stage 3 + // + // See https://github.com/babel/proposals/issues/12 for progress + require.resolve('@babel/plugin-proposal-class-properties'), + + // Optional Chaining proposal is stage 4 (https://github.com/tc39/proposal-optional-chaining) + // Need this since we are using TypeScript 3.7+ + require.resolve('@babel/plugin-proposal-optional-chaining'), + + // Nullish coalescing proposal is stage 4 (https://github.com/tc39/proposal-nullish-coalescing) + // Need this since we are using TypeScript 3.7+ + require.resolve('@babel/plugin-proposal-nullish-coalescing-operator'), + + // Proposal is on stage 4, and included in ECMA-262 (https://github.com/tc39/proposal-export-ns-from) + // Need this since we are using TypeScript 3.8+ + require.resolve('@babel/plugin-proposal-export-namespace-from'), + + // Proposal is on stage 4, and included in ECMA-262 (https://github.com/tc39/proposal-export-ns-from) + // Need this since we are using TypeScript 3.9+ + require.resolve('@babel/plugin-proposal-private-methods'), + + // It enables the @babel/runtime so we can decrease the bundle sizes of the produced outputs + [ + require.resolve('@babel/plugin-transform-runtime'), + { + version: '^7.12.5', + }, + ], + ], }, - ], -]; -module.exports = { - presets: [ - [require.resolve('@babel/preset-typescript'), { allowNamespaces: true }], require.resolve('@babel/preset-react'), + + [ + require.resolve('@babel/preset-typescript'), + { + allowNamespaces: true, + allowDeclareFields: true, + }, + ], ], - plugins, }; diff --git a/packages/kbn-es-query/src/filters/build_filters/phrase_filter.test.ts b/packages/kbn-es-query/src/filters/build_filters/phrase_filter.test.ts index 87a7812165a66..7c7f7dd28f6ca 100644 --- a/packages/kbn-es-query/src/filters/build_filters/phrase_filter.test.ts +++ b/packages/kbn-es-query/src/filters/build_filters/phrase_filter.test.ts @@ -5,16 +5,19 @@ * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ - +import { set } from 'lodash'; import { buildInlineScriptForPhraseFilter, buildPhraseFilter, getPhraseFilterField, PhraseFilter, + isPhraseFilter, + isScriptedPhraseFilter, } from './phrase_filter'; import { fields, getField } from '../stubs'; import { DataViewBase } from '../../es_query'; import * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; +import { Filter } from './types'; describe('Phrase filter builder', () => { let indexPattern: DataViewBase; @@ -164,3 +167,23 @@ describe('getPhraseFilterField', function () { expect(result).toBe('extension'); }); }); + +describe('isPhraseFilter', () => { + it('should return true if the filter is a phrases filter false otherwise', () => { + const filter: Filter = set({ meta: {} }, 'query.match_phrase', {}) as Filter; + const unknownFilter = {} as Filter; + + expect(isPhraseFilter(filter)).toBe(true); + expect(isPhraseFilter(unknownFilter)).toBe(false); + }); +}); + +describe('isScriptedPhraseFilter', () => { + it('should return true if the filter is a phrases filter false otherwise', () => { + const filter: Filter = set({ meta: {} }, 'query.script.script.params.value', {}) as Filter; + const unknownFilter = {} as Filter; + + expect(isScriptedPhraseFilter(filter)).toBe(true); + expect(isPhraseFilter(unknownFilter)).toBe(false); + }); +}); diff --git a/packages/kbn-es-query/src/filters/build_filters/phrase_filter.ts b/packages/kbn-es-query/src/filters/build_filters/phrase_filter.ts index 4c1827dc58c04..525463c9de246 100644 --- a/packages/kbn-es-query/src/filters/build_filters/phrase_filter.ts +++ b/packages/kbn-es-query/src/filters/build_filters/phrase_filter.ts @@ -31,8 +31,10 @@ export type PhraseFilter = Filter & { export type ScriptedPhraseFilter = Filter & { meta: PhraseFilterMeta; - script: { - script: estypes.InlineScript; + query: { + script: { + script: estypes.InlineScript; + }; }; }; @@ -58,7 +60,7 @@ export const isPhraseFilter = (filter: Filter): filter is PhraseFilter => { * @public */ export const isScriptedPhraseFilter = (filter: Filter): filter is ScriptedPhraseFilter => - has(filter, 'script.script.params.value'); + has(filter, 'query.script.script.params.value'); /** @internal */ export const getPhraseFilterField = (filter: PhraseFilter) => { @@ -77,7 +79,7 @@ export const getPhraseFilterValue = ( const queryValue = Object.values(queryConfig)[0]; return isPlainObject(queryValue) ? queryValue.query : queryValue; } else { - return filter.script.script.params?.value; + return filter.query?.script?.script?.params?.value; } }; diff --git a/packages/kbn-optimizer/src/babel_runtime_helpers/find_babel_runtime_helpers_in_entry_bundles.ts b/packages/kbn-optimizer/src/babel_runtime_helpers/find_babel_runtime_helpers_in_entry_bundles.ts index beff36023343d..f00905f3f4920 100644 --- a/packages/kbn-optimizer/src/babel_runtime_helpers/find_babel_runtime_helpers_in_entry_bundles.ts +++ b/packages/kbn-optimizer/src/babel_runtime_helpers/find_babel_runtime_helpers_in_entry_bundles.ts @@ -35,14 +35,14 @@ export async function runFindBabelHelpersInEntryBundlesCli() { } for (const { userRequest } of module.reasons) { - if (userRequest.startsWith('@babel/runtime/')) { + if (userRequest.startsWith('@babel/runtime')) { imports.add(userRequest); } } } } - log.success('found', imports.size, '@babel/register imports in entry bundles'); + log.success('found', imports.size, '@babel/runtime* imports in entry bundles'); log.write( Array.from(imports, (i) => `'${i}',`) .sort() diff --git a/packages/kbn-optimizer/src/worker/emit_stats_plugin.ts b/packages/kbn-optimizer/src/worker/emit_stats_plugin.ts index c964219e1fed6..5cb60344037fc 100644 --- a/packages/kbn-optimizer/src/worker/emit_stats_plugin.ts +++ b/packages/kbn-optimizer/src/worker/emit_stats_plugin.ts @@ -26,7 +26,7 @@ export class EmitStatsPlugin { (stats) => { Fs.writeFileSync( Path.resolve(this.bundle.outputDir, 'stats.json'), - JSON.stringify(stats.toJson()) + JSON.stringify(stats.toJson(), null, 2) ); } ); diff --git a/packages/kbn-storybook/src/webpack.config.ts b/packages/kbn-storybook/src/webpack.config.ts index 27e887eda65ce..53f9c82b86815 100644 --- a/packages/kbn-storybook/src/webpack.config.ts +++ b/packages/kbn-storybook/src/webpack.config.ts @@ -9,11 +9,13 @@ import { externals } from '@kbn/ui-shared-deps-src'; import { stringifyRequest } from 'loader-utils'; import { resolve } from 'path'; -import { Configuration, Stats } from 'webpack'; +import webpack, { Configuration, Stats } from 'webpack'; import webpackMerge from 'webpack-merge'; import { REPO_ROOT } from './lib/constants'; import { IgnoreNotFoundExportPlugin } from './ignore_not_found_export_plugin'; +type Preset = string | [string, Record] | Record; + const stats = { ...Stats.presetToOptions('minimal'), colors: true, @@ -22,6 +24,46 @@ const stats = { moduleTrace: true, }; +function isProgressPlugin(plugin: any) { + return 'handler' in plugin && plugin.showActiveModules && plugin.showModules; +} + +function isHtmlPlugin(plugin: any): plugin is { options: { template: string } } { + return !!(typeof plugin.options?.template === 'string'); +} + +function isBabelLoaderRule(rule: webpack.RuleSetRule): rule is webpack.RuleSetRule & { + use: webpack.RuleSetLoader[]; +} { + return !!( + rule.use && + Array.isArray(rule.use) && + rule.use.some( + (l) => + typeof l === 'object' && typeof l.loader === 'string' && l.loader.includes('babel-loader') + ) + ); +} + +function getPresetPath(preset: Preset) { + if (typeof preset === 'string') return preset; + if (Array.isArray(preset)) return preset[0]; + return undefined; +} + +function getTsPreset(preset: Preset) { + if (getPresetPath(preset)?.includes('preset-typescript')) { + if (typeof preset === 'string') return [preset, {}]; + if (Array.isArray(preset)) return preset; + + throw new Error('unsupported preset-typescript format'); + } +} + +function isDesiredPreset(preset: Preset) { + return !getPresetPath(preset)?.includes('preset-flow'); +} + // Extend the Storybook Webpack config with some customizations /* eslint-disable import/no-default-export */ export default function ({ config: storybookConfig }: { config: Configuration }) { @@ -83,21 +125,72 @@ export default function ({ config: storybookConfig }: { config: Configuration }) stats, }; - // Disable the progress plugin - const progressPlugin: any = (storybookConfig.plugins || []).find((plugin: any) => { - return 'handler' in plugin && plugin.showActiveModules && plugin.showModules; - }); - progressPlugin.handler = () => {}; - - // This is the hacky part. We find something that looks like the - // HtmlWebpackPlugin and mutate its `options.template` to point at our - // revised template. - const htmlWebpackPlugin: any = (storybookConfig.plugins || []).find((plugin: any) => { - return plugin.options && typeof plugin.options.template === 'string'; - }); - if (htmlWebpackPlugin) { - htmlWebpackPlugin.options.template = require.resolve('../templates/index.ejs'); + const updatedModuleRules = []; + // clone and modify the module.rules config provided by storybook so that the default babel plugins run after the typescript preset + for (const originalRule of storybookConfig.module?.rules ?? []) { + const rule = { ...originalRule }; + updatedModuleRules.push(rule); + + if (isBabelLoaderRule(rule)) { + rule.use = [...rule.use]; + const loader = (rule.use[0] = { ...rule.use[0] }); + const options = (loader.options = { ...(loader.options as Record) }); + + // capture the plugins defined at the root level + const plugins: string[] = options.plugins; + options.plugins = []; + + // move the plugins to the top of the preset array so they will run after the typescript preset + options.presets = [ + { + plugins, + }, + ...(options.presets as Preset[]).filter(isDesiredPreset).map((preset) => { + const tsPreset = getTsPreset(preset); + if (!tsPreset) { + return preset; + } + + return [ + tsPreset[0], + { + ...tsPreset[1], + allowNamespaces: true, + allowDeclareFields: true, + }, + ]; + }), + ]; + } } - return webpackMerge(storybookConfig, config); + // copy and modify the webpack plugins added by storybook + const filteredStorybookPlugins = []; + for (const plugin of storybookConfig.plugins ?? []) { + // Remove the progress plugin + if (isProgressPlugin(plugin)) { + continue; + } + + // This is the hacky part. We find something that looks like the + // HtmlWebpackPlugin and mutate its `options.template` to point at our + // revised template. + if (isHtmlPlugin(plugin)) { + plugin.options.template = require.resolve('../templates/index.ejs'); + } + + filteredStorybookPlugins.push(plugin); + } + + return webpackMerge( + { + ...storybookConfig, + plugins: filteredStorybookPlugins, + module: { + ...storybookConfig.module, + rules: updatedModuleRules, + }, + }, + config + ); } diff --git a/src/core/public/doc_links/doc_links_service.ts b/src/core/public/doc_links/doc_links_service.ts index 92b4c815f2249..5bc7691d6a40f 100644 --- a/src/core/public/doc_links/doc_links_service.ts +++ b/src/core/public/doc_links/doc_links_service.ts @@ -30,6 +30,9 @@ export class DocLinksService { const APM_DOCS = `${ELASTIC_WEBSITE_URL}guide/en/apm/`; const SECURITY_SOLUTION_DOCS = `${ELASTIC_WEBSITE_URL}guide/en/security/${DOC_LINK_VERSION}/`; const STACK_GETTING_STARTED = `${ELASTIC_WEBSITE_URL}guide/en/elastic-stack-get-started/${DOC_LINK_VERSION}/`; + const APP_SEARCH_DOCS = `${ELASTIC_WEBSITE_URL}guide/en/app-search/${DOC_LINK_VERSION}/`; + const ENTERPRISE_SEARCH_DOCS = `${ELASTIC_WEBSITE_URL}guide/en/enterprise-search/${DOC_LINK_VERSION}/`; + const WORKPLACE_SEARCH_DOCS = `${ELASTIC_WEBSITE_URL}guide/en/workplace-search/${DOC_LINK_VERSION}/`; return deepFreeze({ DOC_LINK_VERSION, @@ -51,6 +54,9 @@ export class DocLinksService { canvas: { guide: `${KIBANA_DOCS}canvas.html`, }, + cloud: { + indexManagement: `${ELASTIC_WEBSITE_URL}/guide/en/cloud/current/ec-configure-index-management.html`, + }, dashboard: { guide: `${KIBANA_DOCS}dashboard.html`, drilldowns: `${KIBANA_DOCS}drilldowns.html`, @@ -77,10 +83,64 @@ export class DocLinksService { auditdModule: `${ELASTIC_WEBSITE_URL}guide/en/beats/auditbeat/${DOC_LINK_VERSION}/auditbeat-module-auditd.html`, systemModule: `${ELASTIC_WEBSITE_URL}guide/en/beats/auditbeat/${DOC_LINK_VERSION}/auditbeat-module-system.html`, }, + appSearch: { + apiRef: `${APP_SEARCH_DOCS}api-reference.html`, + apiClients: `${APP_SEARCH_DOCS}api-clients.html`, + apiKeys: `${APP_SEARCH_DOCS}authentication.html#authentication-api-keys`, + authentication: `${APP_SEARCH_DOCS}authentication.html`, + crawlRules: `${APP_SEARCH_DOCS}crawl-web-content.html#crawl-web-content-manage-crawl-rules`, + curations: `${APP_SEARCH_DOCS}curations-guide.html`, + duplicateDocuments: `${APP_SEARCH_DOCS}web-crawler-reference.html#web-crawler-reference-content-deduplication`, + entryPoints: `${APP_SEARCH_DOCS}crawl-web-content.html#crawl-web-content-manage-entry-points`, + guide: `${APP_SEARCH_DOCS}index.html`, + indexingDocuments: `${APP_SEARCH_DOCS}indexing-documents-guide.html`, + indexingDocumentsSchema: `${APP_SEARCH_DOCS}indexing-documents-guide.html#indexing-documents-guide-schema`, + logSettings: `${APP_SEARCH_DOCS}logs.html`, + metaEngines: `${APP_SEARCH_DOCS}meta-engines-guide.html`, + nativeAuth: `${APP_SEARCH_DOCS}security-and-users.html#app-search-self-managed-security-and-user-management-elasticsearch-native-realm`, + precisionTuning: `${APP_SEARCH_DOCS}precision-tuning.html`, + relevanceTuning: `${APP_SEARCH_DOCS}relevance-tuning-guide.html`, + resultSettings: `${APP_SEARCH_DOCS}result-settings-guide.html`, + searchUI: `${APP_SEARCH_DOCS}reference-ui-guide.html`, + security: `${APP_SEARCH_DOCS}security-and-users.html`, + standardAuth: `${APP_SEARCH_DOCS}security-and-users.html#app-search-self-managed-security-and-user-management-standard`, + synonyms: `${APP_SEARCH_DOCS}synonyms-guide.html`, + webCrawler: `${APP_SEARCH_DOCS}web-crawler.html`, + webCrawlerEventLogs: `${APP_SEARCH_DOCS}view-web-crawler-events-logs.html`, + }, enterpriseSearch: { - base: `${ELASTIC_WEBSITE_URL}guide/en/enterprise-search/${DOC_LINK_VERSION}`, - appSearchBase: `${ELASTIC_WEBSITE_URL}guide/en/app-search/${DOC_LINK_VERSION}`, - workplaceSearchBase: `${ELASTIC_WEBSITE_URL}guide/en/workplace-search/${DOC_LINK_VERSION}`, + configuration: `${ENTERPRISE_SEARCH_DOCS}configuration.html`, + licenseManagement: `${ENTERPRISE_SEARCH_DOCS}license-management.html`, + mailService: `${ENTERPRISE_SEARCH_DOCS}mailer-configuration.html`, + usersAccess: `${ENTERPRISE_SEARCH_DOCS}users-access.html`, + }, + workplaceSearch: { + box: `${WORKPLACE_SEARCH_DOCS}workplace-search-box-connector.html`, + confluenceCloud: `${WORKPLACE_SEARCH_DOCS}workplace-search-confluence-cloud-connector.html`, + confluenceServer: `${WORKPLACE_SEARCH_DOCS}workplace-search-confluence-server-connector.html`, + customSources: `${WORKPLACE_SEARCH_DOCS}workplace-search-custom-api-sources.html`, + customSourcePermissions: `${WORKPLACE_SEARCH_DOCS}workplace-search-custom-api-sources.html#custom-api-source-document-level-access-control`, + documentPermissions: `${WORKPLACE_SEARCH_DOCS}workplace-search-sources-document-permissions.html`, + dropbox: `${WORKPLACE_SEARCH_DOCS}workplace-search-dropbox-connector.html`, + externalIdentities: `${WORKPLACE_SEARCH_DOCS}workplace-search-external-identities-api.html`, + gettingStarted: `${WORKPLACE_SEARCH_DOCS}workplace-search-getting-started.html`, + gitHub: `${WORKPLACE_SEARCH_DOCS}workplace-search-github-connector.html`, + gmail: `${WORKPLACE_SEARCH_DOCS}workplace-search-gmail-connector.html`, + googleDrive: `${WORKPLACE_SEARCH_DOCS}workplace-search-google-drive-connector.html`, + indexingSchedule: `${WORKPLACE_SEARCH_DOCS}workplace-search-customizing-indexing-rules.html#_indexing_schedule`, + jiraCloud: `${WORKPLACE_SEARCH_DOCS}workplace-search-jira-cloud-connector.html`, + jiraServer: `${WORKPLACE_SEARCH_DOCS}workplace-search-jira-server-connector.html`, + nativeAuth: `${WORKPLACE_SEARCH_DOCS}workplace-search-security.html#elasticsearch-native-realm`, + oneDrive: `${WORKPLACE_SEARCH_DOCS}workplace-search-onedrive-connector.html`, + permissions: `${WORKPLACE_SEARCH_DOCS}workplace-search-permissions.html#organizational-sources-private-sources`, + salesforce: `${WORKPLACE_SEARCH_DOCS}workplace-search-salesforce-connector.html`, + security: `${WORKPLACE_SEARCH_DOCS}workplace-search-security.html`, + serviceNow: `${WORKPLACE_SEARCH_DOCS}workplace-search-servicenow-connector.html`, + sharePoint: `${WORKPLACE_SEARCH_DOCS}workplace-search-sharepoint-online-connector.html`, + slack: `${WORKPLACE_SEARCH_DOCS}workplace-search-slack-connector.html`, + standardAuth: `${WORKPLACE_SEARCH_DOCS}workplace-search-security.html#standard`, + synch: `${WORKPLACE_SEARCH_DOCS}workplace-search-customizing-indexing-rules.html`, + zendesk: `${WORKPLACE_SEARCH_DOCS}workplace-search-zendesk-connector.html`, }, metricbeat: { base: `${ELASTIC_WEBSITE_URL}guide/en/beats/metricbeat/${DOC_LINK_VERSION}`, @@ -550,6 +610,9 @@ export interface DocLinksStart { readonly canvas: { readonly guide: string; }; + readonly cloud: { + readonly indexManagement: string; + }; readonly dashboard: { readonly guide: string; readonly drilldowns: string; @@ -581,10 +644,64 @@ export interface DocLinksStart { readonly install: string; readonly start: string; }; + readonly appSearch: { + readonly apiRef: string; + readonly apiClients: string; + readonly apiKeys: string; + readonly authentication: string; + readonly crawlRules: string; + readonly curations: string; + readonly duplicateDocuments: string; + readonly entryPoints: string; + readonly guide: string; + readonly indexingDocuments: string; + readonly indexingDocumentsSchema: string; + readonly logSettings: string; + readonly metaEngines: string; + readonly nativeAuth: string; + readonly precisionTuning: string; + readonly relevanceTuning: string; + readonly resultSettings: string; + readonly searchUI: string; + readonly security: string; + readonly standardAuth: string; + readonly synonyms: string; + readonly webCrawler: string; + readonly webCrawlerEventLogs: string; + }; readonly enterpriseSearch: { - readonly base: string; - readonly appSearchBase: string; - readonly workplaceSearchBase: string; + readonly configuration: string; + readonly licenseManagement: string; + readonly mailService: string; + readonly usersAccess: string; + }; + readonly workplaceSearch: { + readonly box: string; + readonly confluenceCloud: string; + readonly confluenceServer: string; + readonly customSources: string; + readonly customSourcePermissions: string; + readonly documentPermissions: string; + readonly dropbox: string; + readonly externalIdentities: string; + readonly gitHub: string; + readonly gettingStarted: string; + readonly gmail: string; + readonly googleDrive: string; + readonly indexingSchedule: string; + readonly jiraCloud: string; + readonly jiraServer: string; + readonly nativeAuth: string; + readonly oneDrive: string; + readonly permissions: string; + readonly salesforce: string; + readonly security: string; + readonly serviceNow: string; + readonly sharePoint: string; + readonly slack: string; + readonly standardAuth: string; + readonly synch: string; + readonly zendesk: string; }; readonly heartbeat: { readonly base: string; diff --git a/src/core/public/public.api.md b/src/core/public/public.api.md index 772faa5321d98..cec80af843c4c 100644 --- a/src/core/public/public.api.md +++ b/src/core/public/public.api.md @@ -506,6 +506,9 @@ export interface DocLinksStart { readonly canvas: { readonly guide: string; }; + readonly cloud: { + readonly indexManagement: string; + }; readonly dashboard: { readonly guide: string; readonly drilldowns: string; @@ -537,10 +540,64 @@ export interface DocLinksStart { readonly install: string; readonly start: string; }; + readonly appSearch: { + readonly apiRef: string; + readonly apiClients: string; + readonly apiKeys: string; + readonly authentication: string; + readonly crawlRules: string; + readonly curations: string; + readonly duplicateDocuments: string; + readonly entryPoints: string; + readonly guide: string; + readonly indexingDocuments: string; + readonly indexingDocumentsSchema: string; + readonly logSettings: string; + readonly metaEngines: string; + readonly nativeAuth: string; + readonly precisionTuning: string; + readonly relevanceTuning: string; + readonly resultSettings: string; + readonly searchUI: string; + readonly security: string; + readonly standardAuth: string; + readonly synonyms: string; + readonly webCrawler: string; + readonly webCrawlerEventLogs: string; + }; readonly enterpriseSearch: { - readonly base: string; - readonly appSearchBase: string; - readonly workplaceSearchBase: string; + readonly configuration: string; + readonly licenseManagement: string; + readonly mailService: string; + readonly usersAccess: string; + }; + readonly workplaceSearch: { + readonly box: string; + readonly confluenceCloud: string; + readonly confluenceServer: string; + readonly customSources: string; + readonly customSourcePermissions: string; + readonly documentPermissions: string; + readonly dropbox: string; + readonly externalIdentities: string; + readonly gitHub: string; + readonly gettingStarted: string; + readonly gmail: string; + readonly googleDrive: string; + readonly indexingSchedule: string; + readonly jiraCloud: string; + readonly jiraServer: string; + readonly nativeAuth: string; + readonly oneDrive: string; + readonly permissions: string; + readonly salesforce: string; + readonly security: string; + readonly serviceNow: string; + readonly sharePoint: string; + readonly slack: string; + readonly standardAuth: string; + readonly synch: string; + readonly zendesk: string; }; readonly heartbeat: { readonly base: string; diff --git a/src/plugins/console/public/services/history.ts b/src/plugins/console/public/services/history.ts index ee1e97ceb386e..972e5283274de 100644 --- a/src/plugins/console/public/services/history.ts +++ b/src/plugins/console/public/services/history.ts @@ -14,9 +14,11 @@ const MAX_NUMBER_OF_HISTORY_ITEMS = 100; export const isQuotaExceededError = (e: Error): boolean => e.name === 'QuotaExceededError'; export class History { - constructor(private readonly storage: Storage) {} + private changeEmitter: BehaviorSubject; - private changeEmitter = new BehaviorSubject(this.getHistory() || []); + constructor(private readonly storage: Storage) { + this.changeEmitter = new BehaviorSubject(this.getHistory() || []); + } getHistoryKeys() { return this.storage diff --git a/src/plugins/dashboard/kibana.json b/src/plugins/dashboard/kibana.json index cb6a5383688dc..2be6e9b269e71 100644 --- a/src/plugins/dashboard/kibana.json +++ b/src/plugins/dashboard/kibana.json @@ -13,6 +13,7 @@ "navigation", "savedObjects", "share", + "screenshotMode", "uiActions", "urlForwarding", "presentationUtil", diff --git a/src/plugins/dashboard/public/application/actions/add_to_library_action.test.tsx b/src/plugins/dashboard/public/application/actions/add_to_library_action.test.tsx index fa484de2180b4..40f6f872535f9 100644 --- a/src/plugins/dashboard/public/application/actions/add_to_library_action.test.tsx +++ b/src/plugins/dashboard/public/application/actions/add_to_library_action.test.tsx @@ -15,6 +15,7 @@ import { CoreStart } from 'kibana/public'; import { coreMock, uiSettingsServiceMock } from '../../../../../core/public/mocks'; import { embeddablePluginMock } from 'src/plugins/embeddable/public/mocks'; import { getStubPluginServices } from '../../../../presentation_util/public'; +import { screenshotModePluginMock } from '../../../../screenshot_mode/public/mocks'; import { EmbeddableInput, @@ -65,6 +66,7 @@ beforeEach(async () => { uiSettings: uiSettingsServiceMock.createStartContract(), http: coreStart.http, presentationUtil: getStubPluginServices(), + screenshotMode: screenshotModePluginMock.createSetupContract(), }; container = new DashboardContainer(getSampleDashboardInput(), containerOptions); diff --git a/src/plugins/dashboard/public/application/actions/clone_panel_action.test.tsx b/src/plugins/dashboard/public/application/actions/clone_panel_action.test.tsx index 99665d312d32e..fc4c6b299284b 100644 --- a/src/plugins/dashboard/public/application/actions/clone_panel_action.test.tsx +++ b/src/plugins/dashboard/public/application/actions/clone_panel_action.test.tsx @@ -23,6 +23,7 @@ import { } from '../../services/embeddable_test_samples'; import { ErrorEmbeddable, IContainer, isErrorEmbeddable } from '../../services/embeddable'; import { getStubPluginServices } from '../../../../presentation_util/public'; +import { screenshotModePluginMock } from '../../../../screenshot_mode/public/mocks'; const { setup, doStart } = embeddablePluginMock.createInstance(); setup.registerEmbeddableFactory( @@ -56,6 +57,7 @@ beforeEach(async () => { uiSettings: uiSettingsServiceMock.createStartContract(), http: coreStart.http, presentationUtil: getStubPluginServices(), + screenshotMode: screenshotModePluginMock.createSetupContract(), }; const input = getSampleDashboardInput({ panels: { diff --git a/src/plugins/dashboard/public/application/actions/expand_panel_action.test.tsx b/src/plugins/dashboard/public/application/actions/expand_panel_action.test.tsx index 0635152332993..b20a96c79aed6 100644 --- a/src/plugins/dashboard/public/application/actions/expand_panel_action.test.tsx +++ b/src/plugins/dashboard/public/application/actions/expand_panel_action.test.tsx @@ -13,6 +13,7 @@ import { getSampleDashboardInput, getSampleDashboardPanel } from '../test_helper import { embeddablePluginMock } from 'src/plugins/embeddable/public/mocks'; import { isErrorEmbeddable } from '../../services/embeddable'; import { getStubPluginServices } from '../../../../presentation_util/public'; +import { screenshotModePluginMock } from '../../../../screenshot_mode/public/mocks'; import { CONTACT_CARD_EMBEDDABLE, @@ -48,6 +49,7 @@ beforeEach(async () => { uiSettings: uiSettingsServiceMock.createStartContract(), http: coreMock.createStart().http, presentationUtil: getStubPluginServices(), + screenshotMode: screenshotModePluginMock.createSetupContract(), }; const input = getSampleDashboardInput({ panels: { diff --git a/src/plugins/dashboard/public/application/actions/export_csv_action.test.tsx b/src/plugins/dashboard/public/application/actions/export_csv_action.test.tsx index 51c64f1875376..797765eda232d 100644 --- a/src/plugins/dashboard/public/application/actions/export_csv_action.test.tsx +++ b/src/plugins/dashboard/public/application/actions/export_csv_action.test.tsx @@ -25,6 +25,7 @@ import { DataPublicPluginStart } from '../../../../data/public/types'; import { dataPluginMock } from '../../../../data/public/mocks'; import { LINE_FEED_CHARACTER } from 'src/plugins/data/common/exports/export_csv'; import { getStubPluginServices } from '../../../../presentation_util/public'; +import { screenshotModePluginMock } from '../../../../screenshot_mode/public/mocks'; describe('Export CSV action', () => { const { setup, doStart } = embeddablePluginMock.createInstance(); @@ -61,6 +62,7 @@ describe('Export CSV action', () => { uiSettings: uiSettingsServiceMock.createStartContract(), http: coreStart.http, presentationUtil: getStubPluginServices(), + screenshotMode: screenshotModePluginMock.createSetupContract(), }; const input = getSampleDashboardInput({ panels: { diff --git a/src/plugins/dashboard/public/application/actions/library_notification_action.test.tsx b/src/plugins/dashboard/public/application/actions/library_notification_action.test.tsx index 587f741461bb4..ab442bf839e37 100644 --- a/src/plugins/dashboard/public/application/actions/library_notification_action.test.tsx +++ b/src/plugins/dashboard/public/application/actions/library_notification_action.test.tsx @@ -28,6 +28,7 @@ import { CONTACT_CARD_EMBEDDABLE, } from '../../services/embeddable_test_samples'; import { getStubPluginServices } from '../../../../presentation_util/public'; +import { screenshotModePluginMock } from '../../../../screenshot_mode/public/mocks'; const { setup, doStart } = embeddablePluginMock.createInstance(); setup.registerEmbeddableFactory( @@ -62,6 +63,7 @@ beforeEach(async () => { uiSettings: uiSettingsServiceMock.createStartContract(), http: coreStart.http, presentationUtil: getStubPluginServices(), + screenshotMode: screenshotModePluginMock.createSetupContract(), }; container = new DashboardContainer(getSampleDashboardInput(), containerOptions); diff --git a/src/plugins/dashboard/public/application/actions/library_notification_popover.test.tsx b/src/plugins/dashboard/public/application/actions/library_notification_popover.test.tsx index b5efa0447e651..de1a475fdbd18 100644 --- a/src/plugins/dashboard/public/application/actions/library_notification_popover.test.tsx +++ b/src/plugins/dashboard/public/application/actions/library_notification_popover.test.tsx @@ -29,6 +29,7 @@ import { ContactCardEmbeddable, } from '../../services/embeddable_test_samples'; import { getStubPluginServices } from '../../../../presentation_util/public'; +import { screenshotModePluginMock } from '../../../../screenshot_mode/public/mocks'; describe('LibraryNotificationPopover', () => { const { setup, doStart } = embeddablePluginMock.createInstance(); @@ -58,6 +59,7 @@ describe('LibraryNotificationPopover', () => { uiSettings: uiSettingsServiceMock.createStartContract(), http: coreStart.http, presentationUtil: getStubPluginServices(), + screenshotMode: screenshotModePluginMock.createSetupContract(), }; container = new DashboardContainer(getSampleDashboardInput(), containerOptions); diff --git a/src/plugins/dashboard/public/application/actions/replace_panel_action.test.tsx b/src/plugins/dashboard/public/application/actions/replace_panel_action.test.tsx index f8880ac5618fc..fe39f6112a7f3 100644 --- a/src/plugins/dashboard/public/application/actions/replace_panel_action.test.tsx +++ b/src/plugins/dashboard/public/application/actions/replace_panel_action.test.tsx @@ -22,6 +22,7 @@ import { ContactCardEmbeddableOutput, } from '../../services/embeddable_test_samples'; import { getStubPluginServices } from '../../../../presentation_util/public'; +import { screenshotModePluginMock } from '../../../../screenshot_mode/public/mocks'; const { setup, doStart } = embeddablePluginMock.createInstance(); setup.registerEmbeddableFactory( @@ -48,6 +49,7 @@ beforeEach(async () => { uiSettings: uiSettingsServiceMock.createStartContract(), http: coreStart.http, presentationUtil: getStubPluginServices(), + screenshotMode: screenshotModePluginMock.createSetupContract(), }; const input = getSampleDashboardInput({ panels: { diff --git a/src/plugins/dashboard/public/application/actions/unlink_from_library_action.test.tsx b/src/plugins/dashboard/public/application/actions/unlink_from_library_action.test.tsx index 7d87c49bda649..4f10f833f643c 100644 --- a/src/plugins/dashboard/public/application/actions/unlink_from_library_action.test.tsx +++ b/src/plugins/dashboard/public/application/actions/unlink_from_library_action.test.tsx @@ -30,6 +30,7 @@ import { CONTACT_CARD_EMBEDDABLE, } from '../../services/embeddable_test_samples'; import { getStubPluginServices } from '../../../../presentation_util/public'; +import { screenshotModePluginMock } from '../../../../screenshot_mode/public/mocks'; const { setup, doStart } = embeddablePluginMock.createInstance(); setup.registerEmbeddableFactory( @@ -57,6 +58,7 @@ beforeEach(async () => { uiSettings: uiSettingsServiceMock.createStartContract(), http: coreStart.http, presentationUtil: getStubPluginServices(), + screenshotMode: screenshotModePluginMock.createSetupContract(), }; container = new DashboardContainer(getSampleDashboardInput(), containerOptions); diff --git a/src/plugins/dashboard/public/application/dashboard_app.tsx b/src/plugins/dashboard/public/application/dashboard_app.tsx index 3e6566f0da0a4..7aedbe9e11001 100644 --- a/src/plugins/dashboard/public/application/dashboard_app.tsx +++ b/src/plugins/dashboard/public/application/dashboard_app.tsx @@ -17,11 +17,11 @@ import { getDashboardTitle, leaveConfirmStrings, } from '../dashboard_strings'; -import { EmbeddableRenderer } from '../services/embeddable'; +import { createDashboardEditUrl } from '../dashboard_constants'; +import { EmbeddableRenderer, ViewMode } from '../services/embeddable'; import { DashboardTopNav, isCompleteDashboardAppState } from './top_nav/dashboard_top_nav'; import { DashboardAppServices, DashboardEmbedSettings, DashboardRedirect } from '../types'; import { createKbnUrlStateStorage, withNotifyOnErrors } from '../services/kibana_utils'; -import { createDashboardEditUrl } from '../dashboard_constants'; export interface DashboardAppProps { history: History; savedDashboardId?: string; @@ -51,7 +51,6 @@ export function DashboardApp({ const dashboardState = useDashboardSelector((state) => state.dashboardStateReducer); const dashboardAppState = useDashboardAppState({ history, - redirectTo, savedDashboardId, kbnUrlStateStorage, isEmbeddedExternally: Boolean(embedSettings), @@ -101,15 +100,26 @@ export function DashboardApp({ }; }, [data.search.session]); + const printMode = useMemo( + () => dashboardAppState.getLatestDashboardState?.().viewMode === ViewMode.PRINT, + [dashboardAppState] + ); + + useEffect(() => { + if (!embedSettings) chrome.setIsVisible(!printMode); + }, [chrome, printMode, embedSettings]); + return ( <> {isCompleteDashboardAppState(dashboardAppState) && ( <> - + {!printMode && ( + + )} {dashboardAppState.savedDashboard.outcome === 'conflict' && dashboardAppState.savedDashboard.id && diff --git a/src/plugins/dashboard/public/application/dashboard_router.tsx b/src/plugins/dashboard/public/application/dashboard_router.tsx index 4a22899c12e8a..c74ac506e4809 100644 --- a/src/plugins/dashboard/public/application/dashboard_router.tsx +++ b/src/plugins/dashboard/public/application/dashboard_router.tsx @@ -109,6 +109,7 @@ export async function mountApp({ embeddable: embeddableStart, uiSettings: coreStart.uiSettings, scopedHistory: () => scopedHistory, + screenshotModeService: screenshotMode, indexPatterns: dataStart.indexPatterns, savedQueryService: dataStart.query.savedQueries, savedObjectsClient: coreStart.savedObjects.client, @@ -131,7 +132,6 @@ export async function mountApp({ activeSpaceId || 'default' ), spacesService: spacesApi, - screenshotModeService: screenshotMode, }; const getUrlStateStorage = (history: RouteComponentProps['history']) => diff --git a/src/plugins/dashboard/public/application/embeddable/dashboard_container.test.tsx b/src/plugins/dashboard/public/application/embeddable/dashboard_container.test.tsx index 6cd102a4d4770..744d63c1ba04a 100644 --- a/src/plugins/dashboard/public/application/embeddable/dashboard_container.test.tsx +++ b/src/plugins/dashboard/public/application/embeddable/dashboard_container.test.tsx @@ -43,11 +43,13 @@ import { getStubPluginServices } from '../../../../presentation_util/public'; const presentationUtil = getStubPluginServices(); const options: DashboardContainerServices = { + // TODO: clean up use of any application: {} as any, embeddable: {} as any, notifications: {} as any, overlays: {} as any, inspector: {} as any, + screenshotMode: {} as any, SavedObjectFinder: () => null, ExitFullScreenButton: () => null, uiActions: {} as any, diff --git a/src/plugins/dashboard/public/application/embeddable/dashboard_container.tsx b/src/plugins/dashboard/public/application/embeddable/dashboard_container.tsx index 54fa1f05b9c0d..d7081bf020d85 100644 --- a/src/plugins/dashboard/public/application/embeddable/dashboard_container.tsx +++ b/src/plugins/dashboard/public/application/embeddable/dashboard_container.tsx @@ -40,6 +40,7 @@ import { import { PLACEHOLDER_EMBEDDABLE } from './placeholder'; import { DashboardAppCapabilities, DashboardContainerInput } from '../../types'; import { PresentationUtilPluginStart } from '../../services/presentation_util'; +import type { ScreenshotModePluginStart } from '../../services/screenshot_mode'; import { PanelPlacementMethod, IPanelPlacementArgs } from './panel/dashboard_panel_placement'; import { combineDashboardFiltersWithControlGroupFilters, @@ -55,6 +56,7 @@ export interface DashboardContainerServices { application: CoreStart['application']; inspector: InspectorStartContract; overlays: CoreStart['overlays']; + screenshotMode: ScreenshotModePluginStart; uiSettings: IUiSettingsClient; embeddable: EmbeddableStart; uiActions: UiActionsStart; diff --git a/src/plugins/dashboard/public/application/embeddable/grid/dashboard_grid.test.tsx b/src/plugins/dashboard/public/application/embeddable/grid/dashboard_grid.test.tsx index 52f04bcead665..7518a36433d35 100644 --- a/src/plugins/dashboard/public/application/embeddable/grid/dashboard_grid.test.tsx +++ b/src/plugins/dashboard/public/application/embeddable/grid/dashboard_grid.test.tsx @@ -23,6 +23,7 @@ import { } from '../../../services/embeddable_test_samples'; import { coreMock, uiSettingsServiceMock } from '../../../../../../core/public/mocks'; import { getStubPluginServices } from '../../../../../presentation_util/public'; +import { screenshotModePluginMock } from '../../../../../screenshot_mode/public/mocks'; let dashboardContainer: DashboardContainer | undefined; const presentationUtil = getStubPluginServices(); @@ -71,6 +72,7 @@ function prepare(props?: Partial) { uiSettings: uiSettingsServiceMock.createStartContract(), http: coreMock.createStart().http, presentationUtil, + screenshotMode: screenshotModePluginMock.createSetupContract(), }; dashboardContainer = new DashboardContainer(initialInput, options); const defaultTestProps: DashboardGridProps = { diff --git a/src/plugins/dashboard/public/application/embeddable/grid/dashboard_grid.tsx b/src/plugins/dashboard/public/application/embeddable/grid/dashboard_grid.tsx index 9d2afdba36dbc..09ac0c1dd94bb 100644 --- a/src/plugins/dashboard/public/application/embeddable/grid/dashboard_grid.tsx +++ b/src/plugins/dashboard/public/application/embeddable/grid/dashboard_grid.tsx @@ -154,7 +154,7 @@ class DashboardGridUi extends React.Component { id: 'dashboard.dashboardGrid.toast.unableToLoadDashboardDangerMessage', defaultMessage: 'Unable to load dashboard.', }), - body: error.message, + body: (error as { message: string }).message, toastLifeTimeMs: 5000, }); } @@ -254,6 +254,11 @@ class DashboardGridUi extends React.Component { /> )); + // in print mode, dashboard layout is not controlled by React Grid Layout + if (viewMode === ViewMode.PRINT) { + return <>{dashboardPanels}; + } + return ( ; type DivProps = Pick, 'className' | 'style' | 'children'>; @@ -20,6 +21,7 @@ type DivProps = Pick, 'className' | 'style' interface Props extends PanelProps, DivProps { id: DashboardPanelState['explicitInput']['id']; type: DashboardPanelState['type']; + container: DashboardContainer; focusedPanelId?: string; expandedPanelId?: string; key: string; @@ -52,6 +54,8 @@ const Item = React.forwardRef( 'dshDashboardGrid__item--expanded': expandPanel, // eslint-disable-next-line @typescript-eslint/naming-convention 'dshDashboardGrid__item--hidden': hidePanel, + // eslint-disable-next-line @typescript-eslint/naming-convention + printViewport__vis: container.getInput().viewMode === ViewMode.PRINT, }); return ( @@ -116,7 +120,8 @@ export const ObservedItem: FC = (props: Props) => { export const DashboardGridItem: FC = (props: Props) => { const { isProjectEnabled } = useLabs(); - const isEnabled = isProjectEnabled('labs:dashboard:deferBelowFold'); + const isPrintMode = props.container.getInput().viewMode === ViewMode.PRINT; + const isEnabled = !isPrintMode && isProjectEnabled('labs:dashboard:deferBelowFold'); return isEnabled ? : ; }; diff --git a/src/plugins/dashboard/public/application/embeddable/viewport/_index.scss b/src/plugins/dashboard/public/application/embeddable/viewport/_index.scss index 56483d9d10195..02411f5902b3b 100644 --- a/src/plugins/dashboard/public/application/embeddable/viewport/_index.scss +++ b/src/plugins/dashboard/public/application/embeddable/viewport/_index.scss @@ -1 +1,2 @@ @import './dashboard_viewport'; +@import './print_viewport'; diff --git a/src/plugins/dashboard/public/application/embeddable/viewport/_print_viewport.scss b/src/plugins/dashboard/public/application/embeddable/viewport/_print_viewport.scss new file mode 100644 index 0000000000000..a451178cc46b0 --- /dev/null +++ b/src/plugins/dashboard/public/application/embeddable/viewport/_print_viewport.scss @@ -0,0 +1,9 @@ +.printViewport { + &__vis { + height: 600px; // These values might need to be passed in as dimensions for the report. I.e., print should use layout dimensions. + width: 975px; + + // Some vertical space between vis, but center horizontally + margin: 10px auto; + } +} diff --git a/src/plugins/dashboard/public/application/embeddable/viewport/dashboard_viewport.test.tsx b/src/plugins/dashboard/public/application/embeddable/viewport/dashboard_viewport.test.tsx index 7c671ce7736d7..f0333cefd612f 100644 --- a/src/plugins/dashboard/public/application/embeddable/viewport/dashboard_viewport.test.tsx +++ b/src/plugins/dashboard/public/application/embeddable/viewport/dashboard_viewport.test.tsx @@ -27,6 +27,7 @@ import { CONTACT_CARD_EMBEDDABLE, } from '../../../../../embeddable/public/lib/test_samples'; import { getStubPluginServices } from '../../../../../presentation_util/public'; +import { screenshotModePluginMock } from '../../../../../screenshot_mode/public/mocks'; let dashboardContainer: DashboardContainer | undefined; const presentationUtil = getStubPluginServices(); @@ -65,6 +66,7 @@ function getProps(props?: Partial): { getTriggerCompatibleActions: (() => []) as any, } as any, presentationUtil, + screenshotMode: screenshotModePluginMock.createSetupContract(), }; const input = getSampleDashboardInput({ diff --git a/src/plugins/dashboard/public/application/embeddable/viewport/dashboard_viewport.tsx b/src/plugins/dashboard/public/application/embeddable/viewport/dashboard_viewport.tsx index 611a426dd4d71..1e19e495585fe 100644 --- a/src/plugins/dashboard/public/application/embeddable/viewport/dashboard_viewport.tsx +++ b/src/plugins/dashboard/public/application/embeddable/viewport/dashboard_viewport.tsx @@ -32,7 +32,7 @@ interface State { export class DashboardViewport extends React.Component { static contextType = context; - public readonly context!: DashboardReactContextValue; + public declare readonly context: DashboardReactContextValue; private controlsRoot: React.RefObject; diff --git a/src/plugins/dashboard/public/application/hooks/use_dashboard_app_state.test.tsx b/src/plugins/dashboard/public/application/hooks/use_dashboard_app_state.test.tsx index 5561d1676e41c..0ef21fca26f29 100644 --- a/src/plugins/dashboard/public/application/hooks/use_dashboard_app_state.test.tsx +++ b/src/plugins/dashboard/public/application/hooks/use_dashboard_app_state.test.tsx @@ -52,7 +52,6 @@ const createDashboardAppStateProps = (): UseDashboardStateProps => ({ savedDashboardId: 'testDashboardId', history: createBrowserHistory(), isEmbeddedExternally: false, - redirectTo: jest.fn(), }); const createDashboardAppStateServices = () => { diff --git a/src/plugins/dashboard/public/application/hooks/use_dashboard_app_state.ts b/src/plugins/dashboard/public/application/hooks/use_dashboard_app_state.ts index fddcc309e1ef1..cb5c7483f261a 100644 --- a/src/plugins/dashboard/public/application/hooks/use_dashboard_app_state.ts +++ b/src/plugins/dashboard/public/application/hooks/use_dashboard_app_state.ts @@ -22,7 +22,6 @@ import { DashboardBuildContext, DashboardAppServices, DashboardAppState, - DashboardRedirect, DashboardState, } from '../../types'; import { DashboardAppLocatorParams } from '../../locator'; @@ -44,14 +43,12 @@ import { export interface UseDashboardStateProps { history: History; savedDashboardId?: string; - redirectTo: DashboardRedirect; isEmbeddedExternally: boolean; kbnUrlStateStorage: IKbnUrlStateStorage; } export const useDashboardAppState = ({ history, - redirectTo, savedDashboardId, kbnUrlStateStorage, isEmbeddedExternally, @@ -184,12 +181,20 @@ export const useDashboardAppState = ({ savedDashboard, }); + // Backwards compatible way of detecting that we are taking a screenshot + const legacyPrintLayoutDetected = + screenshotModeService?.isScreenshotMode() && + screenshotModeService.getScreenshotLayout() === 'print'; + const initialDashboardState = { ...savedDashboardState, ...dashboardSessionStorageState, ...initialDashboardStateFromUrl, ...forwardedAppState, + // if we are in legacy print mode, dashboard needs to be in print viewMode + ...(legacyPrintLayoutDetected ? { viewMode: ViewMode.PRINT } : {}), + // if there is an incoming embeddable, dashboard always needs to be in edit mode to receive it. ...(incomingEmbeddable ? { viewMode: ViewMode.EDIT } : {}), }; diff --git a/src/plugins/dashboard/public/application/test_helpers/make_default_services.ts b/src/plugins/dashboard/public/application/test_helpers/make_default_services.ts index cce2b4eb042ef..616fe56102df9 100644 --- a/src/plugins/dashboard/public/application/test_helpers/make_default_services.ts +++ b/src/plugins/dashboard/public/application/test_helpers/make_default_services.ts @@ -15,6 +15,7 @@ import { DashboardAppServices, DashboardAppCapabilities } from '../../types'; import { embeddablePluginMock } from '../../../../embeddable/public/mocks'; import { IndexPatternsContract, SavedQueryService } from '../../services/data'; import { savedObjectsPluginMock } from '../../../../saved_objects/public/mocks'; +import { screenshotModePluginMock } from '../../../../screenshot_mode/public/mocks'; import { visualizationsPluginMock } from '../../../../visualizations/public/mocks'; import { PluginInitializerContext, ScopedHistory } from '../../../../../core/public'; import { SavedObjectLoader, SavedObjectLoaderFindOptions } from '../../services/saved_objects'; @@ -72,6 +73,7 @@ export function makeDefaultServices(): DashboardAppServices { } as PluginInitializerContext; return { + screenshotModeService: screenshotModePluginMock.createSetupContract(), visualizations: visualizationsPluginMock.createStartContract(), savedObjects: savedObjectsPluginMock.createStartContract(), embeddable: embeddablePluginMock.createInstance().doStart(), diff --git a/src/plugins/dashboard/public/dashboard_constants.ts b/src/plugins/dashboard/public/dashboard_constants.ts index 409d80e2ef066..6f9a30e3a7041 100644 --- a/src/plugins/dashboard/public/dashboard_constants.ts +++ b/src/plugins/dashboard/public/dashboard_constants.ts @@ -14,6 +14,7 @@ export const DashboardConstants = { LANDING_PAGE_PATH: '/list', CREATE_NEW_DASHBOARD_URL: '/create', VIEW_DASHBOARD_URL: '/view', + PRINT_DASHBOARD_URL: '/print', ADD_EMBEDDABLE_ID: 'addEmbeddableId', ADD_EMBEDDABLE_TYPE: 'addEmbeddableType', DASHBOARDS_ID: 'dashboards', diff --git a/src/plugins/dashboard/public/locator.ts b/src/plugins/dashboard/public/locator.ts index a256a65a5d7f4..b6655e246de36 100644 --- a/src/plugins/dashboard/public/locator.ts +++ b/src/plugins/dashboard/public/locator.ts @@ -128,6 +128,7 @@ export class DashboardAppLocatorDefinition implements LocatorDefinition => { diff --git a/src/plugins/dashboard/public/plugin.tsx b/src/plugins/dashboard/public/plugin.tsx index ff0ac0642ec91..9912aef943144 100644 --- a/src/plugins/dashboard/public/plugin.tsx +++ b/src/plugins/dashboard/public/plugin.tsx @@ -12,7 +12,6 @@ import { filter, map } from 'rxjs/operators'; import { Start as InspectorStartContract } from 'src/plugins/inspector/public'; import { UrlForwardingSetup, UrlForwardingStart } from 'src/plugins/url_forwarding/public'; -import { ScreenshotModePluginStart } from 'src/plugins/screenshot_mode/public'; import { APP_WRAPPER_CLASS } from '../../../core/public'; import { App, @@ -37,6 +36,10 @@ import { NavigationPublicPluginStart as NavigationStart } from './services/navig import { DataPublicPluginSetup, DataPublicPluginStart, esFilters } from './services/data'; import { SharePluginSetup, SharePluginStart, UrlGeneratorContract } from './services/share'; import type { SavedObjectTaggingOssPluginStart } from './services/saved_objects_tagging_oss'; +import type { + ScreenshotModePluginSetup, + ScreenshotModePluginStart, +} from './services/screenshot_mode'; import { getSavedObjectFinder, SavedObjectLoader, @@ -102,6 +105,7 @@ export interface DashboardSetupDependencies { share?: SharePluginSetup; uiActions: UiActionsSetup; usageCollection?: UsageCollectionSetup; + screenshotMode: ScreenshotModePluginSetup; } export interface DashboardStartDependencies { @@ -116,9 +120,9 @@ export interface DashboardStartDependencies { savedObjects: SavedObjectsStart; presentationUtil: PresentationUtilPluginStart; savedObjectsTaggingOss?: SavedObjectTaggingOssPluginStart; - screenshotMode?: ScreenshotModePluginStart; spaces?: SpacesPluginStart; visualizations: VisualizationsStart; + screenshotMode: ScreenshotModePluginStart; } export interface DashboardSetup { @@ -162,7 +166,15 @@ export class DashboardPlugin public setup( core: CoreSetup, - { share, embeddable, home, urlForwarding, data, usageCollection }: DashboardSetupDependencies + { + share, + embeddable, + home, + urlForwarding, + data, + usageCollection, + screenshotMode, + }: DashboardSetupDependencies ): DashboardSetup { this.dashboardFeatureFlagConfig = this.initializerContext.config.get(); @@ -197,6 +209,7 @@ export class DashboardPlugin embeddable: deps.embeddable, uiActions: deps.uiActions, inspector: deps.inspector, + screenshotMode: deps.screenshotMode, http: coreStart.http, ExitFullScreenButton, presentationUtil: deps.presentationUtil, diff --git a/src/plugins/dashboard/public/services/screenshot_mode.ts b/src/plugins/dashboard/public/services/screenshot_mode.ts new file mode 100644 index 0000000000000..12ec1bca2207f --- /dev/null +++ b/src/plugins/dashboard/public/services/screenshot_mode.ts @@ -0,0 +1,14 @@ +/* + * Copyright 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. + */ + +export type { + ScreenshotModePluginStart, + ScreenshotModePluginSetup, +} from '../../../screenshot_mode/public'; + +export type { Layout } from '../../../screenshot_mode/common'; diff --git a/src/plugins/dashboard/public/types.ts b/src/plugins/dashboard/public/types.ts index d4a6cb20bc551..b7b146aeba348 100644 --- a/src/plugins/dashboard/public/types.ts +++ b/src/plugins/dashboard/public/types.ts @@ -19,7 +19,6 @@ import type { import { History } from 'history'; import { AnyAction, Dispatch } from 'redux'; import { BehaviorSubject, Subject } from 'rxjs'; -import { ScreenshotModePluginStart } from 'src/plugins/screenshot_mode/public'; import { Query, Filter, IndexPattern, RefreshInterval, TimeRange } from './services/data'; import { ContainerInput, EmbeddableInput, ViewMode } from './services/embeddable'; import { SharePluginStart } from './services/share'; @@ -33,6 +32,7 @@ import { SavedObjectsTaggingApi } from './services/saved_objects_tagging_oss'; import { DataPublicPluginStart, IndexPatternsContract } from './services/data'; import { SavedObjectLoader, SavedObjectsStart } from './services/saved_objects'; import { IKbnUrlStateStorage } from './services/kibana_utils'; +import type { ScreenshotModePluginStart } from './services/screenshot_mode'; import type { DashboardContainer, DashboardSavedObject } from '.'; import { VisualizationsStart } from '../../visualizations/public'; import { DashboardAppLocatorParams } from './locator'; @@ -206,9 +206,9 @@ export interface DashboardAppServices { onAppLeave: AppMountParameters['onAppLeave']; savedObjectsTagging?: SavedObjectsTaggingApi; savedObjectsClient: SavedObjectsClientContract; + screenshotModeService: ScreenshotModePluginStart; dashboardSessionStorage: DashboardSessionStorage; setHeaderActionMenu: AppMountParameters['setHeaderActionMenu']; savedQueryService: DataPublicPluginStart['query']['savedQueries']; spacesService?: SpacesPluginStart; - screenshotModeService?: ScreenshotModePluginStart; } diff --git a/src/plugins/data/common/search/aggs/agg_configs.test.ts b/src/plugins/data/common/search/aggs/agg_configs.test.ts index 104cd3b2815bc..80e5a079cfd59 100644 --- a/src/plugins/data/common/search/aggs/agg_configs.test.ts +++ b/src/plugins/data/common/search/aggs/agg_configs.test.ts @@ -360,6 +360,7 @@ describe('AggConfigs', () => { "0": Object { "range": Object { "@timestamp": Object { + "format": "strict_date_optional_time", "gte": "2021-05-05T00:00:00.000Z", "lte": "2021-05-10T00:00:00.000Z", }, @@ -368,6 +369,7 @@ describe('AggConfigs', () => { "86400000": Object { "range": Object { "@timestamp": Object { + "format": "strict_date_optional_time", "gte": "2021-05-04T00:00:00.000Z", "lte": "2021-05-09T00:00:00.000Z", }, diff --git a/src/plugins/data/common/search/aggs/agg_configs.ts b/src/plugins/data/common/search/aggs/agg_configs.ts index 9a362466c0fd7..a022abba7fb45 100644 --- a/src/plugins/data/common/search/aggs/agg_configs.ts +++ b/src/plugins/data/common/search/aggs/agg_configs.ts @@ -406,6 +406,7 @@ export class AggConfigs { .map(([filter, field]) => ({ range: { [field]: { + format: 'strict_date_optional_time', gte: moment(filter?.query.range[field].gte).subtract(shift).toISOString(), lte: moment(filter?.query.range[field].lte).subtract(shift).toISOString(), }, diff --git a/src/plugins/data/common/search/aggs/utils/time_splits.ts b/src/plugins/data/common/search/aggs/utils/time_splits.ts index c4a603a383e38..2eb49d76c6082 100644 --- a/src/plugins/data/common/search/aggs/utils/time_splits.ts +++ b/src/plugins/data/common/search/aggs/utils/time_splits.ts @@ -430,6 +430,7 @@ export function insertTimeShiftSplit( filters[key] = { range: { [timeField]: { + format: 'strict_date_optional_time', gte: moment(timeFilter.query.range[timeField].gte).subtract(shift).toISOString(), lte: moment(timeFilter.query.range[timeField].lte).subtract(shift).toISOString(), }, diff --git a/src/plugins/data/public/query/filter_manager/lib/generate_filters.ts b/src/plugins/data/public/query/filter_manager/lib/generate_filters.ts index cfc3ddabe0751..58f5cf8e52c91 100644 --- a/src/plugins/data/public/query/filter_manager/lib/generate_filters.ts +++ b/src/plugins/data/public/query/filter_manager/lib/generate_filters.ts @@ -40,7 +40,9 @@ function getExistingFilter( } if (isScriptedPhraseFilter(filter)) { - return filter.meta.field === fieldName && filter.script.script.params?.value === value; + return ( + filter.meta.field === fieldName && filter.query?.script?.script?.params?.value === value + ); } }) as any; } diff --git a/src/plugins/data/public/query/filter_manager/lib/mappers/map_phrase.ts b/src/plugins/data/public/query/filter_manager/lib/mappers/map_phrase.ts index 64576d4978c99..23cae0ee852ca 100644 --- a/src/plugins/data/public/query/filter_manager/lib/mappers/map_phrase.ts +++ b/src/plugins/data/public/query/filter_manager/lib/mappers/map_phrase.ts @@ -20,7 +20,7 @@ import { import { FilterValueFormatter } from '../../../../../common'; const getScriptedPhraseValue = (filter: PhraseFilter) => - get(filter, ['script', 'script', 'params', 'value']); + get(filter, ['query', 'script', 'script', 'params', 'value']); const getFormattedValueFn = (value: any) => { return (formatter?: FilterValueFormatter) => { diff --git a/src/plugins/discover/public/components/discover_grid/get_render_cell_value.test.tsx b/src/plugins/discover/public/components/discover_grid/get_render_cell_value.test.tsx index 260cbf42c4d8e..e97a2b2901f33 100644 --- a/src/plugins/discover/public/components/discover_grid/get_render_cell_value.test.tsx +++ b/src/plugins/discover/public/components/discover_grid/get_render_cell_value.test.tsx @@ -23,7 +23,7 @@ jest.mock('../../../../kibana_react/public', () => ({ jest.mock('../../kibana_services', () => ({ getServices: () => ({ uiSettings: { - get: jest.fn(), + get: jest.fn((key) => key === 'discover:maxDocFieldsDisplayed' && 200), }, fieldFormats: { getDefaultInstance: jest.fn(() => ({ convert: (value: unknown) => (value ? value : '-') })), diff --git a/src/plugins/discover/public/components/doc_table/lib/row_formatter.tsx b/src/plugins/discover/public/components/doc_table/lib/row_formatter.tsx index 0ec611a307513..53ede4e3f710e 100644 --- a/src/plugins/discover/public/components/doc_table/lib/row_formatter.tsx +++ b/src/plugins/discover/public/components/doc_table/lib/row_formatter.tsx @@ -16,14 +16,17 @@ import { formatHit } from '../../../utils/format_hit'; import './row_formatter.scss'; interface Props { - defPairs: Array<[string, string]>; + defPairs: Array; } const TemplateComponent = ({ defPairs }: Props) => { return (
{defPairs.map((pair, idx) => ( -
{pair[0]}:
+
+ {pair[0]} + {!!pair[1] && ':'} +
{ (dataViewMock.getFormatterForField as jest.Mock).mockReturnValue({ convert: (value: unknown) => `formatted:${value}`, }); + (discoverServiceMock.uiSettings.get as jest.Mock).mockImplementation( + (key) => key === MAX_DOC_FIELDS_DISPLAYED && 220 + ); }); afterEach(() => { @@ -72,6 +75,7 @@ describe('formatHit', () => { expect(formatted).toEqual([ ['extension', 'formatted:png'], ['message', 'formatted:foobar'], + ['and 3 more fields', ''], ]); }); diff --git a/src/plugins/discover/public/utils/format_hit.ts b/src/plugins/discover/public/utils/format_hit.ts index b1bbfcd5aa878..4a06162714a2a 100644 --- a/src/plugins/discover/public/utils/format_hit.ts +++ b/src/plugins/discover/public/utils/format_hit.ts @@ -7,6 +7,7 @@ */ import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; +import { i18n } from '@kbn/i18n'; import { DataView, flattenHit } from '../../../data/common'; import { MAX_DOC_FIELDS_DISPLAYED } from '../../common'; import { getServices } from '../kibana_services'; @@ -14,7 +15,7 @@ import { formatFieldValue } from './format_value'; const formattedHitCache = new WeakMap(); -type FormattedHit = Array<[fieldName: string, formattedValue: string]>; +type FormattedHit = Array; /** * Returns a formatted document in form of key/value pairs of the fields name and a formatted value. @@ -61,7 +62,22 @@ export function formatHit( } }); const maxEntries = getServices().uiSettings.get(MAX_DOC_FIELDS_DISPLAYED); - const formatted = [...highlightPairs, ...sourcePairs].slice(0, maxEntries); + const pairs = [...highlightPairs, ...sourcePairs]; + const formatted = + // If document has more formatted fields than configured via MAX_DOC_FIELDS_DISPLAYED we cut + // off additional fields and instead show a summary how many more field exists. + pairs.length <= maxEntries + ? pairs + : [ + ...pairs.slice(0, maxEntries), + [ + i18n.translate('discover.utils.formatHit.moreFields', { + defaultMessage: 'and {count} more {count, plural, one {field} other {fields}}', + values: { count: pairs.length - maxEntries }, + }), + '', + ] as const, + ]; formattedHitCache.set(hit, formatted); return formatted; } diff --git a/src/plugins/discover/server/ui_settings.ts b/src/plugins/discover/server/ui_settings.ts index e9aa51a7384b2..a58770ed1c1d8 100644 --- a/src/plugins/discover/server/ui_settings.ts +++ b/src/plugins/discover/server/ui_settings.ts @@ -49,7 +49,7 @@ export const getUiSettings: () => Record = () => ({ }), value: 200, description: i18n.translate('discover.advancedSettings.maxDocFieldsDisplayedText', { - defaultMessage: 'Maximum number of fields rendered in the document column', + defaultMessage: 'Maximum number of fields rendered in the document summary', }), category: ['discover'], schema: schema.number(), diff --git a/src/plugins/embeddable/common/types.ts b/src/plugins/embeddable/common/types.ts index c3cac2d5d67db..b9d9d4cc34146 100644 --- a/src/plugins/embeddable/common/types.ts +++ b/src/plugins/embeddable/common/types.ts @@ -13,6 +13,7 @@ import { PersistableStateService, PersistableState } from '../../kibana_utils/co export enum ViewMode { EDIT = 'edit', PREVIEW = 'preview', + PRINT = 'print', VIEW = 'view', } diff --git a/src/plugins/embeddable/public/lib/panel/embeddable_panel.tsx b/src/plugins/embeddable/public/lib/panel/embeddable_panel.tsx index 9807a47698a50..6748e9f3b1d08 100644 --- a/src/plugins/embeddable/public/lib/panel/embeddable_panel.tsx +++ b/src/plugins/embeddable/public/lib/panel/embeddable_panel.tsx @@ -251,7 +251,7 @@ export class EmbeddablePanel extends React.Component { }; public render() { - const viewOnlyMode = this.state.viewMode === ViewMode.VIEW; + const viewOnlyMode = [ViewMode.VIEW, ViewMode.PRINT].includes(this.state.viewMode); const classes = classNames('embPanel', { 'embPanel--editing': !viewOnlyMode, 'embPanel--loading': this.state.loading, diff --git a/src/plugins/input_control_vis/public/control/filter_manager/phrase_filter_manager.ts b/src/plugins/input_control_vis/public/control/filter_manager/phrase_filter_manager.ts index 8ffeefefd0cc3..98ba8b4fbcda8 100644 --- a/src/plugins/input_control_vis/public/control/filter_manager/phrase_filter_manager.ts +++ b/src/plugins/input_control_vis/public/control/filter_manager/phrase_filter_manager.ts @@ -97,8 +97,8 @@ export class PhraseFilterManager extends FilterManager { } // scripted field filter - if (_.has(kbnFilter, 'script')) { - return _.get(kbnFilter, 'script.script.params.value'); + if (_.has(kbnFilter, 'query.script')) { + return _.get(kbnFilter, 'query.script.script.params.value'); } // single phrase filter diff --git a/src/plugins/newsfeed/public/plugin.test.ts b/src/plugins/newsfeed/public/plugin.test.ts index 4be69feb79f55..3497a1e52697d 100644 --- a/src/plugins/newsfeed/public/plugin.test.ts +++ b/src/plugins/newsfeed/public/plugin.test.ts @@ -10,6 +10,7 @@ import { take } from 'rxjs/operators'; import { coreMock } from '../../../core/public/mocks'; import { NewsfeedPublicPlugin } from './plugin'; import { NewsfeedApiEndpoint } from './lib/api'; +import { screenshotModePluginMock } from '../../screenshot_mode/public/mocks'; describe('Newsfeed plugin', () => { let plugin: NewsfeedPublicPlugin; @@ -46,7 +47,7 @@ describe('Newsfeed plugin', () => { describe('base case', () => { it('makes fetch requests', () => { const startContract = plugin.start(coreMock.createStart(), { - screenshotMode: { isScreenshotMode: () => false }, + screenshotMode: screenshotModePluginMock.createSetupContract(), }); const sub = startContract .createNewsFeed$(NewsfeedApiEndpoint.KIBANA) // Any endpoint will do @@ -60,8 +61,10 @@ describe('Newsfeed plugin', () => { describe('when in screenshot mode', () => { it('makes no fetch requests in screenshot mode', () => { + const screenshotMode = screenshotModePluginMock.createSetupContract(); + screenshotMode.isScreenshotMode.mockReturnValue(true); const startContract = plugin.start(coreMock.createStart(), { - screenshotMode: { isScreenshotMode: () => true }, + screenshotMode, }); const sub = startContract .createNewsFeed$(NewsfeedApiEndpoint.KIBANA) // Any endpoint will do diff --git a/src/plugins/screenshot_mode/common/get_set_browser_screenshot_mode.ts b/src/plugins/screenshot_mode/common/get_set_browser_screenshot_mode.ts index ff79ccf0126f4..850f70d2d002a 100644 --- a/src/plugins/screenshot_mode/common/get_set_browser_screenshot_mode.ts +++ b/src/plugins/screenshot_mode/common/get_set_browser_screenshot_mode.ts @@ -7,7 +7,7 @@ */ // **PLEASE NOTE** -// The functionality in this file targets a browser environment and is intended to be used both in public and server. +// The functionality in this file targets a browser environment AND is intended to be used both in public and server. // For instance, reporting uses these functions when starting puppeteer to set the current browser into "screenshot" mode. export const KBN_SCREENSHOT_MODE_ENABLED_KEY = '__KBN_SCREENSHOT_MODE_ENABLED_KEY__'; @@ -61,3 +61,31 @@ export const setScreenshotModeDisabled = () => { } ); }; + +/** @deprecated */ +export const KBN_SCREENSHOT_MODE_LAYOUT_KEY = '__KBN_SCREENSHOT_MODE_LAYOUT_KEY__'; + +/** @deprecated */ +export type Layout = 'canvas' | 'preserve_layout' | 'print'; + +/** @deprecated */ +export const setScreenshotLayout = (value: Layout) => { + Object.defineProperty( + window, + '__KBN_SCREENSHOT_MODE_LAYOUT_KEY__', // Literal value to prevent adding an external reference + { + enumerable: true, + writable: true, + configurable: false, + value, + } + ); +}; + +/** @deprecated */ +export const getScreenshotLayout = (): undefined | Layout => { + return ( + (window as unknown as Record)[KBN_SCREENSHOT_MODE_LAYOUT_KEY] || + (window.localStorage.getItem(KBN_SCREENSHOT_MODE_LAYOUT_KEY) as Layout) + ); +}; diff --git a/src/plugins/screenshot_mode/common/index.ts b/src/plugins/screenshot_mode/common/index.ts index 9c8c3d24ef289..949691911fc27 100644 --- a/src/plugins/screenshot_mode/common/index.ts +++ b/src/plugins/screenshot_mode/common/index.ts @@ -11,6 +11,11 @@ export { setScreenshotModeEnabled, setScreenshotModeDisabled, KBN_SCREENSHOT_MODE_ENABLED_KEY, + KBN_SCREENSHOT_MODE_LAYOUT_KEY, + setScreenshotLayout, + getScreenshotLayout, } from './get_set_browser_screenshot_mode'; +export type { Layout } from './get_set_browser_screenshot_mode'; + export { KBN_SCREENSHOT_MODE_HEADER } from './constants'; diff --git a/src/plugins/screenshot_mode/public/mocks.ts b/src/plugins/screenshot_mode/public/mocks.ts index 7fa93ff0bcea8..d7e69e9d89211 100644 --- a/src/plugins/screenshot_mode/public/mocks.ts +++ b/src/plugins/screenshot_mode/public/mocks.ts @@ -11,9 +11,11 @@ import type { ScreenshotModePluginSetup, ScreenshotModePluginStart } from './typ export const screenshotModePluginMock = { createSetupContract: (): DeeplyMockedKeys => ({ + getScreenshotLayout: jest.fn(), isScreenshotMode: jest.fn(() => false), }), createStartContract: (): DeeplyMockedKeys => ({ + getScreenshotLayout: jest.fn(), isScreenshotMode: jest.fn(() => false), }), }; diff --git a/src/plugins/screenshot_mode/public/plugin.ts b/src/plugins/screenshot_mode/public/plugin.ts index a005bb7c3d055..bb34fe84e2c39 100644 --- a/src/plugins/screenshot_mode/public/plugin.ts +++ b/src/plugins/screenshot_mode/public/plugin.ts @@ -10,11 +10,12 @@ import { CoreSetup, CoreStart, Plugin } from '../../../core/public'; import { ScreenshotModePluginSetup, ScreenshotModePluginStart } from './types'; -import { getScreenshotMode } from '../common'; +import { getScreenshotMode, getScreenshotLayout } from '../common'; export class ScreenshotModePlugin implements Plugin { private publicContract = Object.freeze({ isScreenshotMode: () => getScreenshotMode() === true, + getScreenshotLayout, }); public setup(core: CoreSetup): ScreenshotModePluginSetup { diff --git a/src/plugins/screenshot_mode/public/types.ts b/src/plugins/screenshot_mode/public/types.ts index f6963de0cbd63..d1603cbceb26f 100644 --- a/src/plugins/screenshot_mode/public/types.ts +++ b/src/plugins/screenshot_mode/public/types.ts @@ -6,12 +6,17 @@ * Side Public License, v 1. */ +import type { Layout } from '../common'; + export interface IScreenshotModeService { /** * Returns a boolean indicating whether the current user agent (browser) would like to view UI optimized for * screenshots or printing. */ isScreenshotMode: () => boolean; + + /** @deprecated */ + getScreenshotLayout: () => undefined | Layout; } export type ScreenshotModePluginSetup = IScreenshotModeService; diff --git a/src/plugins/screenshot_mode/server/plugin.ts b/src/plugins/screenshot_mode/server/plugin.ts index 9295451f640c2..b885ff97bf262 100644 --- a/src/plugins/screenshot_mode/server/plugin.ts +++ b/src/plugins/screenshot_mode/server/plugin.ts @@ -30,10 +30,11 @@ export class ScreenshotModePlugin // We use "require" here to ensure the import does not have external references due to code bundling that // commonly happens during transpiling. External references would be missing in the environment puppeteer creates. // eslint-disable-next-line @typescript-eslint/no-var-requires - const { setScreenshotModeEnabled } = require('../common'); + const { setScreenshotModeEnabled, setScreenshotLayout } = require('../common'); return { setScreenshotModeEnabled, + setScreenshotLayout, isScreenshotMode, }; } diff --git a/src/plugins/screenshot_mode/server/types.ts b/src/plugins/screenshot_mode/server/types.ts index 566ae19719454..1b9f3868f0966 100644 --- a/src/plugins/screenshot_mode/server/types.ts +++ b/src/plugins/screenshot_mode/server/types.ts @@ -6,7 +6,8 @@ * Side Public License, v 1. */ -import { RequestHandlerContext, KibanaRequest } from 'src/core/server'; +import type { RequestHandlerContext, KibanaRequest } from 'src/core/server'; +import type { Layout } from '../common'; /** * Any context that requires access to the screenshot mode flag but does not have access @@ -23,6 +24,9 @@ export interface ScreenshotModePluginSetup { * on the page have run to ensure that screenshot mode is detected as early as possible. */ setScreenshotModeEnabled: () => void; + + /** @deprecated */ + setScreenshotLayout: (value: Layout) => void; } export interface ScreenshotModePluginStart { diff --git a/src/plugins/vis_types/vega/public/vega_inspector/vega_adapter.ts b/src/plugins/vis_types/vega/public/vega_inspector/vega_adapter.ts index bc90fe35199b8..def7fefd55173 100644 --- a/src/plugins/vis_types/vega/public/vega_inspector/vega_adapter.ts +++ b/src/plugins/vis_types/vega/public/vega_inspector/vega_adapter.ts @@ -6,14 +6,35 @@ * Side Public License, v 1. */ +import { i18n } from '@kbn/i18n'; + import { Observable, ReplaySubject, fromEventPattern, merge, timer } from 'rxjs'; import { map, switchMap, filter, debounce } from 'rxjs/operators'; -import { View, Runtime, Spec } from 'vega'; -import { i18n } from '@kbn/i18n'; -import { Assign } from '@kbn/utility-types'; +import type { View, Spec } from 'vega'; +import type { Assign } from '@kbn/utility-types'; interface DebugValues { - view: View; + view: Assign< + { + _runtime: { + data: Record< + string, + { + values: { + value: Array>; + }; + } + >; + signals: Record< + string, + { + value: unknown; + } + >; + }; + }, + View + >; spec: Spec; } @@ -38,8 +59,11 @@ const vegaAdapterValueLabel = i18n.translate('visTypeVega.inspector.vegaAdapter. /** Get Runtime Scope for Vega View * @link https://vega.github.io/vega/docs/api/debugging/#scope **/ -const getVegaRuntimeScope = (debugValues: DebugValues) => - (debugValues.view as any)._runtime as Runtime; +const getVegaRuntimeScope = (debugValues: DebugValues) => { + const { data, signals } = debugValues.view._runtime ?? {}; + + return { data, signals }; +}; const serializeColumns = (item: Record, columns: string[]) => { const nonSerializableFieldLabel = '(..)'; @@ -69,7 +93,7 @@ export class VegaAdapter { const runtimeScope = getVegaRuntimeScope(debugValues); return Object.keys(runtimeScope.data || []).reduce((acc: InspectDataSets[], key) => { - const value = runtimeScope.data[key].values.value; + const { value } = runtimeScope.data[key].values; if (value && value[0]) { const columns = Object.keys(value[0]); diff --git a/test/plugin_functional/config.ts b/test/plugin_functional/config.ts index 8ac1633e61e49..5b74463703797 100644 --- a/test/plugin_functional/config.ts +++ b/test/plugin_functional/config.ts @@ -29,7 +29,6 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { require.resolve('./test_suites/panel_actions'), require.resolve('./test_suites/core_plugins'), require.resolve('./test_suites/management'), - require.resolve('./test_suites/doc_views'), require.resolve('./test_suites/application_links'), require.resolve('./test_suites/data_plugin'), require.resolve('./test_suites/saved_objects_management'), diff --git a/test/plugin_functional/test_suites/doc_views/doc_views.ts b/test/plugin_functional/test_suites/doc_views/doc_views.ts deleted file mode 100644 index e9a6434e8f337..0000000000000 --- a/test/plugin_functional/test_suites/doc_views/doc_views.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 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 expect from '@kbn/expect'; -import { PluginFunctionalProviderContext } from '../../services'; - -export default function ({ getService, getPageObjects }: PluginFunctionalProviderContext) { - const testSubjects = getService('testSubjects'); - const find = getService('find'); - const PageObjects = getPageObjects(['common', 'discover', 'timePicker']); - - describe('custom doc views', function () { - before(async () => { - await PageObjects.common.navigateToApp('discover'); - await PageObjects.timePicker.setDefaultAbsoluteRange(); - }); - - it('should show custom doc views', async () => { - await testSubjects.click('docTableExpandToggleColumn'); - const angularTab = await find.byButtonText('Angular doc view'); - const reactTab = await find.byButtonText('React doc view'); - expect(await angularTab.isDisplayed()).to.be(true); - expect(await reactTab.isDisplayed()).to.be(true); - }); - - it('should render angular doc view', async () => { - const angularTab = await find.byButtonText('Angular doc view'); - await angularTab.click(); - const angularContent = await testSubjects.find('angular-docview'); - expect(await angularContent.getVisibleText()).to.be('logstash-2015.09.22'); - }); - - it('should render react doc view', async () => { - const reactTab = await find.byButtonText('React doc view'); - await reactTab.click(); - const reactContent = await testSubjects.find('react-docview'); - expect(await reactContent.getVisibleText()).to.be('logstash-2015.09.22'); - }); - }); -} diff --git a/test/plugin_functional/test_suites/doc_views/index.ts b/test/plugin_functional/test_suites/doc_views/index.ts deleted file mode 100644 index a790f062e32d4..0000000000000 --- a/test/plugin_functional/test_suites/doc_views/index.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 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 { PluginFunctionalProviderContext } from '../../services'; - -export default function ({ getService, loadTestFile }: PluginFunctionalProviderContext) { - const esArchiver = getService('esArchiver'); - - // SKIPPED: https://github.com/elastic/kibana/issues/100060 - describe.skip('doc views', function () { - before(async () => { - await esArchiver.loadIfNeeded('test/functional/fixtures/es_archiver/discover'); - }); - - loadTestFile(require.resolve('./doc_views')); - }); -} diff --git a/x-pack/plugins/alerting/common/alert.ts b/x-pack/plugins/alerting/common/alert.ts index 4431f185ac9ca..8db51e223056a 100644 --- a/x-pack/plugins/alerting/common/alert.ts +++ b/x-pack/plugins/alerting/common/alert.ts @@ -31,6 +31,7 @@ export enum AlertExecutionStatusErrorReasons { Unknown = 'unknown', License = 'license', Timeout = 'timeout', + Disabled = 'disabled', } export interface AlertExecutionStatus { diff --git a/x-pack/plugins/alerting/server/task_runner/task_runner.test.ts b/x-pack/plugins/alerting/server/task_runner/task_runner.test.ts index f70cbaa13f7d1..d370a278e0a5c 100644 --- a/x-pack/plugins/alerting/server/task_runner/task_runner.test.ts +++ b/x-pack/plugins/alerting/server/task_runner/task_runner.test.ts @@ -212,6 +212,7 @@ describe('Task Runner', () => { type: 'alert', attributes: { apiKey: Buffer.from('123:abc').toString('base64'), + enabled: true, }, references: [], }); @@ -399,6 +400,7 @@ describe('Task Runner', () => { type: 'alert', attributes: { apiKey: Buffer.from('123:abc').toString('base64'), + enabled: true, }, references: [], }); @@ -642,6 +644,7 @@ describe('Task Runner', () => { type: 'alert', attributes: { apiKey: Buffer.from('123:abc').toString('base64'), + enabled: true, }, references: [], }); @@ -842,6 +845,7 @@ describe('Task Runner', () => { type: 'alert', attributes: { apiKey: Buffer.from('123:abc').toString('base64'), + enabled: true, }, references: [], }); @@ -913,6 +917,7 @@ describe('Task Runner', () => { type: 'alert', attributes: { apiKey: Buffer.from('123:abc').toString('base64'), + enabled: true, }, references: [], }); @@ -1090,6 +1095,7 @@ describe('Task Runner', () => { type: 'alert', attributes: { apiKey: Buffer.from('123:abc').toString('base64'), + enabled: true, }, references: [], }); @@ -1158,6 +1164,7 @@ describe('Task Runner', () => { type: 'alert', attributes: { apiKey: Buffer.from('123:abc').toString('base64'), + enabled: true, }, references: [], }); @@ -1204,6 +1211,7 @@ describe('Task Runner', () => { type: 'alert', attributes: { apiKey: Buffer.from('123:abc').toString('base64'), + enabled: true, }, references: [], }); @@ -1514,6 +1522,7 @@ describe('Task Runner', () => { type: 'alert', attributes: { apiKey: Buffer.from('123:abc').toString('base64'), + enabled: true, }, references: [], }); @@ -1872,6 +1881,7 @@ describe('Task Runner', () => { type: 'alert', attributes: { apiKey: Buffer.from('123:abc').toString('base64'), + enabled: true, }, references: [], }); @@ -1994,6 +2004,7 @@ describe('Task Runner', () => { type: 'alert', attributes: { apiKey: Buffer.from('123:abc').toString('base64'), + enabled: true, }, references: [], }); @@ -2097,6 +2108,7 @@ describe('Task Runner', () => { type: 'alert', attributes: { apiKey: Buffer.from('123:abc').toString('base64'), + enabled: true, }, references: [], }); @@ -2300,6 +2312,7 @@ describe('Task Runner', () => { type: 'alert', attributes: { apiKey: Buffer.from('123:abc').toString('base64'), + enabled: true, }, references: [], }); @@ -2328,6 +2341,7 @@ describe('Task Runner', () => { type: 'alert', attributes: { apiKey: Buffer.from('123:abc').toString('base64'), + enabled: true, }, references: [], }); @@ -2359,7 +2373,9 @@ describe('Task Runner', () => { encryptedSavedObjectsClient.getDecryptedAsInternalUser.mockResolvedValueOnce({ id: '1', type: 'alert', - attributes: {}, + attributes: { + enabled: true, + }, references: [], }); @@ -2396,6 +2412,7 @@ describe('Task Runner', () => { type: 'alert', attributes: { apiKey: Buffer.from('123:abc').toString('base64'), + enabled: true, }, references: [], }); @@ -2441,6 +2458,7 @@ describe('Task Runner', () => { type: 'alert', attributes: { apiKey: Buffer.from('123:abc').toString('base64'), + enabled: true, }, references: [], }); @@ -2667,6 +2685,7 @@ describe('Task Runner', () => { type: 'alert', attributes: { apiKey: Buffer.from('123:abc').toString('base64'), + enabled: true, }, references: [], }); @@ -2784,6 +2803,7 @@ describe('Task Runner', () => { type: 'alert', attributes: { apiKey: Buffer.from('123:abc').toString('base64'), + enabled: true, }, references: [], }); @@ -2900,6 +2920,7 @@ describe('Task Runner', () => { type: 'alert', attributes: { apiKey: Buffer.from('123:abc').toString('base64'), + enabled: true, }, references: [], }); @@ -3020,6 +3041,7 @@ describe('Task Runner', () => { type: 'alert', attributes: { apiKey: Buffer.from('123:abc').toString('base64'), + enabled: true, }, references: [], }); @@ -3070,6 +3092,7 @@ describe('Task Runner', () => { type: 'alert', attributes: { apiKey: Buffer.from('123:abc').toString('base64'), + enabled: true, }, references: [], }); @@ -3103,6 +3126,7 @@ describe('Task Runner', () => { type: 'alert', attributes: { apiKey: Buffer.from('123:abc').toString('base64'), + enabled: true, }, references: [], }); @@ -3144,6 +3168,7 @@ describe('Task Runner', () => { type: 'alert', attributes: { apiKey: Buffer.from('123:abc').toString('base64'), + enabled: true, }, references: [], }); @@ -3200,6 +3225,7 @@ describe('Task Runner', () => { type: 'alert', attributes: { apiKey: Buffer.from('123:abc').toString('base64'), + enabled: true, }, references: [], }); @@ -3486,6 +3512,7 @@ describe('Task Runner', () => { type: 'alert', attributes: { apiKey: Buffer.from('123:abc').toString('base64'), + enabled: true, }, references: [], }); @@ -3692,6 +3719,7 @@ describe('Task Runner', () => { type: 'alert', attributes: { apiKey: Buffer.from('123:abc').toString('base64'), + enabled: true, }, references: [], }); @@ -3889,6 +3917,7 @@ describe('Task Runner', () => { type: 'alert', attributes: { apiKey: Buffer.from('123:abc').toString('base64'), + enabled: true, }, references: [], }); @@ -4092,6 +4121,7 @@ describe('Task Runner', () => { type: 'alert', attributes: { apiKey: Buffer.from('123:abc').toString('base64'), + enabled: true, }, references: [], }); @@ -4266,6 +4296,7 @@ describe('Task Runner', () => { type: 'alert', attributes: { apiKey: Buffer.from('123:abc').toString('base64'), + enabled: true, }, references: [], }); @@ -4401,4 +4432,86 @@ describe('Task Runner', () => { { refresh: false, namespace: undefined } ); }); + + test('successfully bails on execution if the rule is disabled', async () => { + const state = { + ...mockedTaskInstance.state, + previousStartedAt: new Date(Date.now() - 5 * 60 * 1000).toISOString(), + }; + const taskRunner = new TaskRunner( + alertType, + { + ...mockedTaskInstance, + state, + }, + taskRunnerFactoryInitializerParams + ); + rulesClient.get.mockResolvedValue(mockedAlertTypeSavedObject); + encryptedSavedObjectsClient.getDecryptedAsInternalUser.mockResolvedValue({ + id: '1', + type: 'alert', + attributes: { + apiKey: Buffer.from('123:abc').toString('base64'), + enabled: false, + }, + references: [], + }); + const runnerResult = await taskRunner.run(); + expect(runnerResult.state.previousStartedAt?.toISOString()).toBe(state.previousStartedAt); + expect(runnerResult.schedule).toStrictEqual(mockedTaskInstance.schedule); + + const eventLogger = taskRunnerFactoryInitializerParams.eventLogger; + expect(eventLogger.logEvent).toHaveBeenCalledTimes(2); + expect(eventLogger.logEvent.mock.calls[0][0]).toStrictEqual({ + '@timestamp': '1970-01-01T00:00:00.000Z', + event: { + action: 'execute-start', + kind: 'alert', + category: ['alerts'], + }, + kibana: { + saved_objects: [ + { rel: 'primary', type: 'alert', id: '1', namespace: undefined, type_id: 'test' }, + ], + task: { scheduled: '1970-01-01T00:00:00.000Z', schedule_delay: 0 }, + }, + rule: { + id: '1', + license: 'basic', + category: 'test', + ruleset: 'alerts', + }, + message: 'alert execution start: "1"', + }); + expect(eventLogger.logEvent.mock.calls[1][0]).toStrictEqual({ + '@timestamp': '1970-01-01T00:00:00.000Z', + event: { + action: 'execute', + kind: 'alert', + category: ['alerts'], + reason: 'disabled', + outcome: 'failure', + }, + kibana: { + saved_objects: [ + { rel: 'primary', type: 'alert', id: '1', namespace: undefined, type_id: 'test' }, + ], + task: { + scheduled: '1970-01-01T00:00:00.000Z', + schedule_delay: 0, + }, + alerting: { status: 'error' }, + }, + rule: { + id: '1', + license: 'basic', + category: 'test', + ruleset: 'alerts', + }, + error: { + message: 'Rule failed to execute because rule ran after it was disabled.', + }, + message: 'test:1: execution failed', + }); + }); }); diff --git a/x-pack/plugins/alerting/server/task_runner/task_runner.ts b/x-pack/plugins/alerting/server/task_runner/task_runner.ts index fe95ec646387d..fb7268ef529da 100644 --- a/x-pack/plugins/alerting/server/task_runner/task_runner.ts +++ b/x-pack/plugins/alerting/server/task_runner/task_runner.ts @@ -117,19 +117,22 @@ export class TaskRunner< this.cancelled = false; } - async getApiKeyForAlertPermissions(alertId: string, spaceId: string) { + async getDecryptedAttributes( + ruleId: string, + spaceId: string + ): Promise<{ apiKey: string | null; enabled: boolean }> { const namespace = this.context.spaceIdToNamespace(spaceId); // Only fetch encrypted attributes here, we'll create a saved objects client // scoped with the API key to fetch the remaining data. const { - attributes: { apiKey }, + attributes: { apiKey, enabled }, } = await this.context.encryptedSavedObjectsClient.getDecryptedAsInternalUser( 'alert', - alertId, + ruleId, { namespace } ); - return apiKey; + return { apiKey, enabled }; } private getFakeKibanaRequest(spaceId: string, apiKey: RawAlert['apiKey']) { @@ -516,12 +519,23 @@ export class TaskRunner< const { params: { alertId, spaceId }, } = this.taskInstance; + let enabled: boolean; let apiKey: string | null; try { - apiKey = await this.getApiKeyForAlertPermissions(alertId, spaceId); + const decryptedAttributes = await this.getDecryptedAttributes(alertId, spaceId); + apiKey = decryptedAttributes.apiKey; + enabled = decryptedAttributes.enabled; } catch (err) { throw new ErrorWithReason(AlertExecutionStatusErrorReasons.Decrypt, err); } + + if (!enabled) { + throw new ErrorWithReason( + AlertExecutionStatusErrorReasons.Disabled, + new Error(`Rule failed to execute because rule ran after it was disabled.`) + ); + } + const [services, rulesClient] = this.getServicesWithSpaceLevelPermissions(spaceId, apiKey); let alert: SanitizedAlert; diff --git a/x-pack/plugins/alerting/server/task_runner/task_runner_cancel.test.ts b/x-pack/plugins/alerting/server/task_runner/task_runner_cancel.test.ts index 95cb356af3c1a..c82cc0a7f21e8 100644 --- a/x-pack/plugins/alerting/server/task_runner/task_runner_cancel.test.ts +++ b/x-pack/plugins/alerting/server/task_runner/task_runner_cancel.test.ts @@ -171,6 +171,7 @@ describe('Task Runner Cancel', () => { type: 'alert', attributes: { apiKey: Buffer.from('123:abc').toString('base64'), + enabled: true, }, references: [], }); diff --git a/x-pack/plugins/apm/dev_docs/apm_queries.md b/x-pack/plugins/apm/dev_docs/apm_queries.md index e6021fa31b9f7..4dd9a807eb249 100644 --- a/x-pack/plugins/apm/dev_docs/apm_queries.md +++ b/x-pack/plugins/apm/dev_docs/apm_queries.md @@ -461,6 +461,7 @@ GET apm-*-metric-*,metrics-apm*/_search?terminate_after=1000 "aggs": { "throughput": { "rate": { + "field": "span.destination.service.response_time.count", "unit": "minute" } } diff --git a/x-pack/plugins/apm/ftr_e2e/apis/fixtures/package_registry_config.yml b/x-pack/plugins/apm/ftr_e2e/apis/fixtures/package_registry_config.yml new file mode 100644 index 0000000000000..9f2300dedc82b --- /dev/null +++ b/x-pack/plugins/apm/ftr_e2e/apis/fixtures/package_registry_config.yml @@ -0,0 +1,4 @@ +package_paths: + - /packages/production + - /packages/snapshot + - /packages/test-packages diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/fixtures/synthtrace/opbeans.ts b/x-pack/plugins/apm/ftr_e2e/cypress/fixtures/synthtrace/opbeans.ts index bd01c83b9cc6e..a6d2454de99fd 100644 --- a/x-pack/plugins/apm/ftr_e2e/cypress/fixtures/synthtrace/opbeans.ts +++ b/x-pack/plugins/apm/ftr_e2e/cypress/fixtures/synthtrace/opbeans.ts @@ -4,28 +4,24 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { - service, - browser, - timerange, - getChromeUserAgentDefaults, -} from '@elastic/apm-synthtrace'; +import { apm, timerange } from '@elastic/apm-synthtrace'; export function opbeans({ from, to }: { from: number; to: number }) { const range = timerange(from, to); - const opbeansJava = service('opbeans-java', 'production', 'java') + const opbeansJava = apm + .service('opbeans-java', 'production', 'java') .instance('opbeans-java-prod-1') .podId('opbeans-java-prod-1-pod'); - const opbeansNode = service('opbeans-node', 'production', 'nodejs').instance( - 'opbeans-node-prod-1' - ); + const opbeansNode = apm + .service('opbeans-node', 'production', 'nodejs') + .instance('opbeans-node-prod-1'); - const opbeansRum = browser( + const opbeansRum = apm.browser( 'opbeans-rum', 'production', - getChromeUserAgentDefaults() + apm.getChromeUserAgentDefaults() ); return [ diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/integration/read_only_user/errors/generate_data.ts b/x-pack/plugins/apm/ftr_e2e/cypress/integration/read_only_user/errors/generate_data.ts index 7f1c14ac25513..7215d2f435e1a 100644 --- a/x-pack/plugins/apm/ftr_e2e/cypress/integration/read_only_user/errors/generate_data.ts +++ b/x-pack/plugins/apm/ftr_e2e/cypress/integration/read_only_user/errors/generate_data.ts @@ -4,18 +4,19 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { service, timerange } from '@elastic/apm-synthtrace'; +import { apm, timerange } from '@elastic/apm-synthtrace'; export function generateData({ from, to }: { from: number; to: number }) { const range = timerange(from, to); - const opbeansJava = service('opbeans-java', 'production', 'java') + const opbeansJava = apm + .service('opbeans-java', 'production', 'java') .instance('opbeans-java-prod-1') .podId('opbeans-java-prod-1-pod'); - const opbeansNode = service('opbeans-node', 'production', 'nodejs').instance( - 'opbeans-node-prod-1' - ); + const opbeansNode = apm + .service('opbeans-node', 'production', 'nodejs') + .instance('opbeans-node-prod-1'); return [ ...range diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/integration/read_only_user/service_inventory/header_filters/generate_data.ts b/x-pack/plugins/apm/ftr_e2e/cypress/integration/read_only_user/service_inventory/header_filters/generate_data.ts index 9ebaa1747d909..d4a2cdf103027 100644 --- a/x-pack/plugins/apm/ftr_e2e/cypress/integration/read_only_user/service_inventory/header_filters/generate_data.ts +++ b/x-pack/plugins/apm/ftr_e2e/cypress/integration/read_only_user/service_inventory/header_filters/generate_data.ts @@ -4,7 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { service, timerange } from '@elastic/apm-synthtrace'; +import { apm, timerange } from '@elastic/apm-synthtrace'; export function generateData({ from, @@ -17,13 +17,14 @@ export function generateData({ }) { const range = timerange(from, to); - const service1 = service(specialServiceName, 'production', 'java') + const service1 = apm + .service(specialServiceName, 'production', 'java') .instance('service-1-prod-1') .podId('service-1-prod-1-pod'); - const opbeansNode = service('opbeans-node', 'production', 'nodejs').instance( - 'opbeans-node-prod-1' - ); + const opbeansNode = apm + .service('opbeans-node', 'production', 'nodejs') + .instance('opbeans-node-prod-1'); return [ ...range diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/plugins/index.ts b/x-pack/plugins/apm/ftr_e2e/cypress/plugins/index.ts index 350d90ccb3fe4..90cf964691274 100644 --- a/x-pack/plugins/apm/ftr_e2e/cypress/plugins/index.ts +++ b/x-pack/plugins/apm/ftr_e2e/cypress/plugins/index.ts @@ -6,8 +6,7 @@ */ import Fs from 'fs'; import { Client, HttpConnection } from '@elastic/elasticsearch'; -import { SynthtraceEsClient } from '@elastic/apm-synthtrace'; -import { createLogger, LogLevel } from '@elastic/apm-synthtrace'; +import { apm, createLogger, LogLevel } from '@elastic/apm-synthtrace'; import { CA_CERT_PATH } from '@kbn/dev-utils'; // *********************************************************** @@ -41,7 +40,7 @@ const plugin: Cypress.PluginConfig = (on, config) => { ...(isCloud ? { tls: { ca: Fs.readFileSync(CA_CERT_PATH, 'utf-8') } } : {}), }); - const synthtraceEsClient = new SynthtraceEsClient( + const synthtraceEsClient = new apm.ApmSynthtraceEsClient( client, createLogger(LogLevel.info) ); diff --git a/x-pack/plugins/apm/ftr_e2e/ftr_config.ts b/x-pack/plugins/apm/ftr_e2e/ftr_config.ts index 12cc8845264c2..84d1c40930c70 100644 --- a/x-pack/plugins/apm/ftr_e2e/ftr_config.ts +++ b/x-pack/plugins/apm/ftr_e2e/ftr_config.ts @@ -6,8 +6,11 @@ */ import { FtrConfigProviderContext } from '@kbn/test'; - import { CA_CERT_PATH } from '@kbn/dev-utils'; + +// Used to spin up a docker container with package registry service that will be used by fleet +export const packageRegistryPort = 1234; + async function config({ readConfigFile }: FtrConfigProviderContext) { const kibanaCommonTestsConfig = await readConfigFile( require.resolve('../../../../test/common/config.js') @@ -38,6 +41,11 @@ async function config({ readConfigFile }: FtrConfigProviderContext) { '--csp.warnLegacyBrowsers=false', // define custom kibana server args here `--elasticsearch.ssl.certificateAuthorities=${CA_CERT_PATH}`, + + // Fleet config + `--xpack.fleet.packages.0.name=endpoint`, + `--xpack.fleet.packages.0.version=latest`, + `--xpack.fleet.registryUrl=http://localhost:${packageRegistryPort}`, ], }, }; diff --git a/x-pack/plugins/apm/ftr_e2e/ftr_config_run.ts b/x-pack/plugins/apm/ftr_e2e/ftr_config_run.ts index 51c859a8477f2..a5a0b52e3fbef 100644 --- a/x-pack/plugins/apm/ftr_e2e/ftr_config_run.ts +++ b/x-pack/plugins/apm/ftr_e2e/ftr_config_run.ts @@ -5,16 +5,41 @@ * 2.0. */ -import { FtrConfigProviderContext } from '@kbn/test'; +import { defineDockerServersConfig, FtrConfigProviderContext } from '@kbn/test'; import cypress from 'cypress'; +import path from 'path'; import { cypressStart } from './cypress_start'; +import { packageRegistryPort } from './ftr_config'; import { FtrProviderContext } from './ftr_provider_context'; +export const dockerImage = + 'docker.elastic.co/package-registry/distribution@sha256:13d9996dd24161624784704e080f5f5b7f0ef34ff0d9259f8f05010ccae00058'; + async function ftrConfigRun({ readConfigFile }: FtrConfigProviderContext) { const kibanaConfig = await readConfigFile(require.resolve('./ftr_config.ts')); + + // mount the config file for the package registry + const dockerArgs: string[] = [ + '-v', + `${path.join( + path.dirname(__filename), + './apis/fixtures/package_registry_config.yml' + )}:/package-registry/config.yml`, + ]; + return { ...kibanaConfig.getAll(), testRunner, + dockerServers: defineDockerServersConfig({ + registry: { + enabled: true, + image: dockerImage, + portInContainer: 8080, + port: packageRegistryPort, + args: dockerArgs, + waitForLogLine: 'package manifests loaded', + }, + }), }; } diff --git a/x-pack/plugins/apm/public/components/app/Settings/agent_keys/agent_keys_table.tsx b/x-pack/plugins/apm/public/components/app/Settings/agent_keys/agent_keys_table.tsx new file mode 100644 index 0000000000000..4a05f38d8e505 --- /dev/null +++ b/x-pack/plugins/apm/public/components/app/Settings/agent_keys/agent_keys_table.tsx @@ -0,0 +1,168 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license 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 { i18n } from '@kbn/i18n'; +import { + EuiInMemoryTable, + EuiBasicTableColumn, + EuiInMemoryTableProps, +} from '@elastic/eui'; +import { TimestampTooltip } from '../../../shared/TimestampTooltip'; +import { ApiKey } from '../../../../../../security/common/model'; +import { ConfirmDeleteModal } from './confirm_delete_modal'; + +interface Props { + agentKeys: ApiKey[]; + refetchAgentKeys: () => void; +} + +export function AgentKeysTable({ agentKeys, refetchAgentKeys }: Props) { + const [agentKeyToBeDeleted, setAgentKeyToBeDeleted] = useState(); + + const columns: Array> = [ + { + field: 'name', + name: i18n.translate( + 'xpack.apm.settings.agentKeys.table.nameColumnName', + { + defaultMessage: 'Name', + } + ), + sortable: true, + }, + { + field: 'username', + name: i18n.translate( + 'xpack.apm.settings.agentKeys.table.userNameColumnName', + { + defaultMessage: 'User', + } + ), + sortable: true, + }, + { + field: 'realm', + name: i18n.translate( + 'xpack.apm.settings.agentKeys.table.realmColumnName', + { + defaultMessage: 'Realm', + } + ), + sortable: true, + }, + { + field: 'creation', + name: i18n.translate( + 'xpack.apm.settings.agentKeys.table.creationColumnName', + { + defaultMessage: 'Created', + } + ), + dataType: 'date', + sortable: true, + mobileOptions: { + show: false, + }, + render: (date: number) => , + }, + { + actions: [ + { + name: i18n.translate( + 'xpack.apm.settings.agentKeys.table.deleteActionTitle', + { + defaultMessage: 'Delete', + } + ), + description: i18n.translate( + 'xpack.apm.settings.agentKeys.table.deleteActionDescription', + { + defaultMessage: 'Delete this agent key', + } + ), + icon: 'trash', + color: 'danger', + type: 'icon', + onClick: (agentKey: ApiKey) => setAgentKeyToBeDeleted(agentKey), + }, + ], + }, + ]; + + const search: EuiInMemoryTableProps['search'] = { + box: { + incremental: true, + }, + filters: [ + { + type: 'field_value_selection', + field: 'username', + name: i18n.translate( + 'xpack.apm.settings.agentKeys.table.userFilterLabel', + { + defaultMessage: 'User', + } + ), + multiSelect: 'or', + operator: 'exact', + options: Object.keys( + agentKeys.reduce((acc: Record, { username }) => { + acc[username] = true; + return acc; + }, {}) + ).map((value) => ({ value })), + }, + { + type: 'field_value_selection', + field: 'realm', + name: i18n.translate( + 'xpack.apm.settings.agentKeys.table.realmFilterLabel', + { + defaultMessage: 'Realm', + } + ), + multiSelect: 'or', + operator: 'exact', + options: Object.keys( + agentKeys.reduce((acc: Record, { realm }) => { + acc[realm] = true; + return acc; + }, {}) + ).map((value) => ({ value })), + }, + ], + }; + + return ( + + + {agentKeyToBeDeleted && ( + setAgentKeyToBeDeleted(undefined)} + agentKey={agentKeyToBeDeleted} + onConfirm={() => { + setAgentKeyToBeDeleted(undefined); + refetchAgentKeys(); + }} + /> + )} + + ); +} diff --git a/x-pack/plugins/apm/public/components/app/Settings/agent_keys/confirm_delete_modal.tsx b/x-pack/plugins/apm/public/components/app/Settings/agent_keys/confirm_delete_modal.tsx new file mode 100644 index 0000000000000..6125a238f11aa --- /dev/null +++ b/x-pack/plugins/apm/public/components/app/Settings/agent_keys/confirm_delete_modal.tsx @@ -0,0 +1,84 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license 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 { i18n } from '@kbn/i18n'; +import { EuiConfirmModal } from '@elastic/eui'; +import { useApmPluginContext } from '../../../../context/apm_plugin/use_apm_plugin_context'; +import { callApmApi } from '../../../../services/rest/createCallApmApi'; +import { ApiKey } from '../../../../../../security/common/model'; + +interface Props { + agentKey: ApiKey; + onCancel: () => void; + onConfirm: () => void; +} + +export function ConfirmDeleteModal({ agentKey, onCancel, onConfirm }: Props) { + const [isDeleting, setIsDeleting] = useState(false); + const { toasts } = useApmPluginContext().core.notifications; + const { id, name } = agentKey; + + const deleteAgentKey = async () => { + try { + await callApmApi({ + endpoint: 'POST /internal/apm/api_key/invalidate', + signal: null, + params: { + body: { id }, + }, + }); + toasts.addSuccess( + i18n.translate('xpack.apm.settings.agentKeys.invalidate.succeeded', { + defaultMessage: 'Deleted agent key "{name}"', + values: { name }, + }) + ); + } catch (error) { + toasts.addDanger( + i18n.translate('xpack.apm.settings.agentKeys.invalidate.failed', { + defaultMessage: 'Error deleting agent key "{name}"', + values: { name }, + }) + ); + } + }; + + return ( + { + setIsDeleting(true); + await deleteAgentKey(); + setIsDeleting(false); + onConfirm(); + }} + cancelButtonText={i18n.translate( + 'xpack.apm.settings.agentKeys.deleteConfirmModal.cancel', + { + defaultMessage: 'Cancel', + } + )} + confirmButtonText={i18n.translate( + 'xpack.apm.settings.agentKeys.deleteConfirmModal.delete', + { + defaultMessage: 'Delete', + } + )} + confirmButtonDisabled={isDeleting} + buttonColor="danger" + defaultFocusedButton="confirm" + /> + ); +} diff --git a/x-pack/plugins/apm/public/components/app/Settings/agent_keys/index.tsx b/x-pack/plugins/apm/public/components/app/Settings/agent_keys/index.tsx new file mode 100644 index 0000000000000..23acc2e98dd73 --- /dev/null +++ b/x-pack/plugins/apm/public/components/app/Settings/agent_keys/index.tsx @@ -0,0 +1,186 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license 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 } from 'react'; +import { isEmpty } from 'lodash'; +import { i18n } from '@kbn/i18n'; +import { + EuiText, + EuiSpacer, + EuiTitle, + EuiFlexGroup, + EuiFlexItem, + EuiEmptyPrompt, + EuiButton, + EuiLoadingSpinner, +} from '@elastic/eui'; +import { useFetcher, FETCH_STATUS } from '../../../../hooks/use_fetcher'; +import { PermissionDenied } from './prompts/permission_denied'; +import { ApiKeysNotEnabled } from './prompts/api_keys_not_enabled'; +import { AgentKeysTable } from './agent_keys_table'; + +const INITIAL_DATA = { + areApiKeysEnabled: false, + canManage: false, +}; + +export function AgentKeys() { + return ( + + + {i18n.translate('xpack.apm.settings.agentKeys.descriptionText', { + defaultMessage: + 'View and delete agent keys. An agent key sends requests on behalf of a user.', + })} + + + + + +

+ {i18n.translate('xpack.apm.settings.agentKeys.title', { + defaultMessage: 'Agent keys', + })} +

+
+
+
+ + +
+ ); +} + +function AgentKeysContent() { + const { + data: { areApiKeysEnabled, canManage } = INITIAL_DATA, + status: privilegesStatus, + } = useFetcher( + (callApmApi) => { + return callApmApi({ + endpoint: 'GET /internal/apm/agent_keys/privileges', + }); + }, + [], + { showToastOnError: false } + ); + + const { + data, + status, + refetch: refetchAgentKeys, + } = useFetcher( + (callApmApi) => { + if (areApiKeysEnabled && canManage) { + return callApmApi({ + endpoint: 'GET /internal/apm/agent_keys', + }); + } + }, + [areApiKeysEnabled, canManage], + { showToastOnError: false } + ); + + const agentKeys = data?.agentKeys; + const isLoading = + privilegesStatus === FETCH_STATUS.LOADING || + status === FETCH_STATUS.LOADING; + + const requestFailed = + privilegesStatus === FETCH_STATUS.FAILURE || + status === FETCH_STATUS.FAILURE; + + if (!agentKeys) { + if (isLoading) { + return ( + } + titleSize="xs" + title={ +

+ {i18n.translate( + 'xpack.apm.settings.agentKeys.agentKeysLoadingPromptTitle', + { + defaultMessage: 'Loading Agent keys...', + } + )} +

+ } + /> + ); + } + + if (requestFailed) { + return ( + + {i18n.translate( + 'xpack.apm.settings.agentKeys.agentKeysErrorPromptTitle', + { + defaultMessage: 'Could not load agent keys.', + } + )} + + } + /> + ); + } + + if (!canManage) { + return ; + } + + if (!areApiKeysEnabled) { + return ; + } + } + + if (agentKeys && isEmpty(agentKeys)) { + return ( + + {i18n.translate('xpack.apm.settings.agentKeys.emptyPromptTitle', { + defaultMessage: 'Create your first agent key', + })} + + } + body={ +

+ {i18n.translate('xpack.apm.settings.agentKeys.emptyPromptBody', { + defaultMessage: + 'Create agent keys to authorize requests to the APM Server.', + })} +

+ } + actions={ + + {i18n.translate( + 'xpack.apm.settings.agentKeys.createAgentKeyButton', + { + defaultMessage: 'Create agent key', + } + )} + + } + /> + ); + } + + if (agentKeys && !isEmpty(agentKeys)) { + return ( + + ); + } + + return null; +} diff --git a/x-pack/plugins/apm/public/components/app/Settings/agent_keys/prompts/api_keys_not_enabled.tsx b/x-pack/plugins/apm/public/components/app/Settings/agent_keys/prompts/api_keys_not_enabled.tsx new file mode 100644 index 0000000000000..5d667b9f3e1a4 --- /dev/null +++ b/x-pack/plugins/apm/public/components/app/Settings/agent_keys/prompts/api_keys_not_enabled.tsx @@ -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 { EuiEmptyPrompt, EuiLink } from '@elastic/eui'; +import React from 'react'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { i18n } from '@kbn/i18n'; +import { useKibana } from '../../../../../../../../../src/plugins/kibana_react/public'; + +export function ApiKeysNotEnabled() { + const { + services: { docLinks }, + } = useKibana(); + + return ( + + {i18n.translate( + 'xpack.apm.settings.agentKeys.apiKeysDisabledErrorTitle', + { + defaultMessage: 'API keys not enabled in Elasticsearch', + } + )} + + } + iconType="alert" + body={ +

+ + {i18n.translate( + 'xpack.apm.settings.agentKeys.apiKeysDisabledErrorLinkText', + { + defaultMessage: 'docs', + } + )} + + ), + }} + /> +

+ } + /> + ); +} diff --git a/x-pack/plugins/apm/public/components/app/Settings/agent_keys/prompts/permission_denied.tsx b/x-pack/plugins/apm/public/components/app/Settings/agent_keys/prompts/permission_denied.tsx new file mode 100644 index 0000000000000..bcac32bbaa3bc --- /dev/null +++ b/x-pack/plugins/apm/public/components/app/Settings/agent_keys/prompts/permission_denied.tsx @@ -0,0 +1,38 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { EuiEmptyPrompt } from '@elastic/eui'; +import React from 'react'; +import { i18n } from '@kbn/i18n'; + +export function PermissionDenied() { + return ( + + {i18n.translate( + 'xpack.apm.settings.agentKeys.noPermissionToManagelApiKeysTitle', + { + defaultMessage: 'You need permission to manage API keys', + } + )} + + } + body={ +

+ {i18n.translate( + 'xpack.apm.settings.agentKeys.noPermissionToManagelApiKeysDescription', + { + defaultMessage: 'Contact your system administrator', + } + )} +

+ } + /> + ); +} diff --git a/x-pack/plugins/apm/public/components/app/correlations/chart_title_tool_tip.tsx b/x-pack/plugins/apm/public/components/app/correlations/chart_title_tool_tip.tsx new file mode 100644 index 0000000000000..ed2bd49858310 --- /dev/null +++ b/x-pack/plugins/apm/public/components/app/correlations/chart_title_tool_tip.tsx @@ -0,0 +1,25 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license 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 { EuiIconTip } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; + +export function ChartTitleToolTip() { + return ( + + ); +} diff --git a/x-pack/plugins/apm/public/components/app/correlations/failed_transactions_correlations.tsx b/x-pack/plugins/apm/public/components/app/correlations/failed_transactions_correlations.tsx index b2efae74c9c74..c642ca7bd577f 100644 --- a/x-pack/plugins/apm/public/components/app/correlations/failed_transactions_correlations.tsx +++ b/x-pack/plugins/apm/public/components/app/correlations/failed_transactions_correlations.tsx @@ -18,7 +18,6 @@ import { EuiTitle, EuiBetaBadge, EuiBadge, - EuiText, EuiToolTip, EuiSwitch, EuiIconTip, @@ -27,13 +26,11 @@ import type { EuiTableSortingType } from '@elastic/eui/src/components/basic_tabl import type { Direction } from '@elastic/eui/src/services/sort/sort_direction'; import { i18n } from '@kbn/i18n'; -import { FormattedMessage } from '@kbn/i18n-react'; import { useUiTracker } from '../../../../../observability/public'; import { asPercent } from '../../../../common/utils/formatters'; import { FailedTransactionsCorrelation } from '../../../../common/correlations/failed_transactions_correlations/types'; -import { DEFAULT_PERCENTILE_THRESHOLD } from '../../../../common/correlations/constants'; import { FieldStats } from '../../../../common/correlations/field_stats_types'; import { useApmPluginContext } from '../../../context/apm_plugin/use_apm_plugin_context'; @@ -48,18 +45,17 @@ import { CorrelationsTable } from './correlations_table'; import { FailedTransactionsCorrelationsHelpPopover } from './failed_transactions_correlations_help_popover'; import { getFailedTransactionsCorrelationImpactLabel } from './utils/get_failed_transactions_correlation_impact_label'; import { getOverallHistogram } from './utils/get_overall_histogram'; -import { - TransactionDistributionChart, - TransactionDistributionChartData, -} from '../../shared/charts/transaction_distribution_chart'; +import { TransactionDistributionChart } from '../../shared/charts/transaction_distribution_chart'; import { CorrelationsEmptyStatePrompt } from './empty_state_prompt'; import { CrossClusterSearchCompatibilityWarning } from './cross_cluster_search_warning'; import { CorrelationsProgressControls } from './progress_controls'; -import { useTransactionColors } from './use_transaction_colors'; import { CorrelationsContextPopover } from './context_popover'; import { OnAddFilter } from './context_popover/top_values'; import { useFailedTransactionsCorrelations } from './use_failed_transactions_correlations'; +import { getTransactionDistributionChartData } from './get_transaction_distribution_chart_data'; +import { ChartTitleToolTip } from './chart_title_tool_tip'; +import { MIN_TAB_TITLE_HEIGHT } from '../transaction_details/distribution'; export function FailedTransactionsCorrelations({ onFilter, @@ -67,7 +63,6 @@ export function FailedTransactionsCorrelations({ onFilter: () => void; }) { const euiTheme = useTheme(); - const transactionColors = useTransactionColors(); const { core: { notifications }, @@ -427,133 +422,75 @@ export function FailedTransactionsCorrelations({ correlationTerms.length < 1 && (progress.loaded === 1 || !progress.isRunning); - const transactionDistributionChartData: TransactionDistributionChartData[] = - []; - - if (Array.isArray(overallHistogram)) { - transactionDistributionChartData.push({ - id: i18n.translate( - 'xpack.apm.transactionDistribution.chart.allTransactionsLabel', - { defaultMessage: 'All transactions' } - ), - histogram: overallHistogram, - }); - } - - if (Array.isArray(response.errorHistogram)) { - transactionDistributionChartData.push({ - id: i18n.translate( - 'xpack.apm.transactionDistribution.chart.failedTransactionsLabel', - { defaultMessage: 'Failed transactions' } - ), - histogram: response.errorHistogram, - }); - } - - if (selectedTerm && Array.isArray(selectedTerm.histogram)) { - transactionDistributionChartData.push({ - id: `${selectedTerm.fieldName}:${selectedTerm.fieldValue}`, - histogram: selectedTerm.histogram, - }); - } + const transactionDistributionChartData = getTransactionDistributionChartData({ + euiTheme, + allTransactionsHistogram: overallHistogram, + failedTransactionsHistogram: response.errorHistogram, + selectedTerm, + }); return (
- - - - -
- {i18n.translate( - 'xpack.apm.correlations.failedTransactions.panelTitle', - { - defaultMessage: 'Failed transactions latency distribution', - } - )} -
-
-
- - - + + +
+ {i18n.translate( + 'xpack.apm.correlations.failedTransactions.panelTitle', { - defaultMessage: - 'Failed transaction correlations is not GA. Please help us by reporting any bugs.', + defaultMessage: 'Failed transactions latency distribution', } )} - /> - - +
+
+
+ + + + + + + + -
- - {selectedTerm && ( - - , - allTransactions: ( - - - - ), - failedTransactions: ( - - - - ), - focusTransaction: ( - - {selectedTerm?.fieldName}:{selectedTerm?.fieldValue} - - ), - }} - /> - - )} +
diff --git a/x-pack/plugins/apm/public/components/app/correlations/get_transaction_distribution_chart_data.ts b/x-pack/plugins/apm/public/components/app/correlations/get_transaction_distribution_chart_data.ts new file mode 100644 index 0000000000000..49ddd8aec0fe4 --- /dev/null +++ b/x-pack/plugins/apm/public/components/app/correlations/get_transaction_distribution_chart_data.ts @@ -0,0 +1,61 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license 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 { EuiTheme } from '../../../../../../../src/plugins/kibana_react/common'; +import type { + FieldValuePair, + HistogramItem, +} from '../../../../common/correlations/types'; +import { TransactionDistributionChartData } from '../../shared/charts/transaction_distribution_chart'; + +export function getTransactionDistributionChartData({ + euiTheme, + allTransactionsHistogram, + failedTransactionsHistogram, + selectedTerm, +}: { + euiTheme: EuiTheme; + allTransactionsHistogram?: HistogramItem[]; + failedTransactionsHistogram?: HistogramItem[]; + selectedTerm?: FieldValuePair & { histogram: HistogramItem[] }; +}) { + const transactionDistributionChartData: TransactionDistributionChartData[] = + []; + + if (Array.isArray(allTransactionsHistogram)) { + transactionDistributionChartData.push({ + id: i18n.translate( + 'xpack.apm.transactionDistribution.chart.allTransactionsLabel', + { defaultMessage: 'All transactions' } + ), + histogram: allTransactionsHistogram, + areaSeriesColor: euiTheme.eui.euiColorVis1, + }); + } + + if (Array.isArray(failedTransactionsHistogram)) { + transactionDistributionChartData.push({ + id: i18n.translate( + 'xpack.apm.transactionDistribution.chart.failedTransactionsLabel', + { defaultMessage: 'Failed transactions' } + ), + histogram: failedTransactionsHistogram, + areaSeriesColor: euiTheme.eui.euiColorVis7, + }); + } + + if (selectedTerm && Array.isArray(selectedTerm.histogram)) { + transactionDistributionChartData.push({ + id: `${selectedTerm.fieldName}:${selectedTerm.fieldValue}`, + histogram: selectedTerm.histogram, + areaSeriesColor: euiTheme.eui.euiColorVis2, + }); + } + + return transactionDistributionChartData; +} diff --git a/x-pack/plugins/apm/public/components/app/correlations/latency_correlations.tsx b/x-pack/plugins/apm/public/components/app/correlations/latency_correlations.tsx index 629868fb88bf6..f79e955595717 100644 --- a/x-pack/plugins/apm/public/components/app/correlations/latency_correlations.tsx +++ b/x-pack/plugins/apm/public/components/app/correlations/latency_correlations.tsx @@ -15,7 +15,6 @@ import { EuiFlexGroup, EuiFlexItem, EuiSpacer, - EuiText, EuiTitle, EuiToolTip, } from '@elastic/eui'; @@ -23,22 +22,17 @@ import { Direction } from '@elastic/eui/src/services/sort/sort_direction'; import { EuiTableSortingType } from '@elastic/eui/src/components/basic_table/table_types'; import { i18n } from '@kbn/i18n'; -import { FormattedMessage } from '@kbn/i18n-react'; import { useUiTracker } from '../../../../../observability/public'; import { asPreciseDecimal } from '../../../../common/utils/formatters'; -import { DEFAULT_PERCENTILE_THRESHOLD } from '../../../../common/correlations/constants'; import { LatencyCorrelation } from '../../../../common/correlations/latency_correlations/types'; import { FieldStats } from '../../../../common/correlations/field_stats_types'; import { useApmPluginContext } from '../../../context/apm_plugin/use_apm_plugin_context'; import { FETCH_STATUS } from '../../../hooks/use_fetcher'; -import { - TransactionDistributionChart, - TransactionDistributionChartData, -} from '../../shared/charts/transaction_distribution_chart'; +import { TransactionDistributionChart } from '../../shared/charts/transaction_distribution_chart'; import { push } from '../../shared/Links/url_helpers'; import { CorrelationsTable } from './correlations_table'; @@ -47,18 +41,21 @@ import { getOverallHistogram } from './utils/get_overall_histogram'; import { CorrelationsEmptyStatePrompt } from './empty_state_prompt'; import { CrossClusterSearchCompatibilityWarning } from './cross_cluster_search_warning'; import { CorrelationsProgressControls } from './progress_controls'; -import { useTransactionColors } from './use_transaction_colors'; import { CorrelationsContextPopover } from './context_popover'; import { OnAddFilter } from './context_popover/top_values'; import { useLatencyCorrelations } from './use_latency_correlations'; +import { getTransactionDistributionChartData } from './get_transaction_distribution_chart_data'; +import { useTheme } from '../../../hooks/use_theme'; +import { ChartTitleToolTip } from './chart_title_tool_tip'; +import { MIN_TAB_TITLE_HEIGHT } from '../transaction_details/distribution'; export function LatencyCorrelations({ onFilter }: { onFilter: () => void }) { - const transactionColors = useTransactionColors(); - const { core: { notifications }, } = useApmPluginContext(); + const euiTheme = useTheme(); + const { progress, response, startFetch, cancelFetch } = useLatencyCorrelations(); const { overallHistogram, hasData, status } = getOverallHistogram( @@ -274,30 +271,20 @@ export function LatencyCorrelations({ onFilter }: { onFilter: () => void }) { const showCorrelationsEmptyStatePrompt = histogramTerms.length < 1 && (progress.loaded === 1 || !progress.isRunning); - const transactionDistributionChartData: TransactionDistributionChartData[] = - []; - - if (Array.isArray(overallHistogram)) { - transactionDistributionChartData.push({ - id: i18n.translate( - 'xpack.apm.transactionDistribution.chart.allTransactionsLabel', - { defaultMessage: 'All transactions' } - ), - histogram: overallHistogram, - }); - } - - if (selectedHistogram && Array.isArray(selectedHistogram.histogram)) { - transactionDistributionChartData.push({ - id: `${selectedHistogram.fieldName}:${selectedHistogram.fieldValue}`, - histogram: selectedHistogram.histogram, - }); - } + const transactionDistributionChartData = getTransactionDistributionChartData({ + euiTheme, + allTransactionsHistogram: overallHistogram, + selectedTerm: selectedHistogram, + }); return (
- - + +
{i18n.translate( @@ -309,40 +296,19 @@ export function LatencyCorrelations({ onFilter }: { onFilter: () => void }) {
+ + + + +
- {selectedHistogram && ( - - , - allTransactions: ( - - - - ), - focusTransaction: ( - - {selectedHistogram?.fieldName}:{selectedHistogram?.fieldValue} - - ), - }} - /> - - )} - { - const euiTheme = useTheme(); - return { - ALL_TRANSACTIONS: euiTheme.eui.euiColorVis1, - ALL_FAILED_TRANSACTIONS: euiTheme.eui.euiColorVis7, - FOCUS_TRANSACTION: euiTheme.eui.euiColorVis2, - }; -}; diff --git a/x-pack/plugins/apm/public/components/app/trace_overview/trace_list.tsx b/x-pack/plugins/apm/public/components/app/trace_overview/trace_list.tsx index 0fc25b28b60e8..58179366fa42d 100644 --- a/x-pack/plugins/apm/public/components/app/trace_overview/trace_list.tsx +++ b/x-pack/plugins/apm/public/components/app/trace_overview/trace_list.tsx @@ -5,7 +5,13 @@ * 2.0. */ -import { EuiIcon, EuiToolTip, RIGHT_ALIGNMENT } from '@elastic/eui'; +import { + EuiIcon, + EuiToolTip, + EuiFlexGroup, + EuiFlexItem, + RIGHT_ALIGNMENT, +} from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React from 'react'; import { euiStyled } from '../../../../../../../src/plugins/kibana_react/common'; @@ -19,6 +25,7 @@ import { EmptyMessage } from '../../shared/EmptyMessage'; import { ImpactBar } from '../../shared/ImpactBar'; import { TransactionDetailLink } from '../../shared/Links/apm/transaction_detail_link'; import { ITableColumn, ManagedTable } from '../../shared/managed_table'; +import { AgentIcon } from '../../shared/agent_icon'; type TraceGroup = APIReturnType<'GET /internal/apm/traces'>['items'][0]; @@ -65,6 +72,14 @@ const traceListColumns: Array> = [ } ), sortable: true, + render: (_: string, { serviceName, agentName }) => ( + + + + + {serviceName} + + ), }, { field: 'averageResponseTime', diff --git a/x-pack/plugins/apm/public/components/app/transaction_details/distribution/index.tsx b/x-pack/plugins/apm/public/components/app/transaction_details/distribution/index.tsx index e6c189ed0c74e..a2f6fd493313f 100644 --- a/x-pack/plugins/apm/public/components/app/transaction_details/distribution/index.tsx +++ b/x-pack/plugins/apm/public/components/app/transaction_details/distribution/index.tsx @@ -18,18 +18,15 @@ import { } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { FormattedMessage } from '@kbn/i18n-react'; import { useUiTracker } from '../../../../../../observability/public'; import { getDurationFormatter } from '../../../../../common/utils/formatters'; -import { DEFAULT_PERCENTILE_THRESHOLD } from '../../../../../common/correlations/constants'; import { useLegacyUrlParams } from '../../../../context/url_params_context/use_url_params'; import { FETCH_STATUS } from '../../../../hooks/use_fetcher'; import { TransactionDistributionChart } from '../../../shared/charts/transaction_distribution_chart'; -import { useTransactionColors } from '../../correlations/use_transaction_colors'; import type { TabContentProps } from '../types'; import { useWaterfallFetcher } from '../use_waterfall_fetcher'; @@ -37,10 +34,11 @@ import { WaterfallWithSummary } from '../waterfall_with_summary'; import { useTransactionDistributionChartData } from './use_transaction_distribution_chart_data'; import { HeightRetainer } from '../../../shared/HeightRetainer'; +import { ChartTitleToolTip } from '../../correlations/chart_title_tool_tip'; // Enforce min height so it's consistent across all tabs on the same level // to prevent "flickering" behavior -const MIN_TAB_TITLE_HEIGHT = 56; +export const MIN_TAB_TITLE_HEIGHT = 56; type Selection = [number, number]; @@ -69,7 +67,6 @@ export function TransactionDistribution({ selection, traceSamples, }: TransactionDistributionProps) { - const transactionColors = useTransactionColors(); const { urlParams } = useLegacyUrlParams(); const { waterfall, status: waterfallStatus } = useWaterfallFetcher(); @@ -108,8 +105,12 @@ export function TransactionDistribution({ return (
- - + +
{i18n.translate( @@ -121,93 +122,65 @@ export function TransactionDistribution({
- {hasData && !selection && ( - - - - - - - {emptySelectionText} + + + + + + + + {selection ? ( + + + {i18n.translate( + 'xpack.apm.transactionDetails.distribution.selectionText', + { + defaultMessage: `Selection: {formattedSelection}`, + values: { + formattedSelection: getFormattedSelection(selection), + }, + } + )} + - - - )} - {hasData && selection && ( - - - {i18n.translate( - 'xpack.apm.transactionDetails.distribution.selectionText', - { - defaultMessage: `Selection: {formattedSelection}`, - values: { - formattedSelection: getFormattedSelection(selection), - }, - } - )} - - - )} + ) : ( + <> + + + + + {emptySelectionText} + + + )} + +
- - - - - ), - failedTransactions: ( - - - - ), - }} - /> - - diff --git a/x-pack/plugins/apm/public/components/app/transaction_details/distribution/use_transaction_distribution_chart_data.ts b/x-pack/plugins/apm/public/components/app/transaction_details/distribution/use_transaction_distribution_chart_data.ts index a02fc7fe6665f..6d690415d8c6c 100644 --- a/x-pack/plugins/apm/public/components/app/transaction_details/distribution/use_transaction_distribution_chart_data.ts +++ b/x-pack/plugins/apm/public/components/app/transaction_details/distribution/use_transaction_distribution_chart_data.ts @@ -6,23 +6,20 @@ */ import { useEffect } from 'react'; - import { i18n } from '@kbn/i18n'; - import { DEFAULT_PERCENTILE_THRESHOLD } from '../../../../../common/correlations/constants'; import { EVENT_OUTCOME } from '../../../../../common/elasticsearch_fieldnames'; import { EventOutcome } from '../../../../../common/event_outcome'; - import { useApmPluginContext } from '../../../../context/apm_plugin/use_apm_plugin_context'; import { useFetcher, FETCH_STATUS } from '../../../../hooks/use_fetcher'; - -import type { TransactionDistributionChartData } from '../../../shared/charts/transaction_distribution_chart'; - import { isErrorMessage } from '../../correlations/utils/is_error_message'; import { useFetchParams } from '../../correlations/use_fetch_params'; +import { getTransactionDistributionChartData } from '../../correlations/get_transaction_distribution_chart_data'; +import { useTheme } from '../../../../hooks/use_theme'; export const useTransactionDistributionChartData = () => { const params = useFetchParams(); + const euiTheme = useTheme(); const { core: { notifications }, @@ -122,28 +119,11 @@ export const useTransactionDistributionChartData = () => { } }, [errorHistogramError, notifications.toasts]); - const transactionDistributionChartData: TransactionDistributionChartData[] = - []; - - if (Array.isArray(overallLatencyHistogram)) { - transactionDistributionChartData.push({ - id: i18n.translate( - 'xpack.apm.transactionDistribution.chart.allTransactionsLabel', - { defaultMessage: 'All transactions' } - ), - histogram: overallLatencyHistogram, - }); - } - - if (Array.isArray(errorHistogramData.overallHistogram)) { - transactionDistributionChartData.push({ - id: i18n.translate( - 'xpack.apm.transactionDistribution.chart.failedTransactionsLabel', - { defaultMessage: 'Failed transactions' } - ), - histogram: errorHistogramData.overallHistogram, - }); - } + const transactionDistributionChartData = getTransactionDistributionChartData({ + euiTheme, + allTransactionsHistogram: overallLatencyHistogram, + failedTransactionsHistogram: errorHistogramData.overallHistogram, + }); return { chartData: transactionDistributionChartData, diff --git a/x-pack/plugins/apm/public/components/routing/settings/index.tsx b/x-pack/plugins/apm/public/components/routing/settings/index.tsx index e33f60e5593b0..45af7e62260b3 100644 --- a/x-pack/plugins/apm/public/components/routing/settings/index.tsx +++ b/x-pack/plugins/apm/public/components/routing/settings/index.tsx @@ -19,6 +19,7 @@ import { ApmIndices } from '../../app/Settings/ApmIndices'; import { CustomizeUI } from '../../app/Settings/customize_ui'; import { Schema } from '../../app/Settings/schema'; import { AnomalyDetection } from '../../app/Settings/anomaly_detection'; +import { AgentKeys } from '../../app/Settings/agent_keys'; function page({ path, @@ -132,6 +133,14 @@ export const settings = { element: , tab: 'anomaly-detection', }), + page({ + path: '/settings/agent-keys', + title: i18n.translate('xpack.apm.views.settings.agentKeys.title', { + defaultMessage: 'Agent keys', + }), + element: , + tab: 'agent-keys', + }), { path: '/settings', element: , diff --git a/x-pack/plugins/apm/public/components/routing/templates/settings_template.tsx b/x-pack/plugins/apm/public/components/routing/templates/settings_template.tsx index ecca2ddb07ec3..dabe9043495bc 100644 --- a/x-pack/plugins/apm/public/components/routing/templates/settings_template.tsx +++ b/x-pack/plugins/apm/public/components/routing/templates/settings_template.tsx @@ -21,7 +21,8 @@ type Tab = NonNullable[0] & { | 'anomaly-detection' | 'apm-indices' | 'customize-ui' - | 'schema'; + | 'schema' + | 'agent-keys'; hidden?: boolean; }; @@ -116,6 +117,17 @@ function getTabs({ }), href: getLegacyApmHref({ basePath, path: `/settings/schema`, search }), }, + { + key: 'agent-keys', + label: i18n.translate('xpack.apm.settings.agentKeys', { + defaultMessage: 'Agent Keys', + }), + href: getLegacyApmHref({ + basePath, + path: `/settings/agent-keys`, + search, + }), + }, ]; return tabs diff --git a/x-pack/plugins/apm/public/components/shared/charts/transaction_distribution_chart/index.tsx b/x-pack/plugins/apm/public/components/shared/charts/transaction_distribution_chart/index.tsx index d5cd423b2b123..b33f152a63016 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/transaction_distribution_chart/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/charts/transaction_distribution_chart/index.tsx @@ -33,6 +33,7 @@ import { useChartTheme } from '../../../../../../observability/public'; import { getDurationFormatter } from '../../../../../common/utils/formatters'; import type { HistogramItem } from '../../../../../common/correlations/types'; +import { DEFAULT_PERCENTILE_THRESHOLD } from '../../../../../common/correlations/constants'; import { FETCH_STATUS } from '../../../../hooks/use_fetcher'; import { useTheme } from '../../../../hooks/use_theme'; @@ -42,6 +43,7 @@ import { ChartContainer } from '../chart_container'; export interface TransactionDistributionChartData { id: string; histogram: HistogramItem[]; + areaSeriesColor: string; } interface TransactionDistributionChartProps { @@ -49,9 +51,7 @@ interface TransactionDistributionChartProps { hasData: boolean; markerCurrentTransaction?: number; markerValue: number; - markerPercentile: number; onChartSelection?: BrushEndListener; - palette?: string[]; selection?: [number, number]; status: FETCH_STATUS; } @@ -98,19 +98,13 @@ export function TransactionDistributionChart({ hasData, markerCurrentTransaction, markerValue, - markerPercentile, onChartSelection, - palette, selection, status, }: TransactionDistributionChartProps) { const chartTheme = useChartTheme(); const euiTheme = useTheme(); - - const areaSeriesColors = palette ?? [ - euiTheme.eui.euiColorVis1, - euiTheme.eui.euiColorVis2, - ]; + const markerPercentile = DEFAULT_PERCENTILE_THRESHOLD; const annotationsDataValues: LineAnnotationDatum[] = [ { @@ -265,7 +259,7 @@ export function TransactionDistributionChart({ curve={CurveType.CURVE_STEP_AFTER} xAccessor="key" yAccessors={['doc_count']} - color={areaSeriesColors[i]} + color={d.areaSeriesColor} fit="lookahead" // To make the area appear without the orphaned points technique, // we changed the original data to replace values of 0 with 0.0001. diff --git a/x-pack/plugins/apm/server/lib/transaction_groups/__snapshots__/queries.test.ts.snap b/x-pack/plugins/apm/server/lib/transaction_groups/__snapshots__/queries.test.ts.snap index 17c43e36e5cc3..00440b2b51853 100644 --- a/x-pack/plugins/apm/server/lib/transaction_groups/__snapshots__/queries.test.ts.snap +++ b/x-pack/plugins/apm/server/lib/transaction_groups/__snapshots__/queries.test.ts.snap @@ -18,6 +18,9 @@ Array [ Object { "field": "transaction.type", }, + Object { + "field": "agent.name", + }, ], "sort": Object { "@timestamp": "desc", @@ -228,6 +231,9 @@ Array [ Object { "field": "transaction.type", }, + Object { + "field": "agent.name", + }, ], "sort": Object { "@timestamp": "desc", diff --git a/x-pack/plugins/apm/server/lib/transaction_groups/fetcher.ts b/x-pack/plugins/apm/server/lib/transaction_groups/fetcher.ts index aea92d06b7589..bca71ed71b1f6 100644 --- a/x-pack/plugins/apm/server/lib/transaction_groups/fetcher.ts +++ b/x-pack/plugins/apm/server/lib/transaction_groups/fetcher.ts @@ -31,7 +31,7 @@ import { } from '../helpers/transactions'; import { Setup } from '../helpers/setup_request'; import { getAverages, getCounts, getSums } from './get_transaction_group_stats'; - +import { AgentName } from '../../../typings/es_schemas/ui/fields/agent'; export interface TopTraceOptions { environment: string; kuery: string; @@ -51,6 +51,7 @@ export interface TransactionGroup { averageResponseTime: number | null | undefined; transactionsPerMinute: number; impact: number; + agentName: AgentName; } export type ESResponse = Promise<{ items: TransactionGroup[] }>; @@ -142,6 +143,7 @@ function getItemsWithRelativeImpact( avg?: number | null; count?: number | null; transactionType?: string; + agentName?: AgentName; }>, start: number, end: number @@ -166,6 +168,7 @@ function getItemsWithRelativeImpact( item.sum !== null && item.sum !== undefined ? ((item.sum - min) / (max - min)) * 100 || 0 : 0, + agentName: item.agentName as AgentName, }; }); diff --git a/x-pack/plugins/apm/server/lib/transaction_groups/get_transaction_group_stats.ts b/x-pack/plugins/apm/server/lib/transaction_groups/get_transaction_group_stats.ts index c79dde721d138..fd638a6731c63 100644 --- a/x-pack/plugins/apm/server/lib/transaction_groups/get_transaction_group_stats.ts +++ b/x-pack/plugins/apm/server/lib/transaction_groups/get_transaction_group_stats.ts @@ -7,11 +7,14 @@ import { merge } from 'lodash'; import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; -import { TRANSACTION_TYPE } from '../../../common/elasticsearch_fieldnames'; +import { + TRANSACTION_TYPE, + AGENT_NAME, +} from '../../../common/elasticsearch_fieldnames'; import { arrayUnionToCallable } from '../../../common/utils/array_union_to_callable'; import { TransactionGroupRequestBase, TransactionGroupSetup } from './fetcher'; import { getTransactionDurationFieldForTransactions } from '../helpers/transactions'; - +import { AgentName } from '../../../typings/es_schemas/ui/fields/agent'; interface MetricParams { request: TransactionGroupRequestBase; setup: TransactionGroupSetup; @@ -79,6 +82,9 @@ export async function getCounts({ request, setup }: MetricParams) { { field: TRANSACTION_TYPE, } as const, + { + field: AGENT_NAME, + } as const, ], }, }, @@ -98,6 +104,9 @@ export async function getCounts({ request, setup }: MetricParams) { transactionType: bucket.transaction_type.top[0].metrics[ TRANSACTION_TYPE ] as string, + agentName: bucket.transaction_type.top[0].metrics[ + AGENT_NAME + ] as AgentName, }; }); } diff --git a/x-pack/plugins/apm/server/routes/agent_keys/get_agent_keys.ts b/x-pack/plugins/apm/server/routes/agent_keys/get_agent_keys.ts new file mode 100644 index 0000000000000..9c5b3e04c94f2 --- /dev/null +++ b/x-pack/plugins/apm/server/routes/agent_keys/get_agent_keys.ts @@ -0,0 +1,45 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { ApmPluginRequestHandlerContext } from '../typings'; +import { ApiKey } from '../../../../security/common/model'; + +export async function getAgentKeys({ + context, +}: { + context: ApmPluginRequestHandlerContext; +}) { + const body = { + size: 1000, + query: { + bool: { + filter: [ + { + term: { + 'metadata.application': 'apm', + }, + }, + ], + }, + }, + }; + + const esClient = context.core.elasticsearch.client; + const apiResponse = await esClient.asCurrentUser.transport.request<{ + api_keys: ApiKey[]; + }>({ + method: 'GET', + path: '_security/_query/api_key', + body, + }); + + const agentKeys = apiResponse.body.api_keys.filter( + ({ invalidated }) => !invalidated + ); + return { + agentKeys, + }; +} diff --git a/x-pack/plugins/apm/server/routes/agent_keys/get_agent_keys_privileges.ts b/x-pack/plugins/apm/server/routes/agent_keys/get_agent_keys_privileges.ts new file mode 100644 index 0000000000000..4aed9314f433c --- /dev/null +++ b/x-pack/plugins/apm/server/routes/agent_keys/get_agent_keys_privileges.ts @@ -0,0 +1,55 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { ApmPluginRequestHandlerContext } from '../typings'; +import { APMPluginStartDependencies } from '../../types'; + +interface SecurityHasPrivilegesResponse { + cluster: { + manage_security: boolean; + manage_api_key: boolean; + manage_own_api_key: boolean; + }; +} + +export async function getAgentKeysPrivileges({ + context, + securityPluginStart, +}: { + context: ApmPluginRequestHandlerContext; + securityPluginStart: NonNullable; +}) { + const [securityHasPrivilegesResponse, areApiKeysEnabled] = await Promise.all([ + context.core.elasticsearch.client.asCurrentUser.security.hasPrivileges( + { + body: { + cluster: ['manage_security', 'manage_api_key', 'manage_own_api_key'], + }, + } + ), + securityPluginStart.authc.apiKeys.areAPIKeysEnabled(), + ]); + + const { + body: { + cluster: { + manage_security: manageSecurity, + manage_api_key: manageApiKey, + manage_own_api_key: manageOwnApiKey, + }, + }, + } = securityHasPrivilegesResponse; + + const isAdmin = manageSecurity || manageApiKey; + const canManage = manageSecurity || manageApiKey || manageOwnApiKey; + + return { + areApiKeysEnabled, + isAdmin, + canManage, + }; +} diff --git a/x-pack/plugins/apm/server/routes/agent_keys/invalidate_agent_key.ts b/x-pack/plugins/apm/server/routes/agent_keys/invalidate_agent_key.ts new file mode 100644 index 0000000000000..e2f86298efdca --- /dev/null +++ b/x-pack/plugins/apm/server/routes/agent_keys/invalidate_agent_key.ts @@ -0,0 +1,29 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { ApmPluginRequestHandlerContext } from '../typings'; + +export async function invalidateAgentKey({ + context, + id, +}: { + context: ApmPluginRequestHandlerContext; + id: string; +}) { + const { + body: { invalidated_api_keys: invalidatedAgentKeys }, + } = await context.core.elasticsearch.client.asCurrentUser.security.invalidateApiKey( + { + body: { + ids: [id], + }, + } + ); + + return { + invalidatedAgentKeys, + }; +} diff --git a/x-pack/plugins/apm/server/routes/agent_keys/route.ts b/x-pack/plugins/apm/server/routes/agent_keys/route.ts new file mode 100644 index 0000000000000..e5f40205b2912 --- /dev/null +++ b/x-pack/plugins/apm/server/routes/agent_keys/route.ts @@ -0,0 +1,85 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license 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 { i18n } from '@kbn/i18n'; +import * as t from 'io-ts'; +import { createApmServerRoute } from '../apm_routes/create_apm_server_route'; +import { createApmServerRouteRepository } from '../apm_routes/create_apm_server_route_repository'; +import { getAgentKeys } from './get_agent_keys'; +import { getAgentKeysPrivileges } from './get_agent_keys_privileges'; +import { invalidateAgentKey } from './invalidate_agent_key'; + +const agentKeysRoute = createApmServerRoute({ + endpoint: 'GET /internal/apm/agent_keys', + options: { tags: ['access:apm'] }, + + handler: async (resources) => { + const { context } = resources; + const agentKeys = await getAgentKeys({ + context, + }); + + return agentKeys; + }, +}); + +const agentKeysPrivilegesRoute = createApmServerRoute({ + endpoint: 'GET /internal/apm/agent_keys/privileges', + options: { tags: ['access:apm'] }, + + handler: async (resources) => { + const { + plugins: { security }, + context, + } = resources; + + if (!security) { + throw Boom.internal(SECURITY_REQUIRED_MESSAGE); + } + + const securityPluginStart = await security.start(); + const agentKeysPrivileges = await getAgentKeysPrivileges({ + context, + securityPluginStart, + }); + + return agentKeysPrivileges; + }, +}); + +const invalidateAgentKeyRoute = createApmServerRoute({ + endpoint: 'POST /internal/apm/api_key/invalidate', + options: { tags: ['access:apm', 'access:apm_write'] }, + params: t.type({ + body: t.type({ id: t.string }), + }), + handler: async (resources) => { + const { context, params } = resources; + + const { + body: { id }, + } = params; + + const invalidatedKeys = await invalidateAgentKey({ + context, + id, + }); + + return invalidatedKeys; + }, +}); + +export const agentKeysRouteRepository = createApmServerRouteRepository() + .add(agentKeysRoute) + .add(agentKeysPrivilegesRoute) + .add(invalidateAgentKeyRoute); + +const SECURITY_REQUIRED_MESSAGE = i18n.translate( + 'xpack.apm.api.apiKeys.securityRequired', + { defaultMessage: 'Security plugin is required' } +); diff --git a/x-pack/plugins/apm/server/routes/apm_routes/get_global_apm_server_route_repository.ts b/x-pack/plugins/apm/server/routes/apm_routes/get_global_apm_server_route_repository.ts index ee4c9d1c8cfa5..1462e7540650a 100644 --- a/x-pack/plugins/apm/server/routes/apm_routes/get_global_apm_server_route_repository.ts +++ b/x-pack/plugins/apm/server/routes/apm_routes/get_global_apm_server_route_repository.ts @@ -37,6 +37,7 @@ import { APMRouteHandlerResources } from '../typings'; import { historicalDataRouteRepository } from '../historical_data'; import { eventMetadataRouteRepository } from '../event_metadata/route'; import { suggestionsRouteRepository } from '../suggestions/route'; +import { agentKeysRouteRepository } from '../agent_keys/route'; const getTypedGlobalApmServerRouteRepository = () => { const repository = createApmServerRouteRepository() @@ -64,7 +65,9 @@ const getTypedGlobalApmServerRouteRepository = () => { .merge(correlationsRouteRepository) .merge(fallbackToTransactionsRouteRepository) .merge(historicalDataRouteRepository) - .merge(eventMetadataRouteRepository); + .merge(eventMetadataRouteRepository) + .merge(eventMetadataRouteRepository) + .merge(agentKeysRouteRepository); return repository; }; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/components/empty_state.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/components/empty_state.test.tsx index 19f45ced5dc5d..cb1c34a19c018 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/components/empty_state.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/components/empty_state.test.tsx @@ -11,6 +11,8 @@ import { shallow } from 'enzyme'; import { EuiEmptyPrompt, EuiButton } from '@elastic/eui'; +import { docLinks } from '../../../../shared/doc_links'; + import { EmptyState } from './'; describe('EmptyState', () => { @@ -21,7 +23,7 @@ describe('EmptyState', () => { expect(wrapper.find('h2').text()).toEqual('No API events in the last 24 hours'); expect(wrapper.find(EuiButton).prop('href')).toEqual( - expect.stringContaining('/api-reference.html') + expect.stringContaining(docLinks.appSearchApis) ); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/components/empty_state.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/components/empty_state.tsx index 76bd0cba1731f..c78bf3e918737 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/components/empty_state.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/components/empty_state.tsx @@ -8,9 +8,10 @@ import React from 'react'; import { EuiButton, EuiEmptyPrompt } from '@elastic/eui'; + import { i18n } from '@kbn/i18n'; -import { DOCS_PREFIX } from '../../../routes'; +import { API_DOCS_URL } from '../../../routes'; export const EmptyState: React.FC = () => ( (

} actions={ - + {i18n.translate('xpack.enterpriseSearch.appSearch.engine.apiLogs.empty.buttonLabel', { defaultMessage: 'View the API reference', })} diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/crawl_rules_table.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/crawl_rules_table.tsx index d447db60fb25b..bef8ed4462fdc 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/crawl_rules_table.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/crawl_rules_table.tsx @@ -27,7 +27,7 @@ import { clearFlashMessages, flashSuccessToast } from '../../../../shared/flash_ import { GenericEndpointInlineEditableTable } from '../../../../shared/tables/generic_endpoint_inline_editable_table'; import { InlineEditableTableColumn } from '../../../../shared/tables/inline_editable_table/types'; import { ItemWithAnID } from '../../../../shared/tables/types'; -import { DOCS_PREFIX } from '../../../routes'; +import { CRAWL_RULES_DOCS_URL } from '../../../routes'; import { CrawlerSingleDomainLogic } from '../crawler_single_domain_logic'; import { CrawlerPolicies, @@ -53,11 +53,7 @@ const DEFAULT_DESCRIPTION = ( defaultMessage="Create a crawl rule to include or exclude pages whose URL matches the rule. Rules run in sequential order, and each URL is evaluated according to the first match. {link}" values={{ link: ( - + {i18n.translate( 'xpack.enterpriseSearch.appSearch.crawler.crawlRulesTable.descriptionLinkText', { defaultMessage: 'Learn more about crawl rules' } diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/deduplication_panel/deduplication_panel.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/deduplication_panel/deduplication_panel.tsx index ea894e2b00acf..26794d0421353 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/deduplication_panel/deduplication_panel.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/deduplication_panel/deduplication_panel.tsx @@ -27,7 +27,7 @@ import { EuiSelectableLIOption } from '@elastic/eui/src/components/selectable/se import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; -import { DOCS_PREFIX } from '../../../../routes'; +import { DUPLICATE_DOCS_URL } from '../../../../routes'; import { DataPanel } from '../../../data_panel'; import { CrawlerSingleDomainLogic } from '../../crawler_single_domain_logic'; @@ -84,11 +84,7 @@ export const DeduplicationPanel: React.FC = () => { documents on this domain. {documentationLink}." values={{ documentationLink: ( - + {i18n.translate( 'xpack.enterpriseSearch.appSearch.crawler.deduplicationPanel.learnMoreMessage', { diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/entry_points_table.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/entry_points_table.tsx index aaf3cc4516067..4fc7a0569ba0e 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/entry_points_table.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/entry_points_table.tsx @@ -17,7 +17,7 @@ import { FormattedMessage } from '@kbn/i18n-react'; import { GenericEndpointInlineEditableTable } from '../../../../shared/tables/generic_endpoint_inline_editable_table'; import { InlineEditableTableColumn } from '../../../../shared/tables/inline_editable_table/types'; import { ItemWithAnID } from '../../../../shared/tables/types'; -import { DOCS_PREFIX } from '../../../routes'; +import { ENTRY_POINTS_DOCS_URL } from '../../../routes'; import { CrawlerDomain, EntryPoint } from '../types'; import { EntryPointsTableLogic } from './entry_points_table_logic'; @@ -80,11 +80,7 @@ export const EntryPointsTable: React.FC = ({ defaultMessage: 'Include the most important URLs for your website here. Entry point URLs will be the first pages to be indexed and processed for links to other pages.', })}{' '} - + {i18n.translate( 'xpack.enterpriseSearch.appSearch.crawler.entryPointsTable.learnMoreLinkText', { defaultMessage: 'Learn more about entry points.' } diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/manage_crawls_popover/automatic_crawl_scheduler.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/manage_crawls_popover/automatic_crawl_scheduler.tsx index 5f7200cb826df..128dcdcb778cf 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/manage_crawls_popover/automatic_crawl_scheduler.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/manage_crawls_popover/automatic_crawl_scheduler.tsx @@ -37,7 +37,7 @@ import { } from '../../../../..//shared/constants/units'; import { CANCEL_BUTTON_LABEL, SAVE_BUTTON_LABEL } from '../../../../../shared/constants'; -import { DOCS_PREFIX } from '../../../../routes'; +import { WEB_CRAWLER_DOCS_URL } from '../../../../routes'; import { CrawlUnits } from '../../types'; import { AutomaticCrawlSchedulerLogic } from './automatic_crawl_scheduler_logic'; @@ -81,7 +81,7 @@ export const AutomaticCrawlScheduler: React.FC = () => { defaultMessage="Don't worry about it, we'll start a crawl for you. {readMoreMessage}." values={{ readMoreMessage: ( - + {i18n.translate( 'xpack.enterpriseSearch.appSearch.crawler.automaticCrawlSchedule.readMoreLink', { diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/crawler_overview.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/crawler_overview.tsx index 6c3cb51111ae1..c84deb3cb0c99 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/crawler_overview.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/crawler_overview.tsx @@ -13,7 +13,7 @@ import { EuiFlexGroup, EuiFlexItem, EuiLink, EuiSpacer, EuiText, EuiTitle } from import { i18n } from '@kbn/i18n'; -import { DOCS_PREFIX } from '../../routes'; +import { WEB_CRAWLER_DOCS_URL, WEB_CRAWLER_LOG_DOCS_URL } from '../../routes'; import { getEngineBreadcrumbs } from '../engine'; import { AppSearchPageTemplate } from '../layout'; @@ -77,7 +77,7 @@ export const CrawlerOverview: React.FC = () => { defaultMessage: "Easily index your website's content. To get started, enter your domain name, provide optional entry points and crawl rules, and we will handle the rest.", })}{' '} - + {i18n.translate( 'xpack.enterpriseSearch.appSearch.crawler.empty.crawlerDocumentationLinkDescription', { @@ -114,11 +114,7 @@ export const CrawlerOverview: React.FC = () => { defaultMessage: "Recent crawl requests are logged here. Using the request ID of each crawl, you can track progress and examine crawl events in Kibana's Discover or Logs user interfaces.", })}{' '} - + {i18n.translate( 'xpack.enterpriseSearch.appSearch.crawler.configurationDocumentationLinkDescription', { diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/constants.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/constants.ts index 6a5f3df0e86f6..315b4d864b3f2 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/constants.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/constants.ts @@ -7,7 +7,7 @@ import { i18n } from '@kbn/i18n'; -import { DOCS_PREFIX } from '../../routes'; +import { AUTHENTICATION_DOCS_URL } from '../../routes'; export const CREDENTIALS_TITLE = i18n.translate( 'xpack.enterpriseSearch.appSearch.credentials.title', @@ -109,4 +109,4 @@ export const TOKEN_TYPE_INFO = [ export const FLYOUT_ARIA_LABEL_ID = 'credentialsFlyoutTitle'; -export const DOCS_HREF = `${DOCS_PREFIX}/authentication.html`; +export const DOCS_HREF = AUTHENTICATION_DOCS_URL; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_list/credentials_list.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_list/credentials_list.tsx index 040f313b12205..3ea2c022ec489 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_list/credentials_list.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_list/credentials_list.tsx @@ -21,7 +21,7 @@ import { i18n } from '@kbn/i18n'; import { EDIT_BUTTON_LABEL, DELETE_BUTTON_LABEL } from '../../../../shared/constants'; import { HiddenText } from '../../../../shared/hidden_text'; import { convertMetaToPagination, handlePageChange } from '../../../../shared/table_pagination'; -import { DOCS_PREFIX } from '../../../routes'; +import { API_KEYS_DOCS_URL } from '../../../routes'; import { TOKEN_TYPE_DISPLAY_NAMES } from '../constants'; import { CredentialsLogic } from '../credentials_logic'; import { ApiToken } from '../types'; @@ -141,12 +141,7 @@ export const CredentialsList: React.FC = () => { defaultMessage: 'Allow applications to access Elastic App Search on your behalf.', })} actions={ - + {i18n.translate('xpack.enterpriseSearch.appSearch.credentials.empty.buttonLabel', { defaultMessage: 'Learn about API keys', })} diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/components/empty_state.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/components/empty_state.test.tsx index 60ae386bea58e..69c2cc4b987b1 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/components/empty_state.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/components/empty_state.test.tsx @@ -11,6 +11,8 @@ import { shallow } from 'enzyme'; import { EuiEmptyPrompt, EuiButton } from '@elastic/eui'; +import { docLinks } from '../../../../shared/doc_links'; + import { EmptyState } from './'; describe('EmptyState', () => { @@ -21,7 +23,7 @@ describe('EmptyState', () => { expect(wrapper.find('h2').text()).toEqual('Create your first curation'); expect(wrapper.find(EuiButton).prop('href')).toEqual( - expect.stringContaining('/curations-guide.html') + expect.stringContaining(docLinks.appSearchCurations) ); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/components/empty_state.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/components/empty_state.tsx index 872a7282136e3..10d81f1623959 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/components/empty_state.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/components/empty_state.tsx @@ -10,7 +10,7 @@ import React from 'react'; import { EuiButton, EuiEmptyPrompt } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { DOCS_PREFIX } from '../../../routes'; +import { CURATIONS_DOCS_URL } from '../../../routes'; export const EmptyState: React.FC = () => ( (

} actions={ - + {i18n.translate('xpack.enterpriseSearch.appSearch.engine.curations.empty.buttonLabel', { defaultMessage: 'Read the curations guide', })} diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations_settings/curations_settings.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations_settings/curations_settings.test.tsx index 4b4e11c31d4b8..b95ae0bca5bf6 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations_settings/curations_settings.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations_settings/curations_settings.test.tsx @@ -19,6 +19,8 @@ import { EuiButtonEmpty, EuiCallOut, EuiSwitch } from '@elastic/eui'; import { mountWithIntl } from '@kbn/test/jest'; +import { docLinks } from '../../../../../shared/doc_links'; + import { Loading } from '../../../../../shared/loading'; import { EuiButtonTo } from '../../../../../shared/react_router_helpers'; import { DataPanel } from '../../../data_panel'; @@ -227,7 +229,7 @@ describe('CurationsSettings', () => { const wrapper = shallow(); expect(wrapper.is(DataPanel)).toBe(true); expect(wrapper.prop('action').props.to).toEqual('/app/management/stack/license_management'); - expect(wrapper.find(EuiButtonEmpty).prop('href')).toEqual('/license-management.html'); + expect(wrapper.find(EuiButtonEmpty).prop('href')).toEqual(docLinks.licenseManagement); }); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations_settings/curations_settings.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations_settings/curations_settings.tsx index d78ca852ee7d1..ffefea96d3a22 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations_settings/curations_settings.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations_settings/curations_settings.tsx @@ -110,11 +110,7 @@ export const CurationsSettings: React.FC = () => { } > - + {i18n.translate('xpack.enterpriseSearch.curations.settings.licenseUpgradeLink', { defaultMessage: 'Learn more about license upgrades', })} diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/api_code_example.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/api_code_example.tsx index 793c6250d859c..e86b06b423a9d 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/api_code_example.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/api_code_example.tsx @@ -30,7 +30,7 @@ import { FormattedMessage } from '@kbn/i18n-react'; import { CANCEL_BUTTON_LABEL } from '../../../../shared/constants'; import { getEnterpriseSearchUrl } from '../../../../shared/enterprise_search_url'; -import { DOCS_PREFIX } from '../../../routes'; +import { API_CLIENTS_DOCS_URL, INDEXING_DOCS_URL } from '../../../routes'; import { EngineLogic } from '../../engine'; import { EngineDetails } from '../../engine/types'; @@ -74,12 +74,12 @@ export const FlyoutBody: React.FC = () => { defaultMessage="The {documentsApiLink} can be used to add new documents to your engine, update documents, retrieve documents by id, and delete documents. There are a variety of {clientLibrariesLink} to help you get started." values={{ documentsApiLink: ( - + documents API ), clientLibrariesLink: ( - + client libraries ), diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/document_creation_buttons.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/document_creation_buttons.tsx index 5366c00c0e7fc..a8179f297644f 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/document_creation_buttons.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/document_creation_buttons.tsx @@ -27,7 +27,7 @@ import { FormattedMessage } from '@kbn/i18n-react'; import { parseQueryParams } from '../../../shared/query_params'; import { EuiCardTo } from '../../../shared/react_router_helpers'; -import { DOCS_PREFIX, ENGINE_CRAWLER_PATH } from '../../routes'; +import { INDEXING_DOCS_URL, ENGINE_CRAWLER_PATH } from '../../routes'; import { generateEnginePath } from '../engine'; import { DocumentCreationLogic } from './'; @@ -66,7 +66,7 @@ export const DocumentCreationButtons: React.FC = ({ disabled = false }) = jsonCode: .json, postCode: POST, documentsApiLink: ( - + documents API ), diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/components/empty_state.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/components/empty_state.test.tsx index 907dcf8c9c208..b8bb26fa9ad63 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/components/empty_state.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/components/empty_state.test.tsx @@ -11,6 +11,8 @@ import { shallow } from 'enzyme'; import { EuiEmptyPrompt, EuiButton } from '@elastic/eui'; +import { docLinks } from '../../../../shared/doc_links'; + import { EmptyState } from './'; describe('EmptyState', () => { @@ -21,7 +23,7 @@ describe('EmptyState', () => { expect(wrapper.find('h2').text()).toEqual('Add your first documents'); expect(wrapper.find(EuiButton).prop('href')).toEqual( - expect.stringContaining('/indexing-documents-guide.html') + expect.stringContaining(docLinks.appSearchIndexingDocs) ); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/components/empty_state.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/components/empty_state.tsx index 39fe02a84854c..85e834b320751 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/components/empty_state.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/components/empty_state.tsx @@ -10,7 +10,7 @@ import React from 'react'; import { EuiButton, EuiEmptyPrompt } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { DOCS_PREFIX } from '../../../routes'; +import { INDEXING_DOCS_URL } from '../../../routes'; export const EmptyState = () => ( (

} actions={ - + {i18n.translate('xpack.enterpriseSearch.appSearch.engine.documents.empty.buttonLabel', { defaultMessage: 'Read the documents guide', })} diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview_empty.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview_empty.test.tsx index 6750ebf1140e0..54bc7fb26e9d0 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview_empty.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview_empty.test.tsx @@ -33,7 +33,7 @@ describe('EmptyEngineOverview', () => { it('renders a documentation link', () => { expect(getPageHeaderActions(wrapper).find(EuiButton).prop('href')).toEqual( - `${docLinks.appSearchBase}/index.html` + docLinks.appSearchGuide ); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview_empty.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview_empty.tsx index 6f8332e1e332e..ada2df654d52b 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview_empty.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview_empty.tsx @@ -10,7 +10,7 @@ import React from 'react'; import { EuiButton } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { DOCS_PREFIX } from '../../routes'; +import { DOCS_URL } from '../../routes'; import { DocumentCreationButtons, DocumentCreationFlyout } from '../document_creation'; import { getEngineBreadcrumbs } from '../engine'; @@ -26,7 +26,7 @@ export const EmptyEngineOverview: React.FC = () => { { defaultMessage: 'Engine setup' } ), rightSideItems: [ - + {i18n.translate( 'xpack.enterpriseSearch.appSearch.engine.overview.empty.headingAction', { defaultMessage: 'View documentation' } diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/empty_meta_engines_state.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/empty_meta_engines_state.test.tsx index 8b4f5a69b8141..350412825b996 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/empty_meta_engines_state.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/empty_meta_engines_state.test.tsx @@ -11,6 +11,8 @@ import { shallow } from 'enzyme'; import { EuiEmptyPrompt, EuiButton } from '@elastic/eui'; +import { docLinks } from '../../../../shared/doc_links'; + import { EmptyMetaEnginesState } from './'; describe('EmptyMetaEnginesState', () => { @@ -21,7 +23,7 @@ describe('EmptyMetaEnginesState', () => { expect(wrapper.find('h3').text()).toEqual('Create your first meta engine'); expect(wrapper.find(EuiButton).prop('href')).toEqual( - expect.stringContaining('/meta-engines-guide.html') + expect.stringContaining(docLinks.appSearchMetaEngines) ); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/empty_meta_engines_state.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/empty_meta_engines_state.tsx index ad96f21022f2b..3cf461e3f7d45 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/empty_meta_engines_state.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/empty_meta_engines_state.tsx @@ -10,7 +10,7 @@ import React from 'react'; import { EuiEmptyPrompt, EuiButton } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { DOCS_PREFIX } from '../../../routes'; +import { META_ENGINES_DOCS_URL } from '../../../routes'; export const EmptyMetaEnginesState: React.FC = () => ( (

} actions={ - + {i18n.translate( 'xpack.enterpriseSearch.appSearch.engines.metaEngines.emptyPromptButtonLabel', { defaultMessage: 'Learn more about meta engines' } diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/constants.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/constants.tsx index 8fbbf406cf5d3..bf2a122ead42b 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/constants.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/constants.tsx @@ -11,7 +11,7 @@ import { EuiLink } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; -import { DOCS_PREFIX } from '../../routes'; +import { META_ENGINES_DOCS_URL } from '../../routes'; import { META_ENGINE_CREATION_FORM_META_ENGINE_DESCRIPTION, META_ENGINE_CREATION_FORM_DOCUMENTATION_LINK, @@ -40,7 +40,7 @@ export const META_ENGINES_DESCRIPTION = ( defaultMessage="{readDocumentationLink} for more information or upgrade to a Platinum license to get started." values={{ readDocumentationLink: ( - + {META_ENGINE_CREATION_FORM_DOCUMENTATION_LINK} ), diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/meta_engine_creation/constants.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/meta_engine_creation/constants.tsx index af7b6f3201b3e..e41809054e123 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/meta_engine_creation/constants.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/meta_engine_creation/constants.tsx @@ -11,7 +11,7 @@ import { EuiLink } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; -import { DOCS_PREFIX } from '../../routes'; +import { META_ENGINES_DOCS_URL } from '../../routes'; export const DEFAULT_LANGUAGE = 'Universal'; @@ -57,7 +57,7 @@ export const META_ENGINE_CREATION_FORM_DOCUMENTATION_DESCRIPTION = ( defaultMessage="{documentationLink} for information about how to get started." values={{ documentationLink: ( - + {META_ENGINE_CREATION_FORM_DOCUMENTATION_LINK} ), diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/components/empty_state.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/components/empty_state.test.tsx index a60f68c19f6dc..454437a203bc2 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/components/empty_state.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/components/empty_state.test.tsx @@ -11,6 +11,8 @@ import { shallow } from 'enzyme'; import { EuiEmptyPrompt, EuiButton } from '@elastic/eui'; +import { docLinks } from '../../../../shared/doc_links'; + import { EmptyState } from './'; describe('EmptyState', () => { @@ -21,7 +23,7 @@ describe('EmptyState', () => { expect(wrapper.find('h2').text()).toEqual('Add documents to tune relevance'); expect(wrapper.find(EuiButton).prop('href')).toEqual( - expect.stringContaining('/relevance-tuning-guide.html') + expect.stringContaining(docLinks.appSearchRelevance) ); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/components/empty_state.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/components/empty_state.tsx index df29010bd682f..f17f7a582efdf 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/components/empty_state.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/components/empty_state.tsx @@ -10,7 +10,7 @@ import React from 'react'; import { EuiButton, EuiEmptyPrompt } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { DOCS_PREFIX } from '../../../routes'; +import { RELEVANCE_DOCS_URL } from '../../../routes'; export const EmptyState: React.FC = () => ( ( } )} actions={ - + {i18n.translate( 'xpack.enterpriseSearch.appSearch.engine.relevanceTuning.empty.buttonLabel', { defaultMessage: 'Read the relevance tuning guide' } diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/components/precision_slider/precision_slider.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/components/precision_slider/precision_slider.test.tsx index 3be30b77bc2e4..0554b31c88356 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/components/precision_slider/precision_slider.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/components/precision_slider/precision_slider.test.tsx @@ -11,6 +11,8 @@ import React from 'react'; import { shallow, ShallowWrapper } from 'enzyme'; +import { docLinks } from '../../../../../shared/doc_links'; + import { rerender } from '../../../../../test_helpers'; import { STEP_DESCRIPTIONS } from './constants'; @@ -82,7 +84,7 @@ describe('PrecisionSlider', () => { it('contains a documentation link', () => { const documentationLink = wrapper.find('[data-test-subj="documentationLink"]'); - expect(documentationLink.prop('href')).toContain('/precision-tuning.html'); + expect(documentationLink.prop('href')).toContain(docLinks.appSearchPrecision); expect(documentationLink.prop('target')).toEqual('_blank'); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/components/precision_slider/precision_slider.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/components/precision_slider/precision_slider.tsx index 8e7a59c290ce2..e4b2027aa3d6d 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/components/precision_slider/precision_slider.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/components/precision_slider/precision_slider.tsx @@ -21,7 +21,7 @@ import { } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { DOCS_PREFIX } from '../../../../routes'; +import { PRECISION_DOCS_URL } from '../../../../routes'; import { RelevanceTuningLogic } from '../../relevance_tuning_logic'; import { STEP_DESCRIPTIONS } from './constants'; @@ -57,11 +57,7 @@ export const PrecisionSlider: React.FC = () => { defaultMessage: 'Fine tune the precision vs. recall settings on your engine.', } )}{' '} - + {i18n.translate( 'xpack.enterpriseSearch.appSearch.engine.relevanceTuning.precisionSlider.learnMore.link', { diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_callouts.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_callouts.tsx index d8963b33b8ab2..463c61fb60c99 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_callouts.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_callouts.tsx @@ -14,7 +14,7 @@ import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; import { EuiLinkTo } from '../../../shared/react_router_helpers'; -import { DOCS_PREFIX, ENGINE_SCHEMA_PATH } from '../../routes'; +import { META_ENGINES_DOCS_URL, ENGINE_SCHEMA_PATH } from '../../routes'; import { EngineLogic, generateEnginePath } from '../engine'; import { RelevanceTuningLogic } from '.'; @@ -98,7 +98,7 @@ export const RelevanceTuningCallouts: React.FC = () => { values={{ schemaFieldsWithConflictsCount, link: ( - + {i18n.translate( 'xpack.enterpriseSearch.appSearch.engine.relevanceTuning.whatsThisLinkLabel', { diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/components/empty_state.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/components/empty_state.test.tsx index 537fd9ec6a0d4..8798c1a4bc529 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/components/empty_state.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/components/empty_state.test.tsx @@ -11,6 +11,8 @@ import { shallow } from 'enzyme'; import { EuiEmptyPrompt, EuiButton } from '@elastic/eui'; +import { docLinks } from '../../../../shared/doc_links'; + import { EmptyState } from './'; describe('EmptyState', () => { @@ -21,7 +23,7 @@ describe('EmptyState', () => { expect(wrapper.find('h2').text()).toEqual('Add documents to adjust settings'); expect(wrapper.find(EuiButton).prop('href')).toEqual( - expect.stringContaining('/result-settings-guide.html') + expect.stringContaining(docLinks.appSearchResultSettings) ); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/components/empty_state.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/components/empty_state.tsx index dae8390a35fd7..7f91447b910b6 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/components/empty_state.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/components/empty_state.tsx @@ -10,7 +10,7 @@ import React from 'react'; import { EuiButton, EuiEmptyPrompt } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { DOCS_PREFIX } from '../../../routes'; +import { RESULT_SETTINGS_DOCS_URL } from '../../../routes'; export const EmptyState: React.FC = () => ( ( } )} actions={ - + {i18n.translate( 'xpack.enterpriseSearch.appSearch.engine.resultSettings.empty.buttonLabel', { defaultMessage: 'Read the result settings guide' } diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/role_mappings.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/role_mappings.tsx index 3e692aa48623e..e2021ac582d0c 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/role_mappings.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/role_mappings.tsx @@ -22,7 +22,7 @@ import { } from '../../../shared/role_mapping'; import { ROLE_MAPPINGS_TITLE } from '../../../shared/role_mapping/constants'; -import { DOCS_PREFIX } from '../../routes'; +import { SECURITY_DOCS_URL } from '../../routes'; import { AppSearchPageTemplate } from '../layout'; import { ROLE_MAPPINGS_ENGINE_ACCESS_HEADING } from './constants'; @@ -30,8 +30,6 @@ import { RoleMapping } from './role_mapping'; import { RoleMappingsLogic } from './role_mappings_logic'; import { User } from './user'; -const ROLES_DOCS_LINK = `${DOCS_PREFIX}/security-and-users.html`; - export const RoleMappings: React.FC = () => { const { enableRoleBasedAccess, @@ -60,7 +58,7 @@ export const RoleMappings: React.FC = () => { const rolesEmptyState = ( ); @@ -69,7 +67,7 @@ export const RoleMappings: React.FC = () => {
initializeRoleMapping()} /> { expect(wrapper.find('h2').text()).toEqual('Create a schema'); expect(wrapper.find(EuiButton).prop('href')).toEqual( - expect.stringContaining('#indexing-documents-guide-schema') + expect.stringContaining(docLinks.appSearchIndexingDocsSchema) ); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/components/empty_state.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/components/empty_state.tsx index ad9285c7b8fef..3c2d5fc4df66a 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/components/empty_state.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/components/empty_state.tsx @@ -13,7 +13,7 @@ import { EuiEmptyPrompt, EuiButton } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { SchemaAddFieldModal } from '../../../../shared/schema'; -import { DOCS_PREFIX } from '../../../routes'; +import { INDEXING_SCHEMA_DOCS_URL } from '../../../routes'; import { SchemaLogic } from '../schema_logic'; export const EmptyState: React.FC = () => { @@ -40,12 +40,7 @@ export const EmptyState: React.FC = () => {

} actions={ - + {i18n.translate('xpack.enterpriseSearch.appSearch.engine.schema.empty.buttonLabel', { defaultMessage: 'Read the indexing schema guide', })} diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/components/empty_state.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/components/empty_state.test.tsx index 39f0cb376b325..3466542c09739 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/components/empty_state.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/components/empty_state.test.tsx @@ -11,6 +11,8 @@ import { shallow } from 'enzyme'; import { EuiEmptyPrompt, EuiButton } from '@elastic/eui'; +import { docLinks } from '../../../../shared/doc_links'; + import { EmptyState } from './empty_state'; describe('EmptyState', () => { @@ -21,7 +23,7 @@ describe('EmptyState', () => { expect(wrapper.find('h2').text()).toEqual('Add documents to generate a Search UI'); expect(wrapper.find(EuiButton).prop('href')).toEqual( - expect.stringContaining('/reference-ui-guide.html') + expect.stringContaining(docLinks.appSearchSearchUI) ); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/components/empty_state.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/components/empty_state.tsx index b7665a58de300..9a663e1372211 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/components/empty_state.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/components/empty_state.tsx @@ -10,7 +10,7 @@ import React from 'react'; import { EuiButton, EuiEmptyPrompt } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { DOCS_PREFIX } from '../../../routes'; +import { SEARCH_UI_DOCS_URL } from '../../../routes'; export const EmptyState: React.FC = () => ( (

} actions={ - + {i18n.translate('xpack.enterpriseSearch.appSearch.engine.searchUI.empty.buttonLabel', { defaultMessage: 'Read the Search UI guide', })} diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/search_ui.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/search_ui.tsx index 2b210bd07ab4b..43ea60fa84617 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/search_ui.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/search_ui.tsx @@ -12,7 +12,7 @@ import { useActions, useValues } from 'kea'; import { EuiText, EuiFlexItem, EuiFlexGroup, EuiSpacer, EuiLink } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; -import { DOCS_PREFIX } from '../../routes'; +import { SEARCH_UI_DOCS_URL } from '../../routes'; import { EngineLogic, getEngineBreadcrumbs } from '../engine'; import { AppSearchPageTemplate } from '../layout'; @@ -62,7 +62,7 @@ export const SearchUI: React.FC = () => { defaultMessage="Use the fields below to generate a sample search experience built with Search UI. Use the sample to preview search results, or build upon it to create your own custom search experience. {link}." values={{ link: ( - + { defaultMessage: 'Log retention is determined by the ILM policies for your deployment.', })}
- + {i18n.translate('xpack.enterpriseSearch.appSearch.settings.logRetention.learnMore', { defaultMessage: 'Learn more about log retention for Enterprise Search.', })} diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/setup_guide/setup_guide.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/setup_guide/setup_guide.tsx index d460132dddbb1..f1d9beaca5136 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/setup_guide/setup_guide.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/setup_guide/setup_guide.tsx @@ -15,7 +15,7 @@ import { APP_SEARCH_PLUGIN } from '../../../../../common/constants'; import { SetAppSearchChrome as SetPageChrome } from '../../../shared/kibana_chrome'; import { SetupGuideLayout, SETUP_GUIDE_TITLE } from '../../../shared/setup_guide'; import { SendAppSearchTelemetry as SendTelemetry } from '../../../shared/telemetry'; -import { DOCS_PREFIX } from '../../routes'; +import { NATIVE_AUTH_DOCS_URL, STANDARD_AUTH_DOCS_URL } from '../../routes'; import GettingStarted from './assets/getting_started.png'; @@ -23,8 +23,8 @@ export const SetupGuide: React.FC = () => ( diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/synonyms/components/empty_state.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/synonyms/components/empty_state.test.tsx index a43f170e5822f..cdfdbadf6759c 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/synonyms/components/empty_state.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/synonyms/components/empty_state.test.tsx @@ -11,6 +11,8 @@ import { shallow } from 'enzyme'; import { EuiEmptyPrompt, EuiButton } from '@elastic/eui'; +import { docLinks } from '../../../../shared/doc_links'; + import { EmptyState, SynonymModal } from './'; describe('EmptyState', () => { @@ -21,7 +23,7 @@ describe('EmptyState', () => { expect(wrapper.find('h2').text()).toEqual('Create your first synonym set'); expect(wrapper.find(EuiButton).prop('href')).toEqual( - expect.stringContaining('/synonyms-guide.html') + expect.stringContaining(docLinks.appSearchSynonyms) ); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/synonyms/components/empty_state.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/synonyms/components/empty_state.tsx index f856a5c035f81..ac8383ccea9ee 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/synonyms/components/empty_state.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/synonyms/components/empty_state.tsx @@ -10,7 +10,7 @@ import React from 'react'; import { EuiEmptyPrompt, EuiButton } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { DOCS_PREFIX } from '../../../routes'; +import { SYNONYMS_DOCS_URL } from '../../../routes'; import { SynonymModal, SynonymIcon } from './'; @@ -35,12 +35,7 @@ export const EmptyState: React.FC = () => {

} actions={ - + {i18n.translate('xpack.enterpriseSearch.appSearch.engine.synonyms.empty.buttonLabel', { defaultMessage: 'Read the synonyms guide', })} diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/routes.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/routes.ts index 97a9b407b3cd6..1f2e7c883e1cb 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/routes.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/routes.ts @@ -7,7 +7,29 @@ import { docLinks } from '../shared/doc_links'; -export const DOCS_PREFIX = docLinks.appSearchBase; +export const API_DOCS_URL = docLinks.appSearchApis; +export const API_CLIENTS_DOCS_URL = docLinks.appSearchApiClients; +export const API_KEYS_DOCS_URL = docLinks.appSearchApiKeys; +export const AUTHENTICATION_DOCS_URL = docLinks.appSearchAuthentication; +export const CRAWL_RULES_DOCS_URL = docLinks.appSearchCrawlRules; +export const CURATIONS_DOCS_URL = docLinks.appSearchCurations; +export const DOCS_URL = docLinks.appSearchGuide; +export const DUPLICATE_DOCS_URL = docLinks.appSearchDuplicateDocuments; +export const ENTRY_POINTS_DOCS_URL = docLinks.appSearchEntryPoints; +export const INDEXING_DOCS_URL = docLinks.appSearchIndexingDocs; +export const INDEXING_SCHEMA_DOCS_URL = docLinks.appSearchIndexingDocsSchema; +export const LOG_SETTINGS_DOCS_URL = docLinks.appSearchLogSettings; +export const META_ENGINES_DOCS_URL = docLinks.appSearchMetaEngines; +export const NATIVE_AUTH_DOCS_URL = docLinks.appSearchNativeAuth; +export const PRECISION_DOCS_URL = docLinks.appSearchPrecision; +export const RELEVANCE_DOCS_URL = docLinks.appSearchRelevance; +export const RESULT_SETTINGS_DOCS_URL = docLinks.appSearchResultSettings; +export const SEARCH_UI_DOCS_URL = docLinks.appSearchSearchUI; +export const SECURITY_DOCS_URL = docLinks.appSearchSecurity; +export const STANDARD_AUTH_DOCS_URL = docLinks.appSearchStandardAuth; +export const SYNONYMS_DOCS_URL = docLinks.appSearchSynonyms; +export const WEB_CRAWLER_DOCS_URL = docLinks.appSearchWebCrawler; +export const WEB_CRAWLER_LOG_DOCS_URL = docLinks.appSearchWebCrawlerEventLogs; export const ROOT_PATH = '/'; export const SETUP_GUIDE_PATH = '/setup_guide'; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/setup_guide/setup_guide.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/setup_guide/setup_guide.tsx index e82dbcaa41135..c7c85fdd49359 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/setup_guide/setup_guide.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/setup_guide/setup_guide.tsx @@ -12,7 +12,7 @@ import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; import { ENTERPRISE_SEARCH_PLUGIN } from '../../../../../common/constants'; -import { DOCS_PREFIX } from '../../../app_search/routes'; +import { NATIVE_AUTH_DOCS_URL, STANDARD_AUTH_DOCS_URL } from '../../../app_search/routes'; import { SetEnterpriseSearchChrome as SetPageChrome } from '../../../shared/kibana_chrome'; import { SetupGuideLayout, SETUP_GUIDE_TITLE } from '../../../shared/setup_guide'; import { SendEnterpriseSearchTelemetry as SendTelemetry } from '../../../shared/telemetry'; @@ -23,8 +23,8 @@ export const SetupGuide: React.FC = () => ( diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/doc_links/doc_links.test.ts b/x-pack/plugins/enterprise_search/public/applications/shared/doc_links/doc_links.test.ts index cbd7a1c6107b1..b14af1c69795a 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/doc_links/doc_links.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/doc_links/doc_links.test.ts @@ -5,27 +5,23 @@ * 2.0. */ +import { docLinksServiceMock } from '../../../../../../../src/core/public/mocks'; + import { docLinks } from './'; describe('DocLinks', () => { it('setDocLinks', () => { const links = { - DOC_LINK_VERSION: '', - ELASTIC_WEBSITE_URL: 'https://elastic.co/', - links: { - enterpriseSearch: { - base: 'http://elastic.enterprise.search', - appSearchBase: 'http://elastic.app.search', - workplaceSearchBase: 'http://elastic.workplace.search', - }, - }, + DOC_LINK_VERSION: docLinksServiceMock.createStartContract().DOC_LINK_VERSION, + ELASTIC_WEBSITE_URL: docLinksServiceMock.createStartContract().ELASTIC_WEBSITE_URL, + links: docLinksServiceMock.createStartContract().links, }; docLinks.setDocLinks(links as any); - expect(docLinks.enterpriseSearchBase).toEqual('http://elastic.enterprise.search'); - expect(docLinks.appSearchBase).toEqual('http://elastic.app.search'); - expect(docLinks.workplaceSearchBase).toEqual('http://elastic.workplace.search'); - expect(docLinks.cloudBase).toEqual('https://elastic.co/guide/en/cloud/current'); + expect(docLinks.appSearchApis).toEqual(links.links.appSearch.apiRef); + expect(docLinks.cloudIndexManagement).toEqual(links.links.cloud.indexManagement); + expect(docLinks.enterpriseSearchConfig).toEqual(links.links.enterpriseSearch.configuration); + expect(docLinks.workplaceSearchZendesk).toEqual(links.links.workplaceSearch.zendesk); }); }); 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 6034846fac4f1..93bead4d31f4c 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 @@ -8,23 +8,174 @@ import { DocLinksStart } from 'kibana/public'; class DocLinks { - public enterpriseSearchBase: string; - public appSearchBase: string; - public workplaceSearchBase: string; - public cloudBase: string; + public appSearchApis: string; + public appSearchApiClients: string; + public appSearchApiKeys: string; + public appSearchAuthentication: string; + public appSearchCrawlRules: string; + public appSearchCurations: string; + public appSearchDuplicateDocuments: string; + public appSearchEntryPoints: string; + public appSearchGuide: string; + public appSearchIndexingDocs: string; + public appSearchIndexingDocsSchema: string; + public appSearchLogSettings: string; + public appSearchMetaEngines: string; + public appSearchNativeAuth: string; + public appSearchPrecision: string; + public appSearchRelevance: string; + public appSearchResultSettings: string; + public appSearchSearchUI: string; + public appSearchSecurity: string; + public appSearchStandardAuth: string; + public appSearchSynonyms: string; + public appSearchWebCrawler: string; + public appSearchWebCrawlerEventLogs: string; + public cloudIndexManagement: string; + public enterpriseSearchConfig: string; + public enterpriseSearchMailService: string; + public enterpriseSearchUsersAccess: string; + public licenseManagement: string; + public workplaceSearchBox: string; + public workplaceSearchConfluenceCloud: string; + public workplaceSearchConfluenceServer: string; + public workplaceSearchCustomSources: string; + public workplaceSearchCustomSourcePermissions: string; + public workplaceSearchDocumentPermissions: string; + public workplaceSearchDropbox: string; + public workplaceSearchExternalIdentities: string; + public workplaceSearchGettingStarted: string; + public workplaceSearchGitHub: string; + public workplaceSearchGmail: string; + public workplaceSearchGoogleDrive: string; + public workplaceSearchIndexingSchedule: string; + public workplaceSearchJiraCloud: string; + public workplaceSearchJiraServer: string; + public workplaceSearchNativeAuth: string; + public workplaceSearchOneDrive: string; + public workplaceSearchPermissions: string; + public workplaceSearchSalesforce: string; + public workplaceSearchSecurity: string; + public workplaceSearchServiceNow: string; + public workplaceSearchSharePoint: string; + public workplaceSearchSlack: string; + public workplaceSearchStandardAuth: string; + public workplaceSearchSynch: string; + public workplaceSearchZendesk: string; constructor() { - this.enterpriseSearchBase = ''; - this.appSearchBase = ''; - this.workplaceSearchBase = ''; - this.cloudBase = ''; + this.appSearchApis = ''; + this.appSearchApiClients = ''; + this.appSearchApiKeys = ''; + this.appSearchAuthentication = ''; + this.appSearchCrawlRules = ''; + this.appSearchCurations = ''; + this.appSearchDuplicateDocuments = ''; + this.appSearchEntryPoints = ''; + this.appSearchGuide = ''; + this.appSearchIndexingDocs = ''; + this.appSearchIndexingDocsSchema = ''; + this.appSearchLogSettings = ''; + this.appSearchMetaEngines = ''; + this.appSearchNativeAuth = ''; + this.appSearchPrecision = ''; + this.appSearchRelevance = ''; + this.appSearchResultSettings = ''; + this.appSearchSearchUI = ''; + this.appSearchSecurity = ''; + this.appSearchStandardAuth = ''; + this.appSearchSynonyms = ''; + this.appSearchWebCrawler = ''; + this.appSearchWebCrawlerEventLogs = ''; + this.cloudIndexManagement = ''; + this.enterpriseSearchConfig = ''; + this.enterpriseSearchMailService = ''; + this.enterpriseSearchUsersAccess = ''; + this.licenseManagement = ''; + this.workplaceSearchBox = ''; + this.workplaceSearchConfluenceCloud = ''; + this.workplaceSearchConfluenceServer = ''; + this.workplaceSearchCustomSources = ''; + this.workplaceSearchCustomSourcePermissions = ''; + this.workplaceSearchDocumentPermissions = ''; + this.workplaceSearchDropbox = ''; + this.workplaceSearchExternalIdentities = ''; + this.workplaceSearchGettingStarted = ''; + this.workplaceSearchGitHub = ''; + this.workplaceSearchGmail = ''; + this.workplaceSearchGoogleDrive = ''; + this.workplaceSearchIndexingSchedule = ''; + this.workplaceSearchJiraCloud = ''; + this.workplaceSearchJiraServer = ''; + this.workplaceSearchNativeAuth = ''; + this.workplaceSearchOneDrive = ''; + this.workplaceSearchPermissions = ''; + this.workplaceSearchSalesforce = ''; + this.workplaceSearchSecurity = ''; + this.workplaceSearchServiceNow = ''; + this.workplaceSearchSharePoint = ''; + this.workplaceSearchSlack = ''; + this.workplaceSearchStandardAuth = ''; + this.workplaceSearchSynch = ''; + this.workplaceSearchZendesk = ''; } public setDocLinks(docLinks: DocLinksStart): void { - this.enterpriseSearchBase = docLinks.links.enterpriseSearch.base; - this.appSearchBase = docLinks.links.enterpriseSearch.appSearchBase; - this.workplaceSearchBase = docLinks.links.enterpriseSearch.workplaceSearchBase; - this.cloudBase = `${docLinks.ELASTIC_WEBSITE_URL}guide/en/cloud/current`; + this.appSearchApis = docLinks.links.appSearch.apiRef; + this.appSearchApiClients = docLinks.links.appSearch.apiClients; + this.appSearchApiKeys = docLinks.links.appSearch.apiKeys; + this.appSearchAuthentication = docLinks.links.appSearch.authentication; + this.appSearchCrawlRules = docLinks.links.appSearch.crawlRules; + this.appSearchCurations = docLinks.links.appSearch.curations; + this.appSearchDuplicateDocuments = docLinks.links.appSearch.duplicateDocuments; + this.appSearchEntryPoints = docLinks.links.appSearch.entryPoints; + this.appSearchGuide = docLinks.links.appSearch.guide; + this.appSearchIndexingDocs = docLinks.links.appSearch.indexingDocuments; + this.appSearchIndexingDocsSchema = docLinks.links.appSearch.indexingDocumentsSchema; + this.appSearchLogSettings = docLinks.links.appSearch.logSettings; + this.appSearchMetaEngines = docLinks.links.appSearch.metaEngines; + this.appSearchNativeAuth = docLinks.links.appSearch.nativeAuth; + this.appSearchPrecision = docLinks.links.appSearch.precisionTuning; + this.appSearchRelevance = docLinks.links.appSearch.relevanceTuning; + this.appSearchResultSettings = docLinks.links.appSearch.resultSettings; + this.appSearchSearchUI = docLinks.links.appSearch.searchUI; + this.appSearchSecurity = docLinks.links.appSearch.security; + this.appSearchStandardAuth = docLinks.links.appSearch.standardAuth; + this.appSearchSynonyms = docLinks.links.appSearch.synonyms; + this.appSearchWebCrawler = docLinks.links.appSearch.webCrawler; + this.appSearchWebCrawlerEventLogs = docLinks.links.appSearch.webCrawlerEventLogs; + this.cloudIndexManagement = docLinks.links.cloud.indexManagement; + this.enterpriseSearchConfig = docLinks.links.enterpriseSearch.configuration; + this.enterpriseSearchMailService = docLinks.links.enterpriseSearch.mailService; + this.enterpriseSearchUsersAccess = docLinks.links.enterpriseSearch.usersAccess; + this.licenseManagement = docLinks.links.enterpriseSearch.licenseManagement; + this.workplaceSearchBox = docLinks.links.workplaceSearch.box; + this.workplaceSearchConfluenceCloud = docLinks.links.workplaceSearch.confluenceCloud; + this.workplaceSearchConfluenceServer = docLinks.links.workplaceSearch.confluenceServer; + this.workplaceSearchCustomSources = docLinks.links.workplaceSearch.customSources; + this.workplaceSearchCustomSourcePermissions = + docLinks.links.workplaceSearch.customSourcePermissions; + this.workplaceSearchDocumentPermissions = docLinks.links.workplaceSearch.documentPermissions; + this.workplaceSearchDropbox = docLinks.links.workplaceSearch.dropbox; + this.workplaceSearchExternalIdentities = docLinks.links.workplaceSearch.externalIdentities; + this.workplaceSearchGettingStarted = docLinks.links.workplaceSearch.gettingStarted; + this.workplaceSearchGitHub = docLinks.links.workplaceSearch.gitHub; + this.workplaceSearchGmail = docLinks.links.workplaceSearch.gmail; + this.workplaceSearchGoogleDrive = docLinks.links.workplaceSearch.googleDrive; + this.workplaceSearchIndexingSchedule = docLinks.links.workplaceSearch.indexingSchedule; + this.workplaceSearchJiraCloud = docLinks.links.workplaceSearch.jiraCloud; + this.workplaceSearchJiraServer = docLinks.links.workplaceSearch.jiraServer; + this.workplaceSearchNativeAuth = docLinks.links.workplaceSearch.nativeAuth; + this.workplaceSearchOneDrive = docLinks.links.workplaceSearch.oneDrive; + this.workplaceSearchPermissions = docLinks.links.workplaceSearch.permissions; + this.workplaceSearchSalesforce = docLinks.links.workplaceSearch.salesforce; + this.workplaceSearchSecurity = docLinks.links.workplaceSearch.security; + this.workplaceSearchServiceNow = docLinks.links.workplaceSearch.serviceNow; + this.workplaceSearchSharePoint = docLinks.links.workplaceSearch.sharePoint; + this.workplaceSearchSlack = docLinks.links.workplaceSearch.slack; + this.workplaceSearchStandardAuth = docLinks.links.workplaceSearch.standardAuth; + this.workplaceSearchSynch = docLinks.links.workplaceSearch.synch; + this.workplaceSearchZendesk = docLinks.links.workplaceSearch.zendesk; } } diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/licensing/manage_license_button.test.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/licensing/manage_license_button.test.tsx index 1877a4cbd0e42..07c71def01bed 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/licensing/manage_license_button.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/licensing/manage_license_button.test.tsx @@ -13,6 +13,8 @@ import { shallow } from 'enzyme'; import { EuiButton } from '@elastic/eui'; +import { docLinks } from '../../shared/doc_links'; + import { EuiButtonTo } from '../react_router_helpers'; import { ManageLicenseButton } from './'; @@ -35,7 +37,7 @@ describe('ManageLicenseButton', () => { const wrapper = shallow(); expect(wrapper.find(EuiButton).prop('href')).toEqual( - expect.stringContaining('/license-management.html') + expect.stringContaining(docLinks.licenseManagement) ); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/licensing/manage_license_button.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/licensing/manage_license_button.tsx index af3b33e3d7a3d..d0fe98a7c1393 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/licensing/manage_license_button.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/licensing/manage_license_button.tsx @@ -27,12 +27,7 @@ export const ManageLicenseButton: React.FC = (props) => { })} ) : ( - + {i18n.translate('xpack.enterpriseSearch.licenseDocumentationLink', { defaultMessage: 'Learn more about license features', })} diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/role_mappings_table.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/role_mappings_table.tsx index 6e213edf457b1..667980d5f0492 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/role_mappings_table.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/role_mappings_table.tsx @@ -18,7 +18,7 @@ import { RoleRules } from '../types'; import './role_mappings_table.scss'; -const AUTH_PROVIDER_DOCUMENTATION_URL = `${docLinks.enterpriseSearchBase}/users-access.html`; +const AUTH_PROVIDER_DOCUMENTATION_URL = `${docLinks.enterpriseSearchUsersAccess}`; import { ANY_AUTH_PROVIDER, diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/user_selector.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/user_selector.tsx index 25aff5077c680..077ef44c66b2f 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/user_selector.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/user_selector.tsx @@ -24,7 +24,7 @@ import { Role as WSRole } from '../../workplace_search/types'; import { USERNAME_LABEL, EMAIL_LABEL } from '../constants'; import { docLinks } from '../doc_links'; -const SMTP_URL = `${docLinks.enterpriseSearchBase}/mailer-configuration.html`; +const SMTP_URL = `${docLinks.enterpriseSearchMailService}`; import { NEW_USER_LABEL, diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/users_empty_prompt.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/users_empty_prompt.tsx index 42bf690c388c4..56e0a325aafd0 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/users_empty_prompt.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/users_empty_prompt.tsx @@ -20,7 +20,7 @@ import { docLinks } from '../doc_links'; import { NO_USERS_TITLE, NO_USERS_DESCRIPTION, ENABLE_USERS_LINK } from './constants'; -const USERS_DOCS_URL = `${docLinks.enterpriseSearchBase}/users-access.html`; +const USERS_DOCS_URL = `${docLinks.enterpriseSearchUsersAccess}`; export const UsersEmptyPrompt: React.FC = () => ( diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/setup_guide/cloud/instructions.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/setup_guide/cloud/instructions.tsx index 4845d682b8771..8d41e221a2cc7 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/setup_guide/cloud/instructions.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/setup_guide/cloud/instructions.tsx @@ -80,10 +80,7 @@ export const CloudSetupInstructions: React.FC = ({ productName, cloudDepl defaultMessage="After enabling Enterprise Search for your instance you can customize the instance, including fault tolerance, RAM, and other {optionsLink}." values={{ optionsLink: ( - + {i18n.translate( 'xpack.enterpriseSearch.setupGuide.cloud.step3.instruction1LinkText', { defaultMessage: 'configurable options' } @@ -125,10 +122,7 @@ export const CloudSetupInstructions: React.FC = ({ productName, cloudDepl values={{ productName, configurePolicyLink: ( - + {i18n.translate( 'xpack.enterpriseSearch.setupGuide.cloud.step5.instruction1LinkText', { defaultMessage: 'configure an index lifecycle policy' } diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/routes.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/routes.ts index 1be152ad5ca0b..b28343f37ea25 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/routes.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/routes.ts @@ -17,37 +17,36 @@ export const LOGOUT_ROUTE = '/logout'; export const LEAVE_FEEDBACK_EMAIL = 'support@elastic.co'; export const LEAVE_FEEDBACK_URL = `mailto:${LEAVE_FEEDBACK_EMAIL}?Subject=Elastic%20Workplace%20Search%20Feedback`; -export const DOCS_PREFIX = docLinks.workplaceSearchBase; -export const PERMISSIONS_DOCS_URL = `${DOCS_PREFIX}/workplace-search-permissions.html`; -export const DOCUMENT_PERMISSIONS_DOCS_URL = `${DOCS_PREFIX}/workplace-search-sources-document-permissions.html`; -export const DOCUMENT_PERMISSIONS_SYNC_DOCS_URL = `${DOCUMENT_PERMISSIONS_DOCS_URL}#sources-permissions-synchronizing`; -export const PRIVATE_SOURCES_DOCS_URL = `${PERMISSIONS_DOCS_URL}#organizational-sources-private-sources`; -export const EXTERNAL_IDENTITIES_DOCS_URL = `${DOCS_PREFIX}/workplace-search-external-identities-api.html`; -export const SECURITY_DOCS_URL = `${DOCS_PREFIX}/workplace-search-security.html`; -export const SMTP_DOCS_URL = `${DOCS_PREFIX}/workplace-search-smtp-mailer.html`; -export const BOX_DOCS_URL = `${DOCS_PREFIX}/workplace-search-box-connector.html`; -export const CONFLUENCE_DOCS_URL = `${DOCS_PREFIX}/workplace-search-confluence-cloud-connector.html`; -export const CONFLUENCE_SERVER_DOCS_URL = `${DOCS_PREFIX}/workplace-search-confluence-server-connector.html`; -export const DROPBOX_DOCS_URL = `${DOCS_PREFIX}/workplace-search-dropbox-connector.html`; -export const GITHUB_DOCS_URL = `${DOCS_PREFIX}/workplace-search-github-connector.html`; -export const GITHUB_ENTERPRISE_DOCS_URL = `${DOCS_PREFIX}/workplace-search-github-connector.html`; -export const GMAIL_DOCS_URL = `${DOCS_PREFIX}/workplace-search-gmail-connector.html`; -export const GOOGLE_DRIVE_DOCS_URL = `${DOCS_PREFIX}/workplace-search-google-drive-connector.html`; -export const JIRA_DOCS_URL = `${DOCS_PREFIX}/workplace-search-jira-cloud-connector.html`; -export const JIRA_SERVER_DOCS_URL = `${DOCS_PREFIX}/workplace-search-jira-server-connector.html`; -export const ONEDRIVE_DOCS_URL = `${DOCS_PREFIX}/workplace-search-onedrive-connector.html`; -export const SALESFORCE_DOCS_URL = `${DOCS_PREFIX}/workplace-search-salesforce-connector.html`; -export const SERVICENOW_DOCS_URL = `${DOCS_PREFIX}/workplace-search-servicenow-connector.html`; -export const SHAREPOINT_DOCS_URL = `${DOCS_PREFIX}/workplace-search-sharepoint-online-connector.html`; -export const SLACK_DOCS_URL = `${DOCS_PREFIX}/workplace-search-slack-connector.html`; -export const ZENDESK_DOCS_URL = `${DOCS_PREFIX}/workplace-search-zendesk-connector.html`; -export const CUSTOM_SOURCE_DOCS_URL = `${DOCS_PREFIX}/workplace-search-custom-api-sources.html`; -export const CUSTOM_API_DOCS_URL = `${DOCS_PREFIX}/workplace-search-custom-sources-api.html`; -export const CUSTOM_API_DOCUMENT_PERMISSIONS_DOCS_URL = `${CUSTOM_SOURCE_DOCS_URL}#custom-api-source-document-level-access-control`; -export const ENT_SEARCH_LICENSE_MANAGEMENT = `${docLinks.enterpriseSearchBase}/license-management.html`; -export const SYNCHRONIZATION_DOCS_URL = `${DOCS_PREFIX}}/workplace-search-customizing-indexing-rules.html#workplace-search-customizing-indexing-rules`; -export const DIFFERENT_SYNC_TYPES_DOCS_URL = `${DOCS_PREFIX}}/workplace-search-customizing-indexing-rules.html#_indexing_schedule`; -export const OBJECTS_AND_ASSETS_DOCS_URL = `${DOCS_PREFIX}}/workplace-search-customizing-indexing-rules.html#workplace-search-customizing-indexing-rules`; +export const BOX_DOCS_URL = docLinks.workplaceSearchBox; +export const CONFLUENCE_DOCS_URL = docLinks.workplaceSearchConfluenceCloud; +export const CONFLUENCE_SERVER_DOCS_URL = docLinks.workplaceSearchConfluenceServer; +export const CUSTOM_SOURCE_DOCS_URL = docLinks.workplaceSearchCustomSources; +export const CUSTOM_API_DOCUMENT_PERMISSIONS_DOCS_URL = + docLinks.workplaceSearchCustomSourcePermissions; +export const DIFFERENT_SYNC_TYPES_DOCS_URL = docLinks.workplaceSearchIndexingSchedule; +export const DOCUMENT_PERMISSIONS_DOCS_URL = docLinks.workplaceSearchDocumentPermissions; +export const DROPBOX_DOCS_URL = docLinks.workplaceSearchDropbox; +export const ENT_SEARCH_LICENSE_MANAGEMENT = docLinks.licenseManagement; +export const EXTERNAL_IDENTITIES_DOCS_URL = docLinks.workplaceSearchExternalIdentities; +export const GETTING_STARTED_DOCS_URL = docLinks.workplaceSearchGettingStarted; +export const GITHUB_DOCS_URL = docLinks.workplaceSearchGitHub; +export const GITHUB_ENTERPRISE_DOCS_URL = docLinks.workplaceSearchGitHub; +export const GMAIL_DOCS_URL = docLinks.workplaceSearchGmail; +export const GOOGLE_DRIVE_DOCS_URL = docLinks.workplaceSearchGoogleDrive; +export const JIRA_DOCS_URL = docLinks.workplaceSearchJiraCloud; +export const JIRA_SERVER_DOCS_URL = docLinks.workplaceSearchJiraServer; +export const NATIVE_AUTH_DOCS_URL = docLinks.workplaceSearchNativeAuth; +export const OBJECTS_AND_ASSETS_DOCS_URL = docLinks.workplaceSearchSynch; +export const ONEDRIVE_DOCS_URL = docLinks.workplaceSearchOneDrive; +export const PRIVATE_SOURCES_DOCS_URL = docLinks.workplaceSearchPermissions; +export const SALESFORCE_DOCS_URL = docLinks.workplaceSearchSalesforce; +export const SECURITY_DOCS_URL = docLinks.workplaceSearchSecurity; +export const SERVICENOW_DOCS_URL = docLinks.workplaceSearchServiceNow; +export const SHAREPOINT_DOCS_URL = docLinks.workplaceSearchSharePoint; +export const SLACK_DOCS_URL = docLinks.workplaceSearchSlack; +export const STANDARD_AUTH_DOCS_URL = docLinks.workplaceSearchStandardAuth; +export const SYNCHRONIZATION_DOCS_URL = docLinks.workplaceSearchSynch; +export const ZENDESK_DOCS_URL = docLinks.workplaceSearchZendesk; export const PERSONAL_PATH = '/p'; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/overview.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/overview.tsx index d506ed9c32ba5..a33f5ec90e3a0 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/overview.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/overview.tsx @@ -222,11 +222,11 @@ export const Overview: React.FC = () => { {activities.map(({ details: activityDetails, event, time, status }, i) => ( - {event} + {event} {!custom && ( - + {status} {activityDetails && } @@ -234,7 +234,7 @@ export const Overview: React.FC = () => { )} - + {time} @@ -544,6 +544,7 @@ export const Overview: React.FC = () => { + {showSyncTriggerCallout && syncTriggerCallout} {groups.length > 0 && groupsSummary} {details.length > 0 && {detailsSummary}} {!custom && serviceTypeSupportsPermissions && ( @@ -587,7 +588,6 @@ export const Overview: React.FC = () => { )} )} - {showSyncTriggerCallout && syncTriggerCallout} diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/settings/components/oauth_application.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/settings/components/oauth_application.test.tsx index 4d329ff357b89..a992cf49f75fb 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/settings/components/oauth_application.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/settings/components/oauth_application.test.tsx @@ -124,9 +124,9 @@ describe('OauthApplication', () => { `); }); + /* This href test should ultimately use the docLinkServiceMock */ it('renders description', () => { const wrapper = shallow(); - expect(wrapper.prop('pageHeader').description).toMatchInlineSnapshot(` { Explore Platinum features diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/setup_guide/setup_guide.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/setup_guide/setup_guide.tsx index 905ba20e4f660..e52a174850c4c 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/setup_guide/setup_guide.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/setup_guide/setup_guide.tsx @@ -15,19 +15,23 @@ import { WORKPLACE_SEARCH_PLUGIN } from '../../../../../common/constants'; import { SetWorkplaceSearchChrome as SetPageChrome } from '../../../shared/kibana_chrome'; import { SetupGuideLayout, SETUP_GUIDE_TITLE } from '../../../shared/setup_guide'; import { SendWorkplaceSearchTelemetry as SendTelemetry } from '../../../shared/telemetry'; -import { DOCS_PREFIX } from '../../routes'; +import { + GETTING_STARTED_DOCS_URL, + NATIVE_AUTH_DOCS_URL, + STANDARD_AUTH_DOCS_URL, +} from '../../routes'; import GettingStarted from './assets/getting_started.png'; -const GETTING_STARTED_LINK_URL = `${DOCS_PREFIX}/workplace-search-getting-started.html`; +const GETTING_STARTED_LINK_URL = GETTING_STARTED_DOCS_URL; export const SetupGuide: React.FC = () => { return ( diff --git a/x-pack/plugins/fleet/public/components/enrollment_instructions/manual/index.tsx b/x-pack/plugins/fleet/public/components/enrollment_instructions/manual/index.tsx index 32846620221ae..16ed53020a313 100644 --- a/x-pack/plugins/fleet/public/components/enrollment_instructions/manual/index.tsx +++ b/x-pack/plugins/fleet/public/components/enrollment_instructions/manual/index.tsx @@ -6,8 +6,10 @@ */ import React from 'react'; + import { useStartServices } from '../../../hooks'; import type { EnrollmentAPIKey } from '../../../types'; + import { PlatformSelector } from './platform_selector'; interface Props { diff --git a/x-pack/plugins/fleet/server/services/package_policy.test.ts b/x-pack/plugins/fleet/server/services/package_policy.test.ts index 36976bea4a970..ac88204f082b7 100644 --- a/x-pack/plugins/fleet/server/services/package_policy.test.ts +++ b/x-pack/plugins/fleet/server/services/package_policy.test.ts @@ -39,7 +39,8 @@ import type { import { IngestManagerError } from '../errors'; import { - overridePackageInputs, + preconfigurePackageInputs, + updatePackageInputs, packagePolicyService, _applyIndexPrivileges, } from './package_policy'; @@ -1170,7 +1171,776 @@ describe('Package policy service', () => { }); }); - describe('overridePackageInputs', () => { + describe('preconfigurePackageInputs', () => { + describe('when variable is already defined', () => { + it('override original variable value', () => { + const basePackagePolicy: NewPackagePolicy = { + name: 'base-package-policy', + description: 'Base Package Policy', + namespace: 'default', + enabled: true, + policy_id: 'xxxx', + output_id: 'xxxx', + package: { + name: 'test-package', + title: 'Test Package', + version: '0.0.1', + }, + inputs: [ + { + type: 'logs', + policy_template: 'template_1', + enabled: true, + vars: { + path: { + type: 'text', + value: ['/var/log/logfile.log'], + }, + }, + streams: [], + }, + ], + }; + + const packageInfo: PackageInfo = { + name: 'test-package', + description: 'Test Package', + title: 'Test Package', + version: '0.0.1', + latestVersion: '0.0.1', + release: 'experimental', + format_version: '1.0.0', + owner: { github: 'elastic/fleet' }, + policy_templates: [ + { + name: 'template_1', + title: 'Template 1', + description: 'Template 1', + inputs: [ + { + type: 'logs', + title: 'Log', + description: 'Log Input', + vars: [ + { + name: 'path', + type: 'text', + }, + ], + }, + ], + }, + ], + // @ts-ignore + assets: {}, + }; + + const inputsOverride: NewPackagePolicyInput[] = [ + { + type: 'logs', + enabled: true, + streams: [], + vars: { + path: { + type: 'text', + value: '/var/log/new-logfile.log', + }, + }, + }, + ]; + + const result = preconfigurePackageInputs( + basePackagePolicy, + packageInfo, + // TODO: Update this type assertion when the `InputsOverride` type is updated such + // that it no longer causes unresolvable type errors when used directly + inputsOverride as InputsOverride[] + ); + expect(result.inputs[0]?.vars?.path.value).toEqual('/var/log/new-logfile.log'); + }); + }); + + describe('when variable is undefined in original object', () => { + it('adds the variable definition to the resulting object', () => { + const basePackagePolicy: NewPackagePolicy = { + name: 'base-package-policy', + description: 'Base Package Policy', + namespace: 'default', + enabled: true, + policy_id: 'xxxx', + output_id: 'xxxx', + package: { + name: 'test-package', + title: 'Test Package', + version: '0.0.1', + }, + inputs: [ + { + type: 'logs', + policy_template: 'template_1', + enabled: true, + vars: { + path: { + type: 'text', + value: ['/var/log/logfile.log'], + }, + }, + streams: [], + }, + ], + }; + + const packageInfo: PackageInfo = { + name: 'test-package', + description: 'Test Package', + title: 'Test Package', + version: '0.0.1', + latestVersion: '0.0.1', + release: 'experimental', + format_version: '1.0.0', + owner: { github: 'elastic/fleet' }, + policy_templates: [ + { + name: 'template_1', + title: 'Template 1', + description: 'Template 1', + inputs: [ + { + type: 'logs', + title: 'Log', + description: 'Log Input', + vars: [ + { + name: 'path', + type: 'text', + }, + { + name: 'path_2', + type: 'text', + }, + ], + }, + ], + }, + ], + // @ts-ignore + assets: {}, + }; + + const inputsOverride: NewPackagePolicyInput[] = [ + { + type: 'logs', + enabled: true, + streams: [], + policy_template: 'template_1', + vars: { + path: { + type: 'text', + value: '/var/log/new-logfile.log', + }, + path_2: { + type: 'text', + value: '/var/log/custom.log', + }, + }, + }, + ]; + + const result = preconfigurePackageInputs( + basePackagePolicy, + packageInfo, + // TODO: Update this type assertion when the `InputsOverride` type is updated such + // that it no longer causes unresolvable type errors when used directly + inputsOverride as InputsOverride[] + ); + + expect(result.inputs[0]?.vars?.path_2.value).toEqual('/var/log/custom.log'); + }); + }); + + describe('when variable is undefined in original object and policy_template is undefined', () => { + it('adds the variable definition to the resulting object', () => { + const basePackagePolicy: NewPackagePolicy = { + name: 'base-package-policy', + description: 'Base Package Policy', + namespace: 'default', + enabled: true, + policy_id: 'xxxx', + output_id: 'xxxx', + package: { + name: 'test-package', + title: 'Test Package', + version: '0.0.1', + }, + inputs: [ + { + type: 'logs', + policy_template: 'template_1', + enabled: true, + vars: { + path: { + type: 'text', + value: ['/var/log/logfile.log'], + }, + }, + streams: [], + }, + ], + }; + + const packageInfo: PackageInfo = { + name: 'test-package', + description: 'Test Package', + title: 'Test Package', + version: '0.0.1', + latestVersion: '0.0.1', + release: 'experimental', + format_version: '1.0.0', + owner: { github: 'elastic/fleet' }, + policy_templates: [ + { + name: 'template_1', + title: 'Template 1', + description: 'Template 1', + inputs: [ + { + type: 'logs', + title: 'Log', + description: 'Log Input', + vars: [ + { + name: 'path', + type: 'text', + }, + { + name: 'path_2', + type: 'text', + }, + ], + }, + ], + }, + ], + // @ts-ignore + assets: {}, + }; + + const inputsOverride: NewPackagePolicyInput[] = [ + { + type: 'logs', + enabled: true, + streams: [], + policy_template: undefined, // preconfigured input overrides don't have a policy_template + vars: { + path: { + type: 'text', + value: '/var/log/new-logfile.log', + }, + path_2: { + type: 'text', + value: '/var/log/custom.log', + }, + }, + }, + ]; + + const result = preconfigurePackageInputs( + basePackagePolicy, + packageInfo, + // TODO: Update this type assertion when the `InputsOverride` type is updated such + // that it no longer causes unresolvable type errors when used directly + inputsOverride as InputsOverride[] + ); + + expect(result.inputs[0]?.vars?.path_2.value).toEqual('/var/log/custom.log'); + }); + }); + + describe('when an input of the same type exists under multiple policy templates', () => { + it('adds variable definitions to the proper streams', () => { + const basePackagePolicy: NewPackagePolicy = { + name: 'base-package-policy', + description: 'Base Package Policy', + namespace: 'default', + enabled: true, + policy_id: 'xxxx', + output_id: 'xxxx', + package: { + name: 'test-package', + title: 'Test Package', + version: '0.0.1', + }, + inputs: [ + { + type: 'logs', + policy_template: 'template_1', + enabled: true, + streams: [ + { + enabled: true, + data_stream: { + dataset: 'test.logs', + type: 'logfile', + }, + vars: { + log_file_path: { + type: 'text', + }, + }, + }, + ], + }, + { + type: 'logs', + policy_template: 'template_2', + enabled: true, + streams: [ + { + enabled: true, + data_stream: { + dataset: 'test.logs', + type: 'logfile', + }, + vars: { + log_file_path: { + type: 'text', + }, + }, + }, + ], + }, + ], + }; + + const packageInfo: PackageInfo = { + name: 'test-package', + description: 'Test Package', + title: 'Test Package', + version: '0.0.1', + latestVersion: '0.0.1', + release: 'experimental', + format_version: '1.0.0', + owner: { github: 'elastic/fleet' }, + policy_templates: [ + { + name: 'template_1', + title: 'Template 1', + description: 'Template 1', + inputs: [ + { + type: 'logs', + title: 'Log', + description: 'Log Input', + vars: [], + }, + ], + }, + { + name: 'template_2', + title: 'Template 2', + description: 'Template 2', + inputs: [ + { + type: 'logs', + title: 'Log', + description: 'Log Input', + vars: [], + }, + ], + }, + ], + // @ts-ignore + assets: {}, + }; + + const inputsOverride: NewPackagePolicyInput[] = [ + { + type: 'logs', + enabled: true, + policy_template: 'template_1', + streams: [ + { + enabled: true, + data_stream: { + dataset: 'test.logs', + type: 'logfile', + }, + vars: { + log_file_path: { + type: 'text', + value: '/var/log/template1-logfile.log', + }, + }, + }, + ], + }, + { + type: 'logs', + enabled: true, + policy_template: 'template_2', + streams: [ + { + enabled: true, + data_stream: { + dataset: 'test.logs', + type: 'logfile', + }, + vars: { + log_file_path: { + type: 'text', + value: '/var/log/template2-logfile.log', + }, + }, + }, + ], + }, + ]; + + const result = preconfigurePackageInputs( + basePackagePolicy, + packageInfo, + // TODO: Update this type assertion when the `InputsOverride` type is updated such + // that it no longer causes unresolvable type errors when used directly + inputsOverride as InputsOverride[] + ); + + expect(result.inputs).toHaveLength(2); + + const template1Input = result.inputs.find( + (input) => input.policy_template === 'template_1' + ); + const template2Input = result.inputs.find( + (input) => input.policy_template === 'template_2' + ); + + expect(template1Input).toBeDefined(); + expect(template2Input).toBeDefined(); + + expect(template1Input?.streams[0].vars?.log_file_path.value).toBe( + '/var/log/template1-logfile.log' + ); + + expect(template2Input?.streams[0].vars?.log_file_path.value).toBe( + '/var/log/template2-logfile.log' + ); + }); + }); + + describe('when an input or stream is disabled on the original policy object', () => { + it('remains disabled on the resulting policy object', () => { + const basePackagePolicy: NewPackagePolicy = { + name: 'base-package-policy', + description: 'Base Package Policy', + namespace: 'default', + enabled: true, + policy_id: 'xxxx', + output_id: 'xxxx', + package: { + name: 'test-package', + title: 'Test Package', + version: '0.0.1', + }, + inputs: [ + { + type: 'logs', + policy_template: 'template_1', + enabled: false, + streams: [ + { + enabled: false, + data_stream: { + dataset: 'test.logs', + type: 'logfile', + }, + vars: { + log_file_path: { + type: 'text', + }, + }, + }, + { + enabled: true, + data_stream: { + dataset: 'test.logs', + type: 'logfile2', + }, + vars: { + log_file_path_2: { + type: 'text', + }, + }, + }, + ], + }, + { + type: 'logs_2', + policy_template: 'template_1', + enabled: true, + streams: [ + { + enabled: true, + data_stream: { + dataset: 'test.logs', + type: 'logfile', + }, + vars: { + log_file_path: { + type: 'text', + }, + }, + }, + ], + }, + { + type: 'logs', + policy_template: 'template_2', + enabled: true, + streams: [ + { + enabled: true, + data_stream: { + dataset: 'test.logs', + type: 'logfile', + }, + vars: { + log_file_path: { + type: 'text', + }, + }, + }, + ], + }, + ], + }; + + const packageInfo: PackageInfo = { + name: 'test-package', + description: 'Test Package', + title: 'Test Package', + version: '0.0.1', + latestVersion: '0.0.1', + release: 'experimental', + format_version: '1.0.0', + owner: { github: 'elastic/fleet' }, + policy_templates: [ + { + name: 'template_1', + title: 'Template 1', + description: 'Template 1', + inputs: [ + { + type: 'logs', + title: 'Log', + description: 'Log Input', + vars: [], + }, + { + type: 'logs_2', + title: 'Log 2', + description: 'Log Input 2', + vars: [], + }, + ], + }, + { + name: 'template_2', + title: 'Template 2', + description: 'Template 2', + inputs: [ + { + type: 'logs', + title: 'Log', + description: 'Log Input', + vars: [], + }, + ], + }, + ], + // @ts-ignore + assets: {}, + }; + + const inputsOverride: NewPackagePolicyInput[] = [ + { + type: 'logs', + enabled: true, + policy_template: 'template_1', + streams: [ + { + enabled: true, + data_stream: { + dataset: 'test.logs', + type: 'logfile', + }, + vars: { + log_file_path: { + type: 'text', + value: '/var/log/template1-logfile.log', + }, + }, + }, + { + enabled: true, + data_stream: { + dataset: 'test.logs', + type: 'logfile2', + }, + vars: { + log_file_path_2: { + type: 'text', + value: '/var/log/template1-logfile2.log', + }, + }, + }, + ], + }, + { + type: 'logs', + enabled: true, + policy_template: 'template_2', + streams: [ + { + enabled: true, + data_stream: { + dataset: 'test.logs', + type: 'logfile', + }, + vars: { + log_file_path: { + type: 'text', + value: '/var/log/template2-logfile.log', + }, + }, + }, + ], + }, + ]; + + const result = preconfigurePackageInputs( + basePackagePolicy, + packageInfo, + // TODO: Update this type assertion when the `InputsOverride` type is updated such + // that it no longer causes unresolvable type errors when used directly + inputsOverride as InputsOverride[] + ); + + const template1Inputs = result.inputs.filter( + (input) => input.policy_template === 'template_1' + ); + + const template2Inputs = result.inputs.filter( + (input) => input.policy_template === 'template_2' + ); + + expect(template1Inputs).toHaveLength(2); + expect(template2Inputs).toHaveLength(1); + + const logsInput = template1Inputs?.find((input) => input.type === 'logs'); + expect(logsInput?.enabled).toBe(false); + + const logfileStream = logsInput?.streams.find( + (stream) => stream.data_stream.type === 'logfile' + ); + + expect(logfileStream?.enabled).toBe(false); + }); + }); + + describe('when a datastream is deleted from an input', () => { + it('it remove the non existing datastream', () => { + const basePackagePolicy: NewPackagePolicy = { + name: 'base-package-policy', + description: 'Base Package Policy', + namespace: 'default', + enabled: true, + policy_id: 'xxxx', + output_id: 'xxxx', + package: { + name: 'test-package', + title: 'Test Package', + version: '0.0.1', + }, + inputs: [ + { + type: 'logs', + policy_template: 'template_1', + enabled: true, + vars: { + path: { + type: 'text', + value: ['/var/log/logfile.log'], + }, + }, + streams: [ + { + enabled: true, + data_stream: { dataset: 'dataset.test123', type: 'log' }, + }, + ], + }, + ], + }; + + const packageInfo: PackageInfo = { + name: 'test-package', + description: 'Test Package', + title: 'Test Package', + version: '0.0.1', + latestVersion: '0.0.1', + release: 'experimental', + format_version: '1.0.0', + owner: { github: 'elastic/fleet' }, + policy_templates: [ + { + name: 'template_1', + title: 'Template 1', + description: 'Template 1', + inputs: [ + { + type: 'logs', + title: 'Log', + description: 'Log Input', + vars: [ + { + name: 'path', + type: 'text', + }, + ], + }, + ], + }, + ], + // @ts-ignore + assets: {}, + }; + + const inputsOverride: NewPackagePolicyInput[] = [ + { + type: 'logs', + enabled: true, + streams: [], + vars: { + path: { + type: 'text', + value: '/var/log/new-logfile.log', + }, + }, + }, + ]; + + const result = preconfigurePackageInputs( + basePackagePolicy, + packageInfo, + // TODO: Update this type assertion when the `InputsOverride` type is updated such + // that it no longer causes unresolvable type errors when used directly + inputsOverride as InputsOverride[] + ); + expect(result.inputs[0]?.vars?.path.value).toEqual('/var/log/new-logfile.log'); + }); + }); + }); + + describe('updatePackageInputs', () => { describe('when variable is already defined', () => { it('preserves original variable value without overwriting', () => { const basePackagePolicy: NewPackagePolicy = { @@ -1248,7 +2018,7 @@ describe('Package policy service', () => { }, ]; - const result = overridePackageInputs( + const result = updatePackageInputs( basePackagePolicy, packageInfo, // TODO: Update this type assertion when the `InputsOverride` type is updated such @@ -1346,7 +2116,7 @@ describe('Package policy service', () => { }, ]; - const result = overridePackageInputs( + const result = updatePackageInputs( basePackagePolicy, packageInfo, // TODO: Update this type assertion when the `InputsOverride` type is updated such @@ -1445,7 +2215,7 @@ describe('Package policy service', () => { }, ]; - const result = overridePackageInputs( + const result = updatePackageInputs( basePackagePolicy, packageInfo, // TODO: Update this type assertion when the `InputsOverride` type is updated such @@ -1598,7 +2368,7 @@ describe('Package policy service', () => { }, ]; - const result = overridePackageInputs( + const result = updatePackageInputs( basePackagePolicy, packageInfo, // TODO: Update this type assertion when the `InputsOverride` type is updated such @@ -1819,7 +2589,7 @@ describe('Package policy service', () => { }, ]; - const result = overridePackageInputs( + const result = updatePackageInputs( basePackagePolicy, packageInfo, // TODO: Update this type assertion when the `InputsOverride` type is updated such @@ -1932,7 +2702,7 @@ describe('Package policy service', () => { }, ]; - const result = overridePackageInputs( + const result = updatePackageInputs( basePackagePolicy, packageInfo, // TODO: Update this type assertion when the `InputsOverride` type is updated such diff --git a/x-pack/plugins/fleet/server/services/package_policy.ts b/x-pack/plugins/fleet/server/services/package_policy.ts index 535d93cc3eceb..5ac348ad7c8a2 100644 --- a/x-pack/plugins/fleet/server/services/package_policy.ts +++ b/x-pack/plugins/fleet/server/services/package_policy.ts @@ -590,7 +590,7 @@ class PackagePolicyService { try { const { packagePolicy, packageInfo } = await this.getUpgradePackagePolicyInfo(soClient, id); - const updatePackagePolicy = overridePackageInputs( + const updatePackagePolicy = updatePackageInputs( { ...omit(packagePolicy, 'id'), inputs: packagePolicy.inputs, @@ -648,7 +648,7 @@ class PackagePolicyService { packageVersion ); - const updatedPackagePolicy = overridePackageInputs( + const updatedPackagePolicy = updatePackageInputs( { ...omit(packagePolicy, 'id'), inputs: packagePolicy.inputs, @@ -1030,13 +1030,13 @@ export const packagePolicyService = new PackagePolicyService(); export type { PackagePolicyService }; -export function overridePackageInputs( +export function updatePackageInputs( basePackagePolicy: NewPackagePolicy, packageInfo: PackageInfo, - inputsOverride?: InputsOverride[], + inputsUpdated?: InputsOverride[], dryRun?: boolean ): DryRunPackagePolicy { - if (!inputsOverride) return basePackagePolicy; + if (!inputsUpdated) return basePackagePolicy; const availablePolicyTemplates = packageInfo.policy_templates ?? []; @@ -1065,42 +1065,40 @@ export function overridePackageInputs( }), ]; - for (const override of inputsOverride) { - // Preconfiguration does not currently support multiple policy templates, so overrides will have an undefined - // policy template, so we only match on `type` in that case. - let originalInput = override.policy_template - ? inputs.find( - (i) => i.type === override.type && i.policy_template === override.policy_template - ) - : inputs.find((i) => i.type === override.type); + for (const update of inputsUpdated) { + // If update have an undefined policy template + // we only match on `type` . + let originalInput = update.policy_template + ? inputs.find((i) => i.type === update.type && i.policy_template === update.policy_template) + : inputs.find((i) => i.type === update.type); // If there's no corresponding input on the original package policy, just // take the override value from the new package as-is. This case typically // occurs when inputs or package policy templates are added/removed between versions. if (originalInput === undefined) { - inputs.push(override as NewPackagePolicyInput); + inputs.push(update as NewPackagePolicyInput); continue; } // For flags like this, we only want to override the original value if it was set // as `undefined` in the original object. An explicit true/false value should be // persisted from the original object to the result after the override process is complete. - if (originalInput.enabled === undefined && override.enabled !== undefined) { - originalInput.enabled = override.enabled; + if (originalInput.enabled === undefined && update.enabled !== undefined) { + originalInput.enabled = update.enabled; } - if (originalInput.keep_enabled === undefined && override.keep_enabled !== undefined) { - originalInput.keep_enabled = override.keep_enabled; + if (originalInput.keep_enabled === undefined && update.keep_enabled !== undefined) { + originalInput.keep_enabled = update.keep_enabled; } - if (override.vars) { + if (update.vars) { const indexOfInput = inputs.indexOf(originalInput); - inputs[indexOfInput] = deepMergeVars(originalInput, override) as NewPackagePolicyInput; + inputs[indexOfInput] = deepMergeVars(originalInput, update, true) as NewPackagePolicyInput; originalInput = inputs[indexOfInput]; } - if (override.streams) { - for (const stream of override.streams) { + if (update.streams) { + for (const stream of update.streams) { let originalStream = originalInput?.streams.find( (s) => s.data_stream.dataset === stream.data_stream.dataset ); @@ -1118,7 +1116,8 @@ export function overridePackageInputs( const indexOfStream = originalInput.streams.indexOf(originalStream); originalInput.streams[indexOfStream] = deepMergeVars( originalStream, - stream as InputsOverride + stream as InputsOverride, + true ); originalStream = originalInput.streams[indexOfStream]; } @@ -1128,9 +1127,8 @@ export function overridePackageInputs( // Filter all stream that have been removed from the input originalInput.streams = originalInput.streams.filter((originalStream) => { return ( - override.streams?.some( - (s) => s.data_stream.dataset === originalStream.data_stream.dataset - ) ?? false + update.streams?.some((s) => s.data_stream.dataset === originalStream.data_stream.dataset) ?? + false ); }); } @@ -1171,7 +1169,110 @@ export function overridePackageInputs( return resultingPackagePolicy; } -function deepMergeVars(original: any, override: any): any { +export function preconfigurePackageInputs( + basePackagePolicy: NewPackagePolicy, + packageInfo: PackageInfo, + preconfiguredInputs?: InputsOverride[] +): NewPackagePolicy { + if (!preconfiguredInputs) return basePackagePolicy; + + const inputs = [...basePackagePolicy.inputs]; + + for (const preconfiguredInput of preconfiguredInputs) { + // Preconfiguration does not currently support multiple policy templates, so overrides will have an undefined + // policy template, so we only match on `type` in that case. + let originalInput = preconfiguredInput.policy_template + ? inputs.find( + (i) => + i.type === preconfiguredInput.type && + i.policy_template === preconfiguredInput.policy_template + ) + : inputs.find((i) => i.type === preconfiguredInput.type); + + // If the input do not exist skip + if (originalInput === undefined) { + continue; + } + + // For flags like this, we only want to override the original value if it was set + // as `undefined` in the original object. An explicit true/false value should be + // persisted from the original object to the result after the override process is complete. + if (originalInput.enabled === undefined && preconfiguredInput.enabled !== undefined) { + originalInput.enabled = preconfiguredInput.enabled; + } + + if (originalInput.keep_enabled === undefined && preconfiguredInput.keep_enabled !== undefined) { + originalInput.keep_enabled = preconfiguredInput.keep_enabled; + } + + if (preconfiguredInput.vars) { + const indexOfInput = inputs.indexOf(originalInput); + inputs[indexOfInput] = deepMergeVars( + originalInput, + preconfiguredInput + ) as NewPackagePolicyInput; + originalInput = inputs[indexOfInput]; + } + + if (preconfiguredInput.streams) { + for (const stream of preconfiguredInput.streams) { + let originalStream = originalInput?.streams.find( + (s) => s.data_stream.dataset === stream.data_stream.dataset + ); + + if (originalStream === undefined) { + continue; + } + + if (originalStream?.enabled === undefined) { + originalStream.enabled = stream.enabled; + } + + if (stream.vars) { + const indexOfStream = originalInput.streams.indexOf(originalStream); + originalInput.streams[indexOfStream] = deepMergeVars( + originalStream, + stream as InputsOverride + ); + originalStream = originalInput.streams[indexOfStream]; + } + } + } + } + + const resultingPackagePolicy: NewPackagePolicy = { + ...basePackagePolicy, + inputs, + }; + + const validationResults = validatePackagePolicy(resultingPackagePolicy, packageInfo, safeLoad); + + if (validationHasErrors(validationResults)) { + const responseFormattedValidationErrors = Object.entries(getFlattenedObject(validationResults)) + .map(([key, value]) => ({ + key, + message: value, + })) + .filter(({ message }) => !!message); + + if (responseFormattedValidationErrors.length) { + throw new PackagePolicyValidationError( + i18n.translate('xpack.fleet.packagePolicyInvalidError', { + defaultMessage: 'Package policy is invalid: {errors}', + values: { + errors: responseFormattedValidationErrors + .map(({ key, message }) => `${key}: ${message}`) + .join('\n'), + }, + }) + ); + } + } + + return resultingPackagePolicy; +} + +function deepMergeVars(original: any, override: any, keepOriginalValue = false): any { if (!original.vars) { original.vars = { ...override.vars }; } @@ -1192,7 +1293,7 @@ function deepMergeVars(original: any, override: any): any { // Ensure that any value from the original object is persisted on the newly merged resulting object, // even if we merge other data about the given variable - if (originalVar?.value) { + if (keepOriginalValue && originalVar?.value) { result.vars[name].value = originalVar.value; } } diff --git a/x-pack/plugins/fleet/server/services/preconfiguration.ts b/x-pack/plugins/fleet/server/services/preconfiguration.ts index 8b906b68556a4..76fa7778eafa2 100644 --- a/x-pack/plugins/fleet/server/services/preconfiguration.ts +++ b/x-pack/plugins/fleet/server/services/preconfiguration.ts @@ -34,7 +34,7 @@ import { ensurePackagesCompletedInstall } from './epm/packages/install'; import { bulkInstallPackages } from './epm/packages/bulk_install_packages'; import { agentPolicyService, addPackageToAgentPolicy } from './agent_policy'; import type { InputsOverride } from './package_policy'; -import { overridePackageInputs, packagePolicyService } from './package_policy'; +import { preconfigurePackageInputs, packagePolicyService } from './package_policy'; import { appContextService } from './app_context'; import type { UpgradeManagedPackagePoliciesResult } from './managed_package_policies'; import { upgradeManagedPackagePolicies } from './managed_package_policies'; @@ -428,7 +428,7 @@ async function addPreconfiguredPolicyPackages( defaultOutput, name, description, - (policy) => overridePackageInputs(policy, packageInfo, inputs), + (policy) => preconfigurePackageInputs(policy, packageInfo, inputs), bumpAgentPolicyRevison ); } diff --git a/x-pack/plugins/lens/server/routes/existing_fields.ts b/x-pack/plugins/lens/server/routes/existing_fields.ts index d3a79dc7d8bfc..3a57a77a97726 100644 --- a/x-pack/plugins/lens/server/routes/existing_fields.ts +++ b/x-pack/plugins/lens/server/routes/existing_fields.ts @@ -183,6 +183,7 @@ async function fetchIndexPatternStats({ { range: { [timeFieldName]: { + format: 'strict_date_optional_time', gte: fromDate, lte: toDate, }, diff --git a/x-pack/plugins/maps/public/actions/map_actions.ts b/x-pack/plugins/maps/public/actions/map_actions.ts index 9ec9a42986fbb..ddfc441471c1e 100644 --- a/x-pack/plugins/maps/public/actions/map_actions.ts +++ b/x-pack/plugins/maps/public/actions/map_actions.ts @@ -92,10 +92,16 @@ export function updateMapSetting( settingKey: string, settingValue: string | boolean | number | object ) { - return { - type: UPDATE_MAP_SETTING, - settingKey, - settingValue, + return (dispatch: ThunkDispatch) => { + dispatch({ + type: UPDATE_MAP_SETTING, + settingKey, + settingValue, + }); + + if (settingKey === 'autoFitToDataBounds' && settingValue === true) { + dispatch(autoFitToBounds()); + } }; } diff --git a/x-pack/plugins/maps/public/classes/layers/heatmap_layer/heatmap_layer.ts b/x-pack/plugins/maps/public/classes/layers/heatmap_layer/heatmap_layer.ts index 2bc79bfaea746..9b49d2b597e46 100644 --- a/x-pack/plugins/maps/public/classes/layers/heatmap_layer/heatmap_layer.ts +++ b/x-pack/plugins/maps/public/classes/layers/heatmap_layer/heatmap_layer.ts @@ -11,7 +11,7 @@ import { HeatmapStyle } from '../../styles/heatmap/heatmap_style'; import { LAYER_TYPE } from '../../../../common/constants'; import { HeatmapLayerDescriptor } from '../../../../common/descriptor_types'; import { ESGeoGridSource } from '../../sources/es_geo_grid_source'; -import { getVectorSourceBounds, MvtSourceData, syncMvtSourceData } from '../vector_layer'; +import { syncBoundsData, MvtSourceData, syncMvtSourceData } from '../vector_layer'; import { DataRequestContext } from '../../../actions'; import { buildVectorRequestMeta } from '../build_vector_request_meta'; import { ITiledSingleLayerVectorSource } from '../../sources/tiled_single_layer_vector_source'; @@ -193,7 +193,7 @@ export class HeatmapLayer extends AbstractLayer { } async getBounds(syncContext: DataRequestContext) { - return await getVectorSourceBounds({ + return await syncBoundsData({ layerId: this.getId(), syncContext, source: this.getSource(), diff --git a/x-pack/plugins/maps/public/classes/layers/vector_layer/bounds_data.ts b/x-pack/plugins/maps/public/classes/layers/vector_layer/bounds_data.ts new file mode 100644 index 0000000000000..34edf8cd09607 --- /dev/null +++ b/x-pack/plugins/maps/public/classes/layers/vector_layer/bounds_data.ts @@ -0,0 +1,54 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license 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 { Query } from 'src/plugins/data/common'; +import { SOURCE_BOUNDS_DATA_REQUEST_ID } from '../../../../common/constants'; +import { MapExtent } from '../../../../common/descriptor_types'; +import { DataRequestContext } from '../../../actions'; +import { IVectorSource } from '../../sources/vector_source'; + +export async function syncBoundsData({ + layerId, + syncContext, + source, + sourceQuery, +}: { + layerId: string; + syncContext: DataRequestContext; + source: IVectorSource; + sourceQuery: Query | null; +}): Promise { + const { startLoading, stopLoading, registerCancelCallback, dataFilters } = syncContext; + + const requestToken = Symbol(`${SOURCE_BOUNDS_DATA_REQUEST_ID}-${layerId}`); + + // Do not pass all searchFilters to source.getBoundsForFilters(). + // For example, do not want to filter bounds request by extent and buffer. + const boundsFilters = { + sourceQuery: sourceQuery ? sourceQuery : undefined, + query: dataFilters.query, + timeFilters: dataFilters.timeFilters, + timeslice: dataFilters.timeslice, + filters: dataFilters.filters, + applyGlobalQuery: source.getApplyGlobalQuery(), + applyGlobalTime: source.getApplyGlobalTime(), + }; + + let bounds = null; + try { + startLoading(SOURCE_BOUNDS_DATA_REQUEST_ID, requestToken, boundsFilters); + bounds = await source.getBoundsForFilters( + boundsFilters, + registerCancelCallback.bind(null, requestToken) + ); + } finally { + // Use stopLoading callback instead of onLoadError callback. + // Function is loading bounds and not feature data. + stopLoading(SOURCE_BOUNDS_DATA_REQUEST_ID, requestToken, bounds ? bounds : {}); + } + return bounds; +} diff --git a/x-pack/plugins/maps/public/classes/layers/vector_layer/geojson_vector_layer/utils.tsx b/x-pack/plugins/maps/public/classes/layers/vector_layer/geojson_vector_layer/geojson_source_data.tsx similarity index 60% rename from x-pack/plugins/maps/public/classes/layers/vector_layer/geojson_vector_layer/utils.tsx rename to x-pack/plugins/maps/public/classes/layers/vector_layer/geojson_vector_layer/geojson_source_data.tsx index 4385adbd4de65..1f484b7ecfc50 100644 --- a/x-pack/plugins/maps/public/classes/layers/vector_layer/geojson_vector_layer/utils.tsx +++ b/x-pack/plugins/maps/public/classes/layers/vector_layer/geojson_vector_layer/geojson_source_data.tsx @@ -6,17 +6,13 @@ */ import { FeatureCollection } from 'geojson'; -import type { Map as MbMap } from '@kbn/mapbox-gl'; -import type { Query } from 'src/plugins/data/common'; import { EMPTY_FEATURE_COLLECTION, - SOURCE_BOUNDS_DATA_REQUEST_ID, SOURCE_DATA_REQUEST_ID, VECTOR_SHAPE_TYPE, } from '../../../../../common/constants'; import { DataRequestMeta, - MapExtent, Timeslice, VectorSourceRequestMeta, } from '../../../../../common/descriptor_types'; @@ -28,30 +24,7 @@ import { getCentroidFeatures } from './get_centroid_features'; import { canSkipSourceUpdate } from '../../../util/can_skip_fetch'; import { assignFeatureIds } from './assign_feature_ids'; -export function addGeoJsonMbSource(mbSourceId: string, mbLayerIds: string[], mbMap: MbMap) { - const mbSource = mbMap.getSource(mbSourceId); - if (!mbSource) { - mbMap.addSource(mbSourceId, { - type: 'geojson', - data: EMPTY_FEATURE_COLLECTION, - }); - } else if (mbSource.type !== 'geojson') { - // Recreate source when existing source is not geojson. This can occur when layer changes from tile layer to vector layer. - mbLayerIds.forEach((mbLayerId) => { - if (mbMap.getLayer(mbLayerId)) { - mbMap.removeLayer(mbLayerId); - } - }); - - mbMap.removeSource(mbSourceId); - mbMap.addSource(mbSourceId, { - type: 'geojson', - data: EMPTY_FEATURE_COLLECTION, - }); - } -} - -export async function syncVectorSource({ +export async function syncGeojsonSourceData({ layerId, layerName, prevDataRequest, @@ -129,45 +102,3 @@ export async function syncVectorSource({ throw error; } } - -export async function getVectorSourceBounds({ - layerId, - syncContext, - source, - sourceQuery, -}: { - layerId: string; - syncContext: DataRequestContext; - source: IVectorSource; - sourceQuery: Query | null; -}): Promise { - const { startLoading, stopLoading, registerCancelCallback, dataFilters } = syncContext; - - const requestToken = Symbol(`${SOURCE_BOUNDS_DATA_REQUEST_ID}-${layerId}`); - - // Do not pass all searchFilters to source.getBoundsForFilters(). - // For example, do not want to filter bounds request by extent and buffer. - const boundsFilters = { - sourceQuery: sourceQuery ? sourceQuery : undefined, - query: dataFilters.query, - timeFilters: dataFilters.timeFilters, - timeslice: dataFilters.timeslice, - filters: dataFilters.filters, - applyGlobalQuery: source.getApplyGlobalQuery(), - applyGlobalTime: source.getApplyGlobalTime(), - }; - - let bounds = null; - try { - startLoading(SOURCE_BOUNDS_DATA_REQUEST_ID, requestToken, boundsFilters); - bounds = await source.getBoundsForFilters( - boundsFilters, - registerCancelCallback.bind(null, requestToken) - ); - } finally { - // Use stopLoading callback instead of onLoadError callback. - // Function is loading bounds and not feature data. - stopLoading(SOURCE_BOUNDS_DATA_REQUEST_ID, requestToken, bounds ? bounds : {}); - } - return bounds; -} diff --git a/x-pack/plugins/maps/public/classes/layers/vector_layer/geojson_vector_layer/geojson_vector_layer.tsx b/x-pack/plugins/maps/public/classes/layers/vector_layer/geojson_vector_layer/geojson_vector_layer.tsx index 3f7c782ca469b..3152ac27189b3 100644 --- a/x-pack/plugins/maps/public/classes/layers/vector_layer/geojson_vector_layer/geojson_vector_layer.tsx +++ b/x-pack/plugins/maps/public/classes/layers/vector_layer/geojson_vector_layer/geojson_vector_layer.tsx @@ -39,7 +39,7 @@ import { DataRequestAbortError } from '../../../util/data_request'; import { canSkipSourceUpdate } from '../../../util/can_skip_fetch'; import { getFeatureCollectionBounds } from '../../../util/get_feature_collection_bounds'; import { GEOJSON_FEATURE_ID_PROPERTY_NAME } from './assign_feature_ids'; -import { addGeoJsonMbSource, syncVectorSource } from './utils'; +import { syncGeojsonSourceData } from './geojson_source_data'; import { JoinState, performInnerJoins } from './perform_inner_joins'; import { buildVectorRequestMeta } from '../../build_vector_request_meta'; @@ -138,8 +138,26 @@ export class GeoJsonVectorLayer extends AbstractVectorLayer { return await style.pluckStyleMetaFromSourceDataRequest(sourceDataRequest); } + _requiresPrevSourceCleanup(mbMap: MbMap) { + const mbSource = mbMap.getSource(this.getMbSourceId()); + if (!mbSource) { + return false; + } + + return mbSource.type !== 'geojson'; + } + syncLayerWithMB(mbMap: MbMap, timeslice?: Timeslice) { - addGeoJsonMbSource(this.getMbSourceId(), this.getMbLayerIds(), mbMap); + this._removeStaleMbSourcesAndLayers(mbMap); + + const mbSourceId = this.getMbSourceId(); + const mbSource = mbMap.getSource(mbSourceId); + if (!mbSource) { + mbMap.addSource(mbSourceId, { + type: 'geojson', + data: EMPTY_FEATURE_COLLECTION, + }); + } this._syncFeatureCollectionWithMb(mbMap); @@ -211,7 +229,7 @@ export class GeoJsonVectorLayer extends AbstractVectorLayer { try { await this._syncSourceStyleMeta(syncContext, source, style); await this._syncSourceFormatters(syncContext, source, style); - const sourceResult = await syncVectorSource({ + const sourceResult = await syncGeojsonSourceData({ layerId: this.getId(), layerName: await this.getDisplayName(source), prevDataRequest: this.getSourceDataRequest(), diff --git a/x-pack/plugins/maps/public/classes/layers/vector_layer/index.ts b/x-pack/plugins/maps/public/classes/layers/vector_layer/index.ts index 2f46bfa871934..5780c2c723dd6 100644 --- a/x-pack/plugins/maps/public/classes/layers/vector_layer/index.ts +++ b/x-pack/plugins/maps/public/classes/layers/vector_layer/index.ts @@ -5,11 +5,7 @@ * 2.0. */ -export { - addGeoJsonMbSource, - getVectorSourceBounds, - syncVectorSource, -} from './geojson_vector_layer/utils'; +export { syncBoundsData } from './bounds_data'; export type { IVectorLayer, VectorLayerArguments } from './vector_layer'; export { isVectorLayer, NO_RESULTS_ICON_AND_TOOLTIPCONTENT } from './vector_layer'; diff --git a/x-pack/plugins/maps/public/classes/layers/vector_layer/vector_layer.tsx b/x-pack/plugins/maps/public/classes/layers/vector_layer/vector_layer.tsx index 2c6bd64e1bc65..71a960fc1919b 100644 --- a/x-pack/plugins/maps/public/classes/layers/vector_layer/vector_layer.tsx +++ b/x-pack/plugins/maps/public/classes/layers/vector_layer/vector_layer.tsx @@ -58,7 +58,7 @@ import { IESSource } from '../../sources/es_source'; import { ITermJoinSource } from '../../sources/term_join_source'; import { buildVectorRequestMeta } from '../build_vector_request_meta'; import { getJoinAggKey } from '../../../../common/get_agg_key'; -import { getVectorSourceBounds } from './geojson_vector_layer/utils'; +import { syncBoundsData } from './bounds_data'; export function isVectorLayer(layer: ILayer) { return (layer as IVectorLayer).canShowTooltip !== undefined; @@ -287,7 +287,7 @@ export class AbstractVectorLayer extends AbstractLayer implements IVectorLayer { } async getBounds(syncContext: DataRequestContext) { - return getVectorSourceBounds({ + return syncBoundsData({ layerId: this.getId(), syncContext, source: this.getSource(), diff --git a/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_size_property.tsx b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_size_property.tsx index 3dc44c171d415..e76e9e936faec 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_size_property.tsx +++ b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_size_property.tsx @@ -5,7 +5,6 @@ * 2.0. */ -import _ from 'lodash'; import React from 'react'; import type { Map as MbMap } from '@kbn/mapbox-gl'; import { DynamicStyleProperty } from './dynamic_style_property'; @@ -111,7 +110,11 @@ export class DynamicSizeProperty extends DynamicStyleProperty= 0 ? this._options.minSize : null; } return this._getMbDataDrivenSize({ @@ -156,8 +159,8 @@ export class DynamicSizeProperty extends DynamicStyleProperty= 0 && + this._options.maxSize >= 0 ); } diff --git a/x-pack/plugins/maps/public/connected_components/map_settings_panel/index.ts b/x-pack/plugins/maps/public/connected_components/map_settings_panel/index.ts index 015aad84cde58..c858c74c819d5 100644 --- a/x-pack/plugins/maps/public/connected_components/map_settings_panel/index.ts +++ b/x-pack/plugins/maps/public/connected_components/map_settings_panel/index.ts @@ -5,8 +5,9 @@ * 2.0. */ -import { AnyAction, Dispatch } from 'redux'; +import { AnyAction } from 'redux'; import { connect } from 'react-redux'; +import { ThunkDispatch } from 'redux-thunk'; import { FLYOUT_STATE } from '../../reducers/ui'; import { MapStoreState } from '../../reducers/store'; import { MapSettingsPanel } from './map_settings_panel'; @@ -27,7 +28,7 @@ function mapStateToProps(state: MapStoreState) { }; } -function mapDispatchToProps(dispatch: Dispatch) { +function mapDispatchToProps(dispatch: ThunkDispatch) { return { cancelChanges: () => { dispatch(rollbackMapSettings()); diff --git a/x-pack/plugins/maps/public/connected_components/toolbar_overlay/fit_to_data/fit_to_data.tsx b/x-pack/plugins/maps/public/connected_components/toolbar_overlay/fit_to_data/fit_to_data.tsx index f975bc293d823..54f99c37b6d81 100644 --- a/x-pack/plugins/maps/public/connected_components/toolbar_overlay/fit_to_data/fit_to_data.tsx +++ b/x-pack/plugins/maps/public/connected_components/toolbar_overlay/fit_to_data/fit_to_data.tsx @@ -11,10 +11,23 @@ import { EuiButtonIcon, EuiPanel } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; export interface Props { + autoFitToDataBounds: boolean; fitToBounds: () => void; } export function FitToData(props: Props) { + const label = i18n.translate('xpack.maps.fitToData.label', { + defaultMessage: 'Fit to data bounds', + }); + let title = label; + if (props.autoFitToDataBounds) { + title = + `${title}. ` + + i18n.translate('xpack.maps.fitToData.autoFitToDataBounds', { + defaultMessage: + 'Map setting "auto fit map to data bounds" enabled, map will automatically pan and zoom to show the data bounds.', + }); + } return ( ); diff --git a/x-pack/plugins/maps/public/connected_components/toolbar_overlay/fit_to_data/index.ts b/x-pack/plugins/maps/public/connected_components/toolbar_overlay/fit_to_data/index.ts index b4322c93097f0..2979bec06b95a 100644 --- a/x-pack/plugins/maps/public/connected_components/toolbar_overlay/fit_to_data/index.ts +++ b/x-pack/plugins/maps/public/connected_components/toolbar_overlay/fit_to_data/index.ts @@ -10,10 +10,14 @@ import { ThunkDispatch } from 'redux-thunk'; import { connect } from 'react-redux'; import { MapStoreState } from '../../../reducers/store'; import { fitToDataBounds } from '../../../actions'; +import { getMapSettings } from '../../../selectors/map_selectors'; import { FitToData } from './fit_to_data'; function mapStateToProps(state: MapStoreState) { - return {}; + const mapSettings = getMapSettings(state); + return { + autoFitToDataBounds: mapSettings.autoFitToDataBounds, + }; } function mapDispatchToProps(dispatch: ThunkDispatch) { diff --git a/x-pack/plugins/observability/common/typings.ts b/x-pack/plugins/observability/common/typings.ts index bccb0f4491009..1e247fd06739f 100644 --- a/x-pack/plugins/observability/common/typings.ts +++ b/x-pack/plugins/observability/common/typings.ts @@ -7,6 +7,10 @@ import * as t from 'io-ts'; export type Maybe = T | null | undefined; +import { + ALERT_STATUS_ACTIVE, + ALERT_STATUS_RECOVERED, +} from '@kbn/rule-data-utils/alerts_as_data_status'; export const alertWorkflowStatusRt = t.keyof({ open: null, @@ -25,3 +29,12 @@ export interface ApmIndicesConfig { apmAgentConfigurationIndex: string; apmCustomLinkIndex: string; } +export type AlertStatusFilterButton = + | typeof ALERT_STATUS_ACTIVE + | typeof ALERT_STATUS_RECOVERED + | ''; +export interface AlertStatusFilter { + status: AlertStatusFilterButton; + query: string; + label: string; +} diff --git a/x-pack/plugins/observability/public/application/application.test.tsx b/x-pack/plugins/observability/public/application/application.test.tsx index 6b5863c8b122a..dddc44c3c26ea 100644 --- a/x-pack/plugins/observability/public/application/application.test.tsx +++ b/x-pack/plugins/observability/public/application/application.test.tsx @@ -46,7 +46,13 @@ describe('renderApp', () => { uiSettings: { get: () => false }, http: { basePath: { prepend: (path: string) => path } }, } as unknown as CoreStart; - const config = { unsafe: { alertingExperience: { enabled: true }, cases: { enabled: true } } }; + const config = { + unsafe: { + alertingExperience: { enabled: true }, + cases: { enabled: true }, + overviewNext: { enabled: false }, + }, + }; const params = { element: window.document.createElement('div'), history: createMemoryHistory(), diff --git a/x-pack/plugins/observability/public/components/app/section/apm/index.test.tsx b/x-pack/plugins/observability/public/components/app/section/apm/index.test.tsx index c9c2ed549a1c3..35835cd0bc8e6 100644 --- a/x-pack/plugins/observability/public/components/app/section/apm/index.test.tsx +++ b/x-pack/plugins/observability/public/components/app/section/apm/index.test.tsx @@ -42,7 +42,13 @@ describe('APMSection', () => { http: { basePath: { prepend: jest.fn() } }, } as unknown as CoreStart, appMountParameters: {} as AppMountParameters, - config: { unsafe: { alertingExperience: { enabled: true }, cases: { enabled: true } } }, + config: { + unsafe: { + alertingExperience: { enabled: true }, + cases: { enabled: true }, + overviewNext: { enabled: false }, + }, + }, observabilityRuleTypeRegistry: createObservabilityRuleTypeRegistryMock(), plugins: { data: { diff --git a/x-pack/plugins/observability/public/components/app/section/ux/index.test.tsx b/x-pack/plugins/observability/public/components/app/section/ux/index.test.tsx index 8a99b6a53cf06..b4dda3ed3559e 100644 --- a/x-pack/plugins/observability/public/components/app/section/ux/index.test.tsx +++ b/x-pack/plugins/observability/public/components/app/section/ux/index.test.tsx @@ -42,7 +42,13 @@ describe('UXSection', () => { http: { basePath: { prepend: jest.fn() } }, } as unknown as CoreStart, appMountParameters: {} as AppMountParameters, - config: { unsafe: { alertingExperience: { enabled: true }, cases: { enabled: true } } }, + config: { + unsafe: { + alertingExperience: { enabled: true }, + cases: { enabled: true }, + overviewNext: { enabled: false }, + }, + }, plugins: { data: { query: { diff --git a/x-pack/plugins/observability/public/hooks/use_time_range.test.ts b/x-pack/plugins/observability/public/hooks/use_time_range.test.ts index bf513d8a1a99a..bbf3096e55107 100644 --- a/x-pack/plugins/observability/public/hooks/use_time_range.test.ts +++ b/x-pack/plugins/observability/public/hooks/use_time_range.test.ts @@ -24,7 +24,13 @@ describe('useTimeRange', () => { jest.spyOn(pluginContext, 'usePluginContext').mockImplementation(() => ({ core: {} as CoreStart, appMountParameters: {} as AppMountParameters, - config: { unsafe: { alertingExperience: { enabled: true }, cases: { enabled: true } } }, + config: { + unsafe: { + alertingExperience: { enabled: true }, + cases: { enabled: true }, + overviewNext: { enabled: false }, + }, + }, plugins: { data: { query: { @@ -67,7 +73,13 @@ describe('useTimeRange', () => { jest.spyOn(pluginContext, 'usePluginContext').mockImplementation(() => ({ core: {} as CoreStart, appMountParameters: {} as AppMountParameters, - config: { unsafe: { alertingExperience: { enabled: true }, cases: { enabled: true } } }, + config: { + unsafe: { + alertingExperience: { enabled: true }, + cases: { enabled: true }, + overviewNext: { enabled: false }, + }, + }, plugins: { data: { query: { diff --git a/x-pack/plugins/observability/public/index.ts b/x-pack/plugins/observability/public/index.ts index 0dab3e5135717..2383044bc14c2 100644 --- a/x-pack/plugins/observability/public/index.ts +++ b/x-pack/plugins/observability/public/index.ts @@ -26,7 +26,11 @@ export type { export { enableInspectEsQueries } from '../common/ui_settings_keys'; export interface ConfigSchema { - unsafe: { alertingExperience: { enabled: boolean }; cases: { enabled: boolean } }; + unsafe: { + alertingExperience: { enabled: boolean }; + cases: { enabled: boolean }; + overviewNext: { enabled: boolean }; + }; } export const plugin: PluginInitializer< @@ -60,7 +64,9 @@ export { METRIC_TYPE, } from './hooks/use_track_metric'; -export const LazyAlertsFlyout = lazy(() => import('./pages/alerts/alerts_flyout')); +export const LazyAlertsFlyout = lazy( + () => import('./pages/alerts/components/alerts_flyout/alerts_flyout') +); export { useFetcher, FETCH_STATUS } from './hooks/use_fetcher'; export { useEsSearch, createEsParams } from './hooks/use_es_search'; diff --git a/x-pack/plugins/observability/public/pages/alerts/alerts_disclaimer.tsx b/x-pack/plugins/observability/public/pages/alerts/components/alerts_disclaimer.tsx similarity index 100% rename from x-pack/plugins/observability/public/pages/alerts/alerts_disclaimer.tsx rename to x-pack/plugins/observability/public/pages/alerts/components/alerts_disclaimer.tsx diff --git a/x-pack/plugins/observability/public/pages/alerts/alerts_flyout/alerts_flyout.stories.tsx b/x-pack/plugins/observability/public/pages/alerts/components/alerts_flyout/alerts_flyout.stories.tsx similarity index 85% rename from x-pack/plugins/observability/public/pages/alerts/alerts_flyout/alerts_flyout.stories.tsx rename to x-pack/plugins/observability/public/pages/alerts/components/alerts_flyout/alerts_flyout.stories.tsx index 64d495dbbc798..36b1fc2f2b6e2 100644 --- a/x-pack/plugins/observability/public/pages/alerts/alerts_flyout/alerts_flyout.stories.tsx +++ b/x-pack/plugins/observability/public/pages/alerts/components/alerts_flyout/alerts_flyout.stories.tsx @@ -7,11 +7,11 @@ import { ALERT_UUID } from '@kbn/rule-data-utils/technical_field_names'; import React, { ComponentType } from 'react'; -import { KibanaContextProvider } from '../../../../../../../src/plugins/kibana_react/public'; -import { PluginContext, PluginContextValue } from '../../../context/plugin_context'; -import { createObservabilityRuleTypeRegistryMock } from '../../../rules/observability_rule_type_registry_mock'; -import { apmAlertResponseExample } from '../example_data'; -import { AlertsFlyout } from './'; +import { KibanaContextProvider } from '../../../../../../../../src/plugins/kibana_react/public'; +import { PluginContext, PluginContextValue } from '../../../../context/plugin_context'; +import { createObservabilityRuleTypeRegistryMock } from '../../../../rules/observability_rule_type_registry_mock'; +import { apmAlertResponseExample } from './example_data'; +import { AlertsFlyout } from '..'; interface Args { alerts: Array>; diff --git a/x-pack/plugins/observability/public/pages/alerts/alerts_flyout/alerts_flyout.test.tsx b/x-pack/plugins/observability/public/pages/alerts/components/alerts_flyout/alerts_flyout.test.tsx similarity index 91% rename from x-pack/plugins/observability/public/pages/alerts/alerts_flyout/alerts_flyout.test.tsx rename to x-pack/plugins/observability/public/pages/alerts/components/alerts_flyout/alerts_flyout.test.tsx index 4fdc8d245799a..13fb5d805fb81 100644 --- a/x-pack/plugins/observability/public/pages/alerts/alerts_flyout/alerts_flyout.test.tsx +++ b/x-pack/plugins/observability/public/pages/alerts/components/alerts_flyout/alerts_flyout.test.tsx @@ -6,11 +6,11 @@ */ import React from 'react'; -import * as useUiSettingHook from '../../../../../../../src/plugins/kibana_react/public/ui_settings/use_ui_setting'; -import { createObservabilityRuleTypeRegistryMock } from '../../../rules/observability_rule_type_registry_mock'; -import { render } from '../../../utils/test_helper'; -import type { TopAlert } from '../'; -import { AlertsFlyout } from './'; +import * as useUiSettingHook from '../../../../../../../../src/plugins/kibana_react/public/ui_settings/use_ui_setting'; +import { createObservabilityRuleTypeRegistryMock } from '../../../../rules/observability_rule_type_registry_mock'; +import { render } from '../../../../utils/test_helper'; +import type { TopAlert } from '../../containers/alerts_page'; +import { AlertsFlyout } from '..'; describe('AlertsFlyout', () => { jest diff --git a/x-pack/plugins/observability/public/pages/alerts/alerts_flyout/index.tsx b/x-pack/plugins/observability/public/pages/alerts/components/alerts_flyout/alerts_flyout.tsx similarity index 89% rename from x-pack/plugins/observability/public/pages/alerts/alerts_flyout/index.tsx rename to x-pack/plugins/observability/public/pages/alerts/components/alerts_flyout/alerts_flyout.tsx index c5cad5f3b1c8c..ced4896c5f31d 100644 --- a/x-pack/plugins/observability/public/pages/alerts/alerts_flyout/index.tsx +++ b/x-pack/plugins/observability/public/pages/alerts/components/alerts_flyout/alerts_flyout.tsx @@ -35,14 +35,14 @@ import { } from '@kbn/rule-data-utils/alerts_as_data_status'; import moment from 'moment-timezone'; import React, { useMemo } from 'react'; -import type { TopAlert } from '../'; -import { useKibana, useUiSetting } from '../../../../../../../src/plugins/kibana_react/public'; -import { asDuration } from '../../../../common/utils/formatters'; -import type { ObservabilityRuleTypeRegistry } from '../../../rules/create_observability_rule_type_registry'; +import type { TopAlert } from '../../containers'; +import { useKibana, useUiSetting } from '../../../../../../../../src/plugins/kibana_react/public'; +import { asDuration } from '../../../../../common/utils/formatters'; +import type { ObservabilityRuleTypeRegistry } from '../../../../rules/create_observability_rule_type_registry'; import { parseAlert } from '../parse_alert'; -import { AlertStatusIndicator } from '../../../components/shared/alert_status_indicator'; -import { ExperimentalBadge } from '../../../components/shared/experimental_badge'; -import { translations, paths } from '../../../config'; +import { AlertStatusIndicator } from '../../../../components/shared/alert_status_indicator'; +import { ExperimentalBadge } from '../../../../components/shared/experimental_badge'; +import { translations, paths } from '../../../../config'; type AlertsFlyoutProps = { alert?: TopAlert; diff --git a/x-pack/plugins/observability/public/pages/alerts/example_data.ts b/x-pack/plugins/observability/public/pages/alerts/components/alerts_flyout/example_data.ts similarity index 100% rename from x-pack/plugins/observability/public/pages/alerts/example_data.ts rename to x-pack/plugins/observability/public/pages/alerts/components/alerts_flyout/example_data.ts diff --git a/x-pack/plugins/observability/public/pages/alerts/components/alerts_flyout/index.ts b/x-pack/plugins/observability/public/pages/alerts/components/alerts_flyout/index.ts new file mode 100644 index 0000000000000..4153ab6e5b596 --- /dev/null +++ b/x-pack/plugins/observability/public/pages/alerts/components/alerts_flyout/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 { AlertsFlyout } from './alerts_flyout'; diff --git a/x-pack/plugins/observability/public/pages/alerts/alerts_search_bar.tsx b/x-pack/plugins/observability/public/pages/alerts/components/alerts_search_bar.tsx similarity index 92% rename from x-pack/plugins/observability/public/pages/alerts/alerts_search_bar.tsx rename to x-pack/plugins/observability/public/pages/alerts/components/alerts_search_bar.tsx index 926f03acf01d8..14d47d1e7e9d3 100644 --- a/x-pack/plugins/observability/public/pages/alerts/alerts_search_bar.tsx +++ b/x-pack/plugins/observability/public/pages/alerts/components/alerts_search_bar.tsx @@ -8,8 +8,8 @@ import { IndexPatternBase } from '@kbn/es-query'; import { i18n } from '@kbn/i18n'; import React, { useMemo, useState } from 'react'; -import { SearchBar, TimeHistory } from '../../../../../../src/plugins/data/public'; -import { Storage } from '../../../../../../src/plugins/kibana_utils/public'; +import { SearchBar, TimeHistory } from '../../../../../../../src/plugins/data/public'; +import { Storage } from '../../../../../../../src/plugins/kibana_utils/public'; export function AlertsSearchBar({ dynamicIndexPatterns, diff --git a/x-pack/plugins/observability/public/pages/alerts/components/alerts_status_filter.tsx b/x-pack/plugins/observability/public/pages/alerts/components/alerts_status_filter.tsx new file mode 100644 index 0000000000000..d717e916de2c6 --- /dev/null +++ b/x-pack/plugins/observability/public/pages/alerts/components/alerts_status_filter.tsx @@ -0,0 +1,79 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { EuiButtonGroup, EuiButtonGroupOptionProps } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import React from 'react'; +import { + ALERT_STATUS_ACTIVE, + ALERT_STATUS_RECOVERED, +} from '@kbn/rule-data-utils/alerts_as_data_status'; +import { ALERT_STATUS } from '@kbn/rule-data-utils/technical_field_names'; +import { AlertStatusFilterButton } from '../../../../common/typings'; +import { AlertStatusFilter } from '../../../../common/typings'; + +export interface AlertStatusFilterProps { + status: AlertStatusFilterButton; + onChange: (id: string, value: string) => void; +} + +export const allAlerts: AlertStatusFilter = { + status: '', + query: '', + label: i18n.translate('xpack.observability.alerts.alertStatusFilter.showAll', { + defaultMessage: 'Show all', + }), +}; + +export const activeAlerts: AlertStatusFilter = { + status: ALERT_STATUS_ACTIVE, + query: `${ALERT_STATUS}: "${ALERT_STATUS_ACTIVE}"`, + label: i18n.translate('xpack.observability.alerts.alertStatusFilter.active', { + defaultMessage: 'Active', + }), +}; + +export const recoveredAlerts: AlertStatusFilter = { + status: ALERT_STATUS_RECOVERED, + query: `${ALERT_STATUS}: "${ALERT_STATUS_RECOVERED}"`, + label: i18n.translate('xpack.observability.alerts.alertStatusFilter.recovered', { + defaultMessage: 'Recovered', + }), +}; + +const options: EuiButtonGroupOptionProps[] = [ + { + id: allAlerts.status, + label: allAlerts.label, + value: allAlerts.query, + 'data-test-subj': 'alert-status-filter-show-all-button', + }, + { + id: activeAlerts.status, + label: activeAlerts.label, + value: activeAlerts.query, + 'data-test-subj': 'alert-status-filter-active-button', + }, + { + id: recoveredAlerts.status, + label: recoveredAlerts.label, + value: recoveredAlerts.query, + 'data-test-subj': 'alert-status-filter-recovered-button', + }, +]; + +export function AlertsStatusFilter({ status, onChange }: AlertStatusFilterProps) { + return ( + + ); +} diff --git a/x-pack/plugins/observability/public/pages/alerts/default_cell_actions.tsx b/x-pack/plugins/observability/public/pages/alerts/components/default_cell_actions.tsx similarity index 85% rename from x-pack/plugins/observability/public/pages/alerts/default_cell_actions.tsx rename to x-pack/plugins/observability/public/pages/alerts/components/default_cell_actions.tsx index 3adfb0a1d9c89..5126647161fa5 100644 --- a/x-pack/plugins/observability/public/pages/alerts/default_cell_actions.tsx +++ b/x-pack/plugins/observability/public/pages/alerts/components/default_cell_actions.tsx @@ -9,9 +9,9 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; import { getMappedNonEcsValue } from './render_cell_value'; import FilterForValueButton from './filter_for_value'; -import { TimelineNonEcsData } from '../../../../timelines/common/search_strategy'; -import { TGridCellAction } from '../../../../timelines/common/types/timeline'; -import { getPageRowIndex } from '../../../../timelines/public'; +import { TimelineNonEcsData } from '../../../../../timelines/common/search_strategy'; +import { TGridCellAction } from '../../../../../timelines/common/types/timeline'; +import { getPageRowIndex } from '../../../../../timelines/public'; export const FILTER_FOR_VALUE = i18n.translate('xpack.observability.hoverActions.filterForValue', { defaultMessage: 'Filter for value', diff --git a/x-pack/plugins/observability/public/pages/alerts/filter_for_value.tsx b/x-pack/plugins/observability/public/pages/alerts/components/filter_for_value.tsx similarity index 100% rename from x-pack/plugins/observability/public/pages/alerts/filter_for_value.tsx rename to x-pack/plugins/observability/public/pages/alerts/components/filter_for_value.tsx diff --git a/x-pack/plugins/observability/public/pages/alerts/components/index.ts b/x-pack/plugins/observability/public/pages/alerts/components/index.ts new file mode 100644 index 0000000000000..57ad311f65d1c --- /dev/null +++ b/x-pack/plugins/observability/public/pages/alerts/components/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export * from './alerts_flyout'; +export * from './render_cell_value'; +export * from './severity_badge'; +export * from './workflow_status_filter'; +export * from './alerts_search_bar'; +export * from './alerts_disclaimer'; +export * from './default_cell_actions'; +export * from './filter_for_value'; +export * from './parse_alert'; +export * from './alerts_status_filter'; diff --git a/x-pack/plugins/observability/public/pages/alerts/parse_alert.ts b/x-pack/plugins/observability/public/pages/alerts/components/parse_alert.ts similarity index 78% rename from x-pack/plugins/observability/public/pages/alerts/parse_alert.ts rename to x-pack/plugins/observability/public/pages/alerts/components/parse_alert.ts index 7b28803084067..680798811e9ab 100644 --- a/x-pack/plugins/observability/public/pages/alerts/parse_alert.ts +++ b/x-pack/plugins/observability/public/pages/alerts/components/parse_alert.ts @@ -12,10 +12,10 @@ import { ALERT_RULE_NAME, } from '@kbn/rule-data-utils/technical_field_names'; import { ALERT_STATUS_ACTIVE } from '@kbn/rule-data-utils/alerts_as_data_status'; -import type { TopAlert } from '.'; -import { parseTechnicalFields } from '../../../../rule_registry/common/parse_technical_fields'; -import { asDuration, asPercent } from '../../../common/utils/formatters'; -import { ObservabilityRuleTypeRegistry } from '../../rules/create_observability_rule_type_registry'; +import type { TopAlert } from '../'; +import { parseTechnicalFields } from '../../../../../rule_registry/common/parse_technical_fields'; +import { asDuration, asPercent } from '../../../../common/utils/formatters'; +import { ObservabilityRuleTypeRegistry } from '../../../rules/create_observability_rule_type_registry'; export const parseAlert = (observabilityRuleTypeRegistry: ObservabilityRuleTypeRegistry) => diff --git a/x-pack/plugins/observability/public/pages/alerts/components/render_cell_value/index.ts b/x-pack/plugins/observability/public/pages/alerts/components/render_cell_value/index.ts new file mode 100644 index 0000000000000..b6df77f075888 --- /dev/null +++ b/x-pack/plugins/observability/public/pages/alerts/components/render_cell_value/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 { getRenderCellValue, getMappedNonEcsValue } from './render_cell_value'; diff --git a/x-pack/plugins/observability/public/pages/alerts/render_cell_value.test.tsx b/x-pack/plugins/observability/public/pages/alerts/components/render_cell_value/render_cell_value.test.tsx similarity index 87% rename from x-pack/plugins/observability/public/pages/alerts/render_cell_value.test.tsx rename to x-pack/plugins/observability/public/pages/alerts/components/render_cell_value/render_cell_value.test.tsx index 79a27faa96c69..25de2e36b08c3 100644 --- a/x-pack/plugins/observability/public/pages/alerts/render_cell_value.test.tsx +++ b/x-pack/plugins/observability/public/pages/alerts/components/render_cell_value/render_cell_value.test.tsx @@ -10,10 +10,10 @@ import { ALERT_STATUS_RECOVERED, } from '@kbn/rule-data-utils/alerts_as_data_status'; import { ALERT_STATUS } from '@kbn/rule-data-utils/technical_field_names'; -import type { CellValueElementProps } from '../../../../timelines/common'; -import { createObservabilityRuleTypeRegistryMock } from '../../rules/observability_rule_type_registry_mock'; -import * as PluginHook from '../../hooks/use_plugin_context'; -import { render } from '../../utils/test_helper'; +import type { CellValueElementProps } from '../../../../../../timelines/common'; +import { createObservabilityRuleTypeRegistryMock } from '../../../../rules/observability_rule_type_registry_mock'; +import * as PluginHook from '../../../../hooks/use_plugin_context'; +import { render } from '../../../../utils/test_helper'; import { getRenderCellValue } from './render_cell_value'; interface AlertsTableRow { diff --git a/x-pack/plugins/observability/public/pages/alerts/render_cell_value.tsx b/x-pack/plugins/observability/public/pages/alerts/components/render_cell_value/render_cell_value.tsx similarity index 86% rename from x-pack/plugins/observability/public/pages/alerts/render_cell_value.tsx rename to x-pack/plugins/observability/public/pages/alerts/components/render_cell_value/render_cell_value.tsx index 80ccd4a69b281..d9fa6c6e2221b 100644 --- a/x-pack/plugins/observability/public/pages/alerts/render_cell_value.tsx +++ b/x-pack/plugins/observability/public/pages/alerts/components/render_cell_value/render_cell_value.tsx @@ -17,14 +17,14 @@ import { ALERT_STATUS_ACTIVE, ALERT_STATUS_RECOVERED, } from '@kbn/rule-data-utils/alerts_as_data_status'; -import type { CellValueElementProps, TimelineNonEcsData } from '../../../../timelines/common'; -import { AlertStatusIndicator } from '../../components/shared/alert_status_indicator'; -import { TimestampTooltip } from '../../components/shared/timestamp_tooltip'; -import { asDuration } from '../../../common/utils/formatters'; -import { SeverityBadge } from './severity_badge'; -import { TopAlert } from '.'; -import { parseAlert } from './parse_alert'; -import { usePluginContext } from '../../hooks/use_plugin_context'; +import type { CellValueElementProps, TimelineNonEcsData } from '../../../../../../timelines/common'; +import { AlertStatusIndicator } from '../../../../components/shared/alert_status_indicator'; +import { TimestampTooltip } from '../../../../components/shared/timestamp_tooltip'; +import { asDuration } from '../../../../../common/utils/formatters'; +import { SeverityBadge } from '../severity_badge'; +import { TopAlert } from '../../'; +import { parseAlert } from '../parse_alert'; +import { usePluginContext } from '../../../../hooks/use_plugin_context'; export const getMappedNonEcsValue = ({ data, diff --git a/x-pack/plugins/observability/public/pages/alerts/components/severity_badge/index.ts b/x-pack/plugins/observability/public/pages/alerts/components/severity_badge/index.ts new file mode 100644 index 0000000000000..7974156327085 --- /dev/null +++ b/x-pack/plugins/observability/public/pages/alerts/components/severity_badge/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 { SeverityBadge } from './severity_badge'; diff --git a/x-pack/plugins/observability/public/pages/alerts/severity_badge.stories.tsx b/x-pack/plugins/observability/public/pages/alerts/components/severity_badge/severity_badge.stories.tsx similarity index 100% rename from x-pack/plugins/observability/public/pages/alerts/severity_badge.stories.tsx rename to x-pack/plugins/observability/public/pages/alerts/components/severity_badge/severity_badge.stories.tsx diff --git a/x-pack/plugins/observability/public/pages/alerts/severity_badge.tsx b/x-pack/plugins/observability/public/pages/alerts/components/severity_badge/severity_badge.tsx similarity index 100% rename from x-pack/plugins/observability/public/pages/alerts/severity_badge.tsx rename to x-pack/plugins/observability/public/pages/alerts/components/severity_badge/severity_badge.tsx diff --git a/x-pack/plugins/observability/public/pages/alerts/components/workflow_status_filter/index.ts b/x-pack/plugins/observability/public/pages/alerts/components/workflow_status_filter/index.ts new file mode 100644 index 0000000000000..84badecd29dcd --- /dev/null +++ b/x-pack/plugins/observability/public/pages/alerts/components/workflow_status_filter/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 { WorkflowStatusFilter } from './workflow_status_filter'; diff --git a/x-pack/plugins/observability/public/pages/alerts/workflow_status_filter.stories.tsx b/x-pack/plugins/observability/public/pages/alerts/components/workflow_status_filter/workflow_status_filter.stories.tsx similarity index 92% rename from x-pack/plugins/observability/public/pages/alerts/workflow_status_filter.stories.tsx rename to x-pack/plugins/observability/public/pages/alerts/components/workflow_status_filter/workflow_status_filter.stories.tsx index e06b5d333a9a6..4dce3ee80b833 100644 --- a/x-pack/plugins/observability/public/pages/alerts/workflow_status_filter.stories.tsx +++ b/x-pack/plugins/observability/public/pages/alerts/components/workflow_status_filter/workflow_status_filter.stories.tsx @@ -6,7 +6,7 @@ */ import React, { ComponentProps, useState } from 'react'; -import type { AlertWorkflowStatus } from '../../../common/typings'; +import type { AlertWorkflowStatus } from '../../../../../common/typings'; import { WorkflowStatusFilter } from './workflow_status_filter'; type Args = ComponentProps; diff --git a/x-pack/plugins/observability/public/pages/alerts/workflow_status_filter.test.tsx b/x-pack/plugins/observability/public/pages/alerts/components/workflow_status_filter/workflow_status_filter.test.tsx similarity index 95% rename from x-pack/plugins/observability/public/pages/alerts/workflow_status_filter.test.tsx rename to x-pack/plugins/observability/public/pages/alerts/components/workflow_status_filter/workflow_status_filter.test.tsx index 29c5e88788a89..a9819a6619dc5 100644 --- a/x-pack/plugins/observability/public/pages/alerts/workflow_status_filter.test.tsx +++ b/x-pack/plugins/observability/public/pages/alerts/components/workflow_status_filter/workflow_status_filter.test.tsx @@ -8,7 +8,7 @@ import { render } from '@testing-library/react'; import { Simulate } from 'react-dom/test-utils'; import React from 'react'; -import type { AlertWorkflowStatus } from '../../../common/typings'; +import type { AlertWorkflowStatus } from '../../../../../common/typings'; import { WorkflowStatusFilter } from './workflow_status_filter'; describe('StatusFilter', () => { diff --git a/x-pack/plugins/observability/public/pages/alerts/workflow_status_filter.tsx b/x-pack/plugins/observability/public/pages/alerts/components/workflow_status_filter/workflow_status_filter.tsx similarity index 95% rename from x-pack/plugins/observability/public/pages/alerts/workflow_status_filter.tsx rename to x-pack/plugins/observability/public/pages/alerts/components/workflow_status_filter/workflow_status_filter.tsx index d857b9d6bd650..86116fb969682 100644 --- a/x-pack/plugins/observability/public/pages/alerts/workflow_status_filter.tsx +++ b/x-pack/plugins/observability/public/pages/alerts/components/workflow_status_filter/workflow_status_filter.tsx @@ -8,7 +8,7 @@ import { EuiButtonGroup, EuiButtonGroupOptionProps } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React from 'react'; -import type { AlertWorkflowStatus } from '../../../common/typings'; +import type { AlertWorkflowStatus } from '../../../../../common/typings'; export interface WorkflowStatusFilterProps { status: AlertWorkflowStatus; diff --git a/x-pack/plugins/observability/public/pages/alerts/index.tsx b/x-pack/plugins/observability/public/pages/alerts/containers/alerts_page/alerts_page.tsx similarity index 57% rename from x-pack/plugins/observability/public/pages/alerts/index.tsx rename to x-pack/plugins/observability/public/pages/alerts/containers/alerts_page/alerts_page.tsx index a3c060f5dc5df..b19a1dbe86fe1 100644 --- a/x-pack/plugins/observability/public/pages/alerts/index.tsx +++ b/x-pack/plugins/observability/public/pages/alerts/containers/alerts_page/alerts_page.tsx @@ -9,25 +9,30 @@ import { EuiButtonEmpty, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { IndexPatternBase } from '@kbn/es-query'; import { i18n } from '@kbn/i18n'; -import React, { useCallback, useRef } from 'react'; +import React, { useCallback, useRef, useState, useEffect } from 'react'; import useAsync from 'react-use/lib/useAsync'; -import { ParsedTechnicalFields } from '../../../../rule_registry/common/parse_technical_fields'; -import type { AlertWorkflowStatus } from '../../../common/typings'; -import { ExperimentalBadge } from '../../components/shared/experimental_badge'; -import { useBreadcrumbs } from '../../hooks/use_breadcrumbs'; -import { useFetcher } from '../../hooks/use_fetcher'; -import { useHasData } from '../../hooks/use_has_data'; -import { usePluginContext } from '../../hooks/use_plugin_context'; -import { useTimefilterService } from '../../hooks/use_timefilter_service'; -import { callObservabilityApi } from '../../services/call_observability_api'; -import { getNoDataConfig } from '../../utils/no_data_config'; -import { LoadingObservability } from '../overview/loading_observability'; -import { AlertsSearchBar } from './alerts_search_bar'; -import { AlertsTableTGrid } from './alerts_table_t_grid'; -import { Provider, alertsPageStateContainer, useAlertsPageStateContainer } from './state_container'; +import { AlertStatus } from '@kbn/rule-data-utils/alerts_as_data_status'; +import { ALERT_STATUS } from '@kbn/rule-data-utils/technical_field_names'; + +import { AlertStatusFilterButton } from '../../../../../common/typings'; +import { ParsedTechnicalFields } from '../../../../../../rule_registry/common/parse_technical_fields'; +import { ExperimentalBadge } from '../../../../components/shared/experimental_badge'; +import { useBreadcrumbs } from '../../../../hooks/use_breadcrumbs'; +import { useFetcher } from '../../../../hooks/use_fetcher'; +import { useHasData } from '../../../../hooks/use_has_data'; +import { usePluginContext } from '../../../../hooks/use_plugin_context'; +import { useTimefilterService } from '../../../../hooks/use_timefilter_service'; +import { callObservabilityApi } from '../../../../services/call_observability_api'; +import { getNoDataConfig } from '../../../../utils/no_data_config'; +import { LoadingObservability } from '../../../overview/loading_observability'; +import { AlertsTableTGrid } from '../alerts_table_t_grid'; +import { + Provider, + alertsPageStateContainer, + useAlertsPageStateContainer, +} from '../state_container'; import './styles.scss'; -import { WorkflowStatusFilter } from './workflow_status_filter'; -import { AlertsDisclaimer } from './alerts_disclaimer'; +import { AlertsStatusFilter, AlertsDisclaimer, AlertsSearchBar } from '../../components'; export interface TopAlert { fields: ParsedTechnicalFields; @@ -36,25 +41,29 @@ export interface TopAlert { link?: string; active: boolean; } - +const regExpEscape = (str: string) => str.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&'); const NO_INDEX_NAMES: string[] = []; const NO_INDEX_PATTERNS: IndexPatternBase[] = []; +const BASE_ALERT_REGEX = new RegExp(`\\s*${regExpEscape(ALERT_STATUS)}\\s*:\\s*"(.*?|\\*?)"`); +const ALERT_STATUS_REGEX = new RegExp( + `\\s*and\\s*${regExpEscape(ALERT_STATUS)}\\s*:\\s*(".+?"|\\*?)|${regExpEscape( + ALERT_STATUS + )}\\s*:\\s*(".+?"|\\*?)`, + 'gm' +); function AlertsPage() { const { core, plugins, ObservabilityPageTemplate } = usePluginContext(); + const [alertFilterStatus, setAlertFilterStatus] = useState('' as AlertStatusFilterButton); const { prepend } = core.http.basePath; const refetch = useRef<() => void>(); const timefilterService = useTimefilterService(); - const { - rangeFrom, - setRangeFrom, - rangeTo, - setRangeTo, - kuery, - setKuery, - workflowStatus, - setWorkflowStatus, - } = useAlertsPageStateContainer(); + const { rangeFrom, setRangeFrom, rangeTo, setRangeTo, kuery, setKuery, workflowStatus } = + useAlertsPageStateContainer(); + + useEffect(() => { + syncAlertStatusFilterStatus(kuery as string); + }, [kuery]); useBreadcrumbs([ { @@ -103,36 +112,56 @@ function AlertsPage() { ]; }, [indexNames]); - const setWorkflowStatusFilter = useCallback( - (value: AlertWorkflowStatus) => { - setWorkflowStatus(value); - }, - [setWorkflowStatus] - ); + // Keep the Workflow status code commented (no delete) as requested: https://github.com/elastic/kibana/issues/117686 + + // const setWorkflowStatusFilter = useCallback( + // (value: AlertWorkflowStatus) => { + // setWorkflowStatus(value); + // }, + // [setWorkflowStatus] + // ); const onQueryChange = useCallback( ({ dateRange, query }) => { if (rangeFrom === dateRange.from && rangeTo === dateRange.to && kuery === (query ?? '')) { return refetch.current && refetch.current(); } - timefilterService.setTime(dateRange); setRangeFrom(dateRange.from); setRangeTo(dateRange.to); setKuery(query); + syncAlertStatusFilterStatus(query as string); }, [rangeFrom, setRangeFrom, rangeTo, setRangeTo, kuery, setKuery, timefilterService] ); - const addToQuery = useCallback( - (value: string) => { - let output = value; - if (kuery !== '') { - output = `${kuery} and ${value}`; + const syncAlertStatusFilterStatus = (query: string) => { + const [, alertStatus] = BASE_ALERT_REGEX.exec(query) || []; + if (!alertStatus) { + setAlertFilterStatus(''); + return; + } + setAlertFilterStatus(alertStatus.toLowerCase() as AlertStatus); + }; + const setAlertStatusFilter = useCallback( + (id: string, query: string) => { + setAlertFilterStatus(id as AlertStatusFilterButton); + // Updating the KQL query bar alongside with user inputs is tricky. + // To avoid issue, this function always remove the AlertFilter and add it + // at the end of the query, each time the filter is added/updated/removed (Show All) + // NOTE: This (query appending) will be changed entirely: https://github.com/elastic/kibana/issues/116135 + let output = kuery; + if (kuery === '') { + output = query; + } else { + // console.log(ALERT_STATUS_REGEX); + const queryWithoutAlertFilter = kuery.replace(ALERT_STATUS_REGEX, ''); + output = `${queryWithoutAlertFilter} and ${query}`; } onQueryChange({ dateRange: { from: rangeFrom, to: rangeTo }, - query: output, + // Clean up the kuery from unwanted trailing/ahead ANDs after appending and removing filters. + query: output.replace(/^\s*and\s*|\s*and\s*$/gm, ''), }); }, [kuery, onQueryChange, rangeFrom, rangeTo] @@ -194,7 +223,9 @@ function AlertsPage() { - + {/* Keep the Workflow status code commented (no delete) as requested: https://github.com/elastic/kibana/issues/117686*/} + {/* */} + @@ -207,7 +238,6 @@ function AlertsPage() { kuery={kuery} workflowStatus={workflowStatus} setRefetch={setRefetch} - addToQuery={addToQuery} /> @@ -215,12 +245,10 @@ function AlertsPage() { ); } -function WrappedAlertsPage() { +export function WrappedAlertsPage() { return ( ); } - -export { WrappedAlertsPage as AlertsPage }; diff --git a/x-pack/plugins/observability/public/pages/alerts/containers/alerts_page/index.ts b/x-pack/plugins/observability/public/pages/alerts/containers/alerts_page/index.ts new file mode 100644 index 0000000000000..e3509e04b2f2b --- /dev/null +++ b/x-pack/plugins/observability/public/pages/alerts/containers/alerts_page/index.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor 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 { WrappedAlertsPage as AlertsPage } from './alerts_page'; +export type { TopAlert } from './alerts_page'; diff --git a/x-pack/plugins/observability/public/pages/alerts/styles.scss b/x-pack/plugins/observability/public/pages/alerts/containers/alerts_page/styles.scss similarity index 100% rename from x-pack/plugins/observability/public/pages/alerts/styles.scss rename to x-pack/plugins/observability/public/pages/alerts/containers/alerts_page/styles.scss diff --git a/x-pack/plugins/observability/public/pages/alerts/alerts_table_t_grid.tsx b/x-pack/plugins/observability/public/pages/alerts/containers/alerts_table_t_grid/alerts_table_t_grid.tsx similarity index 83% rename from x-pack/plugins/observability/public/pages/alerts/alerts_table_t_grid.tsx rename to x-pack/plugins/observability/public/pages/alerts/containers/alerts_table_t_grid/alerts_table_t_grid.tsx index 69a6672db6e98..bf99bcedc16be 100644 --- a/x-pack/plugins/observability/public/pages/alerts/alerts_table_t_grid.tsx +++ b/x-pack/plugins/observability/public/pages/alerts/containers/alerts_table_t_grid/alerts_table_t_grid.tsx @@ -13,8 +13,6 @@ import { ALERT_DURATION, ALERT_REASON, - ALERT_RULE_CONSUMER, - ALERT_RULE_PRODUCER, ALERT_STATUS, ALERT_WORKFLOW_STATUS, TIMESTAMP, @@ -34,37 +32,34 @@ import { import styled from 'styled-components'; import React, { Suspense, useMemo, useState, useCallback, useEffect } from 'react'; import usePrevious from 'react-use/lib/usePrevious'; -import { get, pick } from 'lodash'; -import { - getAlertsPermissions, - useGetUserAlertsPermissions, -} from '../../hooks/use_alert_permission'; +import { pick } from 'lodash'; +import { getAlertsPermissions } from '../../../../hooks/use_alert_permission'; import type { TimelinesUIStart, TGridType, TGridState, TGridModel, SortDirection, -} from '../../../../timelines/public'; -import { useStatusBulkActionItems } from '../../../../timelines/public'; -import type { TopAlert } from './'; -import { useKibana } from '../../../../../../src/plugins/kibana_react/public'; +} from '../../../../../../timelines/public'; + +import type { TopAlert } from '../alerts_page/alerts_page'; +import { useKibana } from '../../../../../../../../src/plugins/kibana_react/public'; import type { ActionProps, AlertWorkflowStatus, ColumnHeaderOptions, + ControlColumnProps, RowRenderer, -} from '../../../../timelines/common'; - -import { getRenderCellValue } from './render_cell_value'; -import { observabilityAppId, observabilityFeatureId } from '../../../common'; -import { useGetUserCasesPermissions } from '../../hooks/use_get_user_cases_permissions'; -import { usePluginContext } from '../../hooks/use_plugin_context'; -import { getDefaultCellActions } from './default_cell_actions'; -import { LazyAlertsFlyout } from '../..'; -import { parseAlert } from './parse_alert'; -import { CoreStart } from '../../../../../../src/core/public'; -import { translations, paths } from '../../config'; +} from '../../../../../../timelines/common'; + +import { getRenderCellValue } from '../../components/render_cell_value'; +import { observabilityAppId, observabilityFeatureId } from '../../../../../common'; +import { useGetUserCasesPermissions } from '../../../../hooks/use_get_user_cases_permissions'; +import { usePluginContext } from '../../../../hooks/use_plugin_context'; +import { LazyAlertsFlyout } from '../../../..'; +import { parseAlert } from '../../components/parse_alert'; +import { CoreStart } from '../../../../../../../../src/core/public'; +import { translations, paths } from '../../../../config'; const ALERT_TABLE_STATE_STORAGE_KEY = 'xpack.observability.alert.tableState'; @@ -75,7 +70,6 @@ interface AlertsTableTGridProps { kuery: string; workflowStatus: AlertWorkflowStatus; setRefetch: (ref: () => void) => void; - addToQuery: (value: string) => void; } interface ObservabilityActionsProps extends ActionProps { @@ -154,21 +148,21 @@ function ObservabilityActions({ const [openActionsPopoverId, setActionsPopover] = useState(null); const { timelines, - application: { capabilities }, + application: {}, } = useKibana().services; const parseObservabilityAlert = useMemo( () => parseAlert(observabilityRuleTypeRegistry), [observabilityRuleTypeRegistry] ); - const alertDataConsumer = useMemo( - () => get(dataFieldEs, ALERT_RULE_CONSUMER, [''])[0], - [dataFieldEs] - ); - const alertDataProducer = useMemo( - () => get(dataFieldEs, ALERT_RULE_PRODUCER, [''])[0], - [dataFieldEs] - ); + // const alertDataConsumer = useMemo( + // () => get(dataFieldEs, ALERT_RULE_CONSUMER, [''])[0], + // [dataFieldEs] + // ); + // const alertDataProducer = useMemo( + // () => get(dataFieldEs, ALERT_RULE_PRODUCER, [''])[0], + // [dataFieldEs] + // ); const alert = parseObservabilityAlert(dataFieldEs); const { prepend } = core.http.basePath; @@ -194,27 +188,29 @@ function ObservabilityActions({ }; }, [data, eventId, ecsData]); - const onAlertStatusUpdated = useCallback(() => { - setActionsPopover(null); - if (refetch) { - refetch(); - } - }, [setActionsPopover, refetch]); - - const alertPermissions = useGetUserAlertsPermissions( - capabilities, - alertDataConsumer === 'alerts' ? alertDataProducer : alertDataConsumer - ); - - const statusActionItems = useStatusBulkActionItems({ - eventIds: [eventId], - currentStatus, - indexName: ecsData._index ?? '', - setEventsLoading, - setEventsDeleted, - onUpdateSuccess: onAlertStatusUpdated, - onUpdateFailure: onAlertStatusUpdated, - }); + // Hide the WorkFlow filter, but keep its code as required in https://github.com/elastic/kibana/issues/117686 + + // const onAlertStatusUpdated = useCallback(() => { + // setActionsPopover(null); + // if (refetch) { + // refetch(); + // } + // }, [setActionsPopover, refetch]); + + // const alertPermissions = useGetUserAlertsPermissions( + // capabilities, + // alertDataConsumer === 'alerts' ? alertDataProducer : alertDataConsumer + // ); + + // const statusActionItems = useStatusBulkActionItems({ + // eventIds: [eventId], + // currentStatus, + // indexName: ecsData._index ?? '', + // setEventsLoading, + // setEventsDeleted, + // onUpdateSuccess: onAlertStatusUpdated, + // onUpdateFailure: onAlertStatusUpdated, + // }); const ruleId = alert.fields['kibana.alert.rule.uuid'] ?? null; const linkToRule = ruleId ? prepend(paths.management.ruleDetails(ruleId)) : null; @@ -239,7 +235,8 @@ function ObservabilityActions({ }), ] : []), - ...(alertPermissions.crud ? statusActionItems : []), + // Hide the WorkFlow filter, but keep its code as required in https://github.com/elastic/kibana/issues/117686 + // ...(alertPermissions.crud ? statusActionItems : []), ...(!!linkToRule ? [ ); } +// Hide the WorkFlow filter, but keep its code as required in https://github.com/elastic/kibana/issues/117686 const FIELDS_WITHOUT_CELL_ACTIONS = [ '@timestamp', @@ -330,7 +320,7 @@ const FIELDS_WITHOUT_CELL_ACTIONS = [ ]; export function AlertsTableTGrid(props: AlertsTableTGridProps) { - const { indexNames, rangeFrom, rangeTo, kuery, workflowStatus, setRefetch, addToQuery } = props; + const { indexNames, rangeFrom, rangeTo, kuery, workflowStatus, setRefetch } = props; const prevWorkflowStatus = usePrevious(workflowStatus); const { timelines, @@ -382,7 +372,7 @@ export function AlertsTableTGrid(props: AlertsTableTGridProps) { } }, []); - const leadingControlColumns = useMemo(() => { + const leadingControlColumns: ControlColumnProps[] = useMemo(() => { return [ { id: 'expand', @@ -428,7 +418,8 @@ export function AlertsTableTGrid(props: AlertsTableTGridProps) { type, columns: tGridState?.columns ?? columns, deletedEventIds, - defaultCellActions: getDefaultCellActions({ addToQuery }), + // Hide the WorkFlow filter, but keep its code as required in https://github.com/elastic/kibana/issues/117686 + // defaultCellActions: getDefaultCellActions({ addToQuery }), disabledCellActions: FIELDS_WITHOUT_CELL_ACTIONS, end: rangeTo, filters: [], @@ -462,7 +453,6 @@ export function AlertsTableTGrid(props: AlertsTableTGridProps) { }; }, [ casePermissions, - addToQuery, rangeTo, hasAlertsCrudPermissions, indexNames, diff --git a/x-pack/plugins/observability/public/pages/alerts/containers/alerts_table_t_grid/index.ts b/x-pack/plugins/observability/public/pages/alerts/containers/alerts_table_t_grid/index.ts new file mode 100644 index 0000000000000..7bbcc43230a44 --- /dev/null +++ b/x-pack/plugins/observability/public/pages/alerts/containers/alerts_table_t_grid/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 { AlertsTableTGrid } from './alerts_table_t_grid'; diff --git a/x-pack/plugins/observability/public/pages/alerts/containers/index.ts b/x-pack/plugins/observability/public/pages/alerts/containers/index.ts new file mode 100644 index 0000000000000..074f48f426640 --- /dev/null +++ b/x-pack/plugins/observability/public/pages/alerts/containers/index.ts @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export * from './alerts_page'; +export * from './alerts_table_t_grid'; +export * from './state_container'; diff --git a/x-pack/plugins/observability/public/pages/alerts/state_container/index.tsx b/x-pack/plugins/observability/public/pages/alerts/containers/state_container/index.tsx similarity index 100% rename from x-pack/plugins/observability/public/pages/alerts/state_container/index.tsx rename to x-pack/plugins/observability/public/pages/alerts/containers/state_container/index.tsx diff --git a/x-pack/plugins/observability/public/pages/alerts/state_container/state_container.tsx b/x-pack/plugins/observability/public/pages/alerts/containers/state_container/state_container.tsx similarity index 92% rename from x-pack/plugins/observability/public/pages/alerts/state_container/state_container.tsx rename to x-pack/plugins/observability/public/pages/alerts/containers/state_container/state_container.tsx index 3e0a801fedbe2..d00109cc5d63f 100644 --- a/x-pack/plugins/observability/public/pages/alerts/state_container/state_container.tsx +++ b/x-pack/plugins/observability/public/pages/alerts/containers/state_container/state_container.tsx @@ -8,8 +8,8 @@ import { createStateContainer, createStateContainerReactHelpers, -} from '../../../../../../../src/plugins/kibana_utils/public'; -import type { AlertWorkflowStatus } from '../../../../common/typings'; +} from '../../../../../../../../src/plugins/kibana_utils/public'; +import type { AlertWorkflowStatus } from '../../../../../common/typings'; interface AlertsPageContainerState { rangeFrom: string; diff --git a/x-pack/plugins/observability/public/pages/alerts/state_container/use_alerts_page_state_container.tsx b/x-pack/plugins/observability/public/pages/alerts/containers/state_container/use_alerts_page_state_container.tsx similarity index 92% rename from x-pack/plugins/observability/public/pages/alerts/state_container/use_alerts_page_state_container.tsx rename to x-pack/plugins/observability/public/pages/alerts/containers/state_container/use_alerts_page_state_container.tsx index dfa4afcd939cc..5e81286affba7 100644 --- a/x-pack/plugins/observability/public/pages/alerts/state_container/use_alerts_page_state_container.tsx +++ b/x-pack/plugins/observability/public/pages/alerts/containers/state_container/use_alerts_page_state_container.tsx @@ -8,14 +8,14 @@ import { useEffect } from 'react'; import { useHistory } from 'react-router-dom'; -import { TimefilterContract } from '../../../../../../../src/plugins/data/public'; +import { TimefilterContract } from '../../../../../../../../src/plugins/data/public'; import { createKbnUrlStateStorage, syncState, IKbnUrlStateStorage, useContainerSelector, -} from '../../../../../../../src/plugins/kibana_utils/public'; -import { useTimefilterService } from '../../../hooks/use_timefilter_service'; +} from '../../../../../../../../src/plugins/kibana_utils/public'; +import { useTimefilterService } from '../../../../hooks/use_timefilter_service'; import { useContainer, diff --git a/x-pack/plugins/observability/public/pages/alerts/index.ts b/x-pack/plugins/observability/public/pages/alerts/index.ts new file mode 100644 index 0000000000000..525f3441c4470 --- /dev/null +++ b/x-pack/plugins/observability/public/pages/alerts/index.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export * from './components'; +export * from './containers'; diff --git a/x-pack/plugins/observability/public/pages/cases/helpers.ts b/x-pack/plugins/observability/public/pages/cases/helpers.ts index 91f45c711d6a6..f4bc5af7f604d 100644 --- a/x-pack/plugins/observability/public/pages/cases/helpers.ts +++ b/x-pack/plugins/observability/public/pages/cases/helpers.ts @@ -6,10 +6,8 @@ */ import { useEffect, useState } from 'react'; import { isEmpty } from 'lodash'; - import { usePluginContext } from '../../hooks/use_plugin_context'; -import { parseAlert } from '../../pages/alerts/parse_alert'; -import { TopAlert } from '../../pages/alerts/'; +import { TopAlert, parseAlert } from '../../pages/alerts/'; import { useKibana } from '../../utils/kibana_react'; import { Ecs } from '../../../../cases/common'; diff --git a/x-pack/plugins/observability/public/pages/overview/index.test.tsx b/x-pack/plugins/observability/public/pages/overview/index.test.tsx new file mode 100644 index 0000000000000..b37ed1d873ba7 --- /dev/null +++ b/x-pack/plugins/observability/public/pages/overview/index.test.tsx @@ -0,0 +1,51 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license 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 { shallow } from 'enzyme'; +import * as PluginContext from '../../hooks/use_plugin_context'; +import { PluginContextValue } from '../../context/plugin_context'; +import { OverviewPage } from './'; +import { OverviewPage as OldOverviewPage } from './old_overview_page'; +import { OverviewPage as NewOverviewPage } from './overview_page'; + +describe('Overview page', () => { + it('should render the old overview page when feature flag is disabled', () => { + const pluginContext = { + config: { + unsafe: { + overviewNext: { enabled: false }, + }, + }, + }; + + jest + .spyOn(PluginContext, 'usePluginContext') + .mockReturnValue(pluginContext as PluginContextValue); + + const component = shallow(); + expect(component.find(OldOverviewPage)).toHaveLength(1); + expect(component.find(NewOverviewPage)).toHaveLength(0); + }); + + it('should render the new overview page when feature flag is enabled', () => { + const pluginContext = { + config: { + unsafe: { + overviewNext: { enabled: true }, + }, + }, + }; + + jest + .spyOn(PluginContext, 'usePluginContext') + .mockReturnValue(pluginContext as PluginContextValue); + + const component = shallow(); + expect(component.find(OldOverviewPage)).toHaveLength(0); + expect(component.find(NewOverviewPage)).toHaveLength(1); + }); +}); diff --git a/x-pack/plugins/observability/public/pages/overview/index.tsx b/x-pack/plugins/observability/public/pages/overview/index.tsx index 7100a0552876d..cc38445e3a0f2 100644 --- a/x-pack/plugins/observability/public/pages/overview/index.tsx +++ b/x-pack/plugins/observability/public/pages/overview/index.tsx @@ -4,133 +4,24 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ - -import { EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiPanel } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; import React from 'react'; -import { useTrackPageview } from '../..'; -import { EmptySections } from '../../components/app/empty_sections'; -import { ObservabilityHeaderMenu } from '../../components/app/header'; -import { NewsFeed } from '../../components/app/news_feed'; -import { Resources } from '../../components/app/resources'; -import { AlertsSection } from '../../components/app/section/alerts'; -import { DatePicker } from '../../components/shared/date_picker'; -import { useBreadcrumbs } from '../../hooks/use_breadcrumbs'; -import { useFetcher } from '../../hooks/use_fetcher'; -import { useHasData } from '../../hooks/use_has_data'; -import { usePluginContext } from '../../hooks/use_plugin_context'; -import { useTimeRange } from '../../hooks/use_time_range'; import { RouteParams } from '../../routes'; -import { getNewsFeed } from '../../services/get_news_feed'; -import { getBucketSize } from '../../utils/get_bucket_size'; -import { getNoDataConfig } from '../../utils/no_data_config'; -import { DataSections } from './data_sections'; -import { LoadingObservability } from './loading_observability'; +import { usePluginContext } from '../../hooks/use_plugin_context'; +import { OverviewPage as OldOverviewPage } from './old_overview_page'; +import { OverviewPage as NewOverviewPage } from './overview_page'; + +export type { BucketSize } from './old_overview_page'; interface Props { routeParams: RouteParams<'/overview'>; } -export type BucketSize = ReturnType; -function calculateBucketSize({ start, end }: { start?: number; end?: number }) { - if (start && end) { - return getBucketSize({ start, end, minInterval: '60s' }); - } -} - -export function OverviewPage({ routeParams }: Props) { - useTrackPageview({ app: 'observability-overview', path: 'overview' }); - useTrackPageview({ app: 'observability-overview', path: 'overview', delay: 15000 }); - useBreadcrumbs([ - { - text: i18n.translate('xpack.observability.breadcrumbs.overviewLinkText', { - defaultMessage: 'Overview', - }), - }, - ]); - - const { core, ObservabilityPageTemplate } = usePluginContext(); - - const { relativeStart, relativeEnd, absoluteStart, absoluteEnd } = useTimeRange(); - const relativeTime = { start: relativeStart, end: relativeEnd }; - const absoluteTime = { start: absoluteStart, end: absoluteEnd }; +export function OverviewPage(props: Props) { + const { config } = usePluginContext(); - const { data: newsFeed } = useFetcher(() => getNewsFeed({ core }), [core]); - - const { hasDataMap, hasAnyData, isAllRequestsComplete } = useHasData(); - - if (hasAnyData === undefined) { - return ; + if (config.unsafe.overviewNext.enabled) { + return ; + } else { + return ; } - - const hasData = hasAnyData === true || (isAllRequestsComplete === false ? undefined : false); - - const noDataConfig = getNoDataConfig({ - hasData, - basePath: core.http.basePath, - docsLink: core.docLinks.links.observability.guide, - }); - - const { refreshInterval = 10000, refreshPaused = true } = routeParams.query; - - const bucketSize = calculateBucketSize({ - start: absoluteTime.start, - end: absoluteTime.end, - }); - - return ( - , - ], - } - : undefined - } - > - {hasData && ( - <> - - - - {/* Data sections */} - {hasAnyData && } - - - - - {/* Resources / What's New sections */} - - - - {!!newsFeed?.items?.length && } - - - {hasDataMap?.alert?.hasData && ( - - - - - - )} - - - - - )} - - ); } - -const overviewPageTitle = i18n.translate('xpack.observability.overview.pageTitle', { - defaultMessage: 'Overview', -}); diff --git a/x-pack/plugins/observability/public/pages/overview/old_overview_page.tsx b/x-pack/plugins/observability/public/pages/overview/old_overview_page.tsx new file mode 100644 index 0000000000000..7100a0552876d --- /dev/null +++ b/x-pack/plugins/observability/public/pages/overview/old_overview_page.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 { EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiPanel } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import React from 'react'; +import { useTrackPageview } from '../..'; +import { EmptySections } from '../../components/app/empty_sections'; +import { ObservabilityHeaderMenu } from '../../components/app/header'; +import { NewsFeed } from '../../components/app/news_feed'; +import { Resources } from '../../components/app/resources'; +import { AlertsSection } from '../../components/app/section/alerts'; +import { DatePicker } from '../../components/shared/date_picker'; +import { useBreadcrumbs } from '../../hooks/use_breadcrumbs'; +import { useFetcher } from '../../hooks/use_fetcher'; +import { useHasData } from '../../hooks/use_has_data'; +import { usePluginContext } from '../../hooks/use_plugin_context'; +import { useTimeRange } from '../../hooks/use_time_range'; +import { RouteParams } from '../../routes'; +import { getNewsFeed } from '../../services/get_news_feed'; +import { getBucketSize } from '../../utils/get_bucket_size'; +import { getNoDataConfig } from '../../utils/no_data_config'; +import { DataSections } from './data_sections'; +import { LoadingObservability } from './loading_observability'; + +interface Props { + routeParams: RouteParams<'/overview'>; +} +export type BucketSize = ReturnType; +function calculateBucketSize({ start, end }: { start?: number; end?: number }) { + if (start && end) { + return getBucketSize({ start, end, minInterval: '60s' }); + } +} + +export function OverviewPage({ routeParams }: Props) { + useTrackPageview({ app: 'observability-overview', path: 'overview' }); + useTrackPageview({ app: 'observability-overview', path: 'overview', delay: 15000 }); + useBreadcrumbs([ + { + text: i18n.translate('xpack.observability.breadcrumbs.overviewLinkText', { + defaultMessage: 'Overview', + }), + }, + ]); + + const { core, ObservabilityPageTemplate } = usePluginContext(); + + const { relativeStart, relativeEnd, absoluteStart, absoluteEnd } = useTimeRange(); + + const relativeTime = { start: relativeStart, end: relativeEnd }; + const absoluteTime = { start: absoluteStart, end: absoluteEnd }; + + const { data: newsFeed } = useFetcher(() => getNewsFeed({ core }), [core]); + + const { hasDataMap, hasAnyData, isAllRequestsComplete } = useHasData(); + + if (hasAnyData === undefined) { + return ; + } + + const hasData = hasAnyData === true || (isAllRequestsComplete === false ? undefined : false); + + const noDataConfig = getNoDataConfig({ + hasData, + basePath: core.http.basePath, + docsLink: core.docLinks.links.observability.guide, + }); + + const { refreshInterval = 10000, refreshPaused = true } = routeParams.query; + + const bucketSize = calculateBucketSize({ + start: absoluteTime.start, + end: absoluteTime.end, + }); + + return ( + , + ], + } + : undefined + } + > + {hasData && ( + <> + + + + {/* Data sections */} + {hasAnyData && } + + + + + {/* Resources / What's New sections */} + + + + {!!newsFeed?.items?.length && } + + + {hasDataMap?.alert?.hasData && ( + + + + + + )} + + + + + )} + + ); +} + +const overviewPageTitle = i18n.translate('xpack.observability.overview.pageTitle', { + defaultMessage: 'Overview', +}); diff --git a/x-pack/plugins/observability/public/pages/overview/overview.stories.tsx b/x-pack/plugins/observability/public/pages/overview/overview.stories.tsx index 6549e892cab12..6213ea3e66d40 100644 --- a/x-pack/plugins/observability/public/pages/overview/overview.stories.tsx +++ b/x-pack/plugins/observability/public/pages/overview/overview.stories.tsx @@ -66,7 +66,11 @@ const withCore = makeDecorator({ setHeaderActionMenu: () => {}, } as unknown as AppMountParameters, config: { - unsafe: { alertingExperience: { enabled: true }, cases: { enabled: true } }, + unsafe: { + alertingExperience: { enabled: true }, + cases: { enabled: true }, + overviewNext: { enabled: false }, + }, }, core: options as CoreStart, plugins: { diff --git a/x-pack/plugins/observability/public/pages/overview/overview_page.tsx b/x-pack/plugins/observability/public/pages/overview/overview_page.tsx new file mode 100644 index 0000000000000..f4cdec680af94 --- /dev/null +++ b/x-pack/plugins/observability/public/pages/overview/overview_page.tsx @@ -0,0 +1,82 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license 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 from 'react'; +import { useTrackPageview } from '../..'; +import { DatePicker } from '../../components/shared/date_picker'; +import { useBreadcrumbs } from '../../hooks/use_breadcrumbs'; +import { useHasData } from '../../hooks/use_has_data'; +import { usePluginContext } from '../../hooks/use_plugin_context'; +import { useTimeRange } from '../../hooks/use_time_range'; +import { RouteParams } from '../../routes'; +import { getNoDataConfig } from '../../utils/no_data_config'; +import { LoadingObservability } from './loading_observability'; + +interface Props { + routeParams: RouteParams<'/overview'>; +} + +export function OverviewPage({ routeParams }: Props) { + useTrackPageview({ app: 'observability-overview', path: 'overview' }); + useTrackPageview({ app: 'observability-overview', path: 'overview', delay: 15000 }); + useBreadcrumbs([ + { + text: i18n.translate('xpack.observability.breadcrumbs.overviewLinkText', { + defaultMessage: 'Overview', + }), + }, + ]); + + const { core, ObservabilityPageTemplate } = usePluginContext(); + + const { relativeStart, relativeEnd } = useTimeRange(); + + const relativeTime = { start: relativeStart, end: relativeEnd }; + + const { hasAnyData, isAllRequestsComplete } = useHasData(); + + if (hasAnyData === undefined) { + return ; + } + + const hasData = hasAnyData === true || (isAllRequestsComplete === false ? undefined : false); + + const noDataConfig = getNoDataConfig({ + hasData, + basePath: core.http.basePath, + docsLink: core.docLinks.links.observability.guide, + }); + + const { refreshInterval = 10000, refreshPaused = true } = routeParams.query; + + return ( + , + ], + } + : undefined + } + > + {hasData &&
New observability content goes here
} +
+ ); +} + +const overviewPageTitle = i18n.translate('xpack.observability.overview.pageTitle', { + defaultMessage: 'Overview', +}); diff --git a/x-pack/plugins/observability/public/routes/index.tsx b/x-pack/plugins/observability/public/routes/index.tsx index 169f4b5254c04..6f38a66cdb643 100644 --- a/x-pack/plugins/observability/public/routes/index.tsx +++ b/x-pack/plugins/observability/public/routes/index.tsx @@ -8,8 +8,8 @@ import * as t from 'io-ts'; import React from 'react'; import { casesPath } from '../../common'; -import { AlertsPage } from '../pages/alerts'; import { CasesPage } from '../pages/cases'; +import { AlertsPage } from '../pages/alerts/containers/alerts_page'; import { HomePage } from '../pages/home'; import { LandingPage } from '../pages/landing'; import { OverviewPage } from '../pages/overview'; diff --git a/x-pack/plugins/observability/public/utils/test_helper.tsx b/x-pack/plugins/observability/public/utils/test_helper.tsx index 544f3feecb2bb..a3ec446e5c307 100644 --- a/x-pack/plugins/observability/public/utils/test_helper.tsx +++ b/x-pack/plugins/observability/public/utils/test_helper.tsx @@ -34,7 +34,13 @@ export const core = { }, } as unknown as CoreStart; -const config = { unsafe: { alertingExperience: { enabled: true }, cases: { enabled: true } } }; +const config = { + unsafe: { + alertingExperience: { enabled: true }, + cases: { enabled: true }, + overviewNext: { enabled: false }, + }, +}; const plugins = { data: { query: { timefilter: { timefilter: { setTime: jest.fn() } } } }, diff --git a/x-pack/plugins/observability/server/index.ts b/x-pack/plugins/observability/server/index.ts index d99cf0865c0dd..51204c7512a3d 100644 --- a/x-pack/plugins/observability/server/index.ts +++ b/x-pack/plugins/observability/server/index.ts @@ -34,6 +34,7 @@ export const config: PluginConfigDescriptor = { unsafe: schema.object({ alertingExperience: schema.object({ enabled: schema.boolean({ defaultValue: true }) }), cases: schema.object({ enabled: schema.boolean({ defaultValue: true }) }), + overviewNext: schema.object({ enabled: schema.boolean({ defaultValue: false }) }), }), }), }; diff --git a/x-pack/plugins/reporting/server/browsers/chromium/driver/chromium_driver.ts b/x-pack/plugins/reporting/server/browsers/chromium/driver/chromium_driver.ts index 0947d24f827c2..0f2572ff2b2e4 100644 --- a/x-pack/plugins/reporting/server/browsers/chromium/driver/chromium_driver.ts +++ b/x-pack/plugins/reporting/server/browsers/chromium/driver/chromium_driver.ts @@ -17,7 +17,7 @@ import { ReportingCore } from '../../..'; import { KBN_SCREENSHOT_MODE_HEADER } from '../../../../../../../src/plugins/screenshot_mode/server'; import { ConditionalHeaders, ConditionalHeadersConditions } from '../../../export_types/common'; import { LevelLogger } from '../../../lib'; -import { ViewZoomWidthHeight } from '../../../lib/layouts/layout'; +import { Layout, ViewZoomWidthHeight } from '../../../lib/layouts/layout'; import { ElementPosition } from '../../../lib/screenshots'; import { allowRequest, NetworkPolicy } from '../../network_policy'; @@ -97,11 +97,13 @@ export class HeadlessChromiumDriver { waitForSelector: pageLoadSelector, timeout, locator, + layout, }: { conditionalHeaders: ConditionalHeaders; waitForSelector: string; timeout: number; locator?: LocatorParams; + layout?: Layout; }, logger: LevelLogger ): Promise { @@ -116,6 +118,10 @@ export class HeadlessChromiumDriver { */ await this.page.evaluateOnNewDocument(this.core.getEnableScreenshotMode()); + if (layout) { + await this.page.evaluateOnNewDocument(this.core.getSetScreenshotLayout(), layout.id); + } + if (locator) { await this.page.evaluateOnNewDocument( (key: string, value: unknown) => { diff --git a/x-pack/plugins/reporting/server/core.ts b/x-pack/plugins/reporting/server/core.ts index bc74f5463ba33..43aefb73aebb9 100644 --- a/x-pack/plugins/reporting/server/core.ts +++ b/x-pack/plugins/reporting/server/core.ts @@ -253,9 +253,16 @@ export class ReportingCore { .toPromise(); } + private getScreenshotModeDep() { + return this.getPluginSetupDeps().screenshotMode; + } + public getEnableScreenshotMode() { - const { screenshotMode } = this.getPluginSetupDeps(); - return screenshotMode.setScreenshotModeEnabled; + return this.getScreenshotModeDep().setScreenshotModeEnabled; + } + + public getSetScreenshotLayout() { + return this.getScreenshotModeDep().setScreenshotLayout; } /* diff --git a/x-pack/plugins/reporting/server/lib/layouts/preserve_layout.css b/x-pack/plugins/reporting/server/lib/layouts/preserve_layout.css index 508d217cdd030..60513c417165f 100644 --- a/x-pack/plugins/reporting/server/lib/layouts/preserve_layout.css +++ b/x-pack/plugins/reporting/server/lib/layouts/preserve_layout.css @@ -5,7 +5,7 @@ ****** */ - /** +/** * global */ @@ -27,7 +27,6 @@ filter-bar, * Discover Tweaks */ - /* hide unusable controls */ discover-app .dscTimechart, discover-app .dscSidebar__container, @@ -36,7 +35,6 @@ discover-app .discover-table-footer { display: none; } - /** * The global banner (e.g. "Help us improve Elastic...") should not print. */ @@ -73,7 +71,8 @@ discover-app .discover-table-footer { position: fixed; width: 100%; height: 100%; - top: 0; left: 0; + top: 0; + left: 0; } /** diff --git a/x-pack/plugins/reporting/server/lib/layouts/preserve_layout.ts b/x-pack/plugins/reporting/server/lib/layouts/preserve_layout.ts index 424e85327c22b..7f6bc9e5d9505 100644 --- a/x-pack/plugins/reporting/server/lib/layouts/preserve_layout.ts +++ b/x-pack/plugins/reporting/server/lib/layouts/preserve_layout.ts @@ -4,7 +4,6 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ - import path from 'path'; import { CustomPageSize } from 'pdfmake/interfaces'; import { LAYOUT_TYPES } from '../../../common/constants'; @@ -37,6 +36,7 @@ export class PreserveLayout extends Layout implements LayoutInstance { } public getCssOverridesPath() { + // TODO: Remove this path once we have migrated all plugins away from depending on this hiding page elements. return path.join(__dirname, 'preserve_layout.css'); } diff --git a/x-pack/plugins/reporting/server/lib/layouts/print.css b/x-pack/plugins/reporting/server/lib/layouts/print.css deleted file mode 100644 index 7d38ebe81e4e8..0000000000000 --- a/x-pack/plugins/reporting/server/lib/layouts/print.css +++ /dev/null @@ -1,122 +0,0 @@ -/* - ****** - ****** This is a collection of CSS overrides that make Kibana look better for - ****** generating PDF reports with headless browser - ****** - */ - -/** - * global - */ - -/* elements can hide themselves when shared */ -.hide-for-sharing { - display: none !important; -} - -/* hide unusable controls */ -kbn-top-nav, -filter-bar, -.kbnTopNavMenu__wrapper, -::-webkit-scrollbar, -.euiNavDrawer { - display: none !important; -} - -/** - * Discover Tweaks - */ - -/* hide unusable controls */ -discover-app .dscTimechart, -discover-app .dscSidebar__container, -discover-app .dscCollapsibleSidebar__collapseButton, -discover-app .discover-table-footer { - display: none; -} - -/** - * The global banner (e.g. "Help us improve Elastic...") should not print. - */ - -#globalBannerList { - display: none; -} - -/** - * Visualize Editor Tweaks - */ - -/* hide unusable controls -* !important is required to override resizable panel inline display */ -.visEditor__content .visEditor--default > :not(.visEditor__visualization__wrapper) { - display: none !important; -} -/** THIS IS FOR TSVB UNTIL REFACTOR **/ -.tvbEditorVisualization { - position: static !important; -} -.visualize .tvbVisTimeSeries__legendToggle, -.tvbEditor--hideForReporting { - /* all non-content rows in interface */ - display: none; -} -/** END TSVB BAD BAD HACKS **/ - -/* remove left padding from visualizations so that map lines up with .leaflet-container and -* setting the position to be fixed and to take up the entire screen, because some zoom levels/viewports -* are triggering the media breakpoints that cause the .visEditor__canvas to take up more room than the viewport */ -.visEditor .visEditor__canvas { - padding-left: 0px; - position: fixed; - width: 100%; - height: 100%; - top: 0; - left: 0; -} - -/** - * Visualization tweaks - */ - -/* hide unusable controls */ -.visualize .visLegend__toggle, -.visualize .kbnAggTable__controls/* export raw, export formatted, etc. */, -.visualize .leaflet-container .leaflet-top.leaflet-left/* tilemap controls */, -.visualize paginate-controls { - display: none; -} - -/* Ensure the min-height of the small breakpoint isn't used */ -.vis-editor visualization { - min-height: 0 !important; -} - -/** -* Dashboard tweaks -*/ - -.dashboardViewport .embPanel__header { - /* hide the panel heading with the controls and title */ - display: none !important; -} - -.dashboardViewport .euiPanel { - /* Remove the border from the eui panel */ - border: none !important; -} - -/** - * 1. Reporting manually makes each visualization it wants to screenshot larger, so we need to hide - * the visualizations in the other panels. We can only use properties that will be manually set in - * reporting/export_types/printable_pdf/lib/screenshot.js or this will also hide the visualization - * we want to capture. - * 2. React grid item's transform affects the visualizations, even when they are using fixed positioning. Chrome seems - * to handle this fine, but firefox moves the visualizations around. - */ -.dashboardViewport .react-grid-item { - height: 0 !important; /* 1. */ - width: 0 !important; /* 1. */ - transform: none !important; /* 2. */ - -webkit-transform: none !important; /* 2. */ -} diff --git a/x-pack/plugins/reporting/server/lib/layouts/print_layout.ts b/x-pack/plugins/reporting/server/lib/layouts/print_layout.ts index 0849f8850f91d..68226affb41e4 100644 --- a/x-pack/plugins/reporting/server/lib/layouts/print_layout.ts +++ b/x-pack/plugins/reporting/server/lib/layouts/print_layout.ts @@ -5,13 +5,8 @@ * 2.0. */ -import path from 'path'; import { PageOrientation, PredefinedPageSize } from 'pdfmake/interfaces'; -import { EvaluateFn, SerializableOrJSHandle } from 'puppeteer'; -import { LevelLogger } from '../'; import { DEFAULT_VIEWPORT, LAYOUT_TYPES } from '../../../common/constants'; -import { Size } from '../../../common/types'; -import { HeadlessChromiumDriver } from '../../browsers'; import { CaptureConfig } from '../../types'; import { getDefaultLayoutSelectors, LayoutInstance, LayoutSelectorDictionary } from './'; import { Layout } from './layout'; @@ -31,7 +26,7 @@ export class PrintLayout extends Layout implements LayoutInstance { } public getCssOverridesPath() { - return path.join(__dirname, 'print.css'); + return undefined; } public getBrowserViewport() { @@ -49,40 +44,6 @@ export class PrintLayout extends Layout implements LayoutInstance { height: this.viewport.height * itemsCount, }; } - - public async positionElements( - browser: HeadlessChromiumDriver, - logger: LevelLogger - ): Promise { - logger.debug('positioning elements'); - - const elementSize: Size = { - width: this.viewport.width / this.captureConfig.zoom, - height: this.viewport.height / this.captureConfig.zoom, - }; - const evalOptions: { fn: EvaluateFn; args: SerializableOrJSHandle[] } = { - fn: (selector: string, height: number, width: number) => { - const visualizations = document.querySelectorAll(selector) as NodeListOf; - const visualizationsLength = visualizations.length; - - for (let i = 0; i < visualizationsLength; i++) { - const visualization = visualizations[i]; - const style = visualization.style; - style.position = 'fixed'; - style.top = `${height * i}px`; - style.left = '0'; - style.width = `${width}px`; - style.height = `${height}px`; - style.zIndex = '1'; - style.backgroundColor = 'inherit'; - } - }, - args: [this.selectors.screenshot, elementSize.height, elementSize.width], - }; - - await browser.evaluate(evalOptions, { context: 'PositionElements' }, logger); - } - public getPdfImageSize() { return { width: 500, diff --git a/x-pack/plugins/reporting/server/lib/screenshots/observable_handler.ts b/x-pack/plugins/reporting/server/lib/screenshots/observable_handler.ts index 1db313b091025..cdbddb8d89c89 100644 --- a/x-pack/plugins/reporting/server/lib/screenshots/observable_handler.ts +++ b/x-pack/plugins/reporting/server/lib/screenshots/observable_handler.ts @@ -76,6 +76,7 @@ export class ScreenshotObservableHandler { index, urlOrUrlLocatorTuple, this.conditionalHeaders, + this.layout, this.logger ) ).pipe(this.waitUntil(this.timeouts.openUrl)); diff --git a/x-pack/plugins/reporting/server/lib/screenshots/open_url.ts b/x-pack/plugins/reporting/server/lib/screenshots/open_url.ts index 63a5e80289e3e..b26037aa917b8 100644 --- a/x-pack/plugins/reporting/server/lib/screenshots/open_url.ts +++ b/x-pack/plugins/reporting/server/lib/screenshots/open_url.ts @@ -10,6 +10,7 @@ import { LevelLogger, startTrace } from '../'; import { LocatorParams, UrlOrUrlLocatorTuple } from '../../../common/types'; import { HeadlessChromiumDriver } from '../../browsers'; import { ConditionalHeaders } from '../../export_types/common'; +import { Layout } from '../layouts'; import { DEFAULT_PAGELOAD_SELECTOR } from './constants'; export const openUrl = async ( @@ -18,6 +19,7 @@ export const openUrl = async ( index: number, urlOrUrlLocatorTuple: UrlOrUrlLocatorTuple, conditionalHeaders: ConditionalHeaders, + layout: undefined | Layout, logger: LevelLogger ): Promise => { // If we're moving to another page in the app, we'll want to wait for the app to tell us @@ -36,7 +38,11 @@ export const openUrl = async ( } try { - await browser.open(url, { conditionalHeaders, waitForSelector, timeout, locator }, logger); + await browser.open( + url, + { conditionalHeaders, waitForSelector, timeout, locator, layout }, + logger + ); } catch (err) { logger.error(err); throw new Error( diff --git a/x-pack/plugins/rule_registry/common/assets/field_maps/ecs_field_map.ts b/x-pack/plugins/rule_registry/common/assets/field_maps/ecs_field_map.ts index 859070bd498e3..1ea85e5a5434e 100644 --- a/x-pack/plugins/rule_registry/common/assets/field_maps/ecs_field_map.ts +++ b/x-pack/plugins/rule_registry/common/assets/field_maps/ecs_field_map.ts @@ -2550,6 +2550,426 @@ export const ecsFieldMap = { array: false, required: false, }, + 'threat.enrichments': { + type: 'nested', + array: true, + required: false, + }, + 'threat.enrichments.indicator': { + type: 'object', + array: false, + required: false, + }, + 'threat.enrichments.indicator.as.number': { + type: 'long', + array: false, + required: false, + }, + 'threat.enrichments.indicator.as.organization.name': { + type: 'keyword', + array: false, + required: false, + }, + 'threat.enrichments.indicator.confidence': { + type: 'keyword', + array: false, + required: false, + }, + 'threat.enrichments.indicator.description': { + type: 'keyword', + array: false, + required: false, + }, + 'threat.enrichments.indicator.email.address': { + type: 'keyword', + array: false, + required: false, + }, + 'threat.enrichments.indicator.file.accessed': { + type: 'date', + array: false, + required: false, + }, + 'threat.enrichments.indicator.file.attributes': { + type: 'keyword', + array: true, + required: false, + }, + 'threat.enrichments.indicator.file.code_signature.digest_algorithm': { + type: 'keyword', + array: false, + required: false, + }, + 'threat.enrichments.indicator.file.code_signature.exists': { + type: 'boolean', + array: false, + required: false, + }, + 'threat.enrichments.indicator.file.code_signature.signing_id': { + type: 'keyword', + array: false, + required: false, + }, + 'threat.enrichments.indicator.file.code_signature.status': { + type: 'keyword', + array: false, + required: false, + }, + 'threat.enrichments.indicator.file.code_signature.subject_name': { + type: 'keyword', + array: false, + required: false, + }, + 'threat.enrichments.indicator.file.code_signature.team_id': { + type: 'keyword', + array: false, + required: false, + }, + 'threat.enrichments.indicator.file.code_signature.timestamp': { + type: 'date', + array: false, + required: false, + }, + 'threat.enrichments.indicator.file.code_signature.trusted': { + type: 'boolean', + array: false, + required: false, + }, + 'threat.enrichments.indicator.file.code_signature.valid': { + type: 'boolean', + array: false, + required: false, + }, + 'threat.enrichments.indicator.file.created': { + type: 'date', + array: false, + required: false, + }, + 'threat.enrichments.indicator.file.ctime': { + type: 'date', + array: false, + required: false, + }, + 'threat.enrichments.indicator.file.device': { + type: 'keyword', + array: false, + required: false, + }, + 'threat.enrichments.indicator.file.directory': { + type: 'keyword', + array: false, + required: false, + }, + 'threat.enrichments.indicator.file.drive_letter': { + type: 'keyword', + array: false, + required: false, + }, + 'threat.enrichments.indicator.file.extension': { + type: 'keyword', + array: false, + required: false, + }, + 'threat.enrichments.indicator.file.fork_name': { + type: 'keyword', + array: false, + required: false, + }, + 'threat.enrichments.indicator.file.gid': { + type: 'keyword', + array: false, + required: false, + }, + 'threat.enrichments.indicator.file.group': { + type: 'keyword', + array: false, + required: false, + }, + 'threat.enrichments.indicator.file.hash.md5': { + type: 'keyword', + array: false, + required: false, + }, + 'threat.enrichments.indicator.file.hash.sha1': { + type: 'keyword', + array: false, + required: false, + }, + 'threat.enrichments.indicator.file.hash.sha256': { + type: 'keyword', + array: false, + required: false, + }, + 'threat.enrichments.indicator.file.hash.sha512': { + type: 'keyword', + array: false, + required: false, + }, + 'threat.enrichments.indicator.file.hash.ssdeep': { + type: 'keyword', + array: false, + required: false, + }, + 'threat.enrichments.indicator.file.inode': { + type: 'keyword', + array: false, + required: false, + }, + 'threat.enrichments.indicator.file.mime_type': { + type: 'keyword', + array: false, + required: false, + }, + 'threat.enrichments.indicator.file.mode': { + type: 'keyword', + array: false, + required: false, + }, + 'threat.enrichments.indicator.file.mtime': { + type: 'date', + array: false, + required: false, + }, + 'threat.enrichments.indicator.file.name': { + type: 'keyword', + array: false, + required: false, + }, + 'threat.enrichments.indicator.file.owner': { + type: 'keyword', + array: false, + required: false, + }, + 'threat.enrichments.indicator.file.path': { + type: 'keyword', + array: false, + required: false, + }, + 'threat.enrichments.indicator.file.size': { + type: 'long', + array: false, + required: false, + }, + 'threat.enrichments.indicator.file.target_path': { + type: 'keyword', + array: false, + required: false, + }, + 'threat.enrichments.indicator.file.type': { + type: 'keyword', + array: false, + required: false, + }, + 'threat.enrichments.indicator.file.uid': { + type: 'keyword', + array: false, + required: false, + }, + 'threat.enrichments.indicator.first_seen': { + type: 'date', + array: false, + required: false, + }, + 'threat.enrichments.indicator.ip': { + type: 'ip', + array: false, + required: false, + }, + 'threat.enrichments.indicator.last_seen': { + type: 'date', + array: false, + required: false, + }, + 'threat.enrichments.indicator.marking.tlp': { + type: 'keyword', + array: false, + required: false, + }, + 'threat.enrichments.indicator.modified_at': { + type: 'date', + array: false, + required: false, + }, + 'threat.enrichments.indicator.port': { + type: 'long', + array: false, + required: false, + }, + 'threat.enrichments.indicator.provider': { + type: 'keyword', + array: false, + required: false, + }, + 'threat.enrichments.indicator.reference': { + type: 'keyword', + array: false, + required: false, + }, + 'threat.enrichments.indicator.registry.data.bytes': { + type: 'keyword', + array: false, + required: false, + }, + 'threat.enrichments.indicator.registry.data.strings': { + type: 'wildcard', + array: true, + required: false, + }, + 'threat.enrichments.indicator.registry.data.type': { + type: 'keyword', + array: false, + required: false, + }, + 'threat.enrichments.indicator.registry.hive': { + type: 'keyword', + array: false, + required: false, + }, + 'threat.enrichments.indicator.registry.key': { + type: 'keyword', + array: false, + required: false, + }, + 'threat.enrichments.indicator.registry.path': { + type: 'keyword', + array: false, + required: false, + }, + 'threat.enrichments.indicator.registry.value': { + type: 'keyword', + array: false, + required: false, + }, + 'threat.enrichments.indicator.scanner_stats': { + type: 'long', + array: false, + required: false, + }, + 'threat.enrichments.indicator.sightings': { + type: 'long', + array: false, + required: false, + }, + 'threat.enrichments.indicator.type': { + type: 'keyword', + array: false, + required: false, + }, + 'threat.enrichments.indicator.url.domain': { + type: 'keyword', + array: false, + required: false, + }, + 'threat.enrichments.indicator.url.extension': { + type: 'keyword', + array: false, + required: false, + }, + 'threat.enrichments.indicator.url.fragment': { + type: 'keyword', + array: false, + required: false, + }, + 'threat.enrichments.indicator.url.full': { + type: 'wildcard', + array: false, + required: false, + }, + 'threat.enrichments.indicator.url.original': { + type: 'wildcard', + array: false, + required: false, + }, + 'threat.enrichments.indicator.url.password': { + type: 'keyword', + array: false, + required: false, + }, + 'threat.enrichments.indicator.url.path': { + type: 'wildcard', + array: false, + required: false, + }, + 'threat.enrichments.indicator.url.port': { + type: 'long', + array: false, + required: false, + }, + 'threat.enrichments.indicator.url.query': { + type: 'keyword', + array: false, + required: false, + }, + 'threat.enrichments.indicator.url.registered_domain': { + type: 'keyword', + array: false, + required: false, + }, + 'threat.enrichments.indicator.url.scheme': { + type: 'keyword', + array: false, + required: false, + }, + 'threat.enrichments.indicator.url.subdomain': { + type: 'keyword', + array: false, + required: false, + }, + 'threat.enrichments.indicator.url.top_level_domain': { + type: 'keyword', + array: false, + required: false, + }, + 'threat.enrichments.indicator.url.username': { + type: 'keyword', + array: false, + required: false, + }, + 'threat.enrichments.matched.atomic': { + type: 'keyword', + array: false, + required: false, + }, + 'threat.enrichments.matched.field': { + type: 'keyword', + array: false, + required: false, + }, + 'threat.enrichments.matched.id': { + type: 'keyword', + array: false, + required: false, + }, + 'threat.enrichments.matched.index': { + type: 'keyword', + array: false, + required: false, + }, + 'threat.enrichments.matched.type': { + type: 'keyword', + array: false, + required: false, + }, + 'threat.group.alias': { + type: 'keyword', + array: true, + required: false, + }, + 'threat.group.id': { + type: 'keyword', + array: false, + required: false, + }, + 'threat.group.name': { + type: 'keyword', + array: false, + required: false, + }, + 'threat.group.reference': { + type: 'keyword', + array: false, + required: false, + }, 'threat.tactic.id': { type: 'keyword', array: true, diff --git a/x-pack/plugins/rule_registry/scripts/generate_ecs_fieldmap/index.js b/x-pack/plugins/rule_registry/scripts/generate_ecs_fieldmap/index.js index 6b10ca5f837d5..bbcf651bd6d69 100644 --- a/x-pack/plugins/rule_registry/scripts/generate_ecs_fieldmap/index.js +++ b/x-pack/plugins/rule_registry/scripts/generate_ecs_fieldmap/index.js @@ -19,7 +19,7 @@ const exec = util.promisify(execCb); const ecsDir = path.resolve(__dirname, '../../../../../../ecs'); const ecsYamlFilename = path.join(ecsDir, 'generated/ecs/ecs_flat.yml'); -const outputDir = path.join(__dirname, '../../common/field_map'); +const outputDir = path.join(__dirname, '../../common/assets/field_maps'); const outputFieldMapFilename = path.join(outputDir, 'ecs_field_map.ts'); diff --git a/x-pack/plugins/rule_registry/server/rule_data_plugin_service/resource_installer.ts b/x-pack/plugins/rule_registry/server/rule_data_plugin_service/resource_installer.ts index bfdec28a50987..bbfa17c5694f1 100644 --- a/x-pack/plugins/rule_registry/server/rule_data_plugin_service/resource_installer.ts +++ b/x-pack/plugins/rule_registry/server/rule_data_plugin_service/resource_installer.ts @@ -316,7 +316,7 @@ export class ResourceInstaller { // @ts-expect-error rollover_alias: primaryNamespacedAlias, }, - 'index.mapping.total_fields.limit': 1100, + 'index.mapping.total_fields.limit': 1200, }, mappings: { dynamic: false, diff --git a/x-pack/plugins/security/public/management/roles/roles_management_app.test.tsx b/x-pack/plugins/security/public/management/roles/roles_management_app.test.tsx index 007c3e306372e..1601ea481cf2d 100644 --- a/x-pack/plugins/security/public/management/roles/roles_management_app.test.tsx +++ b/x-pack/plugins/security/public/management/roles/roles_management_app.test.tsx @@ -104,7 +104,7 @@ describe('rolesManagementApp', () => { expect(docTitle.reset).not.toHaveBeenCalled(); expect(container).toMatchInlineSnapshot(`
- Role Edit Page: {"action":"edit","rolesAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{},"externalUrl":{}}},"userAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{},"externalUrl":{}}},"indicesAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{},"externalUrl":{}},"fieldCache":{}},"privilegesAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{},"externalUrl":{}}},"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{},"externalUrl":{}},"notifications":{"toasts":{}},"fatalErrors":{},"license":{"features$":{"_isScalar":false}},"docLinks":{},"uiCapabilities":{"catalogue":{},"management":{},"navLinks":{}},"history":{"action":"PUSH","length":1,"location":{"pathname":"/edit","search":"","hash":""}}} + Role Edit Page: {"action":"edit","rolesAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{},"externalUrl":{}}},"userAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{},"externalUrl":{}}},"indicesAPIClient":{"fieldCache":{},"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{},"externalUrl":{}}},"privilegesAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{},"externalUrl":{}}},"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{},"externalUrl":{}},"notifications":{"toasts":{}},"fatalErrors":{},"license":{"features$":{"_isScalar":false}},"docLinks":{},"uiCapabilities":{"catalogue":{},"management":{},"navLinks":{}},"history":{"action":"PUSH","length":1,"location":{"pathname":"/edit","search":"","hash":""}}}
`); @@ -129,7 +129,7 @@ describe('rolesManagementApp', () => { expect(docTitle.reset).not.toHaveBeenCalled(); expect(container).toMatchInlineSnapshot(`
- Role Edit Page: {"action":"edit","roleName":"role@name","rolesAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{},"externalUrl":{}}},"userAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{},"externalUrl":{}}},"indicesAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{},"externalUrl":{}},"fieldCache":{}},"privilegesAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{},"externalUrl":{}}},"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{},"externalUrl":{}},"notifications":{"toasts":{}},"fatalErrors":{},"license":{"features$":{"_isScalar":false}},"docLinks":{},"uiCapabilities":{"catalogue":{},"management":{},"navLinks":{}},"history":{"action":"PUSH","length":1,"location":{"pathname":"/edit/role@name","search":"","hash":""}}} + Role Edit Page: {"action":"edit","roleName":"role@name","rolesAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{},"externalUrl":{}}},"userAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{},"externalUrl":{}}},"indicesAPIClient":{"fieldCache":{},"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{},"externalUrl":{}}},"privilegesAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{},"externalUrl":{}}},"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{},"externalUrl":{}},"notifications":{"toasts":{}},"fatalErrors":{},"license":{"features$":{"_isScalar":false}},"docLinks":{},"uiCapabilities":{"catalogue":{},"management":{},"navLinks":{}},"history":{"action":"PUSH","length":1,"location":{"pathname":"/edit/role@name","search":"","hash":""}}}
`); @@ -154,7 +154,7 @@ describe('rolesManagementApp', () => { expect(docTitle.reset).not.toHaveBeenCalled(); expect(container).toMatchInlineSnapshot(`
- Role Edit Page: {"action":"clone","roleName":"someRoleName","rolesAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{},"externalUrl":{}}},"userAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{},"externalUrl":{}}},"indicesAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{},"externalUrl":{}},"fieldCache":{}},"privilegesAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{},"externalUrl":{}}},"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{},"externalUrl":{}},"notifications":{"toasts":{}},"fatalErrors":{},"license":{"features$":{"_isScalar":false}},"docLinks":{},"uiCapabilities":{"catalogue":{},"management":{},"navLinks":{}},"history":{"action":"PUSH","length":1,"location":{"pathname":"/clone/someRoleName","search":"","hash":""}}} + Role Edit Page: {"action":"clone","roleName":"someRoleName","rolesAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{},"externalUrl":{}}},"userAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{},"externalUrl":{}}},"indicesAPIClient":{"fieldCache":{},"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{},"externalUrl":{}}},"privilegesAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{},"externalUrl":{}}},"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{},"externalUrl":{}},"notifications":{"toasts":{}},"fatalErrors":{},"license":{"features$":{"_isScalar":false}},"docLinks":{},"uiCapabilities":{"catalogue":{},"management":{},"navLinks":{}},"history":{"action":"PUSH","length":1,"location":{"pathname":"/clone/someRoleName","search":"","hash":""}}}
`); diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/endpoint_details_content.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/endpoint_details_content.tsx index d9fbbcb5ec252..5f51436daff6a 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/endpoint_details_content.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/endpoint_details_content.tsx @@ -31,7 +31,10 @@ import { EndpointAgentStatus } from '../components/endpoint_agent_status'; const EndpointDetailsContentStyled = styled.div` dl dt { - max-width: 220px; + max-width: 27%; + } + dl dd { + max-width: 73%; } .policyLineText { padding-right: 5px; diff --git a/x-pack/plugins/security_solution/public/overview/components/overview_risky_host_links/risky_hosts_panel_view.tsx b/x-pack/plugins/security_solution/public/overview/components/overview_risky_host_links/risky_hosts_panel_view.tsx index eb4e226940c5f..87a5710ab0372 100644 --- a/x-pack/plugins/security_solution/public/overview/components/overview_risky_host_links/risky_hosts_panel_view.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/overview_risky_host_links/risky_hosts_panel_view.tsx @@ -30,6 +30,8 @@ const columns: Array> = [ align: 'right', field: 'count', name: 'Risk Score', + render: (riskScore) => + Number.isNaN(riskScore) ? riskScore : Number.parseFloat(riskScore).toFixed(2), sortable: true, truncateText: true, width: '15%', diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/import_rules_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/import_rules_route.ts index 187de40d33df0..b056fd3ed80f0 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/import_rules_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/import_rules_route.ts @@ -326,6 +326,7 @@ export const importRulesRoute = ( threshold, threatFilters, threatIndex, + threatIndicatorPath, threatQuery, threatMapping, threatLanguage, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/patch_rules_bulk_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/patch_rules_bulk_route.ts index 838bfe63782c8..9e821c8f686f6 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/patch_rules_bulk_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/patch_rules_bulk_route.ts @@ -98,6 +98,7 @@ export const patchRulesBulkRoute = ( threshold, threat_filters: threatFilters, threat_index: threatIndex, + threat_indicator_path: threatIndicatorPath, threat_query: threatQuery, threat_mapping: threatMapping, threat_language: threatLanguage, @@ -178,6 +179,7 @@ export const patchRulesBulkRoute = ( threshold, threatFilters, threatIndex, + threatIndicatorPath, threatQuery, threatMapping, threatLanguage, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/patch_rules_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/patch_rules_route.ts index bb9f7e1475247..da3e4ccc99b99 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/patch_rules_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/patch_rules_route.ts @@ -86,6 +86,7 @@ export const patchRulesRoute = ( threshold, threat_filters: threatFilters, threat_index: threatIndex, + threat_indicator_path: threatIndicatorPath, threat_query: threatQuery, threat_mapping: threatMapping, threat_language: threatLanguage, @@ -179,6 +180,7 @@ export const patchRulesRoute = ( threshold, threatFilters, threatIndex, + threatIndicatorPath, threatQuery, threatMapping, threatLanguage, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/patch_rules.mock.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/patch_rules.mock.ts index 3626bcd5f127e..3a602a54ca099 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/patch_rules.mock.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/patch_rules.mock.ts @@ -49,6 +49,7 @@ export const getPatchRulesOptionsMock = (isRuleRegistryEnabled: boolean): PatchR threshold: undefined, threatFilters: undefined, threatIndex: undefined, + threatIndicatorPath: undefined, threatQuery: undefined, threatMapping: undefined, threatLanguage: undefined, @@ -103,6 +104,7 @@ export const getPatchMlRulesOptionsMock = (isRuleRegistryEnabled: boolean): Patc threshold: undefined, threatFilters: undefined, threatIndex: undefined, + threatIndicatorPath: undefined, threatQuery: undefined, threatMapping: undefined, threatLanguage: undefined, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/patch_rules.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/patch_rules.ts index fd48cd4eebc2c..ee3098b8577d4 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/patch_rules.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/patch_rules.ts @@ -71,6 +71,7 @@ export const patchRules = async ({ threshold, threatFilters, threatIndex, + threatIndicatorPath, threatQuery, threatMapping, threatLanguage, @@ -123,6 +124,7 @@ export const patchRules = async ({ threshold, threatFilters, threatIndex, + threatIndicatorPath, threatQuery, threatMapping, threatLanguage, @@ -170,6 +172,7 @@ export const patchRules = async ({ threshold: threshold ? normalizeThresholdObject(threshold) : undefined, threatFilters, threatIndex, + threatIndicatorPath, threatQuery, threatMapping, threatLanguage, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/types.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/types.ts index f847e385e6477..06328137973c6 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/types.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/types.ts @@ -310,6 +310,7 @@ export interface PatchRulesOptions { threshold: ThresholdOrUndefined; threatFilters: ThreatFiltersOrUndefined; threatIndex: ThreatIndexOrUndefined; + threatIndicatorPath: ThreatIndicatorPathOrUndefined; threatQuery: ThreatQueryOrUndefined; threatMapping: ThreatMappingOrUndefined; threatLanguage: ThreatLanguageOrUndefined; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/update_prepacked_rules.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/update_prepacked_rules.test.ts index 9bd0fe3cef59a..c4cd5d4211c96 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/update_prepacked_rules.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/update_prepacked_rules.test.ts @@ -63,4 +63,42 @@ describe.each([ }) ); }); + + it('should update threat match rules', async () => { + const updatedThreatParams = { + threat_index: ['test-index'], + threat_indicator_path: 'test.path', + threat_query: 'threat:*', + }; + const prepackagedRule = getAddPrepackagedRulesSchemaDecodedMock(); + rulesClient.find.mockResolvedValue(getFindResultWithSingleHit(isRuleRegistryEnabled)); + + await updatePrepackagedRules( + rulesClient, + savedObjectsClient, + 'default', + ruleStatusClient, + [{ ...prepackagedRule, ...updatedThreatParams }], + 'output-index', + isRuleRegistryEnabled + ); + + expect(patchRules).toHaveBeenCalledWith( + expect.objectContaining({ + threatIndicatorPath: 'test.path', + }) + ); + + expect(patchRules).toHaveBeenCalledWith( + expect.objectContaining({ + threatIndex: ['test-index'], + }) + ); + + expect(patchRules).toHaveBeenCalledWith( + expect.objectContaining({ + threatQuery: 'threat:*', + }) + ); + }); }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/update_prepacked_rules.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/update_prepacked_rules.ts index dcf43d41e8d78..e24a6a883b6df 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/update_prepacked_rules.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/update_prepacked_rules.ts @@ -124,6 +124,7 @@ export const createPromises = ( threshold, threat_filters: threatFilters, threat_index: threatIndex, + threat_indicator_path: threatIndicatorPath, threat_query: threatQuery, threat_mapping: threatMapping, threat_language: threatLanguage, @@ -195,6 +196,7 @@ export const createPromises = ( threshold, threatFilters, threatIndex, + threatIndicatorPath, threatQuery, threatMapping, threatLanguage, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/utils.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/utils.test.ts index 2cf7e95f3c621..448d1b1a1db63 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/utils.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/utils.test.ts @@ -75,6 +75,7 @@ describe('utils', () => { threshold: undefined, threatFilters: undefined, threatIndex: undefined, + threatIndicatorPath: undefined, threatQuery: undefined, threatMapping: undefined, threatLanguage: undefined, @@ -126,6 +127,7 @@ describe('utils', () => { threshold: undefined, threatFilters: undefined, threatIndex: undefined, + threatIndicatorPath: undefined, threatQuery: undefined, threatMapping: undefined, threatLanguage: undefined, @@ -177,6 +179,7 @@ describe('utils', () => { threshold: undefined, threatFilters: undefined, threatIndex: undefined, + threatIndicatorPath: undefined, threatQuery: undefined, threatMapping: undefined, threatLanguage: undefined, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/utils.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/utils.ts index ec25b45dd1597..4ab8afd796f6d 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/utils.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/utils.ts @@ -15,6 +15,7 @@ import type { ItemsPerSearchOrUndefined, ThreatFiltersOrUndefined, ThreatIndexOrUndefined, + ThreatIndicatorPathOrUndefined, ThreatLanguageOrUndefined, ThreatMappingOrUndefined, ThreatQueryOrUndefined, @@ -113,6 +114,7 @@ export interface UpdateProperties { threshold: ThresholdOrUndefined; threatFilters: ThreatFiltersOrUndefined; threatIndex: ThreatIndexOrUndefined; + threatIndicatorPath: ThreatIndicatorPathOrUndefined; threatQuery: ThreatQueryOrUndefined; threatMapping: ThreatMappingOrUndefined; threatLanguage: ThreatLanguageOrUndefined; diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 782c0c22d4d1f..e377ccaaf8522 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -1583,7 +1583,6 @@ "discover.advancedSettings.docTableHideTimeColumnTitle": "「時刻」列を非表示", "discover.advancedSettings.fieldsPopularLimitText": "最も頻繁に使用されるフィールドのトップNを表示します", "discover.advancedSettings.fieldsPopularLimitTitle": "頻繁に使用されるフィールドの制限", - "discover.advancedSettings.maxDocFieldsDisplayedText": "ドキュメント列でレンダリングされたフィールドの最大数", "discover.advancedSettings.maxDocFieldsDisplayedTitle": "表示される最大ドキュメントフィールド数", "discover.advancedSettings.sampleSizeText": "表に表示する行数です", "discover.advancedSettings.sampleSizeTitle": "行数", @@ -6241,13 +6240,9 @@ "xpack.apm.transactionDetails.tabs.failedTransactionsCorrelationsBetaDescription": "失敗したトランザクションの相関関係はGAではありません。不具合が発生したら報告してください。", "xpack.apm.transactionDetails.tabs.failedTransactionsCorrelationsBetaLabel": "ベータ", "xpack.apm.transactionDetails.tabs.failedTransactionsCorrelationsBetaTitle": "失敗したトランザクションの相関関係", - "xpack.apm.transactionDetails.tabs.failedTransactionsCorrelationsChartAllTransactions": "すべてのトランザクション", "xpack.apm.transactionDetails.tabs.failedTransactionsCorrelationsLabel": "失敗したトランザクションの相関関係", - "xpack.apm.transactionDetails.tabs.latencyCorrelationsChartAllTransactions": "すべてのトランザクション", - "xpack.apm.transactionDetails.tabs.latencyCorrelationsChartDescription": "{allTransactions}と{focusTransaction}の{br}重複する帯を使用した遅延(x)とトランザクション(y)の両対数プロット。", "xpack.apm.transactionDetails.tabs.latencyLabel": "遅延の相関関係", "xpack.apm.transactionDetails.tabs.traceSamplesLabel": "トレースのサンプル", - "xpack.apm.transactionDetails.tabs.transactionDistributionChartAllTransactions": "すべてのトランザクション", "xpack.apm.transactionDetails.traceNotFound": "選択されたトレースが見つかりません", "xpack.apm.transactionDetails.traceSampleTitle": "トレースのサンプル", "xpack.apm.transactionDetails.transactionLabel": "トランザクション", @@ -15025,8 +15020,6 @@ "xpack.maps.filterEditor.applyGlobalQueryCheckboxLabel": "グローバル検索をレイヤーデータに適用", "xpack.maps.filterEditor.applyGlobalTimeCheckboxLabel": "グローバル時刻をレイヤーデータに適用", "xpack.maps.filterEditor.applyGlobalTimeHelp": "有効にすると、結果がグローバル時刻で絞り込まれます", - "xpack.maps.fitToData.fitAriaLabel": "データバウンドに合わせる", - "xpack.maps.fitToData.fitButtonLabel": "データバウンドに合わせる", "xpack.maps.geoGrid.resolutionLabel": "グリッド解像度", "xpack.maps.geometryFilterForm.geometryLabelLabel": "ジオメトリラベル", "xpack.maps.geometryFilterForm.relationLabel": "空間関係", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 4f7d486dedd7f..6eb0d80250b5c 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -1595,7 +1595,6 @@ "discover.advancedSettings.docTableHideTimeColumnTitle": "隐藏“时间”列", "discover.advancedSettings.fieldsPopularLimitText": "要显示的排名前 N 最常见字段", "discover.advancedSettings.fieldsPopularLimitTitle": "常见字段限制", - "discover.advancedSettings.maxDocFieldsDisplayedText": "在文档列中渲染的最大字段数目", "discover.advancedSettings.maxDocFieldsDisplayedTitle": "显示的最大文档字段数", "discover.advancedSettings.sampleSizeText": "要在表中显示的行数目", "discover.advancedSettings.sampleSizeTitle": "行数目", @@ -6284,13 +6283,9 @@ "xpack.apm.transactionDetails.tabs.failedTransactionsCorrelationsBetaDescription": "失败事务相关性不是 GA 版。请通过报告错误来帮助我们。", "xpack.apm.transactionDetails.tabs.failedTransactionsCorrelationsBetaLabel": "公测版", "xpack.apm.transactionDetails.tabs.failedTransactionsCorrelationsBetaTitle": "失败事务相关性", - "xpack.apm.transactionDetails.tabs.failedTransactionsCorrelationsChartAllTransactions": "所有事务", "xpack.apm.transactionDetails.tabs.failedTransactionsCorrelationsLabel": "失败事务相关性", - "xpack.apm.transactionDetails.tabs.latencyCorrelationsChartAllTransactions": "所有事务", - "xpack.apm.transactionDetails.tabs.latencyCorrelationsChartDescription": "{allTransactions}和{focusTransaction}{br}带重叠的延迟 (x) 与事务 (y) 双对数坐标图。", "xpack.apm.transactionDetails.tabs.latencyLabel": "延迟相关性", "xpack.apm.transactionDetails.tabs.traceSamplesLabel": "跟踪样例", - "xpack.apm.transactionDetails.tabs.transactionDistributionChartAllTransactions": "所有事务", "xpack.apm.transactionDetails.traceNotFound": "找不到所选跟踪", "xpack.apm.transactionDetails.traceSampleTitle": "跟踪样例", "xpack.apm.transactionDetails.transactionLabel": "事务", @@ -15222,8 +15217,6 @@ "xpack.maps.filterEditor.applyGlobalQueryCheckboxLabel": "将全局搜索应用于图层数据", "xpack.maps.filterEditor.applyGlobalTimeCheckboxLabel": "将全局时间应用于图层数据", "xpack.maps.filterEditor.applyGlobalTimeHelp": "启用后,全局时间会缩减结果", - "xpack.maps.fitToData.fitAriaLabel": "适应数据边界", - "xpack.maps.fitToData.fitButtonLabel": "适应数据边界", "xpack.maps.geoGrid.resolutionLabel": "网格分辨率", "xpack.maps.geometryFilterForm.geometryLabelLabel": "几何标签", "xpack.maps.geometryFilterForm.relationLabel": "空间关系", diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/translations.ts b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/translations.ts index 8181a5171d198..293a5e79e29b2 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/translations.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/translations.ts @@ -99,6 +99,13 @@ export const ALERT_ERROR_TIMEOUT_REASON = i18n.translate( } ); +export const ALERT_ERROR_DISABLED_REASON = i18n.translate( + 'xpack.triggersActionsUI.sections.alertsList.alertErrorReasonDisabled', + { + defaultMessage: 'Rule failed to execute because rule ran after it was disabled.', + } +); + export const alertsErrorReasonTranslationsMapping = { read: ALERT_ERROR_READING_REASON, decrypt: ALERT_ERROR_DECRYPTING_REASON, @@ -106,4 +113,5 @@ export const alertsErrorReasonTranslationsMapping = { unknown: ALERT_ERROR_UNKNOWN_REASON, license: ALERT_ERROR_LICENSE_REASON, timeout: ALERT_ERROR_TIMEOUT_REASON, + disabled: ALERT_ERROR_DISABLED_REASON, }; diff --git a/x-pack/plugins/upgrade_assistant/server/lib/es_deprecations_status.ts b/x-pack/plugins/upgrade_assistant/server/lib/es_deprecations_status.ts index 2e2c80b790cd5..e334d214f2463 100644 --- a/x-pack/plugins/upgrade_assistant/server/lib/es_deprecations_status.ts +++ b/x-pack/plugins/upgrade_assistant/server/lib/es_deprecations_status.ts @@ -139,7 +139,7 @@ const getCorrectiveAction = ( ); const requiresReindexAction = /Index created before/.test(message); const requiresIndexSettingsAction = Boolean(indexSettingDeprecation); - const requiresMlAction = /model snapshot/.test(message); + const requiresMlAction = /[Mm]odel snapshot/.test(message); if (requiresReindexAction) { return { diff --git a/x-pack/plugins/upgrade_assistant/server/routes/ml_snapshots.ts b/x-pack/plugins/upgrade_assistant/server/routes/ml_snapshots.ts index fa6af0f5e4228..69c7c24b63123 100644 --- a/x-pack/plugins/upgrade_assistant/server/routes/ml_snapshots.ts +++ b/x-pack/plugins/upgrade_assistant/server/routes/ml_snapshots.ts @@ -63,7 +63,7 @@ const verifySnapshotUpgrade = async ( const { body: deprecations } = await esClient.asCurrentUser.migration.deprecations(); const mlSnapshotDeprecations = deprecations.ml_settings.filter((deprecation) => { - return /model snapshot/.test(deprecation.message); + return /[Mm]odel snapshot/.test(deprecation.message); }); // If there are no ML deprecations, we assume the deprecation was resolved successfully diff --git a/x-pack/plugins/upgrade_assistant/server/routes/status.test.ts b/x-pack/plugins/upgrade_assistant/server/routes/status.test.ts index e442d3b4fd11c..b11993e2baa5a 100644 --- a/x-pack/plugins/upgrade_assistant/server/routes/status.test.ts +++ b/x-pack/plugins/upgrade_assistant/server/routes/status.test.ts @@ -49,9 +49,8 @@ describe('Status API', () => { { level: 'critical', message: - 'model snapshot [1] for job [deprecation_check_job] needs to be deleted or upgraded', - details: - 'model snapshot [%s] for job [%s] supports minimum version [%s] and needs to be at least [%s]', + 'Model snapshot [1] for job [deprecation_check_job] has an obsolete minimum version [6.3.0].', + details: 'Delete model snapshot [1] or update it to 7.0.0 or greater.', url: 'doc_url', correctiveAction: { type: 'mlSnapshot', diff --git a/x-pack/plugins/uptime/common/config.ts b/x-pack/plugins/uptime/common/config.ts index ccd5e7b5a2cc6..8b70869645649 100644 --- a/x-pack/plugins/uptime/common/config.ts +++ b/x-pack/plugins/uptime/common/config.ts @@ -36,7 +36,7 @@ export const config: PluginConfigDescriptor = { username: schema.string(), password: schema.string(), manifestUrl: schema.string(), - hosts: schema.arrayOf(schema.string()), + hosts: schema.maybe(schema.arrayOf(schema.string())), }) ), }) diff --git a/x-pack/plugins/uptime/common/constants/rest_api.ts b/x-pack/plugins/uptime/common/constants/rest_api.ts index bef84c41796d9..9c8098390d129 100644 --- a/x-pack/plugins/uptime/common/constants/rest_api.ts +++ b/x-pack/plugins/uptime/common/constants/rest_api.ts @@ -37,5 +37,6 @@ export enum API_URLS { CONNECTOR_TYPES = '/api/actions/connector_types', // Service end points - INDEX_TEMPLATES = '/api/uptime/service/index_templates', + INDEX_TEMPLATES = '/internal/uptime/service/index_templates', + SYNTHETICS_MONITORS = '/internal/uptime/service/monitors', } diff --git a/x-pack/plugins/uptime/public/components/fleet_package/contexts/index.ts b/x-pack/plugins/uptime/public/components/fleet_package/contexts/index.ts index df2e9cfa6d4ea..37fdad9b195d4 100644 --- a/x-pack/plugins/uptime/public/components/fleet_package/contexts/index.ts +++ b/x-pack/plugins/uptime/public/components/fleet_package/contexts/index.ts @@ -4,6 +4,16 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ +import { DataStream, PolicyConfig } from '../types'; +import { initialValues as defaultHTTPSimpleFields } from './http_context'; +import { initialValues as defaultHTTPAdvancedFields } from './http_context_advanced'; +import { initialValues as defaultTCPSimpleFields } from './tcp_context'; +import { initialValues as defaultICMPSimpleFields } from './icmp_context'; +import { initialValues as defaultTCPAdvancedFields } from './tcp_context_advanced'; +import { initialValues as defaultBrowserSimpleFields } from './browser_context'; +import { initialValues as defaultBrowserAdvancedFields } from './browser_context_advanced'; +import { initialValues as defaultTLSFields } from './tls_fields_context'; + export { PolicyConfigContext, PolicyConfigContextProvider, @@ -61,3 +71,23 @@ export { export { HTTPContextProvider } from './http_provider'; export { TCPContextProvider } from './tcp_provider'; export { BrowserContextProvider } from './browser_provider'; +export { SyntheticsProviders } from './synthetics_context_providers'; + +export const defaultConfig: PolicyConfig = { + [DataStream.HTTP]: { + ...defaultHTTPSimpleFields, + ...defaultHTTPAdvancedFields, + ...defaultTLSFields, + }, + [DataStream.TCP]: { + ...defaultTCPSimpleFields, + ...defaultTCPAdvancedFields, + ...defaultTLSFields, + }, + [DataStream.ICMP]: defaultICMPSimpleFields, + [DataStream.BROWSER]: { + ...defaultBrowserSimpleFields, + ...defaultBrowserAdvancedFields, + ...defaultTLSFields, + }, +}; diff --git a/x-pack/plugins/uptime/public/components/fleet_package/contexts/policy_config_context.tsx b/x-pack/plugins/uptime/public/components/fleet_package/contexts/policy_config_context.tsx index 69c0e1d7ba4fe..ed183e1f81357 100644 --- a/x-pack/plugins/uptime/public/components/fleet_package/contexts/policy_config_context.tsx +++ b/x-pack/plugins/uptime/public/components/fleet_package/contexts/policy_config_context.tsx @@ -10,6 +10,8 @@ import { DataStream } from '../types'; interface IPolicyConfigContext { setMonitorType: React.Dispatch>; + setName: React.Dispatch>; + setLocations: React.Dispatch>; setIsTLSEnabled: React.Dispatch>; setIsZipUrlTLSEnabled: React.Dispatch>; monitorType: DataStream; @@ -19,6 +21,10 @@ interface IPolicyConfigContext { defaultIsTLSEnabled?: boolean; defaultIsZipUrlTLSEnabled?: boolean; isEditable?: boolean; + defaultName?: string; + name?: string; + defaultLocations?: string[]; + locations?: string[]; } interface IPolicyConfigContextProvider { @@ -26,6 +32,8 @@ interface IPolicyConfigContextProvider { defaultMonitorType?: DataStream; defaultIsTLSEnabled?: boolean; defaultIsZipUrlTLSEnabled?: boolean; + defaultName?: string; + defaultLocations?: string[]; isEditable?: boolean; } @@ -35,6 +43,12 @@ 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'); }, @@ -47,19 +61,25 @@ const defaultContext: IPolicyConfigContext = { defaultMonitorType: initialValue, // immutable, defaultIsTLSEnabled: false, defaultIsZipUrlTLSEnabled: false, + defaultName: '', + defaultLocations: [], isEditable: false, }; export const PolicyConfigContext = createContext(defaultContext); -export const PolicyConfigContextProvider = ({ +export function PolicyConfigContextProvider({ children, defaultMonitorType = initialValue, defaultIsTLSEnabled = false, defaultIsZipUrlTLSEnabled = false, + defaultName = '', + defaultLocations = [], isEditable = false, -}: IPolicyConfigContextProvider) => { +}: IPolicyConfigContextProvider) { const [monitorType, setMonitorType] = useState(defaultMonitorType); + const [name, setName] = useState(defaultName); + const [locations, setLocations] = useState(defaultLocations); const [isTLSEnabled, setIsTLSEnabled] = useState(defaultIsTLSEnabled); const [isZipUrlTLSEnabled, setIsZipUrlTLSEnabled] = useState(defaultIsZipUrlTLSEnabled); @@ -75,6 +95,12 @@ export const PolicyConfigContextProvider = ({ defaultIsTLSEnabled, defaultIsZipUrlTLSEnabled, isEditable, + defaultName, + name, + setName, + defaultLocations, + locations, + setLocations, }; }, [ monitorType, @@ -84,9 +110,15 @@ export const PolicyConfigContextProvider = ({ defaultIsTLSEnabled, defaultIsZipUrlTLSEnabled, isEditable, + name, + defaultName, + locations, + defaultLocations, ]); return ; -}; +} -export const usePolicyConfigContext = () => useContext(PolicyConfigContext); +export function usePolicyConfigContext() { + return useContext(PolicyConfigContext); +} diff --git a/x-pack/plugins/uptime/public/components/fleet_package/contexts/synthetics_context_providers.tsx b/x-pack/plugins/uptime/public/components/fleet_package/contexts/synthetics_context_providers.tsx new file mode 100644 index 0000000000000..0d730c5f96e97 --- /dev/null +++ b/x-pack/plugins/uptime/public/components/fleet_package/contexts/synthetics_context_providers.tsx @@ -0,0 +1,60 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license 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, ITLSFields, DataStream } from '../types'; +import { + PolicyConfigContextProvider, + TCPContextProvider, + ICMPSimpleFieldsContextProvider, + HTTPContextProvider, + BrowserContextProvider, + TLSFieldsContextProvider, +} from '.'; + +interface Props { + children: React.ReactNode; + httpDefaultValues?: HTTPFields; + tcpDefaultValues?: TCPFields; + icmpDefaultValues?: ICMPFields; + browserDefaultValues?: BrowserFields; + tlsDefaultValues?: ITLSFields; + policyDefaultValues?: { + defaultMonitorType: DataStream; + defaultIsTLSEnabled: boolean; + defaultIsZipUrlTLSEnabled: boolean; + defaultName?: string; + defaultLocations?: string[]; + isEditable: boolean; + }; +} + +export const SyntheticsProviders = ({ + children, + httpDefaultValues, + tcpDefaultValues, + icmpDefaultValues, + browserDefaultValues, + tlsDefaultValues, + policyDefaultValues, +}: Props) => { + return ( + + + + + + + {children} + + + + + + + ); +}; diff --git a/x-pack/plugins/uptime/public/components/fleet_package/custom_fields.tsx b/x-pack/plugins/uptime/public/components/fleet_package/custom_fields.tsx index f8754307e0820..1952d50cdd92a 100644 --- a/x-pack/plugins/uptime/public/components/fleet_package/custom_fields.tsx +++ b/x-pack/plugins/uptime/public/components/fleet_package/custom_fields.tsx @@ -33,6 +33,7 @@ import { BrowserAdvancedFields } from './browser/advanced_fields'; interface Props { validate: Validation; dataStreams?: DataStream[]; + children?: React.ReactNode; } const dataStreamToString = [ @@ -50,7 +51,7 @@ const dataStreamToString = [ }, ]; -export const CustomFields = memo(({ validate, dataStreams = [] }) => { +export const CustomFields = memo(({ validate, dataStreams = [], children }) => { const { monitorType, setMonitorType, isTLSEnabled, setIsTLSEnabled, isEditable } = usePolicyConfigContext(); @@ -98,6 +99,7 @@ export const CustomFields = memo(({ validate, dataStreams = [] }) => { > + {children} {!isEditable && ( { - const { isTLSEnabled, isZipUrlTLSEnabled } = usePolicyConfigContext(); +export const usePolicy = (fleetPolicyName: string = '') => { + const { + isTLSEnabled, + isZipUrlTLSEnabled, + name: monitorName, // the monitor name can come from two different places, either from fleet or from uptime + } = usePolicyConfigContext(); const { fields: httpSimpleFields } = useHTTPSimpleFieldsContext(); const { fields: tcpSimpleFields } = useTCPSimpleFieldsContext(); const { fields: icmpSimpleFields } = useICMPSimpleFieldsContext(); @@ -77,6 +77,7 @@ export const usePolicy = (name: string) => { [isTLSEnabled, isZipUrlTLSEnabled] ); + /* TODO add locations to policy config for synthetics service */ const policyConfig: PolicyConfig = useMemo( () => ({ [DataStream.HTTP]: { @@ -87,7 +88,7 @@ export const usePolicy = (name: string) => { ...httpSimpleFields[ConfigKeys.METADATA], ...metadata, }, - [ConfigKeys.NAME]: name, + [ConfigKeys.NAME]: fleetPolicyName || monitorName, } as HTTPFields, [DataStream.TCP]: { ...tcpSimpleFields, @@ -97,11 +98,11 @@ export const usePolicy = (name: string) => { ...tcpSimpleFields[ConfigKeys.METADATA], ...metadata, }, - [ConfigKeys.NAME]: name, + [ConfigKeys.NAME]: fleetPolicyName || monitorName, } as TCPFields, [DataStream.ICMP]: { ...icmpSimpleFields, - [ConfigKeys.NAME]: name, + [ConfigKeys.NAME]: fleetPolicyName || monitorName, } as ICMPFields, [DataStream.BROWSER]: { ...browserSimpleFields, @@ -110,7 +111,7 @@ export const usePolicy = (name: string) => { ...browserSimpleFields[ConfigKeys.METADATA], ...metadata, }, - [ConfigKeys.NAME]: name, + [ConfigKeys.NAME]: fleetPolicyName || monitorName, } as BrowserFields, }), [ @@ -123,7 +124,8 @@ export const usePolicy = (name: string) => { browserSimpleFields, browserAdvancedFields, tlsFields, - name, + fleetPolicyName, + monitorName, ] ); diff --git a/x-pack/plugins/uptime/public/components/fleet_package/synthetics_policy_create_extension_wrapper.tsx b/x-pack/plugins/uptime/public/components/fleet_package/synthetics_policy_create_extension_wrapper.tsx index c3b2b632850be..e3e13b16ebf22 100644 --- a/x-pack/plugins/uptime/public/components/fleet_package/synthetics_policy_create_extension_wrapper.tsx +++ b/x-pack/plugins/uptime/public/components/fleet_package/synthetics_policy_create_extension_wrapper.tsx @@ -8,14 +8,7 @@ import React, { memo } from 'react'; import { PackagePolicyCreateExtensionComponentProps } from '../../../../fleet/public'; import { SyntheticsPolicyCreateExtension } from './synthetics_policy_create_extension'; -import { - PolicyConfigContextProvider, - TCPContextProvider, - ICMPSimpleFieldsContextProvider, - HTTPContextProvider, - BrowserContextProvider, - TLSFieldsContextProvider, -} from './contexts'; +import { SyntheticsProviders } from './contexts'; /** * Exports Synthetics-specific package policy instructions @@ -24,19 +17,9 @@ import { export const SyntheticsPolicyCreateExtensionWrapper = memo(({ newPolicy, onChange }) => { return ( - - - - - - - - - - - - - + + + ); }); SyntheticsPolicyCreateExtensionWrapper.displayName = 'SyntheticsPolicyCreateExtensionWrapper'; diff --git a/x-pack/plugins/uptime/public/components/monitor/ml/ml_flyout.test.tsx b/x-pack/plugins/uptime/public/components/monitor/ml/ml_flyout.test.tsx index c2c4baf0751c4..8669bc180f42f 100644 --- a/x-pack/plugins/uptime/public/components/monitor/ml/ml_flyout.test.tsx +++ b/x-pack/plugins/uptime/public/components/monitor/ml/ml_flyout.test.tsx @@ -10,13 +10,23 @@ import { MLFlyoutView } from './ml_flyout'; import { UptimeSettingsContext } from '../../../contexts'; import { CLIENT_DEFAULTS } from '../../../../common/constants'; import * as redux from 'react-redux'; -import { render } from '../../../lib/helper/rtl_helpers'; +import { render, forNearestButton } from '../../../lib/helper/rtl_helpers'; import * as labels from './translations'; describe('ML Flyout component', () => { const createJob = () => {}; const onClose = () => {}; const { DATE_RANGE_START, DATE_RANGE_END } = CLIENT_DEFAULTS; + const defaultContextValue = { + isDevMode: true, + basePath: '', + dateRangeStart: DATE_RANGE_START, + dateRangeEnd: DATE_RANGE_END, + isApmAvailable: true, + isInfraAvailable: true, + isLogsAvailable: true, + config: {}, + }; beforeEach(() => { const spy = jest.spyOn(redux, 'useDispatch'); @@ -32,16 +42,8 @@ describe('ML Flyout component', () => { // return false value for no license spy1.mockReturnValue(false); - const value = { - isDevMode: true, - basePath: '', - dateRangeStart: DATE_RANGE_START, - dateRangeEnd: DATE_RANGE_END, - isApmAvailable: true, - isInfraAvailable: true, - isLogsAvailable: true, - config: {}, - }; + const value = { ...defaultContextValue }; + const { findByText, findAllByText } = render( { }); it('able to create job if valid license is available', async () => { - const value = { - isDevMode: true, - basePath: '', - dateRangeStart: DATE_RANGE_START, - dateRangeEnd: DATE_RANGE_END, - isApmAvailable: true, - isInfraAvailable: true, - isLogsAvailable: true, - config: {}, - }; + const value = { ...defaultContextValue }; + const { queryByText } = render( { expect(queryByText(labels.START_TRAIL)).not.toBeInTheDocument(); }); + + describe("when users don't have Machine Learning privileges", () => { + it('shows an informative callout about the need for ML privileges', async () => { + const value = { ...defaultContextValue }; + + const { queryByText } = render( + + + + ); + + expect( + queryByText('You must have the Kibana privileges for Machine Learning to use this feature.') + ).toBeInTheDocument(); + }); + + it('disables the job creation button', async () => { + const value = { ...defaultContextValue }; + + const { queryByText } = render( + + + + ); + + expect(forNearestButton(queryByText)(labels.CREATE_NEW_JOB)).toBeDisabled(); + }); + }); }); diff --git a/x-pack/plugins/uptime/public/components/monitor/ml/ml_flyout.tsx b/x-pack/plugins/uptime/public/components/monitor/ml/ml_flyout.tsx index 5500324e4bdd8..c367b60a65016 100644 --- a/x-pack/plugins/uptime/public/components/monitor/ml/ml_flyout.tsx +++ b/x-pack/plugins/uptime/public/components/monitor/ml/ml_flyout.tsx @@ -19,6 +19,7 @@ import { EuiSpacer, EuiText, EuiTitle, + EuiCallOut, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; import { useSelector } from 'react-redux'; @@ -69,6 +70,20 @@ export function MLFlyoutView({ isCreatingJob, onClickCreate, onClose, canCreateM

+ {!canCreateMLJob && ( + +

+ +

+
+ )} diff --git a/x-pack/plugins/uptime/public/components/monitor/ml/translations.tsx b/x-pack/plugins/uptime/public/components/monitor/ml/translations.tsx index 1fc4093a67d83..86ca94d5b6499 100644 --- a/x-pack/plugins/uptime/public/components/monitor/ml/translations.tsx +++ b/x-pack/plugins/uptime/public/components/monitor/ml/translations.tsx @@ -179,3 +179,10 @@ export const ENABLE_MANAGE_JOB = i18n.translate( 'You can enable anomaly detection job or if job is already there you can manage the job or alert.', } ); + +export const ADD_JOB_PERMISSIONS_NEEDED = i18n.translate( + 'xpack.uptime.ml.enableAnomalyDetectionPanel.add_job_permissions_needed', + { + defaultMessage: 'Permissions needed', + } +); diff --git a/x-pack/plugins/uptime/public/components/monitor_management/formatters/browser.ts b/x-pack/plugins/uptime/public/components/monitor_management/formatters/browser.ts new file mode 100644 index 0000000000000..e4a84e2bb85b5 --- /dev/null +++ b/x-pack/plugins/uptime/public/components/monitor_management/formatters/browser.ts @@ -0,0 +1,35 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { BrowserFields, ConfigKeys } from '../../fleet_package/types'; +import { Formatter, commonFormatters, objectFormatter, arrayFormatter } from './common'; + +export type BrowserFormatMap = Record; + +export const browserFormatters: BrowserFormatMap = { + [ConfigKeys.METADATA]: (fields) => objectFormatter(fields[ConfigKeys.METADATA]), + [ConfigKeys.SOURCE_ZIP_URL]: null, + [ConfigKeys.SOURCE_ZIP_USERNAME]: null, + [ConfigKeys.SOURCE_ZIP_PASSWORD]: null, + [ConfigKeys.SOURCE_ZIP_FOLDER]: null, + [ConfigKeys.SOURCE_ZIP_PROXY_URL]: null, + [ConfigKeys.SOURCE_INLINE]: null, + [ConfigKeys.PARAMS]: null, + [ConfigKeys.SCREENSHOTS]: null, + [ConfigKeys.SYNTHETICS_ARGS]: (fields) => null, + [ConfigKeys.ZIP_URL_TLS_CERTIFICATE_AUTHORITIES]: null, + [ConfigKeys.ZIP_URL_TLS_CERTIFICATE]: null, + [ConfigKeys.ZIP_URL_TLS_KEY]: null, + [ConfigKeys.ZIP_URL_TLS_KEY_PASSPHRASE]: null, + [ConfigKeys.ZIP_URL_TLS_VERIFICATION_MODE]: null, + [ConfigKeys.ZIP_URL_TLS_VERSION]: (fields) => + arrayFormatter(fields[ConfigKeys.ZIP_URL_TLS_VERSION]), + [ConfigKeys.JOURNEY_FILTERS_MATCH]: null, + [ConfigKeys.JOURNEY_FILTERS_TAGS]: null, + [ConfigKeys.IGNORE_HTTPS_ERRORS]: null, + ...commonFormatters, +}; diff --git a/x-pack/plugins/uptime/public/components/monitor_management/formatters/common.ts b/x-pack/plugins/uptime/public/components/monitor_management/formatters/common.ts new file mode 100644 index 0000000000000..92d2b28f1283d --- /dev/null +++ b/x-pack/plugins/uptime/public/components/monitor_management/formatters/common.ts @@ -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 { ICommonFields, ICustomFields, ConfigKeys } from '../../fleet_package/types'; + +export type Formatter = + | null + | ((fields: Partial) => string | string[] | Record | null); + +export type CommonFormatMap = Record; + +export const commonFormatters: CommonFormatMap = { + [ConfigKeys.NAME]: null, + [ConfigKeys.MONITOR_TYPE]: null, + [ConfigKeys.SCHEDULE]: (fields) => + `@every ${fields[ConfigKeys.SCHEDULE]?.number}${fields[ConfigKeys.SCHEDULE]?.unit}`, + [ConfigKeys.APM_SERVICE_NAME]: null, + [ConfigKeys.TAGS]: null, + [ConfigKeys.TIMEOUT]: (fields) => secondsToCronFormatter(fields[ConfigKeys.TIMEOUT]), +}; + +export const arrayFormatter = (value: string[] = []) => (value.length ? value : null); + +export const secondsToCronFormatter = (value: string = '') => (value ? `${value}s` : null); + +export const objectFormatter = (value: Record = {}) => + Object.keys(value).length ? value : null; diff --git a/x-pack/plugins/uptime/public/components/monitor_management/formatters/http.ts b/x-pack/plugins/uptime/public/components/monitor_management/formatters/http.ts new file mode 100644 index 0000000000000..41e162cff2d05 --- /dev/null +++ b/x-pack/plugins/uptime/public/components/monitor_management/formatters/http.ts @@ -0,0 +1,37 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license 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, ConfigKeys } from '../../fleet_package/types'; +import { Formatter, commonFormatters, objectFormatter, arrayFormatter } from './common'; +import { tlsFormatters } from './tls'; + +export type HTTPFormatMap = Record; + +export const httpFormatters: HTTPFormatMap = { + [ConfigKeys.METADATA]: (fields) => objectFormatter(fields[ConfigKeys.METADATA]), + [ConfigKeys.URLS]: null, + [ConfigKeys.MAX_REDIRECTS]: null, + [ConfigKeys.USERNAME]: null, + [ConfigKeys.PASSWORD]: null, + [ConfigKeys.PROXY_URL]: null, + [ConfigKeys.RESPONSE_BODY_CHECK_NEGATIVE]: (fields) => + arrayFormatter(fields[ConfigKeys.RESPONSE_BODY_CHECK_NEGATIVE]), + [ConfigKeys.RESPONSE_BODY_CHECK_POSITIVE]: (fields) => + arrayFormatter(fields[ConfigKeys.RESPONSE_BODY_CHECK_POSITIVE]), + [ConfigKeys.RESPONSE_BODY_INDEX]: null, + [ConfigKeys.RESPONSE_HEADERS_CHECK]: (fields) => + objectFormatter(fields[ConfigKeys.RESPONSE_HEADERS_CHECK]), + [ConfigKeys.RESPONSE_HEADERS_INDEX]: null, + [ConfigKeys.RESPONSE_STATUS_CHECK]: (fields) => + arrayFormatter(fields[ConfigKeys.RESPONSE_STATUS_CHECK]), + [ConfigKeys.REQUEST_BODY_CHECK]: (fields) => fields[ConfigKeys.REQUEST_BODY_CHECK]?.value || null, + [ConfigKeys.REQUEST_HEADERS_CHECK]: (fields) => + objectFormatter(fields[ConfigKeys.REQUEST_HEADERS_CHECK]), + [ConfigKeys.REQUEST_METHOD_CHECK]: null, + ...tlsFormatters, + ...commonFormatters, +}; diff --git a/x-pack/plugins/uptime/public/components/monitor_management/formatters/icmp.ts b/x-pack/plugins/uptime/public/components/monitor_management/formatters/icmp.ts new file mode 100644 index 0000000000000..841f986309482 --- /dev/null +++ b/x-pack/plugins/uptime/public/components/monitor_management/formatters/icmp.ts @@ -0,0 +1,17 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { ICMPFields, ConfigKeys } from '../../fleet_package/types'; +import { Formatter, commonFormatters, secondsToCronFormatter } from './common'; + +export type ICMPFormatMap = Record; + +export const icmpFormatters: ICMPFormatMap = { + [ConfigKeys.HOSTS]: null, + [ConfigKeys.WAIT]: (fields) => secondsToCronFormatter(fields[ConfigKeys.WAIT]), + ...commonFormatters, +}; diff --git a/x-pack/plugins/uptime/public/components/monitor_management/formatters/index.ts b/x-pack/plugins/uptime/public/components/monitor_management/formatters/index.ts new file mode 100644 index 0000000000000..56dac35596d45 --- /dev/null +++ b/x-pack/plugins/uptime/public/components/monitor_management/formatters/index.ts @@ -0,0 +1,38 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { DataStream } from '../../fleet_package/types'; + +import { httpFormatters, HTTPFormatMap } from './http'; +import { tcpFormatters, TCPFormatMap } from './tcp'; +import { icmpFormatters, ICMPFormatMap } from './icmp'; +import { browserFormatters, BrowserFormatMap } from './browser'; +import { commonFormatters, CommonFormatMap } from './common'; + +type Formatters = HTTPFormatMap & TCPFormatMap & ICMPFormatMap & BrowserFormatMap & CommonFormatMap; + +interface FormatterMap { + [DataStream.HTTP]: HTTPFormatMap; + [DataStream.ICMP]: ICMPFormatMap; + [DataStream.TCP]: TCPFormatMap; + [DataStream.BROWSER]: BrowserFormatMap; +} + +export const formattersMap: FormatterMap = { + [DataStream.HTTP]: httpFormatters, + [DataStream.ICMP]: icmpFormatters, + [DataStream.TCP]: tcpFormatters, + [DataStream.BROWSER]: browserFormatters, +}; + +export const formatters: Formatters = { + ...httpFormatters, + ...icmpFormatters, + ...tcpFormatters, + ...browserFormatters, + ...commonFormatters, +}; diff --git a/x-pack/plugins/uptime/public/components/monitor_management/formatters/tcp.ts b/x-pack/plugins/uptime/public/components/monitor_management/formatters/tcp.ts new file mode 100644 index 0000000000000..cdf77307a6938 --- /dev/null +++ b/x-pack/plugins/uptime/public/components/monitor_management/formatters/tcp.ts @@ -0,0 +1,23 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { TCPFields, ConfigKeys } from '../../fleet_package/types'; +import { Formatter, commonFormatters } from './common'; +import { tlsFormatters } from './tls'; + +export type TCPFormatMap = Record; + +export const tcpFormatters: TCPFormatMap = { + [ConfigKeys.METADATA]: null, + [ConfigKeys.HOSTS]: null, + [ConfigKeys.PROXY_URL]: null, + [ConfigKeys.PROXY_USE_LOCAL_RESOLVER]: null, + [ConfigKeys.RESPONSE_RECEIVE_CHECK]: null, + [ConfigKeys.REQUEST_SEND_CHECK]: null, + ...tlsFormatters, + ...commonFormatters, +}; diff --git a/x-pack/plugins/uptime/public/components/monitor_management/formatters/tls.ts b/x-pack/plugins/uptime/public/components/monitor_management/formatters/tls.ts new file mode 100644 index 0000000000000..5f7ce27637f3e --- /dev/null +++ b/x-pack/plugins/uptime/public/components/monitor_management/formatters/tls.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 { ITLSFields, ConfigKeys } from '../../fleet_package/types'; +import { arrayFormatter, Formatter } from './common'; + +type TLSFormatMap = Record; + +export const tlsFormatters: TLSFormatMap = { + [ConfigKeys.TLS_CERTIFICATE_AUTHORITIES]: null, + [ConfigKeys.TLS_CERTIFICATE]: null, + [ConfigKeys.TLS_KEY]: null, + [ConfigKeys.TLS_KEY_PASSPHRASE]: null, + [ConfigKeys.TLS_VERIFICATION_MODE]: null, + [ConfigKeys.TLS_VERSION]: (fields) => arrayFormatter(fields[ConfigKeys.TLS_VERSION]), +}; diff --git a/x-pack/plugins/uptime/public/components/monitor_management/hooks/use_format_monitor.ts b/x-pack/plugins/uptime/public/components/monitor_management/hooks/use_format_monitor.ts new file mode 100644 index 0000000000000..9027aa4520418 --- /dev/null +++ b/x-pack/plugins/uptime/public/components/monitor_management/hooks/use_format_monitor.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. + */ +import { useEffect, useRef, useState } from 'react'; +import { omitBy, isNil } from 'lodash'; +import { ConfigKeys, DataStream, Validation, ICustomFields } from '../../fleet_package/types'; +import { formatters } from '../formatters'; + +interface Props { + monitorType: DataStream; + defaultConfig: Partial; + config: Partial; + validate: Record; +} + +const formatMonitorConfig = (configKeys: ConfigKeys[], config: Partial) => { + const formattedMonitor = {} as Record; + + configKeys.forEach((key) => { + const value = config[key] ?? null; + if (value && formatters[key]) { + formattedMonitor[key] = formatters[key]?.(config); + } else if (value) { + formattedMonitor[key] = value; + } + }); + + return omitBy(formattedMonitor, isNil) as Partial; +}; + +export const useFormatMonitor = ({ monitorType, defaultConfig, config, validate }: Props) => { + const [formattedMonitor, setFormattedMonitor] = useState>( + formatMonitorConfig(Object.keys(config) as ConfigKeys[], config) + ); + const [isValid, setIsValid] = useState(false); + const currentConfig = useRef>(defaultConfig); + + useEffect(() => { + const configKeys = Object.keys(config) as ConfigKeys[]; + const validationKeys = Object.keys(validate[monitorType]) as ConfigKeys[]; + 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) { + const formattedMonitorT = formatMonitorConfig(configKeys, config); + currentConfig.current = config; + setFormattedMonitor(formattedMonitorT); + setIsValid(isValidT); + } + }, [config, currentConfig, validate, monitorType]); + + return { + config, + isValid, + formattedMonitor, + }; +}; diff --git a/x-pack/plugins/uptime/public/components/monitor_management/monitor_config.tsx b/x-pack/plugins/uptime/public/components/monitor_management/monitor_config.tsx new file mode 100644 index 0000000000000..081ceee1fe63c --- /dev/null +++ b/x-pack/plugins/uptime/public/components/monitor_management/monitor_config.tsx @@ -0,0 +1,37 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license 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 { defaultConfig, usePolicyConfigContext } from '../fleet_package/contexts'; + +import { usePolicy } from '../fleet_package/hooks/use_policy'; +import { validate } from '../fleet_package/validation'; +import { MonitorFields } from './monitor_fields'; +import { useFormatMonitor } from './hooks/use_format_monitor'; + +export const MonitorConfig = () => { + const { monitorType } = usePolicyConfigContext(); + /* TODO - Use Effect to make sure the package/index templates are loaded. Wait for it to load before showing view + * then show error message if it fails */ + + /* 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 */ + useFormatMonitor({ + monitorType, + validate, + config: policyConfig[monitorType], + defaultConfig: defaultConfig[monitorType], + }); + + return ; +}; diff --git a/x-pack/plugins/uptime/public/components/monitor_management/monitor_fields.tsx b/x-pack/plugins/uptime/public/components/monitor_management/monitor_fields.tsx new file mode 100644 index 0000000000000..30659e6c2cf40 --- /dev/null +++ b/x-pack/plugins/uptime/public/components/monitor_management/monitor_fields.tsx @@ -0,0 +1,29 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license 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 { EuiForm } from '@elastic/eui'; +import { DataStream } from '../fleet_package/types'; +import { usePolicyConfigContext } from '../fleet_package/contexts'; + +import { CustomFields } from '../fleet_package/custom_fields'; +import { validate } from '../fleet_package/validation'; +import { MonitorNameAndLocation } from './monitor_name_location'; + +export const MonitorFields = () => { + const { monitorType } = usePolicyConfigContext(); + return ( + + + + + + ); +}; diff --git a/x-pack/plugins/uptime/public/components/monitor_management/monitor_name_location.tsx b/x-pack/plugins/uptime/public/components/monitor_management/monitor_name_location.tsx new file mode 100644 index 0000000000000..6ee3eacd81cd8 --- /dev/null +++ b/x-pack/plugins/uptime/public/components/monitor_management/monitor_name_location.tsx @@ -0,0 +1,37 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license 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 { EuiFormRow, EuiFieldText } from '@elastic/eui'; +import { usePolicyConfigContext } from '../fleet_package/contexts'; + +export const MonitorNameAndLocation = () => { + const { name, setName } = usePolicyConfigContext(); + return ( + <> + + setName(event.target.value)} + /> + + {/* TODO: connect locations */} + {/* + setLocations(selOptions.map(({ value }) => value as string))} + /> + */} + + ); +}; diff --git a/x-pack/plugins/uptime/public/lib/helper/rtl_helpers.tsx b/x-pack/plugins/uptime/public/lib/helper/rtl_helpers.tsx index fe7fd0918450b..84d2566304336 100644 --- a/x-pack/plugins/uptime/public/lib/helper/rtl_helpers.tsx +++ b/x-pack/plugins/uptime/public/lib/helper/rtl_helpers.tsx @@ -8,7 +8,12 @@ import React, { ReactElement } from 'react'; import { of } from 'rxjs'; // eslint-disable-next-line import/no-extraneous-dependencies -import { render as reactTestLibRender, RenderOptions } from '@testing-library/react'; +import { + render as reactTestLibRender, + MatcherFunction, + RenderOptions, + Nullish, +} from '@testing-library/react'; import { Router } from 'react-router-dom'; import { createMemoryHistory, History } from 'history'; import { CoreStart } from 'kibana/public'; @@ -209,3 +214,18 @@ const getHistoryFromUrl = (url: Url) => { initialEntries: [url.path + stringifyUrlParams(url.queryParams)], }); }; + +// This function allows us to query for the nearest button with test +// no matter whether it has nested tags or not (as EuiButton elements do). +export const forNearestButton = + (getByText: (f: MatcherFunction) => HTMLElement | null) => + (text: string): HTMLElement | null => + getByText((_content: string, node: Nullish) => { + if (!node) return false; + const noOtherButtonHasText = Array.from(node.children).every( + (child) => child && (child.textContent !== text || child.tagName.toLowerCase() !== 'button') + ); + return ( + noOtherButtonHasText && node.textContent === text && node.tagName.toLowerCase() === 'button' + ); + }); diff --git a/x-pack/plugins/uptime/public/pages/add_monitor.tsx b/x-pack/plugins/uptime/public/pages/add_monitor.tsx index 10e3d9d6ce29a..a5f925e4712af 100644 --- a/x-pack/plugins/uptime/public/pages/add_monitor.tsx +++ b/x-pack/plugins/uptime/public/pages/add_monitor.tsx @@ -6,7 +6,17 @@ */ import React from 'react'; +import { useTrackPageview } from '../../../observability/public'; +import { SyntheticsProviders } from '../components/fleet_package/contexts'; +import { MonitorConfig } from '../components/monitor_management/monitor_config'; export const AddMonitorPage: React.FC = () => { - return null; + useTrackPageview({ app: 'uptime', path: 'add-monitor' }); + useTrackPageview({ app: 'uptime', path: 'add-monitor', delay: 15000 }); + + return ( + + + + ); }; diff --git a/x-pack/plugins/uptime/public/pages/edit_monitor.tsx b/x-pack/plugins/uptime/public/pages/edit_monitor.tsx index 3be1bc7b35d8d..965639bd12145 100644 --- a/x-pack/plugins/uptime/public/pages/edit_monitor.tsx +++ b/x-pack/plugins/uptime/public/pages/edit_monitor.tsx @@ -5,8 +5,101 @@ * 2.0. */ -import React from 'react'; +import React, { useMemo } from 'react'; +import { + ConfigKeys, + ICustomFields, + ITLSFields, + PolicyConfig, + DataStream, +} from '../components/fleet_package/types'; +import { useTrackPageview } from '../../../observability/public'; +import { SyntheticsProviders } from '../components/fleet_package/contexts'; +import { MonitorConfig } from '../components/monitor_management/monitor_config'; export const EditMonitorPage: React.FC = () => { - return null; + useTrackPageview({ app: 'uptime', path: 'edit-monitor' }); + useTrackPageview({ app: 'uptime', path: 'edit-monitor', delay: 15000 }); + + const { + enableTLS: isTLSEnabled, + enableZipUrlTLS: isZipUrlTLSEnabled, + fullConfig: fullDefaultConfig, + monitorTypeConfig: defaultConfig, + monitorType, + tlsConfig: defaultTLSConfig, + } = useMemo(() => { + /* TODO: fetch current monitor to be edited from saved objects based on url param */ + const monitor = {} as Record; // fetch + + let enableTLS = false; + let enableZipUrlTLS = false; + const getDefaultConfig = () => { + const type: DataStream = monitor[ConfigKeys.MONITOR_TYPE] as DataStream; + + const configKeys: ConfigKeys[] = Object.values(ConfigKeys) || ([] as ConfigKeys[]); + const formattedDefaultConfigForMonitorType: ICustomFields = configKeys.reduce( + (acc: ICustomFields, key: ConfigKeys) => { + return { + ...acc, + key, + }; + }, + {} as ICustomFields + ); + + const tlsConfig: ITLSFields = { + [ConfigKeys.TLS_CERTIFICATE_AUTHORITIES]: + formattedDefaultConfigForMonitorType[ConfigKeys.TLS_CERTIFICATE_AUTHORITIES], + [ConfigKeys.TLS_CERTIFICATE]: + formattedDefaultConfigForMonitorType[ConfigKeys.TLS_CERTIFICATE], + [ConfigKeys.TLS_KEY]: formattedDefaultConfigForMonitorType[ConfigKeys.TLS_KEY], + [ConfigKeys.TLS_KEY_PASSPHRASE]: + formattedDefaultConfigForMonitorType[ConfigKeys.TLS_KEY_PASSPHRASE], + [ConfigKeys.TLS_VERIFICATION_MODE]: + formattedDefaultConfigForMonitorType[ConfigKeys.TLS_VERIFICATION_MODE], + [ConfigKeys.TLS_VERSION]: formattedDefaultConfigForMonitorType[ConfigKeys.TLS_VERSION], + }; + + enableTLS = Boolean(formattedDefaultConfigForMonitorType[ConfigKeys.TLS_VERIFICATION_MODE]); + enableZipUrlTLS = Boolean( + formattedDefaultConfigForMonitorType[ConfigKeys.ZIP_URL_TLS_VERIFICATION_MODE] + ); + + const formattedDefaultConfig: Partial = { + [type]: formattedDefaultConfigForMonitorType, + }; + + return { + fullConfig: formattedDefaultConfig, + monitorTypeConfig: formattedDefaultConfigForMonitorType, + tlsConfig, + monitorType: type, + enableTLS, + enableZipUrlTLS, + }; + }; + + return getDefaultConfig(); + }, []); + + return ( + + + + ); }; diff --git a/x-pack/plugins/uptime/server/lib/adapters/framework/kibana_framework_adapter.ts b/x-pack/plugins/uptime/server/lib/adapters/framework/kibana_framework_adapter.ts index eae9dd5e73cae..d51496d6efaf1 100644 --- a/x-pack/plugins/uptime/server/lib/adapters/framework/kibana_framework_adapter.ts +++ b/x-pack/plugins/uptime/server/lib/adapters/framework/kibana_framework_adapter.ts @@ -20,6 +20,7 @@ export class UMKibanaBackendFrameworkAdapter implements UMBackendFrameworkAdapte validate, options, }; + switch (method) { case 'GET': this.server.router.get(routeDefinition, handler); @@ -27,6 +28,12 @@ export class UMKibanaBackendFrameworkAdapter implements UMBackendFrameworkAdapte case 'POST': this.server.router.post(routeDefinition, handler); break; + case 'PUT': + this.server.router.put(routeDefinition, handler); + break; + case 'DELETE': + this.server.router.delete(routeDefinition, handler); + break; default: throw new Error(`Handler for method ${method} is not defined`); } diff --git a/x-pack/plugins/uptime/server/rest_api/index.ts b/x-pack/plugins/uptime/server/rest_api/index.ts index 344dd4d203d8d..4eb6ae3071256 100644 --- a/x-pack/plugins/uptime/server/rest_api/index.ts +++ b/x-pack/plugins/uptime/server/rest_api/index.ts @@ -28,6 +28,13 @@ import { createNetworkEventsRoute } from './network_events'; import { createJourneyFailedStepsRoute } from './pings/journeys'; import { createLastSuccessfulStepRoute } from './synthetics/last_successful_step'; import { installIndexTemplatesRoute } from './synthetics_service/install_index_templates'; +import { + getAllSyntheticsMonitorRoute, + getSyntheticsMonitorRoute, +} from './synthetics_service/get_monitors'; +import { addSyntheticsMonitorRoute } from './synthetics_service/add_monitor'; +import { editSyntheticsMonitorRoute } from './synthetics_service/edit_monitor'; +import { deleteSyntheticsMonitorRoute } from './synthetics_service/delete_monitor'; export * from './types'; export { createRouteWithAuth } from './create_route_with_auth'; @@ -53,4 +60,9 @@ export const restApiRoutes: UMRestApiRouteFactory[] = [ createLastSuccessfulStepRoute, createJourneyScreenshotBlocksRoute, installIndexTemplatesRoute, + getSyntheticsMonitorRoute, + getAllSyntheticsMonitorRoute, + addSyntheticsMonitorRoute, + editSyntheticsMonitorRoute, + deleteSyntheticsMonitorRoute, ]; diff --git a/x-pack/plugins/uptime/server/rest_api/synthetics_service/add_monitor.ts b/x-pack/plugins/uptime/server/rest_api/synthetics_service/add_monitor.ts new file mode 100644 index 0000000000000..11d7dcedcaa34 --- /dev/null +++ b/x-pack/plugins/uptime/server/rest_api/synthetics_service/add_monitor.ts @@ -0,0 +1,26 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { schema } from '@kbn/config-schema'; +import { UMRestApiRouteFactory } from '../types'; +import { API_URLS } from '../../../common/constants'; +import { SyntheticsMonitorSavedObject } from '../../../common/types'; +import { syntheticsMonitorType } from '../../lib/saved_objects/synthetics_monitor'; + +export const addSyntheticsMonitorRoute: UMRestApiRouteFactory = () => ({ + method: 'POST', + path: API_URLS.SYNTHETICS_MONITORS, + validate: { + body: schema.any(), + }, + handler: async ({ request, savedObjectsClient }): Promise => { + const monitor = request.body as SyntheticsMonitorSavedObject; + + const newMonitor = await savedObjectsClient.create(syntheticsMonitorType, monitor); + // TODO: call to service sync + return newMonitor; + }, +}); diff --git a/x-pack/plugins/uptime/server/rest_api/synthetics_service/delete_monitor.ts b/x-pack/plugins/uptime/server/rest_api/synthetics_service/delete_monitor.ts new file mode 100644 index 0000000000000..68eb8aa130d2e --- /dev/null +++ b/x-pack/plugins/uptime/server/rest_api/synthetics_service/delete_monitor.ts @@ -0,0 +1,34 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { schema } from '@kbn/config-schema'; +import { SavedObjectsErrorHelpers } from '../../../../../../src/core/server'; +import { UMRestApiRouteFactory } from '../types'; +import { API_URLS } from '../../../common/constants'; +import { syntheticsMonitorType } from '../../lib/saved_objects/synthetics_monitor'; + +export const deleteSyntheticsMonitorRoute: UMRestApiRouteFactory = () => ({ + method: 'DELETE', + path: API_URLS.SYNTHETICS_MONITORS + '/{monitorId}', + validate: { + params: schema.object({ + monitorId: schema.string(), + }), + }, + handler: async ({ request, savedObjectsClient }): Promise => { + const { monitorId } = request.params; + + try { + await savedObjectsClient.delete(syntheticsMonitorType, monitorId); + // TODO: call to service sync + return monitorId; + } catch (getErr) { + if (SavedObjectsErrorHelpers.isNotFoundError(getErr)) { + return 'Not found'; + } + } + }, +}); diff --git a/x-pack/plugins/uptime/server/rest_api/synthetics_service/edit_monitor.ts b/x-pack/plugins/uptime/server/rest_api/synthetics_service/edit_monitor.ts new file mode 100644 index 0000000000000..46a91738c380d --- /dev/null +++ b/x-pack/plugins/uptime/server/rest_api/synthetics_service/edit_monitor.ts @@ -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 { schema } from '@kbn/config-schema'; +import { UMRestApiRouteFactory } from '../types'; +import { API_URLS } from '../../../common/constants'; +import { SyntheticsMonitorSavedObject } from '../../../common/types'; +import { syntheticsMonitorType } from '../../lib/saved_objects/synthetics_monitor'; + +export const editSyntheticsMonitorRoute: UMRestApiRouteFactory = () => ({ + method: 'PUT', + path: API_URLS.SYNTHETICS_MONITORS + '/{monitorId}', + validate: { + params: schema.object({ + monitorId: schema.string(), + }), + body: schema.any(), + }, + handler: async ({ request, savedObjectsClient }): Promise => { + const monitor = request.body as SyntheticsMonitorSavedObject['attributes']; + + const { monitorId } = request.params; + + const editMonitor = await savedObjectsClient.update(syntheticsMonitorType, monitorId, monitor); + // TODO: call to service sync + return editMonitor; + }, +}); diff --git a/x-pack/plugins/uptime/server/rest_api/synthetics_service/get_monitors.ts b/x-pack/plugins/uptime/server/rest_api/synthetics_service/get_monitors.ts new file mode 100644 index 0000000000000..537d6c77195ca --- /dev/null +++ b/x-pack/plugins/uptime/server/rest_api/synthetics_service/get_monitors.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 { schema } from '@kbn/config-schema'; +import { UMRestApiRouteFactory } from '../types'; +import { API_URLS } from '../../../common/constants'; +import { syntheticsMonitorType } from '../../lib/saved_objects/synthetics_monitor'; + +export const getSyntheticsMonitorRoute: UMRestApiRouteFactory = () => ({ + method: 'GET', + path: API_URLS.SYNTHETICS_MONITORS + '/{monitorId}', + validate: { + params: schema.object({ + monitorId: schema.string(), + }), + }, + handler: async ({ request, savedObjectsClient }): Promise => { + const { monitorId } = request.params; + return await savedObjectsClient.get(syntheticsMonitorType, monitorId); + }, +}); + +export const getAllSyntheticsMonitorRoute: UMRestApiRouteFactory = () => ({ + method: 'GET', + path: API_URLS.SYNTHETICS_MONITORS, + validate: { + query: schema.object({ + page: schema.maybe(schema.number()), + perPage: schema.maybe(schema.number()), + }), + }, + handler: async ({ request, savedObjectsClient }): Promise => { + const { perPage = 50, page } = request.query; + // TODO: add query/filtering params + return await savedObjectsClient.find({ type: syntheticsMonitorType, perPage, page }); + }, +}); diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/event_log.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/event_log.ts index 9bf7baf95d8d2..806c1fa3a4ea7 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/event_log.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/event_log.ts @@ -490,8 +490,11 @@ export default function eventLogTests({ getService }: FtrProviderContext) { }); }); - const startEvent = events[0]; - const executeEvent = events[1]; + const executeEvents = getEventsByAction(events, 'execute'); + const executeStartEvents = getEventsByAction(events, 'execute-start'); + + const startEvent = executeStartEvents[0]; + const executeEvent = executeEvents[0]; expect(startEvent).to.be.ok(); expect(executeEvent).to.be.ok(); diff --git a/x-pack/test/api_integration/apis/search/session.ts b/x-pack/test/api_integration/apis/search/session.ts index 1fa65172cdee3..868c91cd9ed12 100644 --- a/x-pack/test/api_integration/apis/search/session.ts +++ b/x-pack/test/api_integration/apis/search/session.ts @@ -16,7 +16,8 @@ export default function ({ getService }: FtrProviderContext) { const retry = getService('retry'); const spacesService = getService('spaces'); - describe('search session', () => { + // FLAKY: https://github.com/elastic/kibana/issues/119660 + describe.skip('search session', () => { describe('session management', () => { it('should fail to create a session with no name', async () => { const sessionId = `my-session-${Math.random()}`; diff --git a/x-pack/test/api_integration/apis/uptime/rest/add_monitor.ts b/x-pack/test/api_integration/apis/uptime/rest/add_monitor.ts new file mode 100644 index 0000000000000..a57a03fd3a1f5 --- /dev/null +++ b/x-pack/test/api_integration/apis/uptime/rest/add_monitor.ts @@ -0,0 +1,29 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../../ftr_provider_context'; +import { API_URLS } from '../../../../../plugins/uptime/common/constants'; + +export default function ({ getService }: FtrProviderContext) { + describe('add synthetics monitor', () => { + const supertest = getService('supertest'); + const newMonitor = { + type: 'http', + name: 'Test monitor', + urls: 'https://www.elastic.co', + }; + + it('returns the newly added monitor', async () => { + const apiResponse = await supertest + .post(API_URLS.SYNTHETICS_MONITORS) + .set('kbn-xsrf', 'true') + .send(newMonitor); + + expect(apiResponse.body.attributes).eql(newMonitor); + }); + }); +} diff --git a/x-pack/test/api_integration/apis/uptime/rest/delete_monitor.ts b/x-pack/test/api_integration/apis/uptime/rest/delete_monitor.ts new file mode 100644 index 0000000000000..bc49587fab872 --- /dev/null +++ b/x-pack/test/api_integration/apis/uptime/rest/delete_monitor.ts @@ -0,0 +1,36 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../../ftr_provider_context'; +import { API_URLS } from '../../../../../plugins/uptime/common/constants'; + +export default function ({ getService }: FtrProviderContext) { + describe('delete synthetics monitor', () => { + const supertest = getService('supertest'); + const newMonitor = { + type: 'http', + name: 'Test monitor', + urls: 'https://www.elastic.co', + }; + + it('deleted monitor by id', async () => { + const apiResponse = await supertest + .post(API_URLS.SYNTHETICS_MONITORS) + .set('kbn-xsrf', 'true') + .send(newMonitor); + + const monitorId = apiResponse.body.id; + + const deleteResponse = await supertest + .delete(API_URLS.SYNTHETICS_MONITORS + '/' + monitorId) + .set('kbn-xsrf', 'true'); + // + expect(deleteResponse.body).eql(monitorId); + }); + }); +} diff --git a/x-pack/test/api_integration/apis/uptime/rest/edit_monitor.ts b/x-pack/test/api_integration/apis/uptime/rest/edit_monitor.ts new file mode 100644 index 0000000000000..f5d54c40a8646 --- /dev/null +++ b/x-pack/test/api_integration/apis/uptime/rest/edit_monitor.ts @@ -0,0 +1,37 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../../ftr_provider_context'; +import { API_URLS } from '../../../../../plugins/uptime/common/constants'; +export default function ({ getService }: FtrProviderContext) { + describe('edit synthetics monitor', () => { + const supertest = getService('supertest'); + const newMonitor = { + type: 'http', + name: 'Test monitor', + urls: 'https://www.elastic.co', + }; + + it('edits the monitor', async () => { + const apiResponse = await supertest + .post(API_URLS.SYNTHETICS_MONITORS) + .set('kbn-xsrf', 'true') + .send(newMonitor); + + const monitorId = apiResponse.body.id; + + expect(apiResponse.body.attributes).eql(newMonitor); + + const editResponse = await supertest + .put(API_URLS.SYNTHETICS_MONITORS + '/' + monitorId) + .set('kbn-xsrf', 'true') + .send({ ...newMonitor, name: 'New name' }); + + expect(editResponse.body.attributes.name).eql('New name'); + }); + }); +} diff --git a/x-pack/test/api_integration/apis/uptime/rest/get_monitor.ts b/x-pack/test/api_integration/apis/uptime/rest/get_monitor.ts new file mode 100644 index 0000000000000..76d27ff8a9d1d --- /dev/null +++ b/x-pack/test/api_integration/apis/uptime/rest/get_monitor.ts @@ -0,0 +1,51 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../../ftr_provider_context'; +import { API_URLS } from '../../../../../plugins/uptime/common/constants'; + +export default function ({ getService }: FtrProviderContext) { + describe('get synthetics monitor', () => { + const newMonitor = { + type: 'http', + name: 'Test monitor', + urls: 'https://www.elastic.co', + }; + + const addMonitor = async () => { + const res = await supertest + .post(API_URLS.SYNTHETICS_MONITORS) + .set('kbn-xsrf', 'true') + .send(newMonitor); + return res.body.id; + }; + + const supertest = getService('supertest'); + + it('get all monitors', async () => { + const id1 = await addMonitor(); + const id2 = await addMonitor(); + + const apiResponse = await supertest.get(API_URLS.SYNTHETICS_MONITORS); + + const monitor1 = apiResponse.body.saved_objects.find((obj: any) => obj.id === id1); + const monitor2 = apiResponse.body.saved_objects.find((obj: any) => obj.id === id2); + + expect(monitor1.id).eql(id1); + expect(monitor2.id).eql(id2); + }); + + it('get monitor by id', async () => { + const monitorId = await addMonitor(); + + const apiResponse = await supertest.get(API_URLS.SYNTHETICS_MONITORS + '/' + monitorId); + + expect(apiResponse.body.id).eql(monitorId); + }); + }); +} diff --git a/x-pack/test/api_integration/apis/uptime/rest/index.ts b/x-pack/test/api_integration/apis/uptime/rest/index.ts index dc3c00b03f712..f674879552d6a 100644 --- a/x-pack/test/api_integration/apis/uptime/rest/index.ts +++ b/x-pack/test/api_integration/apis/uptime/rest/index.ts @@ -71,5 +71,12 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) { loadTestFile(require.resolve('./index_status')); loadTestFile(require.resolve('./monitor_states_real_data')); }); + + describe('uptime CRUD routes', () => { + loadTestFile(require.resolve('./get_monitor')); + loadTestFile(require.resolve('./add_monitor')); + loadTestFile(require.resolve('./edit_monitor')); + loadTestFile(require.resolve('./delete_monitor')); + }); }); } diff --git a/x-pack/test/api_integration/config.ts b/x-pack/test/api_integration/config.ts index e2c2e0b52dfdc..bf42a5b0865a2 100644 --- a/x-pack/test/api_integration/config.ts +++ b/x-pack/test/api_integration/config.ts @@ -35,6 +35,10 @@ export async function getApiIntegrationConfig({ readConfigFile }: FtrConfigProvi '--xpack.ruleRegistry.write.enabled=true', '--xpack.ruleRegistry.write.enabled=true', '--xpack.ruleRegistry.write.cache.enabled=false', + '--xpack.uptime.unsafe.service.enabled=true', + '--xpack.uptime.unsafe.service.password=test', + '--xpack.uptime.unsafe.service.manifestUrl=http://test.com', + '--xpack.uptime.unsafe.service.username=user', `--xpack.securitySolution.enableExperimental=${JSON.stringify(['ruleRegistryEnabled'])}`, ], }, diff --git a/x-pack/test/apm_api_integration/common/synthtrace_es_client_service.ts b/x-pack/test/apm_api_integration/common/synthtrace_es_client_service.ts index 14e746a55a3d1..0ff00d415e7ac 100644 --- a/x-pack/test/apm_api_integration/common/synthtrace_es_client_service.ts +++ b/x-pack/test/apm_api_integration/common/synthtrace_es_client_service.ts @@ -5,11 +5,11 @@ * 2.0. */ -import { SynthtraceEsClient, createLogger, LogLevel } from '@elastic/apm-synthtrace'; +import { apm, createLogger, LogLevel } from '@elastic/apm-synthtrace'; import { InheritedFtrProviderContext } from './ftr_provider_context'; export async function synthtraceEsClientService(context: InheritedFtrProviderContext) { const es = context.getService('es'); - return new SynthtraceEsClient(es, createLogger(LogLevel.info)); + return new apm.ApmSynthtraceEsClient(es, createLogger(LogLevel.info)); } diff --git a/x-pack/test/apm_api_integration/tests/dependencies/generate_data.ts b/x-pack/test/apm_api_integration/tests/dependencies/generate_data.ts index e36e99b447aa3..21af5d91d14e1 100644 --- a/x-pack/test/apm_api_integration/tests/dependencies/generate_data.ts +++ b/x-pack/test/apm_api_integration/tests/dependencies/generate_data.ts @@ -4,8 +4,8 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { service, timerange } from '@elastic/apm-synthtrace'; -import type { SynthtraceEsClient } from '@elastic/apm-synthtrace'; +import { apm, timerange } from '@elastic/apm-synthtrace'; +import type { ApmSynthtraceEsClient } from '@elastic/apm-synthtrace'; export const dataConfig = { rate: 20, @@ -26,11 +26,11 @@ export async function generateData({ start, end, }: { - synthtraceEsClient: SynthtraceEsClient; + synthtraceEsClient: ApmSynthtraceEsClient; start: number; end: number; }) { - const instance = service('synth-go', 'production', 'go').instance('instance-a'); + const instance = apm.service('synth-go', 'production', 'go').instance('instance-a'); const { rate, transaction, span } = dataConfig; await synthtraceEsClient.index( diff --git a/x-pack/test/apm_api_integration/tests/error_rate/service_apis.spec.ts b/x-pack/test/apm_api_integration/tests/error_rate/service_apis.spec.ts index 7aca21f4fc7f6..f1aefa06304a1 100644 --- a/x-pack/test/apm_api_integration/tests/error_rate/service_apis.spec.ts +++ b/x-pack/test/apm_api_integration/tests/error_rate/service_apis.spec.ts @@ -4,7 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { service, timerange } from '@elastic/apm-synthtrace'; +import { apm, timerange } from '@elastic/apm-synthtrace'; import expect from '@kbn/expect'; import { mean, meanBy, sumBy } from 'lodash'; import { LatencyAggregationType } from '../../../../plugins/apm/common/latency_aggregation_types'; @@ -121,9 +121,9 @@ export default function ApiTest({ getService }: FtrProviderContext) { const GO_PROD_ID_RATE = 50; const GO_PROD_ID_ERROR_RATE = 50; before(async () => { - const serviceGoProdInstance = service(serviceName, 'production', 'go').instance( - 'instance-a' - ); + const serviceGoProdInstance = apm + .service(serviceName, 'production', 'go') + .instance('instance-a'); const transactionNameProductList = 'GET /api/product/list'; const transactionNameProductId = 'GET /api/product/:id'; diff --git a/x-pack/test/apm_api_integration/tests/errors/error_group_list.spec.ts b/x-pack/test/apm_api_integration/tests/errors/error_group_list.spec.ts index ce27183e84ca1..421b536c6d5a5 100644 --- a/x-pack/test/apm_api_integration/tests/errors/error_group_list.spec.ts +++ b/x-pack/test/apm_api_integration/tests/errors/error_group_list.spec.ts @@ -5,7 +5,7 @@ * 2.0. */ import expect from '@kbn/expect'; -import { service, timerange } from '@elastic/apm-synthtrace'; +import { apm, timerange } from '@elastic/apm-synthtrace'; import { APIClientRequestParamsOf, APIReturnType, @@ -72,7 +72,9 @@ export default function ApiTest({ getService }: FtrProviderContext) { }; before(async () => { - const serviceInstance = service(serviceName, 'production', 'go').instance('instance-a'); + const serviceInstance = apm + .service(serviceName, 'production', 'go') + .instance('instance-a'); await synthtraceEsClient.index([ ...timerange(start, end) diff --git a/x-pack/test/apm_api_integration/tests/errors/generate_data.ts b/x-pack/test/apm_api_integration/tests/errors/generate_data.ts index f7874b1c61495..b9ac77ca34425 100644 --- a/x-pack/test/apm_api_integration/tests/errors/generate_data.ts +++ b/x-pack/test/apm_api_integration/tests/errors/generate_data.ts @@ -4,7 +4,8 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { service, SynthtraceEsClient, timerange } from '@elastic/apm-synthtrace'; +import { apm, timerange } from '@elastic/apm-synthtrace'; +import type { ApmSynthtraceEsClient } from '@elastic/apm-synthtrace'; export const config = { appleTransaction: { @@ -25,12 +26,12 @@ export async function generateData({ start, end, }: { - synthtraceEsClient: SynthtraceEsClient; + synthtraceEsClient: ApmSynthtraceEsClient; serviceName: string; start: number; end: number; }) { - const serviceGoProdInstance = service(serviceName, 'production', 'go').instance('instance-a'); + const serviceGoProdInstance = apm.service(serviceName, 'production', 'go').instance('instance-a'); const interval = '1m'; diff --git a/x-pack/test/apm_api_integration/tests/latency/service_apis.spec.ts b/x-pack/test/apm_api_integration/tests/latency/service_apis.spec.ts index e87f03efeeefe..1c0185c396557 100644 --- a/x-pack/test/apm_api_integration/tests/latency/service_apis.spec.ts +++ b/x-pack/test/apm_api_integration/tests/latency/service_apis.spec.ts @@ -4,7 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { service, timerange } from '@elastic/apm-synthtrace'; +import { apm, timerange } from '@elastic/apm-synthtrace'; import expect from '@kbn/expect'; import { meanBy, sumBy } from 'lodash'; import { LatencyAggregationType } from '../../../../plugins/apm/common/latency_aggregation_types'; @@ -123,12 +123,13 @@ export default function ApiTest({ getService }: FtrProviderContext) { const GO_PROD_DURATION = 1000; const GO_DEV_DURATION = 500; before(async () => { - const serviceGoProdInstance = service(serviceName, 'production', 'go').instance( - 'instance-a' - ); - const serviceGoDevInstance = service(serviceName, 'development', 'go').instance( - 'instance-b' - ); + const serviceGoProdInstance = apm + .service(serviceName, 'production', 'go') + .instance('instance-a'); + const serviceGoDevInstance = apm + .service(serviceName, 'development', 'go') + .instance('instance-b'); + await synthtraceEsClient.index([ ...timerange(start, end) .interval('1m') diff --git a/x-pack/test/apm_api_integration/tests/observability_overview/observability_overview.spec.ts b/x-pack/test/apm_api_integration/tests/observability_overview/observability_overview.spec.ts index 6b6d61fdb1d35..4c1e1850c177e 100644 --- a/x-pack/test/apm_api_integration/tests/observability_overview/observability_overview.spec.ts +++ b/x-pack/test/apm_api_integration/tests/observability_overview/observability_overview.spec.ts @@ -4,7 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { service, timerange } from '@elastic/apm-synthtrace'; +import { apm, timerange } from '@elastic/apm-synthtrace'; import expect from '@kbn/expect'; import { meanBy, sumBy } from 'lodash'; import { FtrProviderContext } from '../../common/ftr_provider_context'; @@ -92,15 +92,16 @@ export default function ApiTest({ getService }: FtrProviderContext) { const GO_DEV_RATE = 5; const JAVA_PROD_RATE = 45; before(async () => { - const serviceGoProdInstance = service('synth-go', 'production', 'go').instance( - 'instance-a' - ); - const serviceGoDevInstance = service('synth-go', 'development', 'go').instance( - 'instance-b' - ); - const serviceJavaInstance = service('synth-java', 'production', 'java').instance( - 'instance-c' - ); + const serviceGoProdInstance = apm + .service('synth-go', 'production', 'go') + .instance('instance-a'); + const serviceGoDevInstance = apm + .service('synth-go', 'development', 'go') + .instance('instance-b'); + + const serviceJavaInstance = apm + .service('synth-java', 'production', 'java') + .instance('instance-c'); await synthtraceEsClient.index([ ...timerange(start, end) diff --git a/x-pack/test/apm_api_integration/tests/service_overview/instances_main_statistics.spec.ts b/x-pack/test/apm_api_integration/tests/service_overview/instances_main_statistics.spec.ts index 5800ddf00480a..a60da5f2bd5c0 100644 --- a/x-pack/test/apm_api_integration/tests/service_overview/instances_main_statistics.spec.ts +++ b/x-pack/test/apm_api_integration/tests/service_overview/instances_main_statistics.spec.ts @@ -8,7 +8,7 @@ import expect from '@kbn/expect'; import { pick, sortBy } from 'lodash'; import moment from 'moment'; -import { service, timerange } from '@elastic/apm-synthtrace'; +import { apm, timerange } from '@elastic/apm-synthtrace'; import { APIReturnType } from '../../../../plugins/apm/public/services/rest/createCallApmApi'; import { isFiniteNumber } from '../../../../plugins/apm/common/utils/is_finite_number'; import { FtrProviderContext } from '../../common/ftr_provider_context'; @@ -298,8 +298,8 @@ export default function ApiTest({ getService }: FtrProviderContext) { const rangeEnd = new Date('2021-01-01T12:15:00.000Z').getTime() - 1; before(async () => { - const goService = service('opbeans-go', 'production', 'go'); - const javaService = service('opbeans-java', 'production', 'java'); + const goService = apm.service('opbeans-go', 'production', 'go'); + const javaService = apm.service('opbeans-java', 'production', 'java'); const goInstanceA = goService.instance('go-instance-a'); const goInstanceB = goService.instance('go-instance-b'); diff --git a/x-pack/test/apm_api_integration/tests/services/error_groups/generate_data.ts b/x-pack/test/apm_api_integration/tests/services/error_groups/generate_data.ts index f02f1e7493ff0..ef77cd4003a16 100644 --- a/x-pack/test/apm_api_integration/tests/services/error_groups/generate_data.ts +++ b/x-pack/test/apm_api_integration/tests/services/error_groups/generate_data.ts @@ -4,8 +4,8 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { service, timerange } from '@elastic/apm-synthtrace'; -import type { SynthtraceEsClient } from '@elastic/apm-synthtrace'; +import { apm, timerange } from '@elastic/apm-synthtrace'; +import type { ApmSynthtraceEsClient } from '@elastic/apm-synthtrace'; export const config = { PROD_LIST_RATE: 75, @@ -22,12 +22,12 @@ export async function generateData({ start, end, }: { - synthtraceEsClient: SynthtraceEsClient; + synthtraceEsClient: ApmSynthtraceEsClient; serviceName: string; start: number; end: number; }) { - const serviceGoProdInstance = service(serviceName, 'production', 'go').instance('instance-a'); + const serviceGoProdInstance = apm.service(serviceName, 'production', 'go').instance('instance-a'); const transactionNameProductList = 'GET /api/product/list'; const transactionNameProductId = 'GET /api/product/:id'; diff --git a/x-pack/test/apm_api_integration/tests/services/throughput.spec.ts b/x-pack/test/apm_api_integration/tests/services/throughput.spec.ts index 077119156c641..87c1f5a04ed25 100644 --- a/x-pack/test/apm_api_integration/tests/services/throughput.spec.ts +++ b/x-pack/test/apm_api_integration/tests/services/throughput.spec.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { service, timerange } from '@elastic/apm-synthtrace'; +import { apm, timerange } from '@elastic/apm-synthtrace'; import expect from '@kbn/expect'; import { first, last, meanBy } from 'lodash'; import moment from 'moment'; @@ -73,16 +73,16 @@ export default function ApiTest({ getService }: FtrProviderContext) { const JAVA_PROD_RATE = 45; before(async () => { - const serviceGoProdInstance = service(serviceName, 'production', 'go').instance( - 'instance-a' - ); - const serviceGoDevInstance = service(serviceName, 'development', 'go').instance( - 'instance-b' - ); - - const serviceJavaInstance = service('synth-java', 'development', 'java').instance( - 'instance-c' - ); + const serviceGoProdInstance = apm + .service(serviceName, 'production', 'go') + .instance('instance-a'); + const serviceGoDevInstance = apm + .service(serviceName, 'development', 'go') + .instance('instance-b'); + + const serviceJavaInstance = apm + .service('synth-java', 'development', 'java') + .instance('instance-c'); await synthtraceEsClient.index([ ...timerange(start, end) diff --git a/x-pack/test/apm_api_integration/tests/services/top_services.spec.ts b/x-pack/test/apm_api_integration/tests/services/top_services.spec.ts index 375206d0a0bc0..d4dacadfee034 100644 --- a/x-pack/test/apm_api_integration/tests/services/top_services.spec.ts +++ b/x-pack/test/apm_api_integration/tests/services/top_services.spec.ts @@ -7,7 +7,7 @@ import expect from '@kbn/expect'; import { sortBy } from 'lodash'; -import { service, timerange } from '@elastic/apm-synthtrace'; +import { apm, timerange } from '@elastic/apm-synthtrace'; import { APIReturnType } from '../../../../plugins/apm/public/services/rest/createCallApmApi'; import { PromiseReturnType } from '../../../../plugins/observability/typings/common'; import { FtrProviderContext } from '../../common/ftr_provider_context'; @@ -65,21 +65,17 @@ export default function ApiTest({ getService }: FtrProviderContext) { const transactionInterval = range.interval('1s'); const metricInterval = range.interval('30s'); - const multipleEnvServiceProdInstance = service( - 'multiple-env-service', - 'production', - 'go' - ).instance('multiple-env-service-production'); - - const multipleEnvServiceDevInstance = service( - 'multiple-env-service', - 'development', - 'go' - ).instance('multiple-env-service-development'); - - const metricOnlyInstance = service('metric-only-service', 'production', 'java').instance( - 'metric-only-production' - ); + const multipleEnvServiceProdInstance = apm + .service('multiple-env-service', 'production', 'go') + .instance('multiple-env-service-production'); + + const multipleEnvServiceDevInstance = apm + .service('multiple-env-service', 'development', 'go') + .instance('multiple-env-service-development'); + + const metricOnlyInstance = apm + .service('metric-only-service', 'production', 'java') + .instance('metric-only-production'); const config = { multiple: { diff --git a/x-pack/test/apm_api_integration/tests/throughput/dependencies_apis.spec.ts b/x-pack/test/apm_api_integration/tests/throughput/dependencies_apis.spec.ts index 1b2c919f538a7..bc2118f55f650 100644 --- a/x-pack/test/apm_api_integration/tests/throughput/dependencies_apis.spec.ts +++ b/x-pack/test/apm_api_integration/tests/throughput/dependencies_apis.spec.ts @@ -4,7 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { service, timerange } from '@elastic/apm-synthtrace'; +import { apm, timerange } from '@elastic/apm-synthtrace'; import expect from '@kbn/expect'; import { meanBy, sumBy } from 'lodash'; import { BackendNode, ServiceNode } from '../../../../plugins/apm/common/connections'; @@ -94,12 +94,12 @@ export default function ApiTest({ getService }: FtrProviderContext) { const GO_PROD_RATE = 75; const JAVA_PROD_RATE = 25; before(async () => { - const serviceGoProdInstance = service('synth-go', 'production', 'go').instance( - 'instance-a' - ); - const serviceJavaInstance = service('synth-java', 'development', 'java').instance( - 'instance-c' - ); + const serviceGoProdInstance = apm + .service('synth-go', 'production', 'go') + .instance('instance-a'); + const serviceJavaInstance = apm + .service('synth-java', 'development', 'java') + .instance('instance-c'); await synthtraceEsClient.index([ ...timerange(start, end) diff --git a/x-pack/test/apm_api_integration/tests/throughput/service_apis.spec.ts b/x-pack/test/apm_api_integration/tests/throughput/service_apis.spec.ts index 7318fc449fcdb..3492d2967a356 100644 --- a/x-pack/test/apm_api_integration/tests/throughput/service_apis.spec.ts +++ b/x-pack/test/apm_api_integration/tests/throughput/service_apis.spec.ts @@ -4,7 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { service, timerange } from '@elastic/apm-synthtrace'; +import { apm, timerange } from '@elastic/apm-synthtrace'; import expect from '@kbn/expect'; import { meanBy, sumBy } from 'lodash'; import { LatencyAggregationType } from '../../../../plugins/apm/common/latency_aggregation_types'; @@ -109,12 +109,13 @@ export default function ApiTest({ getService }: FtrProviderContext) { const GO_PROD_RATE = 80; const GO_DEV_RATE = 20; before(async () => { - const serviceGoProdInstance = service(serviceName, 'production', 'go').instance( - 'instance-a' - ); - const serviceGoDevInstance = service(serviceName, 'development', 'go').instance( - 'instance-b' - ); + const serviceGoProdInstance = apm + .service(serviceName, 'production', 'go') + .instance('instance-a'); + const serviceGoDevInstance = apm + .service(serviceName, 'development', 'go') + .instance('instance-b'); + await synthtraceEsClient.index([ ...timerange(start, end) .interval('1m') diff --git a/x-pack/test/apm_api_integration/tests/traces/__snapshots__/top_traces.spec.snap b/x-pack/test/apm_api_integration/tests/traces/__snapshots__/top_traces.spec.snap index 604348355f38c..528963709712d 100644 --- a/x-pack/test/apm_api_integration/tests/traces/__snapshots__/top_traces.spec.snap +++ b/x-pack/test/apm_api_integration/tests/traces/__snapshots__/top_traces.spec.snap @@ -3,6 +3,7 @@ exports[`APM API tests basic apm_8.0.0 Top traces when data is loaded returns the correct buckets 1`] = ` Array [ Object { + "agentName": "java", "averageResponseTime": 1639, "impact": 0, "key": Object { @@ -15,6 +16,7 @@ Array [ "transactionsPerMinute": 0.0333333333333333, }, Object { + "agentName": "nodejs", "averageResponseTime": 3279, "impact": 0.00144735571024101, "key": Object { @@ -27,6 +29,7 @@ Array [ "transactionsPerMinute": 0.0333333333333333, }, Object { + "agentName": "nodejs", "averageResponseTime": 6175, "impact": 0.00400317408637392, "key": Object { @@ -39,6 +42,7 @@ Array [ "transactionsPerMinute": 0.0333333333333333, }, Object { + "agentName": "dotnet", "averageResponseTime": 3495, "impact": 0.00472243927164613, "key": Object { @@ -51,6 +55,7 @@ Array [ "transactionsPerMinute": 0.0666666666666667, }, Object { + "agentName": "python", "averageResponseTime": 7039, "impact": 0.00476568343615943, "key": Object { @@ -63,6 +68,7 @@ Array [ "transactionsPerMinute": 0.0333333333333333, }, Object { + "agentName": "ruby", "averageResponseTime": 6303, "impact": 0.00967875004525193, "key": Object { @@ -75,6 +81,7 @@ Array [ "transactionsPerMinute": 0.0666666666666667, }, Object { + "agentName": "java", "averageResponseTime": 7209.66666666667, "impact": 0.0176418540534865, "key": Object { @@ -87,6 +94,7 @@ Array [ "transactionsPerMinute": 0.1, }, Object { + "agentName": "java", "averageResponseTime": 4511, "impact": 0.0224401912465233, "key": Object { @@ -99,6 +107,7 @@ Array [ "transactionsPerMinute": 0.2, }, Object { + "agentName": "python", "averageResponseTime": 7607, "impact": 0.0254072704525173, "key": Object { @@ -111,6 +120,7 @@ Array [ "transactionsPerMinute": 0.133333333333333, }, Object { + "agentName": "nodejs", "averageResponseTime": 10143, "impact": 0.025408152986487, "key": Object { @@ -123,6 +133,7 @@ Array [ "transactionsPerMinute": 0.1, }, Object { + "agentName": "ruby", "averageResponseTime": 6105.66666666667, "impact": 0.0308842762682221, "key": Object { @@ -135,6 +146,7 @@ Array [ "transactionsPerMinute": 0.2, }, Object { + "agentName": "java", "averageResponseTime": 6116.33333333333, "impact": 0.0309407584422802, "key": Object { @@ -147,6 +159,7 @@ Array [ "transactionsPerMinute": 0.2, }, Object { + "agentName": "java", "averageResponseTime": 12543, "impact": 0.0317623975680329, "key": Object { @@ -159,6 +172,7 @@ Array [ "transactionsPerMinute": 0.1, }, Object { + "agentName": "nodejs", "averageResponseTime": 5551, "impact": 0.0328461492827744, "key": Object { @@ -171,6 +185,7 @@ Array [ "transactionsPerMinute": 0.233333333333333, }, Object { + "agentName": "java", "averageResponseTime": 13183, "impact": 0.0334568627897785, "key": Object { @@ -183,6 +198,7 @@ Array [ "transactionsPerMinute": 0.1, }, Object { + "agentName": "go", "averageResponseTime": 8050.2, "impact": 0.0340764016364792, "key": Object { @@ -195,6 +211,7 @@ Array [ "transactionsPerMinute": 0.166666666666667, }, Object { + "agentName": "ruby", "averageResponseTime": 10079, "impact": 0.0341337663445071, "key": Object { @@ -207,6 +224,7 @@ Array [ "transactionsPerMinute": 0.133333333333333, }, Object { + "agentName": "nodejs", "averageResponseTime": 8463, "impact": 0.0358979517498557, "key": Object { @@ -219,6 +237,7 @@ Array [ "transactionsPerMinute": 0.166666666666667, }, Object { + "agentName": "ruby", "averageResponseTime": 10799, "impact": 0.0366754641771254, "key": Object { @@ -231,6 +250,7 @@ Array [ "transactionsPerMinute": 0.133333333333333, }, Object { + "agentName": "ruby", "averageResponseTime": 7428.33333333333, "impact": 0.0378880658514371, "key": Object { @@ -243,6 +263,7 @@ Array [ "transactionsPerMinute": 0.2, }, Object { + "agentName": "java", "averageResponseTime": 3105.13333333333, "impact": 0.039659311528543, "key": Object { @@ -255,6 +276,7 @@ Array [ "transactionsPerMinute": 0.5, }, Object { + "agentName": "java", "averageResponseTime": 6883.57142857143, "impact": 0.0410784261517549, "key": Object { @@ -267,6 +289,7 @@ Array [ "transactionsPerMinute": 0.233333333333333, }, Object { + "agentName": "dotnet", "averageResponseTime": 3505, "impact": 0.0480460318422139, "key": Object { @@ -279,6 +302,7 @@ Array [ "transactionsPerMinute": 0.533333333333333, }, Object { + "agentName": "java", "averageResponseTime": 5621.4, "impact": 0.0481642913941483, "key": Object { @@ -291,6 +315,7 @@ Array [ "transactionsPerMinute": 0.333333333333333, }, Object { + "agentName": "nodejs", "averageResponseTime": 8428.71428571429, "impact": 0.0506239135675883, "key": Object { @@ -303,6 +328,7 @@ Array [ "transactionsPerMinute": 0.233333333333333, }, Object { + "agentName": "nodejs", "averageResponseTime": 8520.14285714286, "impact": 0.0511887353081702, "key": Object { @@ -315,6 +341,7 @@ Array [ "transactionsPerMinute": 0.233333333333333, }, Object { + "agentName": "nodejs", "averageResponseTime": 6683.44444444444, "impact": 0.0516388276326964, "key": Object { @@ -327,6 +354,7 @@ Array [ "transactionsPerMinute": 0.3, }, Object { + "agentName": "dotnet", "averageResponseTime": 3482.78947368421, "impact": 0.0569534471979838, "key": Object { @@ -339,6 +367,7 @@ Array [ "transactionsPerMinute": 0.633333333333333, }, Object { + "agentName": "python", "averageResponseTime": 16703, "impact": 0.057517386404596, "key": Object { @@ -351,6 +380,7 @@ Array [ "transactionsPerMinute": 0.133333333333333, }, Object { + "agentName": "dotnet", "averageResponseTime": 4943, "impact": 0.0596266425920813, "key": Object { @@ -363,6 +393,7 @@ Array [ "transactionsPerMinute": 0.466666666666667, }, Object { + "agentName": "nodejs", "averageResponseTime": 7892.33333333333, "impact": 0.0612407972225879, "key": Object { @@ -375,6 +406,7 @@ Array [ "transactionsPerMinute": 0.3, }, Object { + "agentName": "dotnet", "averageResponseTime": 6346.42857142857, "impact": 0.0769666700279444, "key": Object { @@ -387,6 +419,7 @@ Array [ "transactionsPerMinute": 0.466666666666667, }, Object { + "agentName": "go", "averageResponseTime": 7052.84615384615, "impact": 0.0794704188998674, "key": Object { @@ -399,6 +432,7 @@ Array [ "transactionsPerMinute": 0.433333333333333, }, Object { + "agentName": "java", "averageResponseTime": 10484.3333333333, "impact": 0.0818285496667966, "key": Object { @@ -411,6 +445,7 @@ Array [ "transactionsPerMinute": 0.3, }, Object { + "agentName": "nodejs", "averageResponseTime": 23711, "impact": 0.0822565786420813, "key": Object { @@ -423,6 +458,7 @@ Array [ "transactionsPerMinute": 0.133333333333333, }, Object { + "agentName": "dotnet", "averageResponseTime": 4491.36363636364, "impact": 0.0857567083657495, "key": Object { @@ -435,6 +471,7 @@ Array [ "transactionsPerMinute": 0.733333333333333, }, Object { + "agentName": "python", "averageResponseTime": 20715.8, "impact": 0.089965512867054, "key": Object { @@ -447,6 +484,7 @@ Array [ "transactionsPerMinute": 0.166666666666667, }, Object { + "agentName": "nodejs", "averageResponseTime": 9036.33333333333, "impact": 0.0942519803576885, "key": Object { @@ -459,6 +497,7 @@ Array [ "transactionsPerMinute": 0.4, }, Object { + "agentName": "java", "averageResponseTime": 7504.06666666667, "impact": 0.0978924329825326, "key": Object { @@ -471,6 +510,7 @@ Array [ "transactionsPerMinute": 0.5, }, Object { + "agentName": "go", "averageResponseTime": 4250.55555555556, "impact": 0.0998375378516613, "key": Object { @@ -483,6 +523,7 @@ Array [ "transactionsPerMinute": 0.9, }, Object { + "agentName": "nodejs", "averageResponseTime": 21343, "impact": 0.11156906191034, "key": Object { @@ -495,6 +536,7 @@ Array [ "transactionsPerMinute": 0.2, }, Object { + "agentName": "ruby", "averageResponseTime": 16655, "impact": 0.116142352941114, "key": Object { @@ -507,6 +549,7 @@ Array [ "transactionsPerMinute": 0.266666666666667, }, Object { + "agentName": "go", "averageResponseTime": 5749, "impact": 0.12032203382142, "key": Object { @@ -519,6 +562,7 @@ Array [ "transactionsPerMinute": 0.8, }, Object { + "agentName": "ruby", "averageResponseTime": 9951, "impact": 0.121502864272824, "key": Object { @@ -531,6 +575,7 @@ Array [ "transactionsPerMinute": 0.466666666666667, }, Object { + "agentName": "go", "averageResponseTime": 14040.6, "impact": 0.122466591367692, "key": Object { @@ -543,6 +588,7 @@ Array [ "transactionsPerMinute": 0.333333333333333, }, Object { + "agentName": "ruby", "averageResponseTime": 20963.5714285714, "impact": 0.128060974201361, "key": Object { @@ -555,6 +601,7 @@ Array [ "transactionsPerMinute": 0.233333333333333, }, Object { + "agentName": "python", "averageResponseTime": 22874.4285714286, "impact": 0.139865748579522, "key": Object { @@ -567,6 +614,7 @@ Array [ "transactionsPerMinute": 0.233333333333333, }, Object { + "agentName": "python", "averageResponseTime": 32203.8, "impact": 0.140658264084276, "key": Object { @@ -579,6 +627,7 @@ Array [ "transactionsPerMinute": 0.166666666666667, }, Object { + "agentName": "go", "averageResponseTime": 4482.11111111111, "impact": 0.140955678032051, "key": Object { @@ -591,6 +640,7 @@ Array [ "transactionsPerMinute": 1.2, }, Object { + "agentName": "ruby", "averageResponseTime": 12582.3846153846, "impact": 0.142910490774846, "key": Object { @@ -603,6 +653,7 @@ Array [ "transactionsPerMinute": 0.433333333333333, }, Object { + "agentName": "ruby", "averageResponseTime": 10009.9473684211, "impact": 0.166401779979233, "key": Object { @@ -615,6 +666,7 @@ Array [ "transactionsPerMinute": 0.633333333333333, }, Object { + "agentName": "python", "averageResponseTime": 27825.2857142857, "impact": 0.170450845832029, "key": Object { @@ -627,6 +679,7 @@ Array [ "transactionsPerMinute": 0.233333333333333, }, Object { + "agentName": "python", "averageResponseTime": 20562.2, "impact": 0.180021926732983, "key": Object { @@ -639,6 +692,7 @@ Array [ "transactionsPerMinute": 0.333333333333333, }, Object { + "agentName": "dotnet", "averageResponseTime": 7106.76470588235, "impact": 0.21180020991247, "key": Object { @@ -651,6 +705,7 @@ Array [ "transactionsPerMinute": 1.13333333333333, }, Object { + "agentName": "go", "averageResponseTime": 8612.51724137931, "impact": 0.218977858687708, "key": Object { @@ -663,6 +718,7 @@ Array [ "transactionsPerMinute": 0.966666666666667, }, Object { + "agentName": "ruby", "averageResponseTime": 11295, "impact": 0.277663720068132, "key": Object { @@ -675,6 +731,7 @@ Array [ "transactionsPerMinute": 0.933333333333333, }, Object { + "agentName": "python", "averageResponseTime": 65035.8, "impact": 0.285535040543522, "key": Object { @@ -687,6 +744,7 @@ Array [ "transactionsPerMinute": 0.166666666666667, }, Object { + "agentName": "go", "averageResponseTime": 30999.4705882353, "impact": 0.463640986028375, "key": Object { @@ -699,6 +757,7 @@ Array [ "transactionsPerMinute": 0.566666666666667, }, Object { + "agentName": "go", "averageResponseTime": 20197.4, "impact": 0.622424732781511, "key": Object { @@ -711,6 +770,7 @@ Array [ "transactionsPerMinute": 1.16666666666667, }, Object { + "agentName": "python", "averageResponseTime": 64681.6666666667, "impact": 0.68355874339377, "key": Object { @@ -723,6 +783,7 @@ Array [ "transactionsPerMinute": 0.4, }, Object { + "agentName": "dotnet", "averageResponseTime": 41416.1428571429, "impact": 0.766127739061111, "key": Object { @@ -735,6 +796,7 @@ Array [ "transactionsPerMinute": 0.7, }, Object { + "agentName": "go", "averageResponseTime": 19429, "impact": 0.821597646656097, "key": Object { @@ -747,6 +809,7 @@ Array [ "transactionsPerMinute": 1.6, }, Object { + "agentName": "dotnet", "averageResponseTime": 62390.652173913, "impact": 1.26497653527507, "key": Object { @@ -759,6 +822,7 @@ Array [ "transactionsPerMinute": 0.766666666666667, }, Object { + "agentName": "python", "averageResponseTime": 33266.2, "impact": 1.76006661931225, "key": Object { @@ -771,6 +835,7 @@ Array [ "transactionsPerMinute": 2, }, Object { + "agentName": "nodejs", "averageResponseTime": 38491.4444444444, "impact": 1.83293391905112, "key": Object { @@ -783,6 +848,7 @@ Array [ "transactionsPerMinute": 1.8, }, Object { + "agentName": "dotnet", "averageResponseTime": 118488.6, "impact": 2.08995781717084, "key": Object { @@ -795,6 +861,7 @@ Array [ "transactionsPerMinute": 0.666666666666667, }, Object { + "agentName": "dotnet", "averageResponseTime": 250440.142857143, "impact": 4.64001412901584, "key": Object { @@ -807,6 +874,7 @@ Array [ "transactionsPerMinute": 0.7, }, Object { + "agentName": "java", "averageResponseTime": 312096.523809524, "impact": 5.782704992387, "key": Object { @@ -819,6 +887,7 @@ Array [ "transactionsPerMinute": 0.7, }, Object { + "agentName": "ruby", "averageResponseTime": 91519.7032967033, "impact": 7.34855500859826, "key": Object { @@ -831,6 +900,7 @@ Array [ "transactionsPerMinute": 3.03333333333333, }, Object { + "agentName": "rum-js", "averageResponseTime": 648269.769230769, "impact": 7.43611473386403, "key": Object { @@ -843,6 +913,7 @@ Array [ "transactionsPerMinute": 0.433333333333333, }, Object { + "agentName": "python", "averageResponseTime": 1398919.72727273, "impact": 13.5790895084132, "key": Object { @@ -855,6 +926,7 @@ Array [ "transactionsPerMinute": 0.366666666666667, }, Object { + "agentName": "rum-js", "averageResponseTime": 1199907.57142857, "impact": 14.8239822181408, "key": Object { @@ -867,6 +939,7 @@ Array [ "transactionsPerMinute": 0.466666666666667, }, Object { + "agentName": "rum-js", "averageResponseTime": 955876.052631579, "impact": 16.026822184214, "key": Object { @@ -879,6 +952,7 @@ Array [ "transactionsPerMinute": 0.633333333333333, }, Object { + "agentName": "go", "averageResponseTime": 965009.526315789, "impact": 16.1799735991728, "key": Object { @@ -891,6 +965,7 @@ Array [ "transactionsPerMinute": 0.633333333333333, }, Object { + "agentName": "rum-js", "averageResponseTime": 1213675.30769231, "impact": 27.8474053933734, "key": Object { @@ -903,6 +978,7 @@ Array [ "transactionsPerMinute": 0.866666666666667, }, Object { + "agentName": "nodejs", "averageResponseTime": 924019.363636364, "impact": 35.8796065162284, "key": Object { @@ -915,6 +991,7 @@ Array [ "transactionsPerMinute": 1.46666666666667, }, Object { + "agentName": "nodejs", "averageResponseTime": 1060469.15384615, "impact": 36.498655556576, "key": Object { @@ -927,6 +1004,7 @@ Array [ "transactionsPerMinute": 1.3, }, Object { + "agentName": "python", "averageResponseTime": 118686.822222222, "impact": 37.7068083771466, "key": Object { @@ -939,6 +1017,7 @@ Array [ "transactionsPerMinute": 12, }, Object { + "agentName": "nodejs", "averageResponseTime": 1039228.27659574, "impact": 43.1048035741496, "key": Object { @@ -951,6 +1030,7 @@ Array [ "transactionsPerMinute": 1.56666666666667, }, Object { + "agentName": "python", "averageResponseTime": 1949922.55555556, "impact": 61.9499776921889, "key": Object { @@ -963,6 +1043,7 @@ Array [ "transactionsPerMinute": 1.2, }, Object { + "agentName": "dotnet", "averageResponseTime": 5963775, "impact": 100, "key": Object { diff --git a/x-pack/test/apm_api_integration/tests/traces/top_traces.spec.ts b/x-pack/test/apm_api_integration/tests/traces/top_traces.spec.ts index 51b14809982d8..06a24cbd34a4b 100644 --- a/x-pack/test/apm_api_integration/tests/traces/top_traces.spec.ts +++ b/x-pack/test/apm_api_integration/tests/traces/top_traces.spec.ts @@ -63,6 +63,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { expectSnapshot(firstItem).toMatchInline(` Object { + "agentName": "java", "averageResponseTime": 1639, "impact": 0, "key": Object { @@ -78,6 +79,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { expectSnapshot(lastItem).toMatchInline(` Object { + "agentName": "dotnet", "averageResponseTime": 5963775, "impact": 100, "key": Object { diff --git a/x-pack/test/apm_api_integration/tests/transactions/transactions_groups_detailed_statistics.spec.ts b/x-pack/test/apm_api_integration/tests/transactions/transactions_groups_detailed_statistics.spec.ts index 72a0cdbbee48f..be60c655ce50d 100644 --- a/x-pack/test/apm_api_integration/tests/transactions/transactions_groups_detailed_statistics.spec.ts +++ b/x-pack/test/apm_api_integration/tests/transactions/transactions_groups_detailed_statistics.spec.ts @@ -4,7 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { service, timerange } from '@elastic/apm-synthtrace'; +import { apm, timerange } from '@elastic/apm-synthtrace'; import expect from '@kbn/expect'; import { first, isEmpty, last, meanBy } from 'lodash'; import moment from 'moment'; @@ -84,9 +84,9 @@ export default function ApiTest({ getService }: FtrProviderContext) { const GO_PROD_RATE = 75; const GO_PROD_ERROR_RATE = 25; before(async () => { - const serviceGoProdInstance = service(serviceName, 'production', 'go').instance( - 'instance-a' - ); + const serviceGoProdInstance = apm + .service(serviceName, 'production', 'go') + .instance('instance-a'); const transactionName = 'GET /api/product/list'; diff --git a/x-pack/test/functional_with_es_ssl/apps/uptime/alert_flyout.ts b/x-pack/test/functional_with_es_ssl/apps/uptime/alert_flyout.ts index d657db443e4ec..1d8a172e57b78 100644 --- a/x-pack/test/functional_with_es_ssl/apps/uptime/alert_flyout.ts +++ b/x-pack/test/functional_with_es_ssl/apps/uptime/alert_flyout.ts @@ -16,7 +16,8 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { const supertest = getService('supertest'); const retry = getService('retry'); - describe('overview page alert flyout controls', function () { + // FLAKY: https://github.com/elastic/kibana/issues/88177 + describe.skip('overview page alert flyout controls', function () { const DEFAULT_DATE_START = 'Sep 10, 2019 @ 12:40:08.078'; const DEFAULT_DATE_END = 'Sep 11, 2019 @ 19:40:08.078'; let alerts: any; @@ -141,7 +142,8 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { }); }); - describe('tls alert', function () { + // FLAKY: https://github.com/elastic/kibana/issues/116865 + describe.skip('tls alert', function () { const DEFAULT_DATE_START = 'Sep 10, 2019 @ 12:40:08.078'; const DEFAULT_DATE_END = 'Sep 11, 2019 @ 19:40:08.078'; let alerts: any; diff --git a/x-pack/test/load/runner.ts b/x-pack/test/load/runner.ts index e9750bd19881a..ba40bcf294e57 100644 --- a/x-pack/test/load/runner.ts +++ b/x-pack/test/load/runner.ts @@ -57,16 +57,20 @@ export async function GatlingTestRunner({ getService }: FtrProviderContext) { const log = getService('log'); await withProcRunner(log, async (procs) => { - await procs.run('node build/index.js', { - cmd: 'node', - args: ['build/index.js'], - cwd: puppeteerProjectRootPath, - env: { - ...process.env, - }, - wait: true, - }); for (let i = 0; i < simulationClasses.length; i++) { + await procs.run('node build/index.js', { + cmd: 'node', + args: [ + 'build/index.js', + `--simulation='${simulationClasses[i]}'`, + `--config='./config.json'`, + ], + cwd: puppeteerProjectRootPath, + env: { + ...process.env, + }, + wait: true, + }); await procs.run('gatling: test', { cmd: 'mvn', args: [ diff --git a/x-pack/test/observability_functional/apps/observability/alerts/index.ts b/x-pack/test/observability_functional/apps/observability/alerts/index.ts index bd5f2ada4990d..4d2f4b971f080 100644 --- a/x-pack/test/observability_functional/apps/observability/alerts/index.ts +++ b/x-pack/test/observability_functional/apps/observability/alerts/index.ts @@ -186,7 +186,7 @@ export default ({ getService }: FtrProviderContext) => { }); }); - describe('Cell actions', () => { + describe.skip('Cell actions', () => { beforeEach(async () => { await retry.try(async () => { const cells = await observability.alerts.common.getTableCells(); diff --git a/x-pack/test/observability_functional/apps/observability/alerts/state_synchronization.ts b/x-pack/test/observability_functional/apps/observability/alerts/state_synchronization.ts index 5a03f72e540b3..c351b45b2ea93 100644 --- a/x-pack/test/observability_functional/apps/observability/alerts/state_synchronization.ts +++ b/x-pack/test/observability_functional/apps/observability/alerts/state_synchronization.ts @@ -39,7 +39,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { await assertAlertsPageState({ kuery: 'kibana.alert.evaluation.threshold > 75', - workflowStatus: 'Closed', + // workflowStatus: 'Closed', timeRange: '~ a month ago - ~ 10 days ago', }); }); @@ -55,7 +55,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { await assertAlertsPageState({ kuery: '', - workflowStatus: 'Open', + // workflowStatus: 'Open', timeRange: 'Last 15 minutes', }); }); @@ -77,15 +77,15 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { async function assertAlertsPageState(expected: { kuery: string; - workflowStatus: string; + // workflowStatus: string; timeRange: string; }) { expect(await (await observability.alerts.common.getQueryBar()).getVisibleText()).to.be( expected.kuery ); - expect(await observability.alerts.common.getWorkflowStatusFilterValue()).to.be( - expected.workflowStatus - ); + // expect(await observability.alerts.common.getWorkflowStatusFilterValue()).to.be( + // expected.workflowStatus + // ); const timeRange = await observability.alerts.common.getTimeRange(); expect(timeRange).to.be(expected.timeRange); } diff --git a/x-pack/test/observability_functional/apps/observability/alerts/workflow_status.ts b/x-pack/test/observability_functional/apps/observability/alerts/workflow_status.ts index 4976c1c225aba..9f6c781306749 100644 --- a/x-pack/test/observability_functional/apps/observability/alerts/workflow_status.ts +++ b/x-pack/test/observability_functional/apps/observability/alerts/workflow_status.ts @@ -13,7 +13,8 @@ const OPEN_ALERTS_ROWS_COUNT = 33; export default ({ getService }: FtrProviderContext) => { const esArchiver = getService('esArchiver'); - describe('alert workflow status', function () { + // Keep the Workflow status code commented (no delete) as requested: https://github.com/elastic/kibana/issues/117686 + describe.skip('alert workflow status', function () { this.tags('includeFirefox'); const observability = getService('observability'); diff --git a/x-pack/test/saved_object_api_integration/common/suites/bulk_create.ts b/x-pack/test/saved_object_api_integration/common/suites/bulk_create.ts index 45a96f8ebd8b4..d53afdbddd6ed 100644 --- a/x-pack/test/saved_object_api_integration/common/suites/bulk_create.ts +++ b/x-pack/test/saved_object_api_integration/common/suites/bulk_create.ts @@ -109,17 +109,30 @@ export function bulkCreateTestSuiteFactory(esArchiver: any, supertest: SuperTest const { type, id } = testCase; expect(object.type).to.eql(type); expect(object.id).to.eql(id); - let metadata; + let expectedMetadata; if (testCase.fail409Param === 'unresolvableConflict') { - metadata = { isNotOverwritable: true }; + expectedMetadata = { isNotOverwritable: true }; } else if (testCase.fail409Param === 'aliasConflictSpace1') { - metadata = { spacesWithConflictingAliases: ['space_1'] }; + expectedMetadata = { spacesWithConflictingAliases: ['space_1'] }; } else if (testCase.fail409Param === 'aliasConflictAllSpaces') { - metadata = { spacesWithConflictingAliases: ['space_1', 'space_x'] }; + expectedMetadata = { spacesWithConflictingAliases: ['space_1', 'space_x'] }; + } + const expectedError = SavedObjectsErrorHelpers.createConflictError(type, id).output + .payload; + expect(object.error).be.an('object'); + expect(object.error.statusCode).to.eql(expectedError.statusCode); + expect(object.error.error).to.eql(expectedError.error); + expect(object.error.message).to.eql(expectedError.message); + if (expectedMetadata) { + const actualMetadata = object.error.metadata ?? {}; + if (actualMetadata.spacesWithConflictingAliases) { + actualMetadata.spacesWithConflictingAliases = + actualMetadata.spacesWithConflictingAliases.sort(); + } + expect(actualMetadata).to.eql(expectedMetadata); + } else { + expect(object.error.metadata).to.be(undefined); } - const error = SavedObjectsErrorHelpers.createConflictError(type, id); - const payload = { ...error.output.payload, ...(metadata && { metadata }) }; - expect(object.error).to.eql(payload); continue; } await expectResponses.permitted(object, testCase); diff --git a/x-pack/test/security_functional/fixtures/common/test_endpoints/public/plugin.tsx b/x-pack/test/security_functional/fixtures/common/test_endpoints/public/plugin.tsx index b0264998db17d..745a8852968c0 100644 --- a/x-pack/test/security_functional/fixtures/common/test_endpoints/public/plugin.tsx +++ b/x-pack/test/security_functional/fixtures/common/test_endpoints/public/plugin.tsx @@ -8,21 +8,34 @@ import type { CoreSetup, Plugin } from 'src/core/public'; import ReactDOM from 'react-dom'; import React from 'react'; +import { debounce, filter, first } from 'rxjs/operators'; +import { timer } from 'rxjs'; export class TestEndpointsPlugin implements Plugin { public setup(core: CoreSetup) { // Prevent auto-logout on server `401` errors. core.http.anonymousPaths.register('/authentication/app'); + + const networkIdle$ = core.http.getLoadingCount$().pipe( + debounce(() => timer(3000)), + filter((count) => count === 0), + first() + ); + core.application.register({ id: 'authentication_app', title: 'Authentication app', appRoute: '/authentication/app', chromeless: true, async mount({ element }) { - ReactDOM.render( -
Authenticated!
, - element - ); + // Promise is resolved as soon there are no requests has been made in the last 3 seconds. We need this to make + // sure none of the unrelated requests interferes with the test logic. + networkIdle$.toPromise().then(() => { + ReactDOM.render( +
Authenticated!
, + element + ); + }); return () => ReactDOM.unmountComponentAtNode(element); }, }); diff --git a/x-pack/test/security_solution_endpoint/apps/endpoint/policy_details.ts b/x-pack/test/security_solution_endpoint/apps/endpoint/policy_details.ts index 61773aaf825fa..7562f69f673c9 100644 --- a/x-pack/test/security_solution_endpoint/apps/endpoint/policy_details.ts +++ b/x-pack/test/security_solution_endpoint/apps/endpoint/policy_details.ts @@ -337,7 +337,8 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { }); }); - describe('and the save button is clicked', () => { + // FLAKY: https://github.com/elastic/kibana/issues/92567 + describe.skip('and the save button is clicked', () => { let policyInfo: PolicyTestResourceInfo; beforeEach(async () => { diff --git a/x-pack/test/upgrade/apps/dashboard/dashboard_smoke_tests.ts b/x-pack/test/upgrade/apps/dashboard/dashboard_smoke_tests.ts index c983361071170..2f35f0e1e12d3 100644 --- a/x-pack/test/upgrade/apps/dashboard/dashboard_smoke_tests.ts +++ b/x-pack/test/upgrade/apps/dashboard/dashboard_smoke_tests.ts @@ -25,7 +25,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { ]; const dashboardTests = [ - { name: 'flights', numPanels: 17 }, + { name: 'flights', numPanels: 16 }, { name: 'logs', numPanels: 10 }, { name: 'ecommerce', numPanels: 11 }, ]; diff --git a/x-pack/test/upgrade/apps/maps/maps_smoke_tests.ts b/x-pack/test/upgrade/apps/maps/maps_smoke_tests.ts index 53acb8b016313..22e081e88bfc4 100644 --- a/x-pack/test/upgrade/apps/maps/maps_smoke_tests.ts +++ b/x-pack/test/upgrade/apps/maps/maps_smoke_tests.ts @@ -167,7 +167,7 @@ export default function ({ ); await PageObjects.header.waitUntilLoadingHasFinished(); await PageObjects.maps.waitForLayersToLoad(); - await PageObjects.maps.toggleLayerVisibility('Road map - desaturated'); + await PageObjects.maps.toggleLayerVisibility('Road map'); await PageObjects.maps.toggleLayerVisibility('Total Requests by Country'); await PageObjects.timePicker.setCommonlyUsedTime('sample_data range'); await PageObjects.maps.enterFullScreen(); diff --git a/yarn.lock b/yarn.lock index 44185306ca38f..71b82ffdf5af6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5259,10 +5259,10 @@ resolved "https://registry.yarnpkg.com/@types/classnames/-/classnames-2.2.9.tgz#d868b6febb02666330410fe7f58f3c4b8258be7b" integrity sha512-MNl+rT5UmZeilaPxAVs6YaPC2m6aA8rofviZbhbxpPpl61uKodfdQVsBtgJGTqGizEf02oW3tsVe7FYB8kK14A== -"@types/clone@~2.1.0": - version "2.1.0" - resolved "https://registry.yarnpkg.com/@types/clone/-/clone-2.1.0.tgz#cb888a3fe5319275b566ae3a9bc606e310c533d4" - integrity sha512-d/aS/lPOnUSruPhgNtT8jW39fHRVTLQy9sodysP1kkG8EdAtdZu1vt8NJaYA8w/6Z9j8izkAsx1A/yJhcYR1CA== +"@types/clone@~2.1.1": + version "2.1.1" + resolved "https://registry.yarnpkg.com/@types/clone/-/clone-2.1.1.tgz#9b880d0ce9b1f209b5e0bd6d9caa38209db34024" + integrity sha512-BZIU34bSYye0j/BFcPraiDZ5ka6MJADjcDVELGf7glr9K+iE8NYVjFslJFVWzskSxkLLyCrSPScE82/UUoBSvg== "@types/cmd-shim@^2.0.0": version "2.0.0" @@ -5424,7 +5424,7 @@ "@types/estree" "*" "@types/json-schema" "*" -"@types/estree@*": +"@types/estree@*", "@types/estree@^0.0.50": version "0.0.50" resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.50.tgz#1e0caa9364d3fccd2931c3ed96fdbeaa5d4cca83" integrity sha512-C6N5s2ZFtuZRj54k2/zyRhNDjJwwcViAM3Nbm8zjBpbqAdZ00mr0CFxvSKeO8Y/e03WVFLpQMdHYVfUd6SB+Hw== @@ -5468,11 +5468,6 @@ resolved "https://registry.yarnpkg.com/@types/fancy-log/-/fancy-log-1.3.1.tgz#dd94fbc8c2e2ab8ab402ca8d04bb8c34965f0696" integrity sha512-31Dt9JaGfHretvwVxCBrCFL5iC9MQ3zOXpu+8C4qzW0cxc5rJJVGxB5c/vZ+wmeTk/JjPz/D0gv8BZ+Ip6iCqQ== -"@types/fast-json-stable-stringify@^2.0.0": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@types/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz#40363bb847cb86b2c2e1599f1398d11e8329c921" - integrity sha512-mky/O83TXmGY39P1H9YbUpjV6l6voRYlufqfFCvel8l1phuy8HRjdWc1rrPuN53ITBJlbyMSV6z3niOySO5pgQ== - "@types/fetch-mock@^7.3.1": version "7.3.1" resolved "https://registry.yarnpkg.com/@types/fetch-mock/-/fetch-mock-7.3.1.tgz#df7421e8bcb351b430bfbfa5c52bb353826ac94f" @@ -5814,6 +5809,10 @@ version "0.0.0" uid "" +"@types/kbn__alerts@link:bazel-bin/packages/kbn-alerts/npm_module_types": + version "0.0.0" + uid "" + "@types/kbn__i18n-react@link:bazel-bin/packages/kbn-i18n-react/npm_module_types": version "0.0.0" uid "" @@ -27558,16 +27557,11 @@ tslib@^2.0.0, tslib@^2.0.1, tslib@^2.2.0: resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.2.0.tgz#fb2c475977e35e241311ede2693cee1ec6698f5c" integrity sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w== -tslib@^2.3.0: +tslib@^2.3.0, tslib@~2.3.1: version "2.3.1" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.1.tgz#e8a335add5ceae51aa261d32a490158ef042ef01" integrity sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw== -tslib@~2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.1.0.tgz#da60860f1c2ecaa5703ab7d39bc05b6bf988b97a" - integrity sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A== - tsutils@2.27.2: version "2.27.2" resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-2.27.2.tgz#60ba88a23d6f785ec4b89c6e8179cac9b431f1c7" @@ -28574,14 +28568,14 @@ vega-crossfilter@~4.0.5: vega-dataflow "^5.7.3" vega-util "^1.15.2" -vega-dataflow@^5.7.3, vega-dataflow@~5.7.3: - version "5.7.3" - resolved "https://registry.yarnpkg.com/vega-dataflow/-/vega-dataflow-5.7.3.tgz#66ca06a61f72a210b0732e3b6cc1eec5117197f7" - integrity sha512-2ipzKgQUmbSXcQBH+9XF0BYbXyZrHvjlbJ8ifyRWYQk78w8kMvE6wy/rcdXYK6iVZ6aAbEDDT7jTI+rFt3tGLA== +vega-dataflow@^5.7.3, vega-dataflow@^5.7.4, vega-dataflow@~5.7.4: + version "5.7.4" + resolved "https://registry.yarnpkg.com/vega-dataflow/-/vega-dataflow-5.7.4.tgz#7cafc0a41b9d0b11dd2e34a513f8b7ca345dfd74" + integrity sha512-JGHTpUo8XGETH3b1V892we6hdjzCWB977ybycIu8DPqRoyrZuj6t1fCVImazfMgQD1LAfJlQybWP+alwKDpKig== dependencies: vega-format "^1.0.4" vega-loader "^4.3.2" - vega-util "^1.15.2" + vega-util "^1.16.1" vega-encode@~4.8.3: version "4.8.3" @@ -28594,16 +28588,17 @@ vega-encode@~4.8.3: vega-scale "^7.0.3" vega-util "^1.15.2" -vega-event-selector@^2.0.6, vega-event-selector@~2.0.6: - version "2.0.6" - resolved "https://registry.yarnpkg.com/vega-event-selector/-/vega-event-selector-2.0.6.tgz#6beb00e066b78371dde1a0f40cb5e0bbaecfd8bc" - integrity sha512-UwCu50Sqd8kNZ1X/XgiAY+QAyQUmGFAwyDu7y0T5fs6/TPQnDo/Bo346NgSgINBEhEKOAMY1Nd/rPOk4UEm/ew== +vega-event-selector@^3.0.0, vega-event-selector@~3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/vega-event-selector/-/vega-event-selector-3.0.0.tgz#7b855ac0c3ddb59bc5b5caa0d96dbbc9fbd33a4c" + integrity sha512-Gls93/+7tEJGE3kUuUnxrBIxtvaNeF01VIFB2Q2Of2hBIBvtHX74jcAdDtkh5UhhoYGD8Q1J30P5cqEBEwtPoQ== -vega-expression@^4.0.1, vega-expression@~4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/vega-expression/-/vega-expression-4.0.1.tgz#c03e4fc68a00acac49557faa4e4ed6ac8a59c5fd" - integrity sha512-ZrDj0hP8NmrCpdLFf7Rd/xMUHGoSYsAOTaYp7uXZ2dkEH5x0uPy5laECMc8TiQvL8W+8IrN2HAWCMRthTSRe2Q== +vega-expression@^5.0.0, vega-expression@~5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/vega-expression/-/vega-expression-5.0.0.tgz#938f26689693a1e0d26716030cdaed43ca7abdfb" + integrity sha512-y5+c2frq0tGwJ7vYXzZcfVcIRF/QGfhf2e+bV1Z0iQs+M2lI1II1GPDdmOcMKimpoCVp/D61KUJDIGE1DSmk2w== dependencies: + "@types/estree" "^0.0.50" vega-util "^1.16.0" vega-force@~4.0.7: @@ -28626,19 +28621,19 @@ vega-format@^1.0.4, vega-format@~1.0.4: vega-time "^2.0.3" vega-util "^1.15.2" -vega-functions@^5.10.0, vega-functions@^5.12.0, vega-functions@~5.12.0: - version "5.12.0" - resolved "https://registry.yarnpkg.com/vega-functions/-/vega-functions-5.12.0.tgz#44bf08a7b20673dc8cf51d6781c8ea1399501668" - integrity sha512-3hljmGs+gR7TbO/yYuvAP9P5laKISf1GKk4yRHLNdM61fWgKm8pI3f6LY2Hvq9cHQFTiJ3/5/Bx2p1SX5R4quQ== +vega-functions@^5.10.0, vega-functions@^5.12.1, vega-functions@~5.12.1: + version "5.12.1" + resolved "https://registry.yarnpkg.com/vega-functions/-/vega-functions-5.12.1.tgz#b69f9ad4cd9f777dbc942587c02261b2f4cdba2c" + integrity sha512-7cHfcjXOj27qEbh2FTzWDl7FJK4xGcMFF7+oiyqa0fp7BU/wNT5YdNV0t5kCX9WjV7mfJWACKV74usLJbyM6GA== dependencies: d3-array "^2.7.1" d3-color "^2.0.0" d3-geo "^2.0.1" vega-dataflow "^5.7.3" - vega-expression "^4.0.1" + vega-expression "^5.0.0" vega-scale "^7.1.1" vega-scenegraph "^4.9.3" - vega-selections "^5.3.0" + vega-selections "^5.3.1" vega-statistics "^1.7.9" vega-time "^2.0.4" vega-util "^1.16.0" @@ -28671,38 +28666,37 @@ vega-interpreter@^1.0.4: resolved "https://registry.yarnpkg.com/vega-interpreter/-/vega-interpreter-1.0.4.tgz#291ebf85bc2d1c3550a3da22ff75b3ba0d326a39" integrity sha512-6tpYIa/pJz0cZo5fSxDSkZkAA51pID2LjOtQkOQvbzn+sJiCaWKPFhur8MBqbcmYZ9bnap1OYNwlrvpd2qBLvg== -vega-label@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/vega-label/-/vega-label-1.0.0.tgz#c3bea3a608a62217ca554ecc0f7fe0395d81bd1b" - integrity sha512-hCdm2pcHgkKgxnzW9GvX5JmYNiUMlOXOibtMmBzvFBQHX3NiV9giQ5nsPiQiFbV08VxEPtM+VYXr2HyrIcq5zQ== +vega-label@~1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/vega-label/-/vega-label-1.1.0.tgz#0a11ae3ba18d7aed909c51ec67c2a9dde4426c6f" + integrity sha512-LAThIiDEsZxYvbSkvPLJ93eJF+Ts8RXv1IpBh8gmew8XGmaLJvVkzdsMe7WJJwuaVEsK7ZZFyB/Inkp842GW6w== dependencies: vega-canvas "^1.2.5" vega-dataflow "^5.7.3" vega-scenegraph "^4.9.2" vega-util "^1.15.2" -vega-lite@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/vega-lite/-/vega-lite-5.0.0.tgz#93898a910702736da41048f590882b907d78ac65" - integrity sha512-CrMAy3D2E662qtShrOeGttwwthRxUOZUfdu39THyxkOfLNJBCLkNjfQpFekEidxwbtFTO1zMZzyFIP3AE2I8kQ== +vega-lite@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/vega-lite/-/vega-lite-5.2.0.tgz#bc3c5c70a38d9de8f3fb9644c7dd52f3b9f47a1b" + integrity sha512-Yxcg8MvYfxHcG6BbkaKT0oVCIMIcE19UvqIsEwBmyd/7h2nzW7oRnID81T8UrY7hpDrIr6wa2JADOT2dhGNErw== dependencies: - "@types/clone" "~2.1.0" - "@types/fast-json-stable-stringify" "^2.0.0" + "@types/clone" "~2.1.1" array-flat-polyfill "^1.0.1" clone "~2.1.2" fast-deep-equal "~3.1.3" fast-json-stable-stringify "~2.1.0" json-stringify-pretty-compact "~3.0.0" - tslib "~2.1.0" - vega-event-selector "~2.0.6" - vega-expression "~4.0.1" - vega-util "~1.16.0" - yargs "~16.2.0" + tslib "~2.3.1" + vega-event-selector "~3.0.0" + vega-expression "~5.0.0" + vega-util "~1.17.0" + yargs "~17.2.1" -vega-loader@^4.3.2, vega-loader@^4.3.3, vega-loader@~4.4.0: - version "4.4.0" - resolved "https://registry.yarnpkg.com/vega-loader/-/vega-loader-4.4.0.tgz#fc515b7368c46b2be8df1fcf3c35c696c13c453d" - integrity sha512-e5enQECdau7rJob0NFB5pGumh3RaaSWWm90+boxMy3ay2b4Ki/3XIvo+C4F1Lx04qSxvQF7tO2LJcklRm6nqRA== +vega-loader@^4.3.2, vega-loader@^4.3.3, vega-loader@~4.4.1: + version "4.4.1" + resolved "https://registry.yarnpkg.com/vega-loader/-/vega-loader-4.4.1.tgz#8f9de46202f33659d1a2737f6e322a9fc3364275" + integrity sha512-dj65i4qlNhK0mOmjuchHgUrF5YUaWrYpx0A8kXA68lBk5Hkx8FNRztkcl07CZJ1+8V81ymEyJii9jzGbhEX0ag== dependencies: d3-dsv "^2.0.0" node-fetch "^2.6.1" @@ -28710,14 +28704,14 @@ vega-loader@^4.3.2, vega-loader@^4.3.3, vega-loader@~4.4.0: vega-format "^1.0.4" vega-util "^1.16.0" -vega-parser@~6.1.3: - version "6.1.3" - resolved "https://registry.yarnpkg.com/vega-parser/-/vega-parser-6.1.3.tgz#df72785e4b086eceb90ee6219a399210933b507b" - integrity sha512-8oiVhhW26GQ4GZBvolId8FVFvhn3s1KGgPlD7Z+4P2wkV+xe5Nqu0TEJ20F/cn3b88fd0Vj48X3BH3dlSeKNFg== +vega-parser@~6.1.4: + version "6.1.4" + resolved "https://registry.yarnpkg.com/vega-parser/-/vega-parser-6.1.4.tgz#4868e41af2c9645b6d7daeeb205cfad06b9d465c" + integrity sha512-tORdpWXiH/kkXcpNdbSVEvtaxBuuDtgYp9rBunVW9oLsjFvFXbSWlM1wvJ9ZFSaTfx6CqyTyGMiJemmr1QnTjQ== dependencies: vega-dataflow "^5.7.3" - vega-event-selector "^2.0.6" - vega-functions "^5.12.0" + vega-event-selector "^3.0.0" + vega-functions "^5.12.1" vega-scale "^7.1.1" vega-util "^1.16.0" @@ -28758,10 +28752,10 @@ vega-scale@^7.0.3, vega-scale@^7.1.1, vega-scale@~7.1.1: vega-time "^2.0.4" vega-util "^1.15.2" -vega-scenegraph@^4.9.2, vega-scenegraph@^4.9.3, vega-scenegraph@~4.9.3: - version "4.9.3" - resolved "https://registry.yarnpkg.com/vega-scenegraph/-/vega-scenegraph-4.9.3.tgz#c4720550ea7ff5c8d9d0690f47fe2640547cfc6b" - integrity sha512-lBvqLbXqrqRCTGJmSgzZC/tLR/o+TXfakbdhDzNdpgTavTaQ65S/67Gpj5hPpi77DvsfZUIY9lCEeO37aJhy0Q== +vega-scenegraph@^4.9.2, vega-scenegraph@^4.9.3, vega-scenegraph@^4.9.4, vega-scenegraph@~4.9.4: + version "4.9.4" + resolved "https://registry.yarnpkg.com/vega-scenegraph/-/vega-scenegraph-4.9.4.tgz#468408c1e89703fa9d3450445daabff623de2757" + integrity sha512-QaegQzbFE2yhYLNWAmHwAuguW3yTtQrmwvfxYT8tk0g+KKodrQ5WSmNrphWXhqwtsgVSvtdZkfp2IPeumcOQJg== dependencies: d3-path "^2.0.0" d3-shape "^2.0.0" @@ -28770,17 +28764,17 @@ vega-scenegraph@^4.9.2, vega-scenegraph@^4.9.3, vega-scenegraph@~4.9.3: vega-scale "^7.1.1" vega-util "^1.15.2" -vega-schema-url-parser@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/vega-schema-url-parser/-/vega-schema-url-parser-2.1.0.tgz#847f9cf9f1624f36f8a51abc1adb41ebc6673cb4" - integrity sha512-JHT1PfOyVzOohj89uNunLPirs05Nf59isPT5gnwIkJph96rRgTIBJE7l7yLqndd7fLjr3P8JXHGAryRp74sCaQ== +vega-schema-url-parser@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/vega-schema-url-parser/-/vega-schema-url-parser-2.2.0.tgz#a0d1e02915adfbfcb1fd517c8c2ebe2419985c1e" + integrity sha512-yAtdBnfYOhECv9YC70H2gEiqfIbVkq09aaE4y/9V/ovEFmH9gPKaEgzIZqgT7PSPQjKhsNkb6jk6XvSoboxOBw== -vega-selections@^5.3.0: - version "5.3.0" - resolved "https://registry.yarnpkg.com/vega-selections/-/vega-selections-5.3.0.tgz#810f2e7b7642fa836cf98b2e5dcc151093b1f6a7" - integrity sha512-vC4NPsuN+IffruFXfH0L3i2A51RgG4PqpLv85TvrEAIYnSkyKDE4bf+wVraR3aPdnLLkc3+tYuMi6le5FmThIA== +vega-selections@^5.3.1: + version "5.3.1" + resolved "https://registry.yarnpkg.com/vega-selections/-/vega-selections-5.3.1.tgz#af5c3cc6532a55a5b692eb0fcc2a1d8d521605a4" + integrity sha512-cm4Srw1WHjcLGXX7GpxiUlfESv8XPu5b6Vh3mqMDPU94P2FO91SR9gei+EtRdt+KCFgIjr//MnRUjg/hAWwjkQ== dependencies: - vega-expression "^4.0.1" + vega-expression "^5.0.0" vega-util "^1.16.0" vega-spec-injector@^0.0.2: @@ -28788,10 +28782,10 @@ vega-spec-injector@^0.0.2: resolved "https://registry.yarnpkg.com/vega-spec-injector/-/vega-spec-injector-0.0.2.tgz#f1d990109dd9d845c524738f818baa4b72a60ca6" integrity sha512-wOMMqmpssn0/ZFPW7wl1v26vbseRX7zHPWzEyS9TwNXTRCu1TcjIBIR+X23lCWocxhoBqFxmqyn8UowMhlGtAg== -vega-statistics@^1.7.9, vega-statistics@~1.7.9: - version "1.7.9" - resolved "https://registry.yarnpkg.com/vega-statistics/-/vega-statistics-1.7.9.tgz#feec01d463e1b50593d890d20631f72138fcb65d" - integrity sha512-T0sd2Z08k/mHxr1Vb4ajLWytPluLFYnsYqyk4SIS5czzUs4errpP2gUu63QJ0B7CKNu33vnS9WdOMOo/Eprr/Q== +vega-statistics@^1.7.9, vega-statistics@~1.7.10: + version "1.7.10" + resolved "https://registry.yarnpkg.com/vega-statistics/-/vega-statistics-1.7.10.tgz#4353637402e5e96bff2ebd16bd58e2c15cac3018" + integrity sha512-QLb12gcfpDZ9K5h3TLGrlz4UXDH9wSPyg9LLfOJZacxvvJEPohacUQNrGEAVtFO9ccUCerRfH9cs25ZtHsOZrw== dependencies: d3-array "^2.7.1" @@ -28804,35 +28798,37 @@ vega-time@^2.0.3, vega-time@^2.0.4, vega-time@~2.0.4: d3-time "^2.0.0" vega-util "^1.15.2" -vega-tooltip@^0.25.1: - version "0.25.1" - resolved "https://registry.yarnpkg.com/vega-tooltip/-/vega-tooltip-0.25.1.tgz#cb7e438438649eb46896e7bee6f54e25d25b3c09" - integrity sha512-ugGwGi2/p3OpB8N15xieuzP8DyV5DreqMWcmJ9zpWT8GlkyKtef4dGRXnvHeHQ+iJFmWrq4oZJ+kLTrdiECjAg== +vega-tooltip@^0.27.0: + version "0.27.0" + resolved "https://registry.yarnpkg.com/vega-tooltip/-/vega-tooltip-0.27.0.tgz#e03c150cdec78f68938a0dab5ef67a24e6d685da" + integrity sha512-FRcHNfMNo9D/7an5nZuP6JC2JGEsc85qcGjyMU7VlPpjQj9eBj1P+sZSNbb54Z20g7inVSBRyd8qgNn5EYTxJA== dependencies: vega-util "^1.16.0" -vega-transforms@~4.9.3: - version "4.9.3" - resolved "https://registry.yarnpkg.com/vega-transforms/-/vega-transforms-4.9.3.tgz#40e5234b956a68eaa03eedf489ed03293075bbfb" - integrity sha512-PdqQd5oPlRyD405M2w+Sz9Bo+i7Rwi8o03SVK7RaeQsJC2FffKGJ6acIaSEgOq+yD1Q2k/1SePmCXcmLUlIiEA== +vega-transforms@~4.9.4: + version "4.9.4" + resolved "https://registry.yarnpkg.com/vega-transforms/-/vega-transforms-4.9.4.tgz#5cf6b91bda9f184bbbaba63838be8e5e6a571235" + integrity sha512-JGBhm5Bf6fiGTUSB5Qr5ckw/KU9FJcSV5xIe/y4IobM/i/KNwI1i1fP45LzP4F4yZc0DMTwJod2UvFHGk9plKA== dependencies: d3-array "^2.7.1" - vega-dataflow "^5.7.3" + vega-dataflow "^5.7.4" vega-statistics "^1.7.9" vega-time "^2.0.4" - vega-util "^1.15.2" + vega-util "^1.16.1" -vega-typings@~0.19.2: - version "0.19.2" - resolved "https://registry.yarnpkg.com/vega-typings/-/vega-typings-0.19.2.tgz#374fc1020c1abb263a0be87de28d1a4bd0526c3f" - integrity sha512-YU/S9rDk4d+t4+4eTa9fzuw87PMNteeVtpcL51kUO8H7HvGaoW7ll8RHKLkR0NYBEGPRoFDKUxnoyMvhgjsdYw== +vega-typings@~0.22.0: + version "0.22.1" + resolved "https://registry.yarnpkg.com/vega-typings/-/vega-typings-0.22.1.tgz#287c646cfa93b1822d0fb6ea11d5543632f8b56e" + integrity sha512-88cIrjmoTxo/0nWTf+GuitkFhirHWVWCfymADiCUXt6s9arpQ6XPP5xjrN5KDc0LZd9xr7p4FIiEgADghgLTgw== dependencies: + vega-event-selector "^3.0.0" + vega-expression "^5.0.0" vega-util "^1.15.2" -vega-util@^1.15.2, vega-util@^1.16.0, vega-util@~1.16.0: - version "1.16.0" - resolved "https://registry.yarnpkg.com/vega-util/-/vega-util-1.16.0.tgz#77405d8df0a94944d106bdc36015f0d43aa2caa3" - integrity sha512-6mmz6mI+oU4zDMeKjgvE2Fjz0Oh6zo6WGATcvCfxH2gXBzhBHmy5d25uW5Zjnkc6QBXSWPLV9Xa6SiqMsrsKog== +vega-util@^1.15.2, vega-util@^1.16.0, vega-util@^1.16.1, vega-util@~1.17.0: + version "1.17.0" + resolved "https://registry.yarnpkg.com/vega-util/-/vega-util-1.17.0.tgz#b72ae0baa97f943bf591f8f5bb27ceadf06834ac" + integrity sha512-HTaydZd9De3yf+8jH66zL4dXJ1d1p5OIFyoBzFiOli4IJbwkL1jrefCKz6AHDm1kYBzDJ0X4bN+CzZSCTvNk1w== vega-view-transforms@~4.5.8: version "4.5.8" @@ -28843,10 +28839,10 @@ vega-view-transforms@~4.5.8: vega-scenegraph "^4.9.2" vega-util "^1.15.2" -vega-view@~5.9.2: - version "5.9.2" - resolved "https://registry.yarnpkg.com/vega-view/-/vega-view-5.9.2.tgz#cb957e481a952abbe7b3a11aa2d58cc728f295e7" - integrity sha512-XAwKWyVjLClR3aCbTLCWdZj7aZozOULNg7078GxJIgVcBJOENCAidceI/H7JieyUZ96p3AiEHLQdWr167InBpg== +vega-view@~5.10.1: + version "5.10.1" + resolved "https://registry.yarnpkg.com/vega-view/-/vega-view-5.10.1.tgz#b69348bb32a9845a1bd341fdd946df98684fadc3" + integrity sha512-4xvQ5KZcgKdZx1Z7jjenCUumvlyr/j4XcHLRf9gyeFrFvvS596dVpL92V8twhV6O++DmS2+fj+rHagO8Di4nMg== dependencies: d3-array "^2.7.1" d3-timer "^2.0.0" @@ -28854,8 +28850,8 @@ vega-view@~5.9.2: vega-format "^1.0.4" vega-functions "^5.10.0" vega-runtime "^6.1.3" - vega-scenegraph "^4.9.2" - vega-util "^1.15.2" + vega-scenegraph "^4.9.4" + vega-util "^1.16.1" vega-voronoi@~4.1.5: version "4.1.5" @@ -28877,35 +28873,35 @@ vega-wordcloud@~4.1.3: vega-statistics "^1.7.9" vega-util "^1.15.2" -vega@^5.19.1: - version "5.19.1" - resolved "https://registry.yarnpkg.com/vega/-/vega-5.19.1.tgz#64c8350740fe1a11d56cc6617ab3a76811fd704c" - integrity sha512-UE6/c9q9kzuz4HULFuU9HscBASoZa+zcXqGKdbQP545Nwmhd078QpcH+wZsq9lYfiTxmFtzLK/a0OH0zhkghvA== +vega@^5.21.0: + version "5.21.0" + resolved "https://registry.yarnpkg.com/vega/-/vega-5.21.0.tgz#f3d858d7544bfe4ffa3d8cd43d9ea978bf7391e8" + integrity sha512-yqqRa9nAqYoAxe7sVhRpsh0b001fly7Yx05klPkXmrvzjxXd07gClW1mOuGgSnVQqo7jTp/LYgbO1bD37FbEig== dependencies: vega-crossfilter "~4.0.5" - vega-dataflow "~5.7.3" + vega-dataflow "~5.7.4" vega-encode "~4.8.3" - vega-event-selector "~2.0.6" - vega-expression "~4.0.1" + vega-event-selector "~3.0.0" + vega-expression "~5.0.0" vega-force "~4.0.7" vega-format "~1.0.4" - vega-functions "~5.12.0" + vega-functions "~5.12.1" vega-geo "~4.3.8" vega-hierarchy "~4.0.9" - vega-label "~1.0.0" - vega-loader "~4.4.0" - vega-parser "~6.1.3" + vega-label "~1.1.0" + vega-loader "~4.4.1" + vega-parser "~6.1.4" vega-projection "~1.4.5" vega-regression "~1.0.9" vega-runtime "~6.1.3" vega-scale "~7.1.1" - vega-scenegraph "~4.9.3" - vega-statistics "~1.7.9" + vega-scenegraph "~4.9.4" + vega-statistics "~1.7.10" vega-time "~2.0.4" - vega-transforms "~4.9.3" - vega-typings "~0.19.2" - vega-util "~1.16.0" - vega-view "~5.9.2" + vega-transforms "~4.9.4" + vega-typings "~0.22.0" + vega-util "~1.17.0" + vega-view "~5.10.1" vega-view-transforms "~4.5.8" vega-voronoi "~4.1.5" vega-wordcloud "~4.1.3" @@ -29881,7 +29877,7 @@ yargs@^15.0.2, yargs@^15.3.1, yargs@^15.4.1: y18n "^4.0.0" yargs-parser "^18.1.2" -yargs@^16.2.0, yargs@~16.2.0: +yargs@^16.2.0: version "16.2.0" resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66" integrity sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw== @@ -29939,6 +29935,19 @@ yargs@^7.1.0: y18n "^3.2.1" yargs-parser "5.0.0-security.0" +yargs@~17.2.1: + version "17.2.1" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.2.1.tgz#e2c95b9796a0e1f7f3bf4427863b42e0418191ea" + integrity sha512-XfR8du6ua4K6uLGm5S6fA+FIJom/MdJcFNVY8geLlp2v8GYbOXD4EB1tPNZsRn4vBzKGMgb5DRZMeWuFc2GO8Q== + dependencies: + cliui "^7.0.2" + escalade "^3.1.1" + get-caller-file "^2.0.5" + require-directory "^2.1.1" + string-width "^4.2.0" + y18n "^5.0.5" + yargs-parser "^20.2.2" + yargs@~3.10.0: version "3.10.0" resolved "https://registry.yarnpkg.com/yargs/-/yargs-3.10.0.tgz#f7ee7bd857dd7c1d2d38c0e74efbd681d1431fd1"