diff --git a/config/kibana.yml b/config/kibana.yml index f6f85f057172c7..aedea8ce83bfb7 100644 --- a/config/kibana.yml +++ b/config/kibana.yml @@ -99,7 +99,7 @@ # Logs queries sent to Elasticsearch. #logging.loggers: -# - name: elasticsearch.queries +# - name: elasticsearch.query # level: debug # Logs http responses. diff --git a/dev_docs/tutorials/data/search.mdx b/dev_docs/tutorials/data/search.mdx index 425736ddb03bbb..0787c44b632ec8 100644 --- a/dev_docs/tutorials/data/search.mdx +++ b/dev_docs/tutorials/data/search.mdx @@ -129,6 +129,12 @@ setTimeout(() => { }, 1000); ``` + + Users might no longer be interested in search results. For example, they might start a new search + or leave your app without waiting for the results. You should handle such cases by using + `AbortController` with search API. + + #### Search strategies By default, the search service uses the DSL query and aggregation syntax and returns the response from Elasticsearch as is. It also provides several additional basic strategies, such as Async DSL (`x-pack` default) and EQL. diff --git a/docs/api/saved-objects/import.asciidoc b/docs/api/saved-objects/import.asciidoc index 923482954aa2e2..a214598af31afe 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 7eb95405db6bcb..954581faa24607 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 eac26beee1f9b1..457251e62ae8d2 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 61113b5491c297..54137d1f9f2cfd 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 116bcd6f91f774..b379c0ac279e57 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 5c2c1d53175432..7669b9b6449169 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 cbfe53d3eaea0a..6aa528d4f04d14 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 03e40c7cc6cef8..2667729f4b8542 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 00000000000000..229ed0f490b41f 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 c1b673f1ab886f..00000000000000 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 59dd36a4fa5efe..8936e41762c69f 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 cb8237c5aa3841..a9053b90ce722d 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 23a6d1fbcfd3d5..1b0bbf866b8526 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/examples/search_examples/public/search/app.tsx b/examples/search_examples/public/search/app.tsx index 1f8cda9443fa7d..eeceab569d3b3c 100644 --- a/examples/search_examples/public/search/app.tsx +++ b/examples/search_examples/public/search/app.tsx @@ -47,6 +47,7 @@ import { isErrorResponse, } from '../../../../src/plugins/data/public'; import { IMyStrategyResponse } from '../../common/types'; +import { AbortError } from '../../../../src/plugins/kibana_utils/common'; interface SearchExamplesAppDeps { notifications: CoreStart['notifications']; @@ -102,6 +103,8 @@ export const SearchExamplesApp = ({ IndexPatternField | null | undefined >(); const [request, setRequest] = useState>({}); + const [isLoading, setIsLoading] = useState(false); + const [currentAbortController, setAbortController] = useState(); const [rawResponse, setRawResponse] = useState>({}); const [selectedTab, setSelectedTab] = useState(0); @@ -187,16 +190,23 @@ export const SearchExamplesApp = ({ ...(strategy ? { get_cool: getCool } : {}), }; + const abortController = new AbortController(); + setAbortController(abortController); + // Submit the search request using the `data.search` service. setRequest(req.params.body); - const searchSubscription$ = data.search + setIsLoading(true); + + data.search .search(req, { strategy, sessionId, + abortSignal: abortController.signal, }) .subscribe({ next: (res) => { if (isCompleteResponse(res)) { + setIsLoading(false); setResponse(res); const avgResult: number | undefined = res.rawResponse.aggregations ? // @ts-expect-error @elastic/elasticsearch no way to declare a type for aggregation in the search response @@ -226,7 +236,6 @@ export const SearchExamplesApp = ({ toastLifeTimeMs: 300000, } ); - searchSubscription$.unsubscribe(); if (res.warning) { notifications.toasts.addWarning({ title: 'Warning', @@ -236,14 +245,20 @@ export const SearchExamplesApp = ({ } else if (isErrorResponse(res)) { // TODO: Make response error status clearer notifications.toasts.addDanger('An error has occurred'); - searchSubscription$.unsubscribe(); } }, error: (e) => { - notifications.toasts.addDanger({ - title: 'Failed to run search', - text: e.message, - }); + setIsLoading(false); + if (e instanceof AbortError) { + notifications.toasts.addWarning({ + title: e.message, + }); + } else { + notifications.toasts.addDanger({ + title: 'Failed to run search', + text: e.message, + }); + } }, }); }; @@ -286,7 +301,12 @@ export const SearchExamplesApp = ({ } setRequest(searchSource.getSearchRequestBody()); - const { rawResponse: res } = await searchSource.fetch$().toPromise(); + const abortController = new AbortController(); + setAbortController(abortController); + setIsLoading(true); + const { rawResponse: res } = await searchSource + .fetch$({ abortSignal: abortController.signal }) + .toPromise(); setRawResponse(res); const message = Searched {res.hits.total} documents.; @@ -301,7 +321,18 @@ export const SearchExamplesApp = ({ ); } catch (e) { setRawResponse(e.body); - notifications.toasts.addWarning(`An error has occurred: ${e.message}`); + if (e instanceof AbortError) { + notifications.toasts.addWarning({ + title: e.message, + }); + } else { + notifications.toasts.addDanger({ + title: 'Failed to run search', + text: e.message, + }); + } + } finally { + setIsLoading(false); } }; @@ -329,32 +360,44 @@ export const SearchExamplesApp = ({ }, }; + const abortController = new AbortController(); + setAbortController(abortController); + // Submit the search request using the `data.search` service. setRequest(req.params); - const searchSubscription$ = data.search + setIsLoading(true); + data.search .search(req, { strategy: 'fibonacciStrategy', + abortSignal: abortController.signal, }) .subscribe({ next: (res) => { setResponse(res); if (isCompleteResponse(res)) { + setIsLoading(false); notifications.toasts.addSuccess({ title: 'Query result', text: 'Query finished', }); - searchSubscription$.unsubscribe(); } else if (isErrorResponse(res)) { + setIsLoading(false); // TODO: Make response error status clearer notifications.toasts.addWarning('An error has occurred'); - searchSubscription$.unsubscribe(); } }, error: (e) => { - notifications.toasts.addDanger({ - title: 'Failed to run search', - text: e.message, - }); + setIsLoading(false); + if (e instanceof AbortError) { + notifications.toasts.addWarning({ + title: e.message, + }); + } else { + notifications.toasts.addDanger({ + title: 'Failed to run search', + text: e.message, + }); + } }, }); }; @@ -365,17 +408,32 @@ export const SearchExamplesApp = ({ const onServerClickHandler = async () => { if (!indexPattern || !selectedNumericField) return; + const abortController = new AbortController(); + setAbortController(abortController); + setIsLoading(true); try { const res = await http.get(SERVER_SEARCH_ROUTE_PATH, { query: { index: indexPattern.title, field: selectedNumericField!.name, }, + signal: abortController.signal, }); notifications.toasts.addSuccess(`Server returned ${JSON.stringify(res)}`); } catch (e) { - notifications.toasts.addDanger('Failed to run search'); + if (e?.name === 'AbortError') { + notifications.toasts.addWarning({ + title: e.message, + }); + } else { + notifications.toasts.addDanger({ + title: 'Failed to run search', + text: e.message, + }); + } + } finally { + setIsLoading(false); } }; @@ -721,6 +779,11 @@ export const SearchExamplesApp = ({ strategy. This request does not take the configuration of{' '} TopNavMenu into account, but you could pass those down to the server as well. +
+ When executing search on the server, make sure to cancel the search in case user + cancels corresponding network request. This could happen in case user re-runs a + query or leaves the page without waiting for the result. Cancellation API is similar + on client and server and use `AbortController`. setSelectedTab(reqTabs.indexOf(tab))} /> + + {currentAbortController && isLoading && ( + currentAbortController?.abort()}> + + + )} diff --git a/examples/search_examples/server/routes/server_search_route.ts b/examples/search_examples/server/routes/server_search_route.ts index 258587610a2070..0d1302233a39c4 100644 --- a/examples/search_examples/server/routes/server_search_route.ts +++ b/examples/search_examples/server/routes/server_search_route.ts @@ -6,6 +6,7 @@ * Side Public License, v 1. */ +import { Observable } from 'rxjs'; import { IEsSearchRequest } from 'src/plugins/data/server'; import { schema } from '@kbn/config-schema'; import { IEsSearchResponse } from 'src/plugins/data/common'; @@ -26,36 +27,51 @@ export function registerServerSearchRoute(router: IRouter { const { index, field } = request.query; - // Run a synchronous search server side, by enforcing a high keepalive and waiting for completion. - // If you wish to run the search with polling (in basic+), you'd have to poll on the search API. - // Please reach out to the @app-arch-team if you need this to be implemented. - const res = await context - .search!.search( - { - params: { - index, - body: { - aggs: { - '1': { - avg: { - field, + + // User may abort the request without waiting for the results + // we need to handle this scenario by aborting underlying server requests + const abortSignal = getRequestAbortedSignal(request.events.aborted$); + + try { + const res = await context + .search!.search( + { + params: { + index, + body: { + aggs: { + '1': { + avg: { + field, + }, }, }, }, }, - waitForCompletionTimeout: '5m', - keepAlive: '5m', - }, - } as IEsSearchRequest, - {} - ) - .toPromise(); + } as IEsSearchRequest, + { abortSignal } + ) + .toPromise(); - return response.ok({ - body: { - aggs: (res as IEsSearchResponse).rawResponse.aggregations, - }, - }); + return response.ok({ + body: { + aggs: (res as IEsSearchResponse).rawResponse.aggregations, + }, + }); + } catch (e) { + return response.customError({ + statusCode: e.statusCode ?? 500, + body: { + message: e.message, + }, + }); + } } ); } + +function getRequestAbortedSignal(aborted$: Observable): AbortSignal { + const controller = new AbortController(); + aborted$.subscribe(() => controller.abort()); + return controller.signal; +} diff --git a/package.json b/package.json index 983dddda5d4fbe..b0c7e4659a559c 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 07513ac94c85db..c9a0f6a759b2a3 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/kbn-alerts/BUILD.bazel b/packages/kbn-alerts/BUILD.bazel index e567c18807dfc6..15dbc163cd2888 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 b52a6efc351390..13b60ad9fa0724 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 3a3763693db9ac..824a73f9b26117 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 87a7812165a66e..7c7f7dd28f6cad 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 4c1827dc58c049..525463c9de2463 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 beff36023343dd..f00905f3f49200 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 c964219e1fed61..5cb60344037fcd 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 27e887eda65ce3..53f9c82b868159 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 92b4c815f22492..5bc7691d6a40f5 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 772faa5321d982..cec80af843c4c0 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/core/server/elasticsearch/elasticsearch_config.ts b/src/core/server/elasticsearch/elasticsearch_config.ts index 298144ca95a02d..67d7d702c8df94 100644 --- a/src/core/server/elasticsearch/elasticsearch_config.ts +++ b/src/core/server/elasticsearch/elasticsearch_config.ts @@ -250,11 +250,11 @@ const deprecations: ConfigDeprecationProvider = () => [ if (es.logQueries === true) { addDeprecation({ configPath: `${fromPath}.logQueries`, - message: `Setting [${fromPath}.logQueries] is deprecated and no longer used. You should set the log level to "debug" for the "elasticsearch.queries" context in "logging.loggers".`, + message: `Setting [${fromPath}.logQueries] is deprecated and no longer used. You should set the log level to "debug" for the "elasticsearch.query" context in "logging.loggers".`, correctiveActions: { manualSteps: [ `Remove Setting [${fromPath}.logQueries] from your kibana configs`, - `Set the log level to "debug" for the "elasticsearch.queries" context in "logging.loggers".`, + `Set the log level to "debug" for the "elasticsearch.query" context in "logging.loggers".`, ], }, }); diff --git a/src/plugins/console/public/services/history.ts b/src/plugins/console/public/services/history.ts index ee1e97ceb386e2..972e5283274dee 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 cb6a5383688dcd..2be6e9b269e71f 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 fa484de2180b42..40f6f872535f97 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 99665d312d32e3..fc4c6b299284b4 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 0635152332993a..b20a96c79aed63 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 51c64f1875376c..797765eda232da 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 587f741461bb44..ab442bf839e376 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 b5efa0447e6510..de1a475fdbd18c 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 f8880ac5618fcc..fe39f6112a7f31 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 7d87c49bda649d..4f10f833f643c8 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 3e6566f0da0a4e..7aedbe9e110019 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 4a22899c12e8a2..c74ac506e4809a 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 6cd102a4d47701..744d63c1ba04a1 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 54fa1f05b9c0df..d7081bf020d851 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 52f04bcead6653..7518a36433d35e 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 9d2afdba36dbce..09ac0c1dd94bbc 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 56483d9d101957..02411f5902b3ba 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 00000000000000..a451178cc46b08 --- /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 7c671ce7736d7a..f0333cefd612fb 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 611a426dd4d716..1e19e495585fe5 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 5561d1676e41ce..0ef21fca26f292 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 fddcc309e1ef11..cb5c7483f261ab 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 cce2b4eb042eff..616fe56102df94 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 409d80e2ef066d..6f9a30e3a70414 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 a256a65a5d7f4b..b6655e246de36c 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 ff0ac0642ec915..9912aef9431440 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 00000000000000..12ec1bca2207f3 --- /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 d4a6cb20bc5519..b7b146aeba3488 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 104cd3b2815bc4..80e5a079cfd591 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 9a362466c0fd70..a022abba7fb455 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 c4a603a383e380..2eb49d76c60825 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 cfc3ddabe07516..58f5cf8e52c917 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 64576d4978c99f..23cae0ee852ca8 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/embeddable/common/types.ts b/src/plugins/embeddable/common/types.ts index c3cac2d5d67db4..b9d9d4cc341465 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 9807a47698a50f..6748e9f3b1d083 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 8ffeefefd0cc33..98ba8b4fbcda8f 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 4be69feb79f555..3497a1e52697d3 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 ff79ccf0126f4f..850f70d2d002a2 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 9c8c3d24ef289a..949691911fc270 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 7fa93ff0bcea8a..d7e69e9d892116 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 a005bb7c3d055d..bb34fe84e2c398 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 f6963de0cbd63f..d1603cbceb26f9 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 9295451f640c23..b885ff97bf2628 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 566ae197194544..1b9f3868f09669 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 bc90fe35199b8e..def7fefd551737 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/src/plugins/vis_types/xy/public/vis_types/area.ts b/src/plugins/vis_types/xy/public/vis_types/area.ts index 45296fa99cdfee..3b8f78db25d36a 100644 --- a/src/plugins/vis_types/xy/public/vis_types/area.ts +++ b/src/plugins/vis_types/xy/public/vis_types/area.ts @@ -26,6 +26,7 @@ import { import { toExpressionAst } from '../to_ast'; import { ChartType } from '../../common'; import { optionTabs } from '../editor/common_config'; +import { getVisTypeFromParams } from './get_vis_type_from_params'; export const areaVisTypeDefinition = { name: 'area', @@ -36,6 +37,7 @@ export const areaVisTypeDefinition = { }), toExpressionAst, getSupportedTriggers: () => [VIS_EVENT_TO_TRIGGER.filter, VIS_EVENT_TO_TRIGGER.brush], + updateVisTypeOnParamsChange: getVisTypeFromParams, visConfig: { defaults: { type: ChartType.Area, diff --git a/src/plugins/vis_types/xy/public/vis_types/get_vis_type_from_params.test.ts b/src/plugins/vis_types/xy/public/vis_types/get_vis_type_from_params.test.ts new file mode 100644 index 00000000000000..d1ca6bf3455ffd --- /dev/null +++ b/src/plugins/vis_types/xy/public/vis_types/get_vis_type_from_params.test.ts @@ -0,0 +1,105 @@ +/* + * Copyright 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 { VisParams } from 'src/plugins/visualizations/common'; +import { getVisTypeFromParams } from './get_vis_type_from_params'; + +describe('extracting visualization type from vis params', () => { + [ + { + message: 'return undefined when no params', + params: undefined, + expectedType: undefined, + }, + { + message: 'extract a line type', + params: { + seriesParams: [ + { + type: 'line', + }, + ], + } as VisParams, + expectedType: 'line', + }, + { + message: 'extract an area type', + params: { + seriesParams: [ + { + type: 'area', + }, + ], + } as VisParams, + expectedType: 'area', + }, + { + message: 'extract a histogram type when axes not defined', + params: { + seriesParams: [ + { + type: 'histogram', + }, + ], + } as VisParams, + expectedType: 'histogram', + }, + { + message: 'extract a histogram type when first axis on bottom', + params: { + seriesParams: [ + { + type: 'histogram', + }, + ], + categoryAxes: [{ position: 'bottom' }], + } as VisParams, + expectedType: 'histogram', + }, + { + message: 'extract a histogram type when first axis on top', + params: { + seriesParams: [ + { + type: 'histogram', + }, + ], + categoryAxes: [{ position: 'top' }], + } as VisParams, + expectedType: 'histogram', + }, + { + message: 'extract a horizontal_bar type when first axis to left', + params: { + seriesParams: [ + { + type: 'histogram', + }, + ], + categoryAxes: [{ position: 'left' }], + } as VisParams, + expectedType: 'horizontal_bar', + }, + { + message: 'extract a horizontal_bar type when first axis to right', + params: { + seriesParams: [ + { + type: 'histogram', + }, + ], + categoryAxes: [{ position: 'right' }], + } as VisParams, + expectedType: 'horizontal_bar', + }, + ].forEach(({ message, params, expectedType }) => + it(message, () => { + expect(getVisTypeFromParams(params)).toBe(expectedType); + }) + ); +}); diff --git a/src/plugins/vis_types/xy/public/vis_types/get_vis_type_from_params.ts b/src/plugins/vis_types/xy/public/vis_types/get_vis_type_from_params.ts new file mode 100644 index 00000000000000..abe67052cd97ac --- /dev/null +++ b/src/plugins/vis_types/xy/public/vis_types/get_vis_type_from_params.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 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 { VisParams } from 'src/plugins/visualizations/common'; + +export const getVisTypeFromParams = (params?: VisParams) => { + let type = params?.seriesParams?.[0]?.type; + if (type === 'histogram' && ['left', 'right'].includes(params?.categoryAxes?.[0]?.position)) { + type = 'horizontal_bar'; + } + return type; +}; diff --git a/src/plugins/vis_types/xy/public/vis_types/histogram.ts b/src/plugins/vis_types/xy/public/vis_types/histogram.ts index 32b72e753af722..79b3fd72de452f 100644 --- a/src/plugins/vis_types/xy/public/vis_types/histogram.ts +++ b/src/plugins/vis_types/xy/public/vis_types/histogram.ts @@ -26,6 +26,7 @@ import { toExpressionAst } from '../to_ast'; import { ChartType } from '../../common'; import { optionTabs } from '../editor/common_config'; import { defaultCountLabel, LabelRotation } from '../../../../charts/public'; +import { getVisTypeFromParams } from './get_vis_type_from_params'; export const histogramVisTypeDefinition = { name: 'histogram', @@ -38,6 +39,7 @@ export const histogramVisTypeDefinition = { }), toExpressionAst, getSupportedTriggers: () => [VIS_EVENT_TO_TRIGGER.filter, VIS_EVENT_TO_TRIGGER.brush], + updateVisTypeOnParamsChange: getVisTypeFromParams, visConfig: { defaults: { type: ChartType.Histogram, diff --git a/src/plugins/vis_types/xy/public/vis_types/horizontal_bar.ts b/src/plugins/vis_types/xy/public/vis_types/horizontal_bar.ts index ca24f06e6d1cb6..5ac833190dd38b 100644 --- a/src/plugins/vis_types/xy/public/vis_types/horizontal_bar.ts +++ b/src/plugins/vis_types/xy/public/vis_types/horizontal_bar.ts @@ -26,6 +26,7 @@ import { toExpressionAst } from '../to_ast'; import { ChartType } from '../../common'; import { optionTabs } from '../editor/common_config'; import { defaultCountLabel, LabelRotation } from '../../../../charts/public'; +import { getVisTypeFromParams } from './get_vis_type_from_params'; export const horizontalBarVisTypeDefinition = { name: 'horizontal_bar', @@ -38,6 +39,7 @@ export const horizontalBarVisTypeDefinition = { }), toExpressionAst, getSupportedTriggers: () => [VIS_EVENT_TO_TRIGGER.filter, VIS_EVENT_TO_TRIGGER.brush], + updateVisTypeOnParamsChange: getVisTypeFromParams, visConfig: { defaults: { type: ChartType.Histogram, diff --git a/src/plugins/vis_types/xy/public/vis_types/line.ts b/src/plugins/vis_types/xy/public/vis_types/line.ts index dd3196b1a7cb79..f7467ca53fa0e2 100644 --- a/src/plugins/vis_types/xy/public/vis_types/line.ts +++ b/src/plugins/vis_types/xy/public/vis_types/line.ts @@ -26,6 +26,7 @@ import { import { toExpressionAst } from '../to_ast'; import { ChartType } from '../../common'; import { optionTabs } from '../editor/common_config'; +import { getVisTypeFromParams } from './get_vis_type_from_params'; export const lineVisTypeDefinition = { name: 'line', @@ -36,6 +37,7 @@ export const lineVisTypeDefinition = { }), toExpressionAst, getSupportedTriggers: () => [VIS_EVENT_TO_TRIGGER.filter, VIS_EVENT_TO_TRIGGER.brush], + updateVisTypeOnParamsChange: getVisTypeFromParams, visConfig: { defaults: { type: ChartType.Line, diff --git a/src/plugins/visualizations/public/utils/saved_visualize_utils.test.ts b/src/plugins/visualizations/public/utils/saved_visualize_utils.test.ts index 83b16026de391a..5c8c0594d35638 100644 --- a/src/plugins/visualizations/public/utils/saved_visualize_utils.test.ts +++ b/src/plugins/visualizations/public/utils/saved_visualize_utils.test.ts @@ -43,10 +43,10 @@ jest.mock('../../../../plugins/data/public', () => ({ })); const mockInjectReferences = jest.fn(); -const mockExtractReferences = jest.fn(() => ({ references: [], attributes: {} })); +const mockExtractReferences = jest.fn((arg) => arg); jest.mock('./saved_visualization_references', () => ({ injectReferences: jest.fn((...args) => mockInjectReferences(...args)), - extractReferences: jest.fn(() => mockExtractReferences()), + extractReferences: jest.fn((arg) => mockExtractReferences(arg)), })); let isTitleDuplicateConfirmed = true; @@ -184,6 +184,7 @@ describe('saved_visualize_utils', () => { vis = { visState: { type: 'area', + params: {}, }, title: 'test', uiStateJSON: '{}', diff --git a/src/plugins/visualizations/public/vis.ts b/src/plugins/visualizations/public/vis.ts index 2a1e7f2c8c6738..8499bb1428c3cc 100644 --- a/src/plugins/visualizations/public/vis.ts +++ b/src/plugins/visualizations/public/vis.ts @@ -113,7 +113,19 @@ export class Vis { return defaults({}, cloneDeep(params ?? {}), cloneDeep(this.type.visConfig?.defaults ?? {})); } - async setState(state: PartialVisState) { + async setState(inState: PartialVisState) { + let state = inState; + + const { updateVisTypeOnParamsChange } = this.type; + const newType = updateVisTypeOnParamsChange && updateVisTypeOnParamsChange(state.params); + if (newType) { + state = { + ...inState, + type: newType, + params: { ...inState.params, type: newType }, + }; + } + let typeChanged = false; if (state.type && this.type.name !== state.type) { // @ts-ignore diff --git a/src/plugins/visualizations/public/vis_types/base_vis_type.ts b/src/plugins/visualizations/public/vis_types/base_vis_type.ts index 669bfb7f365241..675a1783274aa9 100644 --- a/src/plugins/visualizations/public/vis_types/base_vis_type.ts +++ b/src/plugins/visualizations/public/vis_types/base_vis_type.ts @@ -43,6 +43,7 @@ export class BaseVisType { public readonly inspectorAdapters; public readonly toExpressionAst; public readonly getInfoMessage; + public readonly updateVisTypeOnParamsChange; public readonly schemas; constructor(opts: VisTypeDefinition) { @@ -71,6 +72,7 @@ export class BaseVisType { this.inspectorAdapters = opts.inspectorAdapters; this.toExpressionAst = opts.toExpressionAst; this.getInfoMessage = opts.getInfoMessage; + this.updateVisTypeOnParamsChange = opts.updateVisTypeOnParamsChange; this.schemas = new Schemas(this.editorConfig?.schemas ?? []); } diff --git a/src/plugins/visualizations/public/vis_types/types.ts b/src/plugins/visualizations/public/vis_types/types.ts index 77654c8a157e36..724f9d6ccc662f 100644 --- a/src/plugins/visualizations/public/vis_types/types.ts +++ b/src/plugins/visualizations/public/vis_types/types.ts @@ -147,6 +147,12 @@ export interface VisTypeDefinition { */ readonly toExpressionAst: VisToExpressionAst; + /** + * Should be defined when the visualization type should change + * when certain params are changed + */ + readonly updateVisTypeOnParamsChange?: (params: VisParams) => string | undefined; + readonly setup?: (vis: Vis) => Promise>; hidden?: boolean; diff --git a/test/plugin_functional/config.ts b/test/plugin_functional/config.ts index e2b7457ffec785..4a96b2b4028986 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 e9a6434e8f3371..00000000000000 --- 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 a790f062e32d4c..00000000000000 --- 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 4431f185ac9cac..8db51e223056af 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 f70cbaa13f7d1b..d370a278e0a5ce 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 fe95ec646387d7..fb7268ef529dad 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 95cb356af3c1a7..c82cc0a7f21e8c 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/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 00000000000000..9f2300dedc82ba --- /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/ftr_config.ts b/x-pack/plugins/apm/ftr_e2e/ftr_config.ts index 12cc8845264c23..84d1c40930c707 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 51c859a8477f23..a5a0b52e3fbef6 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 00000000000000..4a05f38d8e505a --- /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 00000000000000..6125a238f11aa6 --- /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 00000000000000..23acc2e98dd736 --- /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 00000000000000..5d667b9f3e1a4f --- /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 00000000000000..bcac32bbaa3bc4 --- /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 00000000000000..ed2bd498583107 --- /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 b2efae74c9c741..c642ca7bd577ff 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 00000000000000..49ddd8aec0fe45 --- /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 629868fb88bf65..f79e9555957170 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 0fc25b28b60e8c..58179366fa42de 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 e6c189ed0c74ed..a2f6fd493313f2 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 a02fc7fe6665f4..6d690415d8c6c5 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 e33f60e5593b0b..45af7e62260b3b 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 ecca2ddb07ec30..dabe9043495bc8 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 d5cd423b2b123a..b33f152a63016e 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 17c43e36e5cc34..00440b2b51853c 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 aea92d06b75897..bca71ed71b1f61 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 c79dde721d1388..fd638a6731c631 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 00000000000000..9c5b3e04c94f24 --- /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 00000000000000..4aed9314f433c4 --- /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 00000000000000..e2f86298efdca1 --- /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 00000000000000..e5f40205b29121 --- /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 ee4c9d1c8cfa52..1462e7540650ac 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 19f45ced5dc5dc..cb1c34a19c0180 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 76bd0cba1731f8..c78bf3e9187378 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 d447db60fb25ba..bef8ed4462fdce 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 ea894e2b00acf6..26794d04213539 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 aaf3cc4516067b..4fc7a0569ba0e3 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 5f7200cb826df6..128dcdcb778cfd 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 6c3cb51111ae15..c84deb3cb0c99b 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 6a5f3df0e86f6a..315b4d864b3f28 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 040f313b12205d..3ea2c022ec489a 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 60ae386bea58ed..69c2cc4b987b18 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 872a7282136e39..10d81f1623959a 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 4b4e11c31d4b88..b95ae0bca5bf6e 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 d78ca852ee7d1e..ffefea96d3a228 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 793c6250d859c0..e86b06b423a9da 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 5366c00c0e7fc7..a8179f297644fb 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 907dcf8c9c208b..b8bb26fa9ad63f 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 39fe02a84854cf..85e834b3207511 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 6750ebf1140e03..54bc7fb26e9d0e 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 6f8332e1e332e7..ada2df654d52bd 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 8b4f5a69b81415..350412825b9967 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 ad96f21022f2b5..3cf461e3f7d453 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 8fbbf406cf5d32..bf2a122ead42b2 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 af7b6f3201b3e8..e41809054e1232 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 a60f68c19f6dce..454437a203bc2f 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 df29010bd682ff..f17f7a582efdf0 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 3be30b77bc2e41..0554b31c883566 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 8e7a59c290ce2e..e4b2027aa3d6dc 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 d8963b33b8ab2f..463c61fb60c99e 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 537fd9ec6a0d44..8798c1a4bc5291 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 dae8390a35fd77..7f91447b910b6f 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 3e692aa48623e3..e2021ac582d0c8 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 ad9285c7b8fefb..3c2d5fc4df66a6 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 39f0cb376b325c..3466542c097391 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 b7665a58de300b..9a663e1372211f 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 2b210bd07ab4b2..43ea60fa84617c 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 d460132dddbb16..f1d9beaca51364 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 a43f170e5822f8..cdfdbadf6759c5 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 f856a5c035f811..ac8383ccea9eee 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 97a9b407b3cd6c..1f2e7c883e1cb0 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 e82dbcaa411359..c7c85fdd49359a 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 cbd7a1c6107b18..b14af1c69795a2 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 6034846fac4f13..93bead4d31f4c8 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 1877a4cbd0e42d..07c71def01bedc 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 af3b33e3d7a3d9..d0fe98a7c1393f 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 6e213edf457b1c..667980d5f0492f 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 25aff5077c680a..077ef44c66b2fe 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 42bf690c388c48..56e0a325aafd0f 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 4845d682b87710..8d41e221a2cc76 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 1be152ad5ca0b0..b28343f37ea25b 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 d506ed9c32ba56..a33f5ec90e3a04 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 4d329ff357b899..a992cf49f75fb5 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 905ba20e4f6602..e52a174850c4c0 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 32846620221aed..16ed53020a3134 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/epm/agent/agent.test.ts b/x-pack/plugins/fleet/server/services/epm/agent/agent.test.ts index ed5d6473760ff6..df4800b241abb3 100644 --- a/x-pack/plugins/fleet/server/services/epm/agent/agent.test.ts +++ b/x-pack/plugins/fleet/server/services/epm/agent/agent.test.ts @@ -258,4 +258,19 @@ my-package: const output = compileTemplate(vars, stringTemplate); expect(output).toEqual(targetOutput); }); + + it('should throw on invalid handlebar template', () => { + const streamTemplate = ` +input: log +paths: +{{ if test}} + - {{ test}} +{{ end }} +`; + const vars = {}; + + expect(() => compileTemplate(vars, streamTemplate)).toThrowError( + 'Error while compiling agent template: options.inverse is not a function' + ); + }); }); diff --git a/x-pack/plugins/fleet/server/services/epm/agent/agent.ts b/x-pack/plugins/fleet/server/services/epm/agent/agent.ts index a01643b22cf9d5..762bc1ea624e17 100644 --- a/x-pack/plugins/fleet/server/services/epm/agent/agent.ts +++ b/x-pack/plugins/fleet/server/services/epm/agent/agent.ts @@ -14,8 +14,14 @@ const handlebars = Handlebars.create(); export function compileTemplate(variables: PackagePolicyConfigRecord, templateStr: string) { const { vars, yamlValues } = buildTemplateVariables(variables, templateStr); - const template = handlebars.compile(templateStr, { noEscape: true }); - let compiledTemplate = template(vars); + let compiledTemplate: string; + try { + const template = handlebars.compile(templateStr, { noEscape: true }); + compiledTemplate = template(vars); + } catch (err) { + throw new Error(`Error while compiling agent template: ${err.message}`); + } + compiledTemplate = replaceRootLevelYamlVariables(yamlValues, compiledTemplate); const yamlFromCompiledTemplate = safeLoad(compiledTemplate, {}); diff --git a/x-pack/plugins/fleet/server/services/epm/elasticsearch/datastream_ilm/install.ts b/x-pack/plugins/fleet/server/services/epm/elasticsearch/datastream_ilm/install.ts index 2e43fe44527b79..a1075b15a64627 100644 --- a/x-pack/plugins/fleet/server/services/epm/elasticsearch/datastream_ilm/install.ts +++ b/x-pack/plugins/fleet/server/services/epm/elasticsearch/datastream_ilm/install.ts @@ -5,7 +5,7 @@ * 2.0. */ -import type { ElasticsearchClient, SavedObjectsClientContract } from 'kibana/server'; +import type { ElasticsearchClient, Logger, SavedObjectsClientContract } from 'kibana/server'; import { ElasticsearchAssetType } from '../../../../../common/types/models'; import type { @@ -18,6 +18,7 @@ import { saveInstalledEsRefs } from '../../packages/install'; import { getAsset } from '../transform/common'; import { getESAssetMetadata } from '../meta'; +import { retryTransientEsErrors } from '../retry'; import { deleteIlmRefs, deleteIlms } from './remove'; @@ -35,7 +36,8 @@ export const installIlmForDataStream = async ( registryPackage: InstallablePackage, paths: string[], esClient: ElasticsearchClient, - savedObjectsClient: SavedObjectsClientContract + savedObjectsClient: SavedObjectsClientContract, + logger: Logger ) => { const installation = await getInstallation({ savedObjectsClient, pkgName: registryPackage.name }); let previousInstalledIlmEsAssets: EsAssetReference[] = []; @@ -90,7 +92,7 @@ export const installIlmForDataStream = async ( ); const installationPromises = ilmInstallations.map(async (ilmInstallation) => { - return handleIlmInstall({ esClient, ilmInstallation }); + return handleIlmInstall({ esClient, ilmInstallation, logger }); }); installedIlms = await Promise.all(installationPromises).then((results) => results.flat()); @@ -117,15 +119,21 @@ export const installIlmForDataStream = async ( async function handleIlmInstall({ esClient, ilmInstallation, + logger, }: { esClient: ElasticsearchClient; ilmInstallation: IlmInstallation; + logger: Logger; }): Promise { - await esClient.transport.request({ - method: 'PUT', - path: `/_ilm/policy/${ilmInstallation.installationName}`, - body: ilmInstallation.content, - }); + await retryTransientEsErrors( + () => + esClient.transport.request({ + method: 'PUT', + path: `/_ilm/policy/${ilmInstallation.installationName}`, + body: ilmInstallation.content, + }), + { logger } + ); return { id: ilmInstallation.installationName, type: ElasticsearchAssetType.dataStreamIlmPolicy }; } diff --git a/x-pack/plugins/fleet/server/services/epm/elasticsearch/ilm/install.ts b/x-pack/plugins/fleet/server/services/epm/elasticsearch/ilm/install.ts index 380bd0e913d6d0..b77a787090ed6e 100644 --- a/x-pack/plugins/fleet/server/services/epm/elasticsearch/ilm/install.ts +++ b/x-pack/plugins/fleet/server/services/epm/elasticsearch/ilm/install.ts @@ -5,18 +5,20 @@ * 2.0. */ -import type { ElasticsearchClient } from 'kibana/server'; +import type { ElasticsearchClient, Logger } from 'kibana/server'; import type { InstallablePackage } from '../../../../types'; import { ElasticsearchAssetType } from '../../../../types'; import { getAsset, getPathParts } from '../../archive'; import { getESAssetMetadata } from '../meta'; +import { retryTransientEsErrors } from '../retry'; export async function installILMPolicy( packageInfo: InstallablePackage, paths: string[], - esClient: ElasticsearchClient + esClient: ElasticsearchClient, + logger: Logger ) { const ilmPaths = paths.filter((path) => isILMPolicy(path)); if (!ilmPaths.length) return; @@ -29,11 +31,15 @@ export async function installILMPolicy( const { file } = getPathParts(path); const name = file.substr(0, file.lastIndexOf('.')); try { - await esClient.transport.request({ - method: 'PUT', - path: '/_ilm/policy/' + name, - body, - }); + await retryTransientEsErrors( + () => + esClient.transport.request({ + method: 'PUT', + path: '/_ilm/policy/' + name, + body, + }), + { logger } + ); } catch (err) { throw new Error(err.message); } diff --git a/x-pack/plugins/fleet/server/services/epm/elasticsearch/ingest_pipeline/install.ts b/x-pack/plugins/fleet/server/services/epm/elasticsearch/ingest_pipeline/install.ts index 560ff08331044c..d857d7c6bc2fb6 100644 --- a/x-pack/plugins/fleet/server/services/epm/elasticsearch/ingest_pipeline/install.ts +++ b/x-pack/plugins/fleet/server/services/epm/elasticsearch/ingest_pipeline/install.ts @@ -6,7 +6,7 @@ */ import type { TransportRequestOptions } from '@elastic/elasticsearch'; -import type { ElasticsearchClient, SavedObjectsClientContract } from 'src/core/server'; +import type { ElasticsearchClient, Logger, SavedObjectsClientContract } from 'src/core/server'; import { ElasticsearchAssetType } from '../../../../types'; import type { EsAssetReference, RegistryDataStream, InstallablePackage } from '../../../../types'; @@ -22,6 +22,8 @@ import { import { appendMetadataToIngestPipeline } from '../meta'; +import { retryTransientEsErrors } from '../retry'; + import { deletePipelineRefs } from './remove'; interface RewriteSubstitution { @@ -41,7 +43,8 @@ export const installPipelines = async ( installablePackage: InstallablePackage, paths: string[], esClient: ElasticsearchClient, - savedObjectsClient: SavedObjectsClientContract + savedObjectsClient: SavedObjectsClientContract, + logger: Logger ) => { // unlike other ES assets, pipeline names are versioned so after a template is updated // it can be created pointing to the new template, without removing the old one and effecting data @@ -105,6 +108,7 @@ export const installPipelines = async ( installAllPipelines({ dataStream, esClient, + logger, paths: pipelinePaths, installablePackage, }) @@ -119,6 +123,7 @@ export const installPipelines = async ( installAllPipelines({ dataStream: undefined, esClient, + logger, paths: topLevelPipelinePaths, installablePackage, }) @@ -151,11 +156,13 @@ export function rewriteIngestPipeline( export async function installAllPipelines({ esClient, + logger, paths, dataStream, installablePackage, }: { esClient: ElasticsearchClient; + logger: Logger; paths: string[]; dataStream?: RegistryDataStream; installablePackage: InstallablePackage; @@ -195,7 +202,7 @@ export async function installAllPipelines({ }); const installationPromises = pipelines.map(async (pipeline) => { - return installPipeline({ esClient, pipeline, installablePackage }); + return installPipeline({ esClient, pipeline, installablePackage, logger }); }); return Promise.all(installationPromises); @@ -203,10 +210,12 @@ export async function installAllPipelines({ async function installPipeline({ esClient, + logger, pipeline, installablePackage, }: { esClient: ElasticsearchClient; + logger: Logger; pipeline: any; installablePackage?: InstallablePackage; }): Promise { @@ -233,7 +242,10 @@ async function installPipeline({ }; } - await esClient.ingest.putPipeline(esClientParams, esClientRequestOptions); + await retryTransientEsErrors( + () => esClient.ingest.putPipeline(esClientParams, esClientRequestOptions), + { logger } + ); return { id: pipelineWithMetadata.nameForInstallation, @@ -241,7 +253,10 @@ async function installPipeline({ }; } -export async function ensureFleetFinalPipelineIsInstalled(esClient: ElasticsearchClient) { +export async function ensureFleetFinalPipelineIsInstalled( + esClient: ElasticsearchClient, + logger: Logger +) { const esClientRequestOptions: TransportRequestOptions = { ignore: [404], }; @@ -258,6 +273,7 @@ export async function ensureFleetFinalPipelineIsInstalled(esClient: Elasticsearc ) { await installPipeline({ esClient, + logger, pipeline: { nameForInstallation: FLEET_FINAL_PIPELINE_ID, contentForInstallation: FLEET_FINAL_PIPELINE_CONTENT, diff --git a/x-pack/plugins/fleet/server/services/epm/elasticsearch/ml_model/install.ts b/x-pack/plugins/fleet/server/services/epm/elasticsearch/ml_model/install.ts index d97081f15aca33..e5c96bea871818 100644 --- a/x-pack/plugins/fleet/server/services/epm/elasticsearch/ml_model/install.ts +++ b/x-pack/plugins/fleet/server/services/epm/elasticsearch/ml_model/install.ts @@ -5,7 +5,7 @@ * 2.0. */ -import type { ElasticsearchClient, SavedObjectsClientContract } from 'kibana/server'; +import type { ElasticsearchClient, Logger, SavedObjectsClientContract } from 'kibana/server'; import { errors } from '@elastic/elasticsearch'; import { saveInstalledEsRefs } from '../../packages/install'; @@ -13,6 +13,8 @@ import { getPathParts } from '../../archive'; import { ElasticsearchAssetType } from '../../../../../common/types/models'; import type { EsAssetReference, InstallablePackage } from '../../../../../common/types/models'; +import { retryTransientEsErrors } from '../retry'; + import { getAsset } from './common'; interface MlModelInstallation { @@ -24,7 +26,8 @@ export const installMlModel = async ( installablePackage: InstallablePackage, paths: string[], esClient: ElasticsearchClient, - savedObjectsClient: SavedObjectsClientContract + savedObjectsClient: SavedObjectsClientContract, + logger: Logger ) => { const mlModelPath = paths.find((path) => isMlModel(path)); @@ -47,7 +50,7 @@ export const installMlModel = async ( content, }; - const result = await handleMlModelInstall({ esClient, mlModel }); + const result = await handleMlModelInstall({ esClient, logger, mlModel }); installedMlModels.push(result); } return installedMlModels; @@ -61,19 +64,25 @@ const isMlModel = (path: string) => { async function handleMlModelInstall({ esClient, + logger, mlModel, }: { esClient: ElasticsearchClient; + logger: Logger; mlModel: MlModelInstallation; }): Promise { try { - await esClient.ml.putTrainedModel({ - model_id: mlModel.installationName, - defer_definition_decompression: true, - timeout: '45s', - // @ts-expect-error expects an object not a string - body: mlModel.content, - }); + await retryTransientEsErrors( + () => + esClient.ml.putTrainedModel({ + model_id: mlModel.installationName, + defer_definition_decompression: true, + timeout: '45s', + // @ts-expect-error expects an object not a string + body: mlModel.content, + }), + { logger } + ); } catch (err) { // swallow the error if the ml model already exists. const isAlreadyExistError = diff --git a/x-pack/plugins/fleet/server/services/epm/elasticsearch/retry.test.ts b/x-pack/plugins/fleet/server/services/epm/elasticsearch/retry.test.ts new file mode 100644 index 00000000000000..5b9a1bf1539f01 --- /dev/null +++ b/x-pack/plugins/fleet/server/services/epm/elasticsearch/retry.test.ts @@ -0,0 +1,88 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +jest.mock('timers/promises'); +import { setTimeout } from 'timers/promises'; + +import { loggerMock } from '@kbn/logging/mocks'; +import { errors as EsErrors } from '@elastic/elasticsearch'; + +import { retryTransientEsErrors } from './retry'; + +const setTimeoutMock = setTimeout as jest.Mock< + ReturnType, + Parameters +>; + +describe('retryTransientErrors', () => { + beforeEach(() => { + setTimeoutMock.mockClear(); + }); + + it("doesn't retry if operation is successful", async () => { + const esCallMock = jest.fn().mockResolvedValue('success'); + expect(await retryTransientEsErrors(esCallMock)).toEqual('success'); + expect(esCallMock).toHaveBeenCalledTimes(1); + }); + + it('logs an warning message on retry', async () => { + const logger = loggerMock.create(); + const esCallMock = jest + .fn() + .mockRejectedValueOnce(new EsErrors.ConnectionError('foo')) + .mockResolvedValue('success'); + + await retryTransientEsErrors(esCallMock, { logger }); + expect(logger.warn).toHaveBeenCalledTimes(1); + expect(logger.warn.mock.calls[0][0]).toMatch( + `Retrying Elasticsearch operation after [2s] due to error: ConnectionError: foo ConnectionError: foo` + ); + }); + + it('retries with an exponential backoff', async () => { + let attempt = 0; + const esCallMock = jest.fn(async () => { + attempt++; + if (attempt < 5) { + throw new EsErrors.ConnectionError('foo'); + } else { + return 'success'; + } + }); + + expect(await retryTransientEsErrors(esCallMock)).toEqual('success'); + expect(setTimeoutMock.mock.calls).toEqual([[2000], [4000], [8000], [16000]]); + expect(esCallMock).toHaveBeenCalledTimes(5); + }); + + it('retries each supported error type', async () => { + const errors = [ + new EsErrors.NoLivingConnectionsError('no living connection', { + warnings: [], + meta: {} as any, + }), + new EsErrors.ConnectionError('no connection'), + new EsErrors.TimeoutError('timeout'), + new EsErrors.ResponseError({ statusCode: 503, meta: {} as any, warnings: [] }), + new EsErrors.ResponseError({ statusCode: 408, meta: {} as any, warnings: [] }), + new EsErrors.ResponseError({ statusCode: 410, meta: {} as any, warnings: [] }), + ]; + + for (const error of errors) { + const esCallMock = jest.fn().mockRejectedValueOnce(error).mockResolvedValue('success'); + expect(await retryTransientEsErrors(esCallMock)).toEqual('success'); + expect(esCallMock).toHaveBeenCalledTimes(2); + } + }); + + it('does not retry unsupported errors', async () => { + const error = new Error('foo!'); + const esCallMock = jest.fn().mockRejectedValueOnce(error).mockResolvedValue('success'); + await expect(retryTransientEsErrors(esCallMock)).rejects.toThrow(error); + expect(esCallMock).toHaveBeenCalledTimes(1); + }); +}); diff --git a/x-pack/plugins/fleet/server/services/epm/elasticsearch/retry.ts b/x-pack/plugins/fleet/server/services/epm/elasticsearch/retry.ts new file mode 100644 index 00000000000000..c8ea36a4addeca --- /dev/null +++ b/x-pack/plugins/fleet/server/services/epm/elasticsearch/retry.ts @@ -0,0 +1,53 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { setTimeout } from 'timers/promises'; + +import { errors as EsErrors } from '@elastic/elasticsearch'; +import type { Logger } from '@kbn/logging'; + +const MAX_ATTEMPTS = 5; + +const retryResponseStatuses = [ + 503, // ServiceUnavailable + 408, // RequestTimeout + 410, // Gone +]; + +const isRetryableError = (e: any) => + e instanceof EsErrors.NoLivingConnectionsError || + e instanceof EsErrors.ConnectionError || + e instanceof EsErrors.TimeoutError || + (e instanceof EsErrors.ResponseError && retryResponseStatuses.includes(e?.statusCode!)); + +/** + * Retries any transient network or configuration issues encountered from Elasticsearch with an exponential backoff. + * Should only be used to wrap operations that are idempotent and can be safely executed more than once. + */ +export const retryTransientEsErrors = async ( + esCall: () => Promise, + { logger, attempt = 0 }: { logger?: Logger; attempt?: number } = {} +): Promise => { + try { + return await esCall(); + } catch (e) { + if (attempt < MAX_ATTEMPTS && isRetryableError(e)) { + const retryCount = attempt + 1; + const retryDelaySec = Math.min(Math.pow(2, retryCount), 64); // 2s, 4s, 8s, 16s, 32s, 64s, 64s, 64s ... + + logger?.warn( + `Retrying Elasticsearch operation after [${retryDelaySec}s] due to error: ${e.toString()} ${ + e.stack + }` + ); + + await setTimeout(retryDelaySec * 1000); + return retryTransientEsErrors(esCall, { logger, attempt: retryCount }); + } + + throw e; + } +}; diff --git a/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/install.test.ts b/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/install.test.ts index 2e6365a9913e49..eba645ae1aae4e 100644 --- a/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/install.test.ts +++ b/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/install.test.ts @@ -6,6 +6,7 @@ */ import { elasticsearchServiceMock } from 'src/core/server/mocks'; import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; +import { loggerMock } from '@kbn/logging/mocks'; import { createAppContextStartContractMock } from '../../../../mocks'; import { appContextService } from '../../../../services'; @@ -44,6 +45,7 @@ describe('EPM install', () => { const templatePriorityDatasetIsPrefixUnset = 200; await installTemplate({ esClient, + logger: loggerMock.create(), fields, dataStream: dataStreamDatasetIsPrefixUnset, packageVersion: pkg.version, @@ -84,6 +86,7 @@ describe('EPM install', () => { const templatePriorityDatasetIsPrefixFalse = 200; await installTemplate({ esClient, + logger: loggerMock.create(), fields, dataStream: dataStreamDatasetIsPrefixFalse, packageVersion: pkg.version, @@ -124,6 +127,7 @@ describe('EPM install', () => { const templatePriorityDatasetIsPrefixTrue = 150; await installTemplate({ esClient, + logger: loggerMock.create(), fields, dataStream: dataStreamDatasetIsPrefixTrue, packageVersion: pkg.version, @@ -174,6 +178,7 @@ describe('EPM install', () => { const templatePriorityDatasetIsPrefixUnset = 200; await installTemplate({ esClient, + logger: loggerMock.create(), fields, dataStream: dataStreamDatasetIsPrefixUnset, packageVersion: pkg.version, diff --git a/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/install.ts b/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/install.ts index de64b99c787adf..eb5b43650dad77 100644 --- a/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/install.ts +++ b/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/install.ts @@ -7,7 +7,7 @@ import { merge } from 'lodash'; import Boom from '@hapi/boom'; -import type { ElasticsearchClient, SavedObjectsClientContract } from 'src/core/server'; +import type { ElasticsearchClient, Logger, SavedObjectsClientContract } from 'src/core/server'; import { ElasticsearchAssetType } from '../../../../types'; import type { @@ -29,6 +29,7 @@ import { import type { ESAssetMetadata } from '../meta'; import { getESAssetMetadata } from '../meta'; +import { retryTransientEsErrors } from '../retry'; import { generateMappings, @@ -42,14 +43,15 @@ import { buildDefaultSettings } from './default_settings'; export const installTemplates = async ( installablePackage: InstallablePackage, esClient: ElasticsearchClient, + logger: Logger, paths: string[], savedObjectsClient: SavedObjectsClientContract ): Promise => { // install any pre-built index template assets, // atm, this is only the base package's global index templates // Install component templates first, as they are used by the index templates - await installPreBuiltComponentTemplates(paths, esClient); - await installPreBuiltTemplates(paths, esClient); + await installPreBuiltComponentTemplates(paths, esClient, logger); + await installPreBuiltTemplates(paths, esClient, logger); // remove package installation's references to index templates await removeAssetTypesFromInstalledEs(savedObjectsClient, installablePackage.name, [ @@ -65,6 +67,7 @@ export const installTemplates = async ( installTemplateForDataStream({ pkg: installablePackage, esClient, + logger, dataStream, }) ) @@ -84,7 +87,11 @@ export const installTemplates = async ( return installedTemplates; }; -const installPreBuiltTemplates = async (paths: string[], esClient: ElasticsearchClient) => { +const installPreBuiltTemplates = async ( + paths: string[], + esClient: ElasticsearchClient, + logger: Logger +) => { const templatePaths = paths.filter((path) => isTemplate(path)); const templateInstallPromises = templatePaths.map(async (path) => { const { file } = getPathParts(path); @@ -96,10 +103,16 @@ const installPreBuiltTemplates = async (paths: string[], esClient: Elasticsearch if (content.hasOwnProperty('template') || content.hasOwnProperty('composed_of')) { // Template is v2 - return esClient.indices.putIndexTemplate(esClientParams, esClientRequestOptions); + return retryTransientEsErrors( + () => esClient.indices.putIndexTemplate(esClientParams, esClientRequestOptions), + { logger } + ); } else { // template is V1 - return esClient.indices.putTemplate(esClientParams, esClientRequestOptions); + return retryTransientEsErrors( + () => esClient.indices.putTemplate(esClientParams, esClientRequestOptions), + { logger } + ); } }); try { @@ -113,7 +126,8 @@ const installPreBuiltTemplates = async (paths: string[], esClient: Elasticsearch const installPreBuiltComponentTemplates = async ( paths: string[], - esClient: ElasticsearchClient + esClient: ElasticsearchClient, + logger: Logger ) => { const templatePaths = paths.filter((path) => isComponentTemplate(path)); const templateInstallPromises = templatePaths.map(async (path) => { @@ -126,7 +140,10 @@ const installPreBuiltComponentTemplates = async ( body: content, }; - return esClient.cluster.putComponentTemplate(esClientParams, { ignore: [404] }); + return retryTransientEsErrors( + () => esClient.cluster.putComponentTemplate(esClientParams, { ignore: [404] }), + { logger } + ); }); try { @@ -157,15 +174,18 @@ const isComponentTemplate = (path: string) => { export async function installTemplateForDataStream({ pkg, esClient, + logger, dataStream, }: { pkg: InstallablePackage; esClient: ElasticsearchClient; + logger: Logger; dataStream: RegistryDataStream; }): Promise { const fields = await loadFieldsFromYaml(pkg, dataStream.path); return installTemplate({ esClient, + logger, fields, dataStream, packageVersion: pkg.version, @@ -186,6 +206,7 @@ interface TemplateMapEntry { type TemplateMap = Record; function putComponentTemplate( esClient: ElasticsearchClient, + logger: Logger, params: { body: TemplateMapEntry; name: string; @@ -194,9 +215,9 @@ function putComponentTemplate( ): { clusterPromise: Promise; name: string } { const { name, body, create = false } = params; return { - clusterPromise: esClient.cluster.putComponentTemplate( - { name, body, create }, - { ignore: [404] } + clusterPromise: retryTransientEsErrors( + () => esClient.cluster.putComponentTemplate({ name, body, create }, { ignore: [404] }), + { logger } ), name, }; @@ -256,10 +277,12 @@ async function installDataStreamComponentTemplates(params: { templateName: string; registryElasticsearch: RegistryElasticsearch | undefined; esClient: ElasticsearchClient; + logger: Logger; packageName: string; defaultSettings: IndexTemplate['template']['settings']; }) { - const { templateName, registryElasticsearch, esClient, packageName, defaultSettings } = params; + const { templateName, registryElasticsearch, esClient, packageName, defaultSettings, logger } = + params; const templates = buildComponentTemplates({ templateName, registryElasticsearch, @@ -274,15 +297,22 @@ async function installDataStreamComponentTemplates(params: { templateEntries.map(async ([name, body]) => { if (isUserSettingsTemplate(name)) { // look for existing user_settings template - const result = await esClient.cluster.getComponentTemplate({ name }, { ignore: [404] }); + const result = await retryTransientEsErrors( + () => esClient.cluster.getComponentTemplate({ name }, { ignore: [404] }), + { logger } + ); const hasUserSettingsTemplate = result.body.component_templates?.length === 1; if (!hasUserSettingsTemplate) { // only add if one isn't already present - const { clusterPromise } = putComponentTemplate(esClient, { body, name, create: true }); + const { clusterPromise } = putComponentTemplate(esClient, logger, { + body, + name, + create: true, + }); return clusterPromise; } } else { - const { clusterPromise } = putComponentTemplate(esClient, { body, name }); + const { clusterPromise } = putComponentTemplate(esClient, logger, { body, name }); return clusterPromise; } }) @@ -291,19 +321,26 @@ async function installDataStreamComponentTemplates(params: { return templateNames; } -export async function ensureDefaultComponentTemplate(esClient: ElasticsearchClient) { - const { body: getTemplateRes } = await esClient.cluster.getComponentTemplate( - { - name: FLEET_GLOBAL_COMPONENT_TEMPLATE_NAME, - }, - { - ignore: [404], - } +export async function ensureDefaultComponentTemplate( + esClient: ElasticsearchClient, + logger: Logger +) { + const { body: getTemplateRes } = await retryTransientEsErrors( + () => + esClient.cluster.getComponentTemplate( + { + name: FLEET_GLOBAL_COMPONENT_TEMPLATE_NAME, + }, + { + ignore: [404], + } + ), + { logger } ); const existingTemplate = getTemplateRes?.component_templates?.[0]; if (!existingTemplate) { - await putComponentTemplate(esClient, { + await putComponentTemplate(esClient, logger, { name: FLEET_GLOBAL_COMPONENT_TEMPLATE_NAME, body: FLEET_GLOBAL_COMPONENT_TEMPLATE_CONTENT, create: true, @@ -315,12 +352,14 @@ export async function ensureDefaultComponentTemplate(esClient: ElasticsearchClie export async function installTemplate({ esClient, + logger, fields, dataStream, packageVersion, packageName, }: { esClient: ElasticsearchClient; + logger: Logger; fields: Field[]; dataStream: RegistryDataStream; packageVersion: string; @@ -342,13 +381,17 @@ export async function installTemplate({ } // Datastream now throw an error if the aliases field is present so ensure that we remove that field. - const { body: getTemplateRes } = await esClient.indices.getIndexTemplate( - { - name: templateName, - }, - { - ignore: [404], - } + const { body: getTemplateRes } = await retryTransientEsErrors( + () => + esClient.indices.getIndexTemplate( + { + name: templateName, + }, + { + ignore: [404], + } + ), + { logger } ); const existingIndexTemplate = getTemplateRes?.index_templates?.[0]; @@ -369,7 +412,10 @@ export async function installTemplate({ }, }; - await esClient.indices.putIndexTemplate(updateIndexTemplateParams, { ignore: [404] }); + await retryTransientEsErrors( + () => esClient.indices.putIndexTemplate(updateIndexTemplateParams, { ignore: [404] }), + { logger } + ); } const defaultSettings = buildDefaultSettings({ @@ -384,6 +430,7 @@ export async function installTemplate({ templateName, registryElasticsearch: dataStream.elasticsearch, esClient, + logger, packageName, defaultSettings, }); @@ -406,7 +453,10 @@ export async function installTemplate({ body: template, }; - await esClient.indices.putIndexTemplate(esClientParams, { ignore: [404] }); + await retryTransientEsErrors( + () => esClient.indices.putIndexTemplate(esClientParams, { ignore: [404] }), + { logger } + ); return { templateName, diff --git a/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/template.ts b/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/template.ts index 5bad33defc5782..05f7b744f4db97 100644 --- a/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/template.ts +++ b/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/template.ts @@ -5,7 +5,7 @@ * 2.0. */ -import type { ElasticsearchClient } from 'kibana/server'; +import type { ElasticsearchClient, Logger } from 'kibana/server'; import type { Field, Fields } from '../../fields/field'; import type { @@ -18,6 +18,7 @@ import { appContextService } from '../../../'; import { getRegistryDataStreamAssetBaseName } from '../index'; import { FLEET_GLOBAL_COMPONENT_TEMPLATE_NAME } from '../../../../constants'; import { getESAssetMetadata } from '../meta'; +import { retryTransientEsErrors } from '../retry'; interface Properties { [key: string]: any; @@ -408,13 +409,14 @@ function getBaseTemplate( export const updateCurrentWriteIndices = async ( esClient: ElasticsearchClient, + logger: Logger, templates: IndexTemplateEntry[] ): Promise => { if (!templates.length) return; const allIndices = await queryDataStreamsFromTemplates(esClient, templates); if (!allIndices.length) return; - return updateAllDataStreams(allIndices, esClient); + return updateAllDataStreams(allIndices, esClient, logger); }; function isCurrentDataStream(item: CurrentDataStream[] | undefined): item is CurrentDataStream[] { @@ -448,11 +450,12 @@ const getDataStreams = async ( const updateAllDataStreams = async ( indexNameWithTemplates: CurrentDataStream[], - esClient: ElasticsearchClient + esClient: ElasticsearchClient, + logger: Logger ): Promise => { const updatedataStreamPromises = indexNameWithTemplates.map( ({ dataStreamName, indexTemplate }) => { - return updateExistingDataStream({ dataStreamName, esClient, indexTemplate }); + return updateExistingDataStream({ dataStreamName, esClient, logger, indexTemplate }); } ); await Promise.all(updatedataStreamPromises); @@ -460,10 +463,12 @@ const updateAllDataStreams = async ( const updateExistingDataStream = async ({ dataStreamName, esClient, + logger, indexTemplate, }: { dataStreamName: string; esClient: ElasticsearchClient; + logger: Logger; indexTemplate: IndexTemplate; }) => { const { settings, mappings } = indexTemplate.template; @@ -476,14 +481,19 @@ const updateExistingDataStream = async ({ // try to update the mappings first try { - await esClient.indices.putMapping({ - index: dataStreamName, - body: mappings, - write_index_only: true, - }); + await retryTransientEsErrors( + () => + esClient.indices.putMapping({ + index: dataStreamName, + body: mappings, + write_index_only: true, + }), + { logger } + ); // if update fails, rollover data stream } catch (err) { try { + // Do no wrap rollovers in retryTransientEsErrors since it is not idempotent const path = `/${dataStreamName}/_rollover`; await esClient.transport.request({ method: 'POST', @@ -498,10 +508,14 @@ const updateExistingDataStream = async ({ // for now, only update the pipeline if (!settings.index.default_pipeline) return; try { - await esClient.indices.putSettings({ - index: dataStreamName, - body: { default_pipeline: settings.index.default_pipeline }, - }); + await retryTransientEsErrors( + () => + esClient.indices.putSettings({ + index: dataStreamName, + body: { default_pipeline: settings.index.default_pipeline }, + }), + { logger } + ); } catch (err) { throw new Error(`could not update index template settings for ${dataStreamName}`); } diff --git a/x-pack/plugins/fleet/server/services/epm/elasticsearch/transform/install.ts b/x-pack/plugins/fleet/server/services/epm/elasticsearch/transform/install.ts index 8b76b5a026fc06..197d463797cac8 100644 --- a/x-pack/plugins/fleet/server/services/epm/elasticsearch/transform/install.ts +++ b/x-pack/plugins/fleet/server/services/epm/elasticsearch/transform/install.ts @@ -5,7 +5,7 @@ * 2.0. */ -import type { ElasticsearchClient, SavedObjectsClientContract } from 'kibana/server'; +import type { ElasticsearchClient, Logger, SavedObjectsClientContract } from 'kibana/server'; import { errors } from '@elastic/elasticsearch'; import { saveInstalledEsRefs } from '../../packages/install'; @@ -13,10 +13,11 @@ import { getPathParts } from '../../archive'; import { ElasticsearchAssetType } from '../../../../../common/types/models'; import type { EsAssetReference, InstallablePackage } from '../../../../../common/types/models'; import { getInstallation } from '../../packages'; -import { appContextService } from '../../../app_context'; import { getESAssetMetadata } from '../meta'; +import { retryTransientEsErrors } from '../retry'; + import { deleteTransforms, deleteTransformRefs } from './remove'; import { getAsset } from './common'; @@ -29,9 +30,9 @@ export const installTransform = async ( installablePackage: InstallablePackage, paths: string[], esClient: ElasticsearchClient, - savedObjectsClient: SavedObjectsClientContract + savedObjectsClient: SavedObjectsClientContract, + logger: Logger ) => { - const logger = appContextService.getLogger(); const installation = await getInstallation({ savedObjectsClient, pkgName: installablePackage.name, @@ -87,7 +88,7 @@ export const installTransform = async ( }); const installationPromises = transforms.map(async (transform) => { - return handleTransformInstall({ esClient, transform }); + return handleTransformInstall({ esClient, logger, transform }); }); installedTransforms = await Promise.all(installationPromises).then((results) => results.flat()); @@ -118,18 +119,24 @@ const isTransform = (path: string) => { async function handleTransformInstall({ esClient, + logger, transform, }: { esClient: ElasticsearchClient; + logger: Logger; transform: TransformInstallation; }): Promise { try { - // defer validation on put if the source index is not available - await esClient.transform.putTransform({ - transform_id: transform.installationName, - defer_validation: true, - body: transform.content, - }); + await retryTransientEsErrors( + () => + // defer validation on put if the source index is not available + esClient.transform.putTransform({ + transform_id: transform.installationName, + defer_validation: true, + body: transform.content, + }), + { logger } + ); } catch (err) { // swallow the error if the transform already exists. const isAlreadyExistError = diff --git a/x-pack/plugins/fleet/server/services/epm/elasticsearch/transform/transform.test.ts b/x-pack/plugins/fleet/server/services/epm/elasticsearch/transform/transform.test.ts index 1aef95a49fdcb3..94e2e3f6d73af8 100644 --- a/x-pack/plugins/fleet/server/services/epm/elasticsearch/transform/transform.test.ts +++ b/x-pack/plugins/fleet/server/services/epm/elasticsearch/transform/transform.test.ts @@ -21,6 +21,7 @@ jest.mock('./common', () => { import { errors } from '@elastic/elasticsearch'; import type { DeeplyMockedKeys } from '@kbn/utility-types/jest'; import type { ElasticsearchClient, SavedObject, SavedObjectsClientContract } from 'kibana/server'; +import { loggerMock } from '@kbn/logging/mocks'; import { ElasticsearchAssetType } from '../../../../types'; import type { Installation, RegistryPackage } from '../../../../types'; @@ -157,7 +158,8 @@ describe('test transform install', () => { 'endpoint-0.16.0-dev.0/elasticsearch/transform/metadata_current/default.json', ], esClient, - savedObjectsClient + savedObjectsClient, + loggerMock.create() ); expect(esClient.transform.getTransform.mock.calls).toEqual([ @@ -329,7 +331,8 @@ describe('test transform install', () => { } as unknown as RegistryPackage, ['endpoint-0.16.0-dev.0/elasticsearch/transform/metadata_current/default.json'], esClient, - savedObjectsClient + savedObjectsClient, + loggerMock.create() ); const meta = getESAssetMetadata({ packageName: 'endpoint' }); @@ -441,7 +444,8 @@ describe('test transform install', () => { } as unknown as RegistryPackage, [], esClient, - savedObjectsClient + savedObjectsClient, + loggerMock.create() ); expect(esClient.transform.getTransform.mock.calls).toEqual([ @@ -556,7 +560,8 @@ describe('test transform install', () => { } as unknown as RegistryPackage, ['endpoint-0.16.0-dev.0/elasticsearch/transform/metadata_current/default.json'], esClient, - savedObjectsClient + savedObjectsClient, + loggerMock.create() ); const meta = getESAssetMetadata({ packageName: 'endpoint' }); diff --git a/x-pack/plugins/fleet/server/services/epm/packages/_install_package.test.ts b/x-pack/plugins/fleet/server/services/epm/packages/_install_package.test.ts index 7996cbfb79ef8d..5ee0f57b6e03ab 100644 --- a/x-pack/plugins/fleet/server/services/epm/packages/_install_package.test.ts +++ b/x-pack/plugins/fleet/server/services/epm/packages/_install_package.test.ts @@ -7,6 +7,7 @@ import type { SavedObjectsClientContract, ElasticsearchClient } from 'src/core/server'; import { savedObjectsClientMock, elasticsearchServiceMock } from 'src/core/server/mocks'; +import { loggerMock } from '@kbn/logging/mocks'; import { appContextService } from '../../app_context'; import { createAppContextStartContractMock } from '../../../mocks'; @@ -66,6 +67,7 @@ describe('_installPackage', () => { const installationPromise = _installPackage({ savedObjectsClient: soClient, esClient, + logger: loggerMock.create(), paths: [], packageInfo: { title: 'title', diff --git a/x-pack/plugins/fleet/server/services/epm/packages/_install_package.ts b/x-pack/plugins/fleet/server/services/epm/packages/_install_package.ts index 776a3d3cd6bc1f..e2027a99463fcc 100644 --- a/x-pack/plugins/fleet/server/services/epm/packages/_install_package.ts +++ b/x-pack/plugins/fleet/server/services/epm/packages/_install_package.ts @@ -5,7 +5,12 @@ * 2.0. */ -import type { ElasticsearchClient, SavedObject, SavedObjectsClientContract } from 'src/core/server'; +import type { + ElasticsearchClient, + Logger, + SavedObject, + SavedObjectsClientContract, +} from 'src/core/server'; import { MAX_TIME_COMPLETE_INSTALL, @@ -44,6 +49,7 @@ import { deleteKibanaSavedObjectsAssets } from './remove'; export async function _installPackage({ savedObjectsClient, esClient, + logger, installedPkg, paths, packageInfo, @@ -52,6 +58,7 @@ export async function _installPackage({ }: { savedObjectsClient: SavedObjectsClientContract; esClient: ElasticsearchClient; + logger: Logger; installedPkg?: SavedObject; paths: string[]; packageInfo: InstallablePackage; @@ -131,41 +138,51 @@ export async function _installPackage({ // currently only the base package has an ILM policy // at some point ILM policies can be installed/modified // per data stream and we should then save them - await installILMPolicy(packageInfo, paths, esClient); + await installILMPolicy(packageInfo, paths, esClient, logger); const installedDataStreamIlm = await installIlmForDataStream( packageInfo, paths, esClient, - savedObjectsClient + savedObjectsClient, + logger ); // installs ml models - const installedMlModel = await installMlModel(packageInfo, paths, esClient, savedObjectsClient); + const installedMlModel = await installMlModel( + packageInfo, + paths, + esClient, + savedObjectsClient, + logger + ); // installs versionized pipelines without removing currently installed ones const installedPipelines = await installPipelines( packageInfo, paths, esClient, - savedObjectsClient + savedObjectsClient, + logger ); // install or update the templates referencing the newly installed pipelines const installedTemplates = await installTemplates( packageInfo, esClient, + logger, paths, savedObjectsClient ); // update current backing indices of each data stream - await updateCurrentWriteIndices(esClient, installedTemplates); + await updateCurrentWriteIndices(esClient, logger, installedTemplates); const installedTransforms = await installTransform( packageInfo, paths, esClient, - savedObjectsClient + savedObjectsClient, + logger ); // If this is an update or retrying an update, delete the previous version's pipelines diff --git a/x-pack/plugins/fleet/server/services/epm/packages/install.ts b/x-pack/plugins/fleet/server/services/epm/packages/install.ts index db26dc3a20a808..330fd84e789b88 100644 --- a/x-pack/plugins/fleet/server/services/epm/packages/install.ts +++ b/x-pack/plugins/fleet/server/services/epm/packages/install.ts @@ -308,6 +308,7 @@ async function installPackageFromRegistry({ return _installPackage({ savedObjectsClient, esClient, + logger, installedPkg, paths, packageInfo, @@ -367,6 +368,7 @@ async function installPackageByUpload({ archiveBuffer, contentType, }: InstallUploadedArchiveParams): Promise { + const logger = appContextService.getLogger(); // if an error happens during getInstallType, report that we don't know let installType: InstallType = 'unknown'; const telemetryEvent: PackageUpdateEvent = getTelemetryEvent('', ''); @@ -409,6 +411,7 @@ async function installPackageByUpload({ return _installPackage({ savedObjectsClient, esClient, + logger, installedPkg, paths, packageInfo, 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 36976bea4a970c..ac88204f082b77 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 535d93cc3eceb1..5ac348ad7c8a2c 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 8b906b68556a4c..76fa7778eafa24 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/fleet/server/services/setup.ts b/x-pack/plugins/fleet/server/services/setup.ts index d39a5f44731997..18c66e82674684 100644 --- a/x-pack/plugins/fleet/server/services/setup.ts +++ b/x-pack/plugins/fleet/server/services/setup.ts @@ -139,8 +139,8 @@ export async function ensureFleetGlobalEsAssets( // Ensure Global Fleet ES assets are installed logger.debug('Creating Fleet component template and ingest pipeline'); const globalAssetsRes = await Promise.all([ - ensureDefaultComponentTemplate(esClient), - ensureFleetFinalPipelineIsInstalled(esClient), + ensureDefaultComponentTemplate(esClient, logger), + ensureFleetFinalPipelineIsInstalled(esClient, logger), ]); if (globalAssetsRes.some((asset) => asset.isCreated)) { diff --git a/x-pack/plugins/index_management/__jest__/client_integration/helpers/http_requests.ts b/x-pack/plugins/index_management/__jest__/client_integration/helpers/http_requests.ts index b9f1191da8af74..e26eeadd4edcd8 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/helpers/http_requests.ts +++ b/x-pack/plugins/index_management/__jest__/client_integration/helpers/http_requests.ts @@ -10,6 +10,12 @@ import { API_BASE_PATH } from '../../../common/constants'; type HttpResponse = Record | any[]; +export interface ResponseError { + statusCode: number; + message: string | Error; + attributes?: Record; +} + // Register helpers to mock HTTP Requests const registerHttpRequestMockHelpers = (server: SinonFakeServer) => { const setLoadTemplatesResponse = (response: HttpResponse = []) => { @@ -101,6 +107,17 @@ const registerHttpRequestMockHelpers = (server: SinonFakeServer) => { ]); }; + const setUpdateIndexSettingsResponse = (response?: HttpResponse, error?: ResponseError) => { + const status = error ? error.statusCode || 400 : 200; + const body = error ?? response; + + server.respondWith('PUT', `${API_BASE_PATH}/settings/:name`, [ + status, + { 'Content-Type': 'application/json' }, + JSON.stringify(body), + ]); + }; + const setSimulateTemplateResponse = (response?: HttpResponse, error?: any) => { const status = error ? error.status || 400 : 200; const body = error ? JSON.stringify(error.body) : JSON.stringify(response); @@ -134,6 +151,7 @@ const registerHttpRequestMockHelpers = (server: SinonFakeServer) => { setLoadTemplateResponse, setCreateTemplateResponse, setUpdateTemplateResponse, + setUpdateIndexSettingsResponse, setSimulateTemplateResponse, setLoadComponentTemplatesResponse, }; diff --git a/x-pack/plugins/index_management/__jest__/client_integration/helpers/test_subjects.ts b/x-pack/plugins/index_management/__jest__/client_integration/helpers/test_subjects.ts index 96775484e0733c..ac4b4c46ad4d1c 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/helpers/test_subjects.ts +++ b/x-pack/plugins/index_management/__jest__/client_integration/helpers/test_subjects.ts @@ -60,4 +60,6 @@ export type TestSubjects = | 'templateTable' | 'title' | 'unfreezeIndexMenuButton' + | 'updateEditIndexSettingsButton' + | 'updateIndexSettingsErrorCallout' | 'viewButton'; diff --git a/x-pack/plugins/index_management/__jest__/client_integration/home/indices_tab.test.ts b/x-pack/plugins/index_management/__jest__/client_integration/home/indices_tab.test.ts index 79fe885820fae3..ec80bf5d712c02 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/home/indices_tab.test.ts +++ b/x-pack/plugins/index_management/__jest__/client_integration/home/indices_tab.test.ts @@ -12,6 +12,31 @@ import { setupEnvironment, nextTick } from '../helpers'; import { IndicesTestBed, setup } from './indices_tab.helpers'; import { createDataStreamPayload, createNonDataStreamIndex } from './data_streams_tab.helpers'; +// Since the editor component being used for editing index settings is not a React +// component but an editor being instantiated on a div reference, we cannot mock +// the component and replace it with something else. In this particular case we're +// mocking the returned instance of the editor to always have the same values. +const mockGetAceEditorValue = jest.fn().mockReturnValue(`{}`); + +jest.mock('../../../public/application/lib/ace.js', () => { + const createAceEditor = () => { + return { + getValue: mockGetAceEditorValue, + getSession: () => { + return { + on: () => null, + getValue: () => null, + }; + }, + destroy: () => null, + }; + }; + + return { + createAceEditor, + }; +}); + /** * The below import is required to avoid a console error warn from the "brace" package * console.warn ../node_modules/brace/index.js:3999 @@ -212,4 +237,43 @@ describe('', () => { expect(exists('unfreezeIndexMenuButton')).toBe(false); }); }); + + describe('Edit index settings', () => { + const indexName = 'testIndex'; + + beforeEach(async () => { + httpRequestsMockHelpers.setLoadIndicesResponse([createNonDataStreamIndex(indexName)]); + + testBed = await setup(); + const { find, component } = testBed; + component.update(); + + find('indexTableIndexNameLink').at(0).simulate('click'); + }); + + test('shows error callout when request fails', async () => { + const { actions, find, component, exists } = testBed; + + mockGetAceEditorValue.mockReturnValue(`{ + "index.routing.allocation.include._tier_preference": "non_existent_tier" + }`); + + const error = { + statusCode: 400, + error: 'Bad Request', + message: 'invalid tier names found in ...', + }; + httpRequestsMockHelpers.setUpdateIndexSettingsResponse(undefined, error); + + await actions.selectIndexDetailsTab('edit_settings'); + + await act(async () => { + find('updateEditIndexSettingsButton').simulate('click'); + }); + + component.update(); + + expect(exists('updateIndexSettingsErrorCallout')).toBe(true); + }); + }); }); diff --git a/x-pack/plugins/index_management/public/application/sections/home/index_list/detail_panel/edit_settings_json/edit_settings_json.js b/x-pack/plugins/index_management/public/application/sections/home/index_list/detail_panel/edit_settings_json/edit_settings_json.js index 2337485e6c82b5..55581190b89b82 100644 --- a/x-pack/plugins/index_management/public/application/sections/home/index_list/detail_panel/edit_settings_json/edit_settings_json.js +++ b/x-pack/plugins/index_management/public/application/sections/home/index_list/detail_panel/edit_settings_json/edit_settings_json.js @@ -6,6 +6,7 @@ */ import React from 'react'; +import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; import { documentationService } from '../../../../../services/documentation'; @@ -13,10 +14,9 @@ import { EuiButton, EuiFlexGroup, EuiFlexItem, - EuiIcon, + EuiCallOut, EuiLink, EuiSpacer, - EuiTextColor, EuiTitle, } from '@elastic/eui'; import { TAB_SETTINGS } from '../../../../../constants'; @@ -90,16 +90,25 @@ export class EditSettingsJson extends React.PureComponent { }; errorMessage() { const { error } = this.props; + if (!error) { return null; } + return ( -
- - - {error} + <> -
+ +

{error}

+
+ ); } render() { @@ -135,6 +144,7 @@ export class EditSettingsJson extends React.PureComponent {
+ {this.errorMessage()} - {this.errorMessage()}
); diff --git a/x-pack/plugins/index_management/public/application/services/api.ts b/x-pack/plugins/index_management/public/application/services/api.ts index 5cfb881cb22cfa..972d7c6c87f36c 100644 --- a/x-pack/plugins/index_management/public/application/services/api.ts +++ b/x-pack/plugins/index_management/public/application/services/api.ts @@ -194,14 +194,17 @@ export async function loadIndexSettings(indexName: string) { } export async function updateIndexSettings(indexName: string, body: object) { - const response = await httpService.httpClient.put( - `${API_BASE_PATH}/settings/${encodeURIComponent(indexName)}`, - { - body: JSON.stringify(body), - } - ); + const response = await sendRequest({ + path: `${API_BASE_PATH}/settings/${encodeURIComponent(indexName)}`, + method: 'put', + body: JSON.stringify(body), + }); + // Only track successful requests. - uiMetricService.trackMetric(METRIC_TYPE.COUNT, UIM_UPDATE_SETTINGS); + if (!response.error) { + uiMetricService.trackMetric(METRIC_TYPE.COUNT, UIM_UPDATE_SETTINGS); + } + return response; } diff --git a/x-pack/plugins/index_management/public/application/store/actions/update_index_settings.js b/x-pack/plugins/index_management/public/application/store/actions/update_index_settings.js index bbb251634976e3..22ebf78fc5fe07 100644 --- a/x-pack/plugins/index_management/public/application/store/actions/update_index_settings.js +++ b/x-pack/plugins/index_management/public/application/store/actions/update_index_settings.js @@ -22,13 +22,9 @@ export const updateIndexSettings = ({ indexName, settings }) => async (dispatch) => { if (Object.keys(settings).length !== 0) { - try { - const { error, message } = await request(indexName, settings); + const { error } = await request(indexName, settings); - if (error) { - return dispatch(updateIndexSettingsError({ error: message })); - } - } catch (error) { + if (error) { return dispatch(updateIndexSettingsError({ error: error.message })); } } diff --git a/x-pack/plugins/lens/public/xy_visualization/color_assignment.ts b/x-pack/plugins/lens/public/xy_visualization/color_assignment.ts index 5ed6ec052a0da4..be7f6f1d1d2252 100644 --- a/x-pack/plugins/lens/public/xy_visualization/color_assignment.ts +++ b/x-pack/plugins/lens/public/xy_visualization/color_assignment.ts @@ -108,7 +108,7 @@ export function getAccessorColorConfig( ): AccessorConfig[] { const layerContainsSplits = Boolean(layer.splitAccessor); const currentPalette: PaletteOutput = layer.palette || { type: 'palette', name: 'default' }; - const totalSeriesCount = colorAssignments[currentPalette.name].totalSeriesCount; + const totalSeriesCount = colorAssignments[currentPalette.name]?.totalSeriesCount; return layer.accessors.map((accessor) => { const currentYConfig = layer.yConfig?.find((yConfig) => yConfig.forAccessor === accessor); if (layerContainsSplits) { @@ -132,17 +132,19 @@ export function getAccessorColorConfig( ); const customColor = currentYConfig?.color || - paletteService.get(currentPalette.name).getCategoricalColor( - [ - { - name: columnToLabel[accessor] || accessor, - rankAtDepth: rank, - totalSeriesAtDepth: totalSeriesCount, - }, - ], - { maxDepth: 1, totalSeries: totalSeriesCount }, - currentPalette.params - ); + (totalSeriesCount != null + ? paletteService.get(currentPalette.name).getCategoricalColor( + [ + { + name: columnToLabel[accessor] || accessor, + rankAtDepth: rank, + totalSeriesAtDepth: totalSeriesCount, + }, + ], + { maxDepth: 1, totalSeries: totalSeriesCount }, + currentPalette.params + ) + : undefined); return { columnId: accessor as string, triggerIcon: customColor ? 'color' : 'disabled', diff --git a/x-pack/plugins/lens/public/xy_visualization/visualization.test.ts b/x-pack/plugins/lens/public/xy_visualization/visualization.test.ts index 086e05b3e4622f..ff7ad2c0f2d85f 100644 --- a/x-pack/plugins/lens/public/xy_visualization/visualization.test.ts +++ b/x-pack/plugins/lens/public/xy_visualization/visualization.test.ts @@ -1040,6 +1040,21 @@ describe('xy_visualization', () => { ]) ); }); + + it('should be excluded and not crash when a custom palette is used for data layer', () => { + const state = getStateWithBaseReferenceLine(); + // now add a breakdown on the data layer with a custom palette + state.layers[0].palette = { type: 'palette', name: 'custom', params: {} }; + state.layers[0].splitAccessor = 'd'; + + const options = xyVisualization.getConfiguration({ + state, + frame, + layerId: 'referenceLine', + }).groups; + // it should not crash basically + expect(options).toHaveLength(1); + }); }); describe('color assignment', () => { diff --git a/x-pack/plugins/maps/public/actions/map_actions.ts b/x-pack/plugins/maps/public/actions/map_actions.ts index 9ec9a42986fbbc..ddfc441471c1e0 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 2bc79bfaea7462..9b49d2b597e46f 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 00000000000000..34edf8cd096077 --- /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 4385adbd4de652..1f484b7ecfc506 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 3f7c782ca469b8..3152ac27189b3a 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 2f46bfa8719344..5780c2c723dd6e 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 2c6bd64e1bc654..71a960fc1919b7 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 3dc44c171d4150..e76e9e936faec8 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 015aad84cde580..c858c74c819d50 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 f975bc293d8239..54f99c37b6d815 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 b4322c93097f0e..2979bec06b95a1 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/public/application/application.test.tsx b/x-pack/plugins/observability/public/application/application.test.tsx index 6b5863c8b122ac..dddc44c3c26ea2 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 c9c2ed549a1c3d..35835cd0bc8e6a 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 8a99b6a53cf063..b4dda3ed3559e6 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 bf513d8a1a99a0..bbf3096e551075 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 0dab3e51357171..2383044bc14c23 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 64d495dbbc798d..36b1fc2f2b6e29 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 4fdc8d245799ad..13fb5d805fb810 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 c5cad5f3b1c8c4..ced4896c5f31d3 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 00000000000000..4153ab6e5b596f --- /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 926f03acf01d89..14d47d1e7e9d3d 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/alerts_status_filter.tsx b/x-pack/plugins/observability/public/pages/alerts/components/alerts_status_filter.tsx similarity index 94% rename from x-pack/plugins/observability/public/pages/alerts/alerts_status_filter.tsx rename to x-pack/plugins/observability/public/pages/alerts/components/alerts_status_filter.tsx index 38c753bbebf3b0..d717e916de2c61 100644 --- a/x-pack/plugins/observability/public/pages/alerts/alerts_status_filter.tsx +++ b/x-pack/plugins/observability/public/pages/alerts/components/alerts_status_filter.tsx @@ -13,8 +13,8 @@ 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 { AlertStatusFilterButton } from '../../../common/typings'; -import { AlertStatusFilter } from '../../../common/typings'; +import { AlertStatusFilterButton } from '../../../../common/typings'; +import { AlertStatusFilter } from '../../../../common/typings'; export interface AlertStatusFilterProps { status: AlertStatusFilterButton; 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 3adfb0a1d9c891..5126647161fa56 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 00000000000000..57ad311f65d1c9 --- /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 7b288030840671..680798811e9abc 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 00000000000000..b6df77f0758887 --- /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 79a27faa96c697..25de2e36b08c34 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 80ccd4a69b281f..d9fa6c6e2221bd 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 00000000000000..7974156327085d --- /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 00000000000000..84badecd29dcd4 --- /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 e06b5d333a9a67..4dce3ee80b8336 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 29c5e88788a895..a9819a6619dc54 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 d857b9d6bd6506..86116fb969682e 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 87% 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 2636463bcfd7ae..b19a1dbe86fe11 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 @@ -14,23 +14,25 @@ import useAsync from 'react-use/lib/useAsync'; 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 { AlertsSearchBar } from './alerts_search_bar'; -import { AlertsTableTGrid } from './alerts_table_t_grid'; -import { Provider, alertsPageStateContainer, useAlertsPageStateContainer } from './state_container'; +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 { AlertsStatusFilter } from './alerts_status_filter'; -import { AlertsDisclaimer } from './alerts_disclaimer'; +import { AlertsStatusFilter, AlertsDisclaimer, AlertsSearchBar } from '../../components'; export interface TopAlert { fields: ParsedTechnicalFields; @@ -243,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 00000000000000..e3509e04b2f2bd --- /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 94% 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 4b64ae07ddf062..cc455567d0079d 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 @@ -33,33 +33,33 @@ import styled from 'styled-components'; import React, { Suspense, useMemo, useState, useCallback, useEffect } from 'react'; import usePrevious from 'react-use/lib/usePrevious'; import { pick } from 'lodash'; -import { getAlertsPermissions } from '../../hooks/use_alert_permission'; +import { getAlertsPermissions } from '../../../../hooks/use_alert_permission'; import type { TimelinesUIStart, TGridType, TGridState, TGridModel, SortDirection, -} from '../../../../timelines/public'; +} from '../../../../../../timelines/public'; -import type { TopAlert } from './'; -import { useKibana } from '../../../../../../src/plugins/kibana_react/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'; +} 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 { LazyAlertsFlyout } from '../..'; -import { parseAlert } from './parse_alert'; -import { CoreStart } from '../../../../../../src/core/public'; -import { translations, paths } from '../../config'; +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'; @@ -439,6 +439,7 @@ export function AlertsTableTGrid(props: AlertsTableTGridProps) { runtimeMappings: {}, start: rangeFrom, setRefetch, + showCheckboxes: false, sort: tGridState?.sort ?? [ { columnId: '@timestamp', 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 00000000000000..7bbcc43230a443 --- /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 00000000000000..074f48f426640e --- /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 3e0a801fedbe22..d00109cc5d63f6 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 dfa4afcd939cc7..5e81286affba7b 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 00000000000000..525f3441c44705 --- /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 91f45c711d6a60..f4bc5af7f604db 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 00000000000000..b37ed1d873ba7f --- /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 7100a0552876d0..cc38445e3a0f28 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 00000000000000..7100a0552876d0 --- /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 6549e892cab12c..6213ea3e66d400 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 00000000000000..f4cdec680af947 --- /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 169f4b5254c045..6f38a66cdb643c 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 544f3feecb2bbd..a3ec446e5c3075 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 d99cf0865c0dd0..51204c7512a3d5 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 0947d24f827c21..0f2572ff2b2e4b 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 bc74f5463ba33f..43aefb73aebb9b 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 508d217cdd030e..60513c417165f0 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 424e85327c22b1..7f6bc9e5d95054 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 7d38ebe81e4e84..00000000000000 --- 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 0849f8850f91d4..68226affb41e44 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 1db313b091025f..cdbddb8d89c89c 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 63a5e80289e3e7..b26037aa917b86 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 859070bd498e32..1ea85e5a5434e3 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 6b10ca5f837d5c..bbcf651bd6d691 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 bfdec28a509870..bbfa17c5694f1e 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 007c3e306372ef..1601ea481cf2d0 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/cypress/integration/detection_alerts/cti_enrichments.spec.ts b/x-pack/plugins/security_solution/cypress/integration/detection_alerts/cti_enrichments.spec.ts index ec3d5a86763028..c9c2ff21593336 100644 --- a/x-pack/plugins/security_solution/cypress/integration/detection_alerts/cti_enrichments.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/detection_alerts/cti_enrichments.spec.ts @@ -55,11 +55,13 @@ describe('CTI Enrichment', () => { goToRuleDetails(); }); - it.skip('Displays enrichment matched.* fields on the timeline', () => { + it('Displays enrichment matched.* fields on the timeline', () => { const expectedFields = { 'threat.enrichments.matched.atomic': getNewThreatIndicatorRule().atomic, - 'threat.enrichments.matched.type': 'indicator_match_rule', + 'threat.enrichments.matched.type': getNewThreatIndicatorRule().matchedType, 'threat.enrichments.matched.field': getNewThreatIndicatorRule().indicatorMappingField, + 'threat.enrichments.matched.id': getNewThreatIndicatorRule().matchedId, + 'threat.enrichments.matched.index': getNewThreatIndicatorRule().matchedIndex, }; const fields = Object.keys(expectedFields) as Array; diff --git a/x-pack/plugins/security_solution/cypress/objects/rule.ts b/x-pack/plugins/security_solution/cypress/objects/rule.ts index 1c81099d43dd57..2c2a743eb96d09 100644 --- a/x-pack/plugins/security_solution/cypress/objects/rule.ts +++ b/x-pack/plugins/security_solution/cypress/objects/rule.ts @@ -80,6 +80,9 @@ export interface ThreatIndicatorRule extends CustomRule { threatIndicatorPath: string; type?: string; atomic?: string; + matchedType?: string; + matchedId?: string; + matchedIndex?: string; } export interface MachineLearningRule { @@ -407,6 +410,9 @@ export const getNewThreatIndicatorRule = (): ThreatIndicatorRule => ({ timeline: getIndicatorMatchTimelineTemplate(), maxSignals: 100, threatIndicatorPath: 'threat.indicator', + matchedType: 'indicator_match_rule', + matchedId: '84cf452c1e0375c3d4412cb550bd1783358468a3b3b777da4829d72c7d6fb74f', + matchedIndex: 'logs-ti_abusech.malware', }); export const duplicatedRuleName = `${getNewThreatIndicatorRule().name} [Duplicate]`; 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 d9fbbcb5ec252a..5f51436daff6a7 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 eb4e226940c5f1..87a5710ab03725 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 187de40d33df06..b056fd3ed80f0e 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 838bfe63782c80..9e821c8f686f6f 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 bb9f7e1475247f..da3e4ccc99b993 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 3626bcd5f127ec..3a602a54ca099f 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 fd48cd4eebc2c5..ee3098b8577d4f 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 f847e385e64771..06328137973c6b 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 9bd0fe3cef59a4..c4cd5d4211c963 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 dcf43d41e8d78a..e24a6a883b6dfb 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 2cf7e95f3c6212..448d1b1a1db638 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 ec25b45dd1597e..4ab8afd796f6df 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/timelines/public/components/t_grid/body/index.tsx b/x-pack/plugins/timelines/public/components/t_grid/body/index.tsx index be9e30a62c5082..8ed848a44c25b4 100644 --- a/x-pack/plugins/timelines/public/components/t_grid/body/index.tsx +++ b/x-pack/plugins/timelines/public/components/t_grid/body/index.tsx @@ -122,6 +122,7 @@ interface OwnProps { ruleProducer?: string; }) => boolean; totalSelectAllAlerts?: number; + showCheckboxes?: boolean; } const defaultUnit = (n: number) => i18n.ALERTS_UNIT(n); diff --git a/x-pack/plugins/timelines/public/components/t_grid/standalone/index.tsx b/x-pack/plugins/timelines/public/components/t_grid/standalone/index.tsx index f08cb4db9b119b..a95683e7de4aad 100644 --- a/x-pack/plugins/timelines/public/components/t_grid/standalone/index.tsx +++ b/x-pack/plugins/timelines/public/components/t_grid/standalone/index.tsx @@ -118,6 +118,7 @@ export interface TGridStandaloneProps { bulkActions?: BulkActionsProp; data?: DataPublicPluginStart; unit?: (total: number) => React.ReactNode; + showCheckboxes?: boolean; } const TGridStandaloneComponent: React.FC = ({ @@ -151,6 +152,7 @@ const TGridStandaloneComponent: React.FC = ({ trailingControlColumns, data, unit, + showCheckboxes = true, }) => { const dispatch = useDispatch(); const columnsHeader = isEmpty(columns) ? defaultHeaders : columns; @@ -320,7 +322,7 @@ const TGridStandaloneComponent: React.FC = ({ indexNames, itemsPerPage: itemsPerPageStore, itemsPerPageOptions, - showCheckboxes: true, + showCheckboxes, }) ); dispatch( @@ -406,6 +408,7 @@ const TGridStandaloneComponent: React.FC = ({ unit={unit} filterStatus={filterStatus} trailingControlColumns={trailingControlColumns} + showCheckboxes={showCheckboxes} /> diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 9fc49535e18ef4..5948e16a5907fa 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -6233,13 +6233,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": "トランザクション", @@ -15035,8 +15031,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 102051ac2a9dfa..0da3642eefa3cb 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -6276,13 +6276,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": "事务", @@ -15232,8 +15228,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 8181a5171d1986..293a5e79e29b21 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 2e2c80b790cd56..e334d214f24638 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 fa6af0f5e4228a..69c7c24b63123a 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 e442d3b4fd11c0..b11993e2baa5a9 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 ccd5e7b5a2cc65..8b70869645649c 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 bef84c41796d96..9c8098390d1292 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 df2e9cfa6d4ead..37fdad9b195d4e 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 69c0e1d7ba4fef..ed183e1f81357a 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 00000000000000..0d730c5f96e970 --- /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 f8754307e0820f..1952d50cdd92a8 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 c3b2b632850be0..e3e13b16ebf22d 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_management/formatters/browser.ts b/x-pack/plugins/uptime/public/components/monitor_management/formatters/browser.ts new file mode 100644 index 00000000000000..e4a84e2bb85b5d --- /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 00000000000000..92d2b28f1283d1 --- /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 00000000000000..41e162cff2d05d --- /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 00000000000000..841f9863094829 --- /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 00000000000000..56dac35596d45d --- /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 00000000000000..cdf77307a6938d --- /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 00000000000000..5f7ce27637f3e2 --- /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 00000000000000..9027aa45204184 --- /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 00000000000000..081ceee1fe63c6 --- /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 00000000000000..30659e6c2cf40d --- /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 00000000000000..6ee3eacd81cd8f --- /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/pages/add_monitor.tsx b/x-pack/plugins/uptime/public/pages/add_monitor.tsx index 10e3d9d6ce29ad..a5f925e4712af7 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 3be1bc7b35d8dd..965639bd121452 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/kibana.index.ts b/x-pack/plugins/uptime/server/kibana.index.ts index 945a4295148a26..3afdd7729c7174 100644 --- a/x-pack/plugins/uptime/server/kibana.index.ts +++ b/x-pack/plugins/uptime/server/kibana.index.ts @@ -11,7 +11,7 @@ import { DEFAULT_APP_CATEGORIES } from '../../../../src/core/server'; import { PLUGIN } from '../common/constants/plugin'; import { compose } from './lib/compose/kibana'; import { initUptimeServer } from './uptime_server'; -import { UptimeCorePluginsSetup, UptimeCoreSetup } from './lib/adapters/framework'; +import { UptimeCorePluginsSetup, UptimeServerSetup } from './lib/adapters/framework'; import { umDynamicSettings } from './lib/saved_objects/uptime_settings'; import { UptimeRuleRegistry } from './plugin'; @@ -28,7 +28,7 @@ export interface KibanaServer extends Server { } export const initServerWithKibana = ( - server: UptimeCoreSetup, + server: UptimeServerSetup, plugins: UptimeCorePluginsSetup, ruleRegistry: UptimeRuleRegistry, logger: Logger diff --git a/x-pack/plugins/uptime/server/lib/adapters/framework/adapter_types.ts b/x-pack/plugins/uptime/server/lib/adapters/framework/adapter_types.ts index 029c6164c04812..64063633d12e70 100644 --- a/x-pack/plugins/uptime/server/lib/adapters/framework/adapter_types.ts +++ b/x-pack/plugins/uptime/server/lib/adapters/framework/adapter_types.ts @@ -6,11 +6,7 @@ */ import { UsageCollectionSetup } from 'src/plugins/usage_collection/server'; -import type { - SavedObjectsClientContract, - ISavedObjectsRepository, - IScopedClusterClient, -} from 'src/core/server'; +import type { SavedObjectsClientContract, IScopedClusterClient } from 'src/core/server'; import { ObservabilityPluginSetup } from '../../../../../observability/server'; import { EncryptedSavedObjectsPluginSetup, @@ -35,16 +31,17 @@ export type UMElasticsearchQueryFn = ( ) => Promise; export type UMSavedObjectsQueryFn = ( - client: SavedObjectsClientContract | ISavedObjectsRepository, + client: SavedObjectsClientContract, params?: P ) => Promise | T; -export interface UptimeCoreSetup { +export interface UptimeServerSetup { router: UptimeRouter; config: UptimeConfig; cloud?: CloudSetup; fleet: FleetStartContract; security: SecurityPluginStart; + savedObjectsClient: SavedObjectsClientContract; encryptedSavedObjects: EncryptedSavedObjectsPluginStart; } 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 eae9dd5e73cae7..ae83c8365a9253 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 @@ -5,12 +5,12 @@ * 2.0. */ -import { UptimeCoreSetup } from './adapter_types'; +import { UptimeServerSetup } from './adapter_types'; import { UMBackendFrameworkAdapter } from './adapter_types'; import { UMKibanaRoute } from '../../../rest_api'; export class UMKibanaBackendFrameworkAdapter implements UMBackendFrameworkAdapter { - constructor(private readonly server: UptimeCoreSetup) { + constructor(private readonly server: UptimeServerSetup) { this.server = server; } @@ -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/lib/adapters/telemetry/kibana_telemetry_adapter.ts b/x-pack/plugins/uptime/server/lib/adapters/telemetry/kibana_telemetry_adapter.ts index d829044da50474..f72af3311affe0 100644 --- a/x-pack/plugins/uptime/server/lib/adapters/telemetry/kibana_telemetry_adapter.ts +++ b/x-pack/plugins/uptime/server/lib/adapters/telemetry/kibana_telemetry_adapter.ts @@ -6,7 +6,7 @@ */ import moment from 'moment'; -import { ISavedObjectsRepository, SavedObjectsClientContract } from 'kibana/server'; +import { SavedObjectsClientContract } from 'kibana/server'; import { CollectorFetchContext, UsageCollectionSetup } from 'src/plugins/usage_collection/server'; import { PageViewParams, UptimeTelemetry, Usage } from './types'; import { savedObjectsAdapter } from '../../saved_objects/saved_objects'; @@ -23,7 +23,7 @@ const BUCKET_NUMBER = 24; export class KibanaTelemetryAdapter { public static registerUsageCollector = ( usageCollector: UsageCollectionSetup, - getSavedObjectsClient: () => ISavedObjectsRepository | undefined + getSavedObjectsClient: () => SavedObjectsClientContract | undefined ) => { if (!usageCollector) { return; @@ -37,7 +37,7 @@ export class KibanaTelemetryAdapter { public static initUsageCollector( usageCollector: UsageCollectionSetup, - getSavedObjectsClient: () => ISavedObjectsRepository | undefined + getSavedObjectsClient: () => SavedObjectsClientContract | undefined ) { return usageCollector.makeUsageCollector({ type: 'uptime', @@ -212,7 +212,7 @@ export class KibanaTelemetryAdapter { public static async countNoOfUniqueMonitorAndLocations( callCluster: UptimeESClient, - savedObjectsClient: ISavedObjectsRepository | SavedObjectsClientContract + savedObjectsClient: SavedObjectsClientContract ) { const dynamicSettings = await savedObjectsAdapter.getUptimeDynamicSettings(savedObjectsClient); const params = { diff --git a/x-pack/plugins/uptime/server/lib/alerts/test_utils/index.ts b/x-pack/plugins/uptime/server/lib/alerts/test_utils/index.ts index 6481a1e2ebdcf0..826259cfa1405c 100644 --- a/x-pack/plugins/uptime/server/lib/alerts/test_utils/index.ts +++ b/x-pack/plugins/uptime/server/lib/alerts/test_utils/index.ts @@ -7,7 +7,7 @@ import { Logger } from 'kibana/server'; import { UMServerLibs } from '../../lib'; -import { UptimeCorePluginsSetup, UptimeCoreSetup } from '../../adapters'; +import { UptimeCorePluginsSetup, UptimeServerSetup } from '../../adapters'; import type { UptimeRouter } from '../../../types'; import type { IRuleDataClient } from '../../../../../rule_registry/server'; import { ruleRegistryMocks } from '../../../../../rule_registry/server/mocks'; @@ -27,7 +27,7 @@ export const bootstrapDependencies = (customRequests?: any, customPlugins: any = const router = {} as UptimeRouter; // these server/libs parameters don't have any functionality, which is fine // because we aren't testing them here - const server = { router, config: {} } as UptimeCoreSetup; + const server = { router, config: {} } as UptimeServerSetup; const plugins: UptimeCorePluginsSetup = customPlugins as any; const libs: UMServerLibs = { requests: {} } as UMServerLibs; libs.requests = { ...libs.requests, ...customRequests }; diff --git a/x-pack/plugins/uptime/server/lib/alerts/types.ts b/x-pack/plugins/uptime/server/lib/alerts/types.ts index f734628e61b958..e8e496cba997e8 100644 --- a/x-pack/plugins/uptime/server/lib/alerts/types.ts +++ b/x-pack/plugins/uptime/server/lib/alerts/types.ts @@ -4,7 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { UptimeCorePluginsSetup, UptimeCoreSetup } from '../adapters'; +import { UptimeCorePluginsSetup, UptimeServerSetup } from '../adapters'; import { UMServerLibs } from '../lib'; import { AlertTypeWithExecutor } from '../../../../rule_registry/server'; import { AlertInstanceContext, AlertTypeState } from '../../../../alerting/common'; @@ -30,7 +30,7 @@ export type DefaultUptimeAlertInstance = AlertTy >; export type UptimeAlertTypeFactory = ( - server: UptimeCoreSetup, + server: UptimeServerSetup, libs: UMServerLibs, plugins: UptimeCorePluginsSetup ) => DefaultUptimeAlertInstance; diff --git a/x-pack/plugins/uptime/server/lib/compose/kibana.ts b/x-pack/plugins/uptime/server/lib/compose/kibana.ts index 6cdee92c40dbe4..42c88a69bf849b 100644 --- a/x-pack/plugins/uptime/server/lib/compose/kibana.ts +++ b/x-pack/plugins/uptime/server/lib/compose/kibana.ts @@ -9,9 +9,9 @@ import { UMKibanaBackendFrameworkAdapter } from '../adapters/framework'; import { requests } from '../requests'; import { licenseCheck } from '../domains'; import { UMServerLibs } from '../lib'; -import { UptimeCoreSetup } from '../adapters/framework'; +import { UptimeServerSetup } from '../adapters/framework'; -export function compose(server: UptimeCoreSetup): UMServerLibs { +export function compose(server: UptimeServerSetup): UMServerLibs { const framework = new UMKibanaBackendFrameworkAdapter(server); return { diff --git a/x-pack/plugins/uptime/server/lib/lib.ts b/x-pack/plugins/uptime/server/lib/lib.ts index 151f7b25adc1eb..577013e9cc5e8d 100644 --- a/x-pack/plugins/uptime/server/lib/lib.ts +++ b/x-pack/plugins/uptime/server/lib/lib.ts @@ -5,12 +5,7 @@ * 2.0. */ -import { - ElasticsearchClient, - SavedObjectsClientContract, - KibanaRequest, - ISavedObjectsRepository, -} from 'kibana/server'; +import { ElasticsearchClient, SavedObjectsClientContract, KibanaRequest } from 'kibana/server'; import chalk from 'chalk'; import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { UMBackendFrameworkAdapter } from './adapters'; @@ -57,7 +52,7 @@ export function createUptimeESClient({ }: { esClient: ElasticsearchClient; request?: KibanaRequest; - savedObjectsClient: SavedObjectsClientContract | ISavedObjectsRepository; + savedObjectsClient: SavedObjectsClientContract; }) { return { baseESClient: esClient, diff --git a/x-pack/plugins/uptime/server/lib/synthetics_service/get_api_key.test.ts b/x-pack/plugins/uptime/server/lib/synthetics_service/get_api_key.test.ts index 1d164f5dd5b625..f9ba0ce545bad0 100644 --- a/x-pack/plugins/uptime/server/lib/synthetics_service/get_api_key.test.ts +++ b/x-pack/plugins/uptime/server/lib/synthetics_service/get_api_key.test.ts @@ -11,13 +11,20 @@ import { securityMock } from '../../../../security/server/mocks'; import { coreMock } from '../../../../../../src/core/server/mocks'; import { syntheticsServiceApiKey } from '../saved_objects/service_api_key'; import { KibanaRequest } from 'kibana/server'; +import { UptimeServerSetup } from '../adapters'; describe('getAPIKeyTest', function () { const core = coreMock.createStart(); const security = securityMock.createStart(); - const encryptedSavedObject = encryptedSavedObjectsMock.createStart(); + const encryptedSavedObjects = encryptedSavedObjectsMock.createStart(); const request = {} as KibanaRequest; + const server = { + security, + encryptedSavedObjects, + savedObjectsClient: core.savedObjects.getScopedClient(request), + } as unknown as UptimeServerSetup; + security.authc.apiKeys.areAPIKeysEnabled = jest.fn().mockReturnValue(true); security.authc.apiKeys.create = jest.fn().mockReturnValue({ id: 'test', @@ -29,9 +36,7 @@ describe('getAPIKeyTest', function () { it('should generate an api key and return it', async () => { const apiKey = await getAPIKeyForSyntheticsService({ request, - security, - encryptedSavedObject, - savedObjectsClient: core.savedObjects.getScopedClient(request), + server, }); expect(security.authc.apiKeys.areAPIKeysEnabled).toHaveBeenCalledTimes(1); @@ -65,21 +70,19 @@ describe('getAPIKeyTest', function () { .fn() .mockReturnValue({ attributes: { apiKey: 'qwerty', id: 'test', name: 'service-api-key' } }); - encryptedSavedObject.getClient = jest.fn().mockReturnValue({ + encryptedSavedObjects.getClient = jest.fn().mockReturnValue({ getDecryptedAsInternalUser: getObject, }); const apiKey = await getAPIKeyForSyntheticsService({ request, - security, - encryptedSavedObject, - savedObjectsClient: core.savedObjects.getScopedClient(request), + server, }); expect(apiKey).toEqual({ apiKey: 'qwerty', id: 'test', name: 'service-api-key' }); - expect(encryptedSavedObject.getClient).toHaveBeenCalledTimes(1); + expect(encryptedSavedObjects.getClient).toHaveBeenCalledTimes(1); expect(getObject).toHaveBeenCalledTimes(1); - expect(encryptedSavedObject.getClient).toHaveBeenCalledWith({ + expect(encryptedSavedObjects.getClient).toHaveBeenCalledWith({ includedHiddenTypes: [syntheticsServiceApiKey.name], }); expect(getObject).toHaveBeenCalledWith( diff --git a/x-pack/plugins/uptime/server/lib/synthetics_service/get_api_key.ts b/x-pack/plugins/uptime/server/lib/synthetics_service/get_api_key.ts index 2a291c64ca2b28..015442dd3c4fe8 100644 --- a/x-pack/plugins/uptime/server/lib/synthetics_service/get_api_key.ts +++ b/x-pack/plugins/uptime/server/lib/synthetics_service/get_api_key.ts @@ -6,7 +6,6 @@ */ import { KibanaRequest, SavedObjectsClientContract } from '../../../../../../src/core/server'; -import { EncryptedSavedObjectsPluginStart } from '../../../../encrypted_saved_objects/server'; import { SecurityPluginStart } from '../../../../security/server'; import { getSyntheticsServiceAPIKey, @@ -14,19 +13,18 @@ import { syntheticsServiceApiKey, } from '../saved_objects/service_api_key'; import { SyntheticsServiceApiKey } from '../../../common/runtime_types/synthetics_service_api_key'; +import { UptimeServerSetup } from '../adapters'; export const getAPIKeyForSyntheticsService = async ({ - encryptedSavedObject, - savedObjectsClient, request, - security, + server, }: { - encryptedSavedObject: EncryptedSavedObjectsPluginStart; - request: KibanaRequest; - security: SecurityPluginStart; - savedObjectsClient: SavedObjectsClientContract; -}): Promise => { - const encryptedClient = encryptedSavedObject.getClient({ + server: UptimeServerSetup; + request?: KibanaRequest; +}): Promise => { + const { security, encryptedSavedObjects, savedObjectsClient } = server; + + const encryptedClient = encryptedSavedObjects.getClient({ includedHiddenTypes: [syntheticsServiceApiKey.name], }); @@ -42,44 +40,44 @@ export const generateAndSaveAPIKey = async ({ request, savedObjectsClient, }: { + request?: KibanaRequest; security: SecurityPluginStart; - request: KibanaRequest; savedObjectsClient: SavedObjectsClientContract; }) => { - try { - const isApiKeysEnabled = await security.authc.apiKeys?.areAPIKeysEnabled(); + const isApiKeysEnabled = await security.authc.apiKeys?.areAPIKeysEnabled(); - if (!isApiKeysEnabled) { - return new Error('Please enable API keys in kibana to use synthetics service.'); - } + if (!isApiKeysEnabled) { + throw new Error('Please enable API keys in kibana to use synthetics service.'); + } - const apiKeyResult = await security.authc.apiKeys?.create(request, { - name: 'synthetics-api-key', - role_descriptors: { - synthetics_writer: { - cluster: ['monitor', 'read_ilm', 'read_pipeline'], - index: [ - { - names: ['synthetics-*'], - privileges: ['view_index_metadata', 'create_doc', 'auto_configure'], - }, - ], - }, - }, - metadata: { - description: - 'Created for synthetics service to be passed to the heartbeat to communicate with ES', + if (!request) { + throw new Error('User authorization is needed for api key generation'); + } + + const apiKeyResult = await security.authc.apiKeys?.create(request, { + name: 'synthetics-api-key', + role_descriptors: { + synthetics_writer: { + cluster: ['monitor', 'read_ilm', 'read_pipeline'], + index: [ + { + names: ['synthetics-*'], + privileges: ['view_index_metadata', 'create_doc', 'auto_configure'], + }, + ], }, - }); + }, + metadata: { + description: + 'Created for synthetics service to be passed to the heartbeat to communicate with ES', + }, + }); - if (apiKeyResult) { - const { id, name, api_key: apiKey } = apiKeyResult; - const apiKeyObject = { id, name, apiKey }; - // discard decoded key and rest of the keys - await setSyntheticsServiceApiKey(savedObjectsClient, apiKeyObject); - return apiKeyObject; - } - } catch (e) { - throw e; + if (apiKeyResult) { + const { id, name, api_key: apiKey } = apiKeyResult; + const apiKeyObject = { id, name, apiKey }; + // discard decoded key and rest of the keys + await setSyntheticsServiceApiKey(savedObjectsClient, apiKeyObject); + return apiKeyObject; } }; diff --git a/x-pack/plugins/uptime/server/lib/synthetics_service/synthetics_service.ts b/x-pack/plugins/uptime/server/lib/synthetics_service/synthetics_service.ts new file mode 100644 index 00000000000000..75c72ef8299076 --- /dev/null +++ b/x-pack/plugins/uptime/server/lib/synthetics_service/synthetics_service.ts @@ -0,0 +1,153 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +/* eslint-disable max-classes-per-file */ + +import axios from 'axios'; +import { + CoreStart, + KibanaRequest, + Logger, + SavedObjectsClient, +} from '../../../../../../src/core/server'; +import { UptimeServerSetup } from '../adapters'; +import { installSyntheticsIndexTemplates } from '../../rest_api/synthetics_service/install_index_templates'; +import { SyntheticsServiceApiKey } from '../../../common/runtime_types/synthetics_service_api_key'; +import { getAPIKeyForSyntheticsService } from './get_api_key'; +import { SyntheticsMonitorSavedObject } from '../../../common/types'; +import { syntheticsMonitorType } from '../saved_objects/synthetics_monitor'; +import { getEsHosts } from './get_es_hosts'; +import { UptimeConfig } from '../../../common/config'; + +export class SyntheticsService { + private logger: Logger; + private readonly server: UptimeServerSetup; + + private readonly config: UptimeConfig; + private readonly esHosts: string[]; + + private apiKey: SyntheticsServiceApiKey | undefined; + + constructor(logger: Logger, server: UptimeServerSetup) { + this.logger = logger; + this.server = server; + this.config = server.config; + + this.esHosts = getEsHosts({ config: this.config, cloud: server.cloud }); + } + + public init(coreStart: CoreStart) { + getAPIKeyForSyntheticsService({ server: this.server }).then((apiKey) => { + if (apiKey) { + this.apiKey = apiKey; + } + }); + + this.setupIndexTemplates(coreStart); + } + + private setupIndexTemplates(coreStart: CoreStart) { + const esClient = coreStart.elasticsearch.client.asInternalUser; + const savedObjectsClient = new SavedObjectsClient( + coreStart.savedObjects.createInternalRepository() + ); + + installSyntheticsIndexTemplates({ + esClient, + server: this.server, + savedObjectsClient, + }).then( + (result) => { + if (result.name === 'synthetics' && result.install_status === 'installed') { + this.logger.info('Installed synthetics index templates'); + } else if (result.name === 'synthetics' && result.install_status === 'install_failed') { + this.logger.warn(new IndexTemplateInstallationError()); + } + }, + () => { + this.logger.warn(new IndexTemplateInstallationError()); + } + ); + } + + public registerSyncTask() { + // handler for registering kibana task manager task + } + + public scheduleSyncTask() { + // handler for scheduling task + } + + async pushConfigs(request: KibanaRequest) { + if (!this.apiKey) { + try { + this.apiKey = await getAPIKeyForSyntheticsService({ server: this.server, request }); + } catch (err) { + throw err; + } + } + + if (!this.apiKey) { + const error = new APIKeyMissingError(); + this.logger.error(error); + throw error; + } + + const monitors = await this.getMonitorConfigs(); + const data = { + monitors, + output: { + hosts: this.esHosts, + api_key: `${this.apiKey.id}:${this.apiKey.apiKey}`, + }, + }; + + const { url, username, password } = this.config.unsafe.service; + + try { + await axios({ + method: 'POST', + url: url + '/monitors', + data, + headers: { + Authorization: 'Basic ' + Buffer.from(`${username}:${password}`).toString('base64'), + }, + }); + } catch (e) { + this.logger.error(e); + } + } + + async getMonitorConfigs() { + const savedObjectsClient = this.server.savedObjectsClient; + const monitorsSavedObjects = await savedObjectsClient.find({ + type: syntheticsMonitorType, + }); + + const savedObjectsList = monitorsSavedObjects.saved_objects; + return savedObjectsList.map(({ attributes, id }) => ({ + ...attributes, + id, + })); + } +} + +class APIKeyMissingError extends Error { + constructor() { + super(); + this.message = 'API key is needed for synthetics service.'; + this.name = 'APIKeyMissingError'; + } +} + +class IndexTemplateInstallationError extends Error { + constructor() { + super(); + this.message = 'Failed to install synthetics index templates.'; + this.name = 'IndexTemplateInstallationError'; + } +} diff --git a/x-pack/plugins/uptime/server/plugin.ts b/x-pack/plugins/uptime/server/plugin.ts index 42764972571112..e7aa57da06ffdb 100644 --- a/x-pack/plugins/uptime/server/plugin.ts +++ b/x-pack/plugins/uptime/server/plugin.ts @@ -10,9 +10,9 @@ import { CoreStart, CoreSetup, Plugin as PluginType, - ISavedObjectsRepository, Logger, SavedObjectsClient, + SavedObjectsClientContract, } from '../../../../src/core/server'; import { uptimeRuleFieldMap } from '../common/rules/uptime_rule_field_map'; import { initServerWithKibana } from './kibana.index'; @@ -20,21 +20,22 @@ import { KibanaTelemetryAdapter, UptimeCorePluginsSetup, UptimeCorePluginsStart, - UptimeCoreSetup, + UptimeServerSetup, } from './lib/adapters'; import { registerUptimeSavedObjects, savedObjectsAdapter } from './lib/saved_objects/saved_objects'; import { mappingFromFieldMap } from '../../rule_registry/common/mapping_from_field_map'; import { Dataset } from '../../rule_registry/server'; import { UptimeConfig } from '../common/config'; -import { installSyntheticsIndexTemplates } from './rest_api/synthetics_service/install_index_templates'; +import { SyntheticsService } from './lib/synthetics_service/synthetics_service'; export type UptimeRuleRegistry = ReturnType['ruleRegistry']; export class Plugin implements PluginType { - private savedObjectsClient?: ISavedObjectsRepository; + private savedObjectsClient?: SavedObjectsClientContract; private initContext: PluginInitializerContext; private logger: Logger; - private server?: UptimeCoreSetup; + private server?: UptimeServerSetup; + private syntheticService?: SyntheticsService; constructor(initializerContext: PluginInitializerContext) { this.initContext = initializerContext; @@ -66,7 +67,11 @@ export class Plugin implements PluginType { config, router: core.http.createRouter(), cloud: plugins.cloud, - } as UptimeCoreSetup; + } as UptimeServerSetup; + + if (this.server?.config?.unsafe?.service.enabled) { + this.syntheticService = new SyntheticsService(this.logger, this.server); + } initServerWithKibana(this.server, plugins, ruleDataClient, this.logger); @@ -82,32 +87,19 @@ export class Plugin implements PluginType { }; } - public start(core: CoreStart, plugins: UptimeCorePluginsStart) { - this.savedObjectsClient = core.savedObjects.createInternalRepository(); + public start(coreStart: CoreStart, plugins: UptimeCorePluginsStart) { + this.savedObjectsClient = new SavedObjectsClient( + coreStart.savedObjects.createInternalRepository() + ); if (this.server) { this.server.security = plugins.security; this.server.fleet = plugins.fleet; this.server.encryptedSavedObjects = plugins.encryptedSavedObjects; + this.server.savedObjectsClient = this.savedObjectsClient; } if (this.server?.config?.unsafe?.service.enabled) { - const esClient = core.elasticsearch.client.asInternalUser; - installSyntheticsIndexTemplates({ - esClient, - server: this.server, - savedObjectsClient: new SavedObjectsClient(core.savedObjects.createInternalRepository()), - }).then( - (result) => { - if (result.name === 'synthetics' && result.install_status === 'installed') { - this.logger.info('Installed synthetics index templates'); - } else if (result.name === 'synthetics' && result.install_status === 'install_failed') { - this.logger.warn('Failed to install synthetics index templates'); - } - }, - () => { - this.logger.warn('Failed to install synthetics index templates'); - } - ); + this.syntheticService?.init(coreStart); } } diff --git a/x-pack/plugins/uptime/server/rest_api/index.ts b/x-pack/plugins/uptime/server/rest_api/index.ts index 344dd4d203d8d5..4eb6ae30712562 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 00000000000000..11d7dcedcaa34e --- /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 00000000000000..68eb8aa130d2e5 --- /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 00000000000000..46a91738c380d0 --- /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 00000000000000..537d6c77195cac --- /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/plugins/uptime/server/rest_api/synthetics_service/install_index_templates.ts b/x-pack/plugins/uptime/server/rest_api/synthetics_service/install_index_templates.ts index b40c6018f966bb..185e526d148feb 100644 --- a/x-pack/plugins/uptime/server/rest_api/synthetics_service/install_index_templates.ts +++ b/x-pack/plugins/uptime/server/rest_api/synthetics_service/install_index_templates.ts @@ -7,7 +7,7 @@ import { ElasticsearchClient, SavedObjectsClientContract } from 'kibana/server'; import { UMRestApiRouteFactory } from '../types'; import { API_URLS } from '../../../common/constants'; -import { UptimeCoreSetup } from '../../lib/adapters'; +import { UptimeServerSetup } from '../../lib/adapters'; export const installIndexTemplatesRoute: UMRestApiRouteFactory = () => ({ method: 'GET', @@ -27,7 +27,7 @@ export async function installSyntheticsIndexTemplates({ server, savedObjectsClient, }: { - server: UptimeCoreSetup; + server: UptimeServerSetup; esClient: ElasticsearchClient; savedObjectsClient: SavedObjectsClientContract; }) { diff --git a/x-pack/plugins/uptime/server/rest_api/types.ts b/x-pack/plugins/uptime/server/rest_api/types.ts index f8027cefd3f589..48e3f58ae0ad52 100644 --- a/x-pack/plugins/uptime/server/rest_api/types.ts +++ b/x-pack/plugins/uptime/server/rest_api/types.ts @@ -17,7 +17,7 @@ import { } from 'kibana/server'; import { UMServerLibs, UptimeESClient } from '../lib/lib'; import type { UptimeRequestHandlerContext } from '../types'; -import { UptimeCoreSetup } from '../lib/adapters'; +import { UptimeServerSetup } from '../lib/adapters'; /** * Defines the basic properties employed by Uptime routes. @@ -61,7 +61,7 @@ export type UMRestApiRouteFactory = (libs: UMServerLibs) => UptimeRoute; */ export type UMKibanaRouteWrapper = ( uptimeRoute: UptimeRoute, - server: UptimeCoreSetup + server: UptimeServerSetup ) => UMKibanaRoute; /** @@ -80,5 +80,5 @@ export type UMRouteHandler = ({ request: KibanaRequest, Record, Record>; response: KibanaResponseFactory; savedObjectsClient: SavedObjectsClientContract; - server: UptimeCoreSetup; + server: UptimeServerSetup; }) => IKibanaResponse | Promise>; diff --git a/x-pack/plugins/uptime/server/uptime_server.ts b/x-pack/plugins/uptime/server/uptime_server.ts index ae606d7d4c3bf2..fd16e8243c2cd9 100644 --- a/x-pack/plugins/uptime/server/uptime_server.ts +++ b/x-pack/plugins/uptime/server/uptime_server.ts @@ -9,7 +9,7 @@ import { Logger } from 'kibana/server'; import { createLifecycleRuleTypeFactory, IRuleDataClient } from '../../rule_registry/server'; import { UMServerLibs } from './lib/lib'; import { createRouteWithAuth, restApiRoutes, uptimeRouteWrapper } from './rest_api'; -import { UptimeCoreSetup, UptimeCorePluginsSetup } from './lib/adapters'; +import { UptimeServerSetup, UptimeCorePluginsSetup } from './lib/adapters'; import { statusCheckAlertFactory } from './lib/alerts/status_check'; import { tlsAlertFactory } from './lib/alerts/tls'; @@ -17,7 +17,7 @@ import { tlsLegacyAlertFactory } from './lib/alerts/tls_legacy'; import { durationAnomalyAlertFactory } from './lib/alerts/duration_anomaly'; export const initUptimeServer = ( - server: UptimeCoreSetup, + server: UptimeServerSetup, libs: UMServerLibs, plugins: UptimeCorePluginsSetup, ruleDataClient: IRuleDataClient, 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 9bf7baf95d8d24..806c1fa3a4ea7a 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/uptime/rest/add_monitor.ts b/x-pack/test/api_integration/apis/uptime/rest/add_monitor.ts new file mode 100644 index 00000000000000..a57a03fd3a1f57 --- /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 00000000000000..bc49587fab872a --- /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 00000000000000..f5d54c40a86468 --- /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 00000000000000..76d27ff8a9d1d4 --- /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 dc3c00b03f7125..f674879552d6a6 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 e2c2e0b52dfdc9..bf42a5b0865a21 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/tests/traces/__snapshots__/top_traces.spec.snap b/x-pack/test/apm_api_integration/tests/traces/__snapshots__/top_traces.spec.snap index 604348355f38c8..528963709712d5 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 51b14809982d81..06a24cbd34a4bc 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/functional_with_es_ssl/apps/uptime/alert_flyout.ts b/x-pack/test/functional_with_es_ssl/apps/uptime/alert_flyout.ts index bb89fa8f683fac..1d8a172e57b78b 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 @@ -142,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/observability_functional/apps/observability/alerts/add_to_case.ts b/x-pack/test/observability_functional/apps/observability/alerts/add_to_case.ts index 67dbf2368c044e..276fab1f1cb444 100644 --- a/x-pack/test/observability_functional/apps/observability/alerts/add_to_case.ts +++ b/x-pack/test/observability_functional/apps/observability/alerts/add_to_case.ts @@ -68,7 +68,7 @@ export default ({ getService, getPageObjects }: FtrProviderContext) => { }); }); - describe('When user has read permissions for cases', () => { + describe.skip('When user has read permissions for cases', () => { before(async () => { await observability.users.setTestUserRole( observability.users.defineBasicObservabilityRole({ @@ -83,6 +83,7 @@ export default ({ getService, getPageObjects }: FtrProviderContext) => { await observability.users.restoreDefaultTestUserRole(); }); + // Hide the WorkFlow filter, but keep its code as required in https://github.com/elastic/kibana/issues/117686 it('does not render case options in the overflow menu', async () => { await observability.alerts.common.openActionsMenuForRow(0); await retry.try(async () => { diff --git a/x-pack/test/observability_functional/apps/observability/alerts/bulk_actions.ts b/x-pack/test/observability_functional/apps/observability/alerts/bulk_actions.ts index 749324a39ba207..3784890e5a8093 100644 --- a/x-pack/test/observability_functional/apps/observability/alerts/bulk_actions.ts +++ b/x-pack/test/observability_functional/apps/observability/alerts/bulk_actions.ts @@ -25,7 +25,8 @@ export default ({ getService, getPageObjects }: FtrProviderContext) => { const retry = getService('retry'); - describe('Observability alerts / Bulk actions', function () { + // Hide the WorkFlow filter, but keep its code as required in https://github.com/elastic/kibana/issues/117686 + describe.skip('Observability alerts / Bulk actions', function () { this.tags('includeFirefox'); before(async () => { await esArchiver.load('x-pack/test/functional/es_archives/observability/alerts'); 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 4d2f4b971f0808..048c007dcb6a46 100644 --- a/x-pack/test/observability_functional/apps/observability/alerts/index.ts +++ b/x-pack/test/observability_functional/apps/observability/alerts/index.ts @@ -15,8 +15,8 @@ async function asyncForEach(array: T[], callback: (item: T, index: number) => } const ACTIVE_ALERTS_CELL_COUNT = 78; -const RECOVERED_ALERTS_CELL_COUNT = 120; -const TOTAL_ALERTS_CELL_COUNT = 198; +const RECOVERED_ALERTS_CELL_COUNT = 100; +const TOTAL_ALERTS_CELL_COUNT = 165; export default ({ getService }: FtrProviderContext) => { const esArchiver = getService('esArchiver'); @@ -205,7 +205,7 @@ export default ({ getService }: FtrProviderContext) => { await observability.alerts.common.submitQuery(''); }); - it('Filter for value works', async () => { + it.skip('Filter for value works', async () => { await (await observability.alerts.common.getFilterForValueButton()).click(); const queryBarValue = await ( await observability.alerts.common.getQueryBar() 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 45a96f8ebd8b44..d53afdbddd6ed3 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 b0264998db17df..745a8852968c01 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/upgrade/apps/dashboard/dashboard_smoke_tests.ts b/x-pack/test/upgrade/apps/dashboard/dashboard_smoke_tests.ts index c9833610711701..2f35f0e1e12d36 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 53acb8b016313a..22e081e88bfc4d 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 44185306ca38f2..71b82ffdf5af63 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"