diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
index a64ab63494b35..61369a37ec3c2 100644
--- a/.github/CODEOWNERS
+++ b/.github/CODEOWNERS
@@ -177,6 +177,8 @@
/x-pack/test/functional/services/ml/ @elastic/ml-ui
/x-pack/test/functional_basic/apps/ml/ @elastic/ml-ui
/x-pack/test/functional_with_es_ssl/apps/ml/ @elastic/ml-ui
+/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/ml_rule_types/ @elastic/ml-ui
+/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/transform_rule_types/ @elastic/ml-ui
# ML team owns and maintains the transform plugin despite it living in the Data management section.
/x-pack/plugins/transform/ @elastic/ml-ui
diff --git a/docs/developer/plugin-list.asciidoc b/docs/developer/plugin-list.asciidoc
index e997c0bc68cde..3d9de2d35b500 100644
--- a/docs/developer/plugin-list.asciidoc
+++ b/docs/developer/plugin-list.asciidoc
@@ -540,6 +540,11 @@ Elastic.
|Add tagging capability to saved objects
+|{kib-repo}blob/{branch}/x-pack/plugins/screenshotting/README.md[screenshotting]
+|This plugin provides functionality to take screenshots of the Kibana pages.
+It uses Chromium and Puppeteer underneath to run the browser in headless mode.
+
+
|{kib-repo}blob/{branch}/x-pack/plugins/searchprofiler/README.md[searchprofiler]
|The search profiler consumes the Profile API
by sending a search API with profile: true enabled in the request body. The response contains
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 403d8594999a7..8f6f1f6c98ab2 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
@@ -88,6 +88,7 @@ readonly links: {
readonly usersAccess: string;
};
readonly workplaceSearch: {
+ readonly apiKeys: string;
readonly box: string;
readonly confluenceCloud: string;
readonly confluenceServer: 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 131d4452c980c..a9828f04672e9 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 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 precisionTuning: string; readonly relevanceTuning: string; readonly resultSettings: string; readonly searchUI: string; readonly security: 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 oneDrive: string; readonly permissions: string; readonly salesforce: string; readonly security: string; readonly serviceNow: string; readonly sharePoint: string; readonly slack: 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; }; } | |
+| [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 recisionTuning: string; readonly relevanceTuning: string; readonly resultSettings: string; readonly searchUI: string; readonly security: 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 apiKeys: string; 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 oneDrive: string; readonly permissions: string; readonly salesforce: string; readonly security: string; readonly serviceNow: string; readonly sharePoint: string; readonly slack: 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/osquery/osquery.asciidoc b/docs/osquery/osquery.asciidoc
index 396135d8d1751..500dc6959fc00 100644
--- a/docs/osquery/osquery.asciidoc
+++ b/docs/osquery/osquery.asciidoc
@@ -288,13 +288,21 @@ This is useful for teams who need in-depth and detailed control.
[float]
=== Customize Osquery configuration
-By default, all Osquery Manager integrations share the same osquery configuration. However, you can customize how Osquery is configured by editing the Osquery Manager integration for each agent policy
+experimental[] By default, all Osquery Manager integrations share the same osquery configuration. However, you can customize how Osquery is configured by editing the Osquery Manager integration for each agent policy
you want to adjust. The custom configuration is then applied to all agents in the policy.
This powerful feature allows you to configure
https://osquery.readthedocs.io/en/stable/deployment/file-integrity-monitoring[File Integrity Monitoring], https://osquery.readthedocs.io/en/stable/deployment/process-auditing[Process auditing],
and https://osquery.readthedocs.io/en/stable/deployment/configuration/#configuration-specification[others].
-IMPORTANT: Take caution when editing this configuration. The changes you make are distributed to all agents in the policy.
+[IMPORTANT]
+=========================
+
+* Take caution when editing this configuration. The changes you make are distributed to all agents in the policy.
+
+* Take caution when editing `packs` using the Advanced *Osquery config* field.
+Any changes you make to `packs` from this field are not reflected in the UI on the Osquery *Packs* page in {kib}, however, these changes are deployed to agents in the policy.
+While this allows you to use advanced Osquery functionality like pack discovery queries, you do lose the ability to manage packs defined this way from the Osquery *Packs* page.
+=========================
. From the {kib} main menu, click *Fleet*, then the *Agent policies* tab.
@@ -315,6 +323,16 @@ IMPORTANT: Take caution when editing this configuration. The changes you make ar
* (Optional) To load a full configuration file, drag and drop an Osquery `.conf` file into the area at the bottom of the page.
. Click *Save integration* to apply the custom configuration to all agents in the policy.
++
+As an example, the following configuration disables two tables.
++
+```ts
+{
+ "options":{
+ "disable_tables":"curl,process_envs"
+ }
+}
+```
[float]
=== Upgrade Osquery versions
diff --git a/docs/settings/monitoring-settings.asciidoc b/docs/settings/monitoring-settings.asciidoc
index d8bc26b7b3987..8bc98a028b8f6 100644
--- a/docs/settings/monitoring-settings.asciidoc
+++ b/docs/settings/monitoring-settings.asciidoc
@@ -72,6 +72,9 @@ For more information, see
| `monitoring.ui.elasticsearch.ssl`
| Shares the same configuration as <>. These settings configure encrypted communication between {kib} and the monitoring cluster.
+| `monitoring.cluster_alerts.allowedSpaces` {ess-icon}
+ | Specifies the spaces where cluster Stack Monitoring alerts can be created. You must specify all spaces where you want to generate alerts, including the default space. Defaults to `[ "default" ]`.
+
|===
[float]
diff --git a/docs/settings/spaces-settings.asciidoc b/docs/settings/spaces-settings.asciidoc
index dd37943101145..3eb91a0d884ef 100644
--- a/docs/settings/spaces-settings.asciidoc
+++ b/docs/settings/spaces-settings.asciidoc
@@ -12,11 +12,3 @@ The maximum number of spaces that you can use with the {kib} instance. Some {kib
return all spaces using a single `_search` from {es}, so you must
configure this setting lower than the `index.max_result_window` in {es}.
The default is `1000`.
-
-`monitoring.cluster_alerts.allowedSpaces` {ess-icon}::
-Specifies the spaces where cluster alerts are automatically generated.
-You must specify all spaces where you want to generate alerts, including the default space.
-When the default space is unspecified, {kib} is unable to generate an alert for the default space.
-{es} clusters that run on {es} services are all containers. To send monitoring data
-from your self-managed {es} installation to {es} services, set to `false`.
-The default is `true`.
diff --git a/docs/settings/url-drilldown-settings.asciidoc b/docs/settings/url-drilldown-settings.asciidoc
index 702829ec34dcc..36dbabbe7fe1e 100644
--- a/docs/settings/url-drilldown-settings.asciidoc
+++ b/docs/settings/url-drilldown-settings.asciidoc
@@ -6,16 +6,13 @@
Configure the URL drilldown settings in your `kibana.yml` configuration file.
-[cols="2*<"]
-|===
-| [[external-URL-policy]] `externalUrl.policy`
- | Configures the external URL policies. URL drilldowns respect the global *External URL* service, which you can use to deny or allow external URLs.
+[[external-URL-policy]] `externalUrl.policy`::
+Configures the external URL policies. URL drilldowns respect the global *External URL* service, which you can use to deny or allow external URLs.
By default all external URLs are allowed.
-|===
-
-For example, to allow external URLs only to the `example.com` domain with the `https` scheme, except for the `danger.example.com` sub-domain,
++
+For example, to allow only external URLs to the `example.com` domain with the `https` scheme, except for the `danger.example.com` sub-domain,
which is denied even when `https` scheme is used:
-
++
["source","yml"]
-----------
externalUrl.policy:
@@ -25,4 +22,3 @@ externalUrl.policy:
host: example.com
protocol: https
-----------
-
diff --git a/docs/setup/docker.asciidoc b/docs/setup/docker.asciidoc
index 3acaf2ddd2c12..0aa6c680a7761 100644
--- a/docs/setup/docker.asciidoc
+++ b/docs/setup/docker.asciidoc
@@ -14,10 +14,13 @@ https://github.com/elastic/dockerfiles/tree/{branch}/kibana[GitHub].
These images contain both free and subscription features.
<> to try out all of the features.
-[float]
+[discrete]
[[run-kibana-on-docker-for-dev]]
=== Run {kib} on Docker for development
+. Start an {es} container for development or testing:
++
+--
ifeval::["{release-state}"=="unreleased"]
NOTE: No Docker images are currently available for {kib} {version}.
@@ -26,14 +29,16 @@ endif::[]
ifeval::["{release-state}"!="unreleased"]
-. Start an {es} container for development or testing:
-+
[source,sh,subs="attributes"]
----
docker network create elastic
docker pull {es-docker-image}
docker run --name es-node01 --net elastic -p 9200:9200 -p 9300:9300 -t {es-docker-image}
----
+
+endif::[]
+
+--
+
When you start {es} for the first time, the following security configuration
occurs automatically:
@@ -51,30 +56,26 @@ and enrollment token.
. Copy the generated password and enrollment token and save them in a secure
location. These values are shown only when you start {es} for the first time.
You'll use these to enroll {kib} with your {es} cluster and log in.
+
+. In a new terminal session, start {kib} and connect it to your {es} container:
+
-[NOTE]
-====
-If you need to reset the password for the `elastic` user or other
-built-in users, run the {ref}/reset-password.html[`elasticsearch-reset-password`]
-tool. To generate new enrollment tokens for {kib} or {es} nodes, run the
-{ref}/create-enrollment-token.html[`elasticsearch-create-enrollment-token`] tool.
-These tools are available in the {es} `bin` directory of the Docker container.
+--
+ifeval::["{release-state}"=="unreleased"]
-For example:
+NOTE: No Docker images are currently available for {kib} {version}.
-[source,sh]
-----
-docker exec -it es-node01 /usr/share/elasticsearch/bin/elasticsearch-reset-password -u elastic
-----
-====
+endif::[]
+
+ifeval::["{release-state}"!="unreleased"]
-. In a new terminal session, start {kib} and connect it to your {es} container:
-+
[source,sh,subs="attributes"]
----
docker pull {docker-image}
docker run --name kib-01 --net elastic -p 5601:5601 {docker-image}
----
+
+endif::[]
+--
+
When you start {kib}, a unique link is output to your terminal.
@@ -86,7 +87,32 @@ When you start {kib}, a unique link is output to your terminal.
.. Log in to {kib} as the `elastic` user with the password that was generated
when you started {es}.
-[float]
+[[docker-generate]]
+[discrete]
+=== Generate passwords and enrollment tokens
+If you need to reset the password for the `elastic` user or other
+built-in users, run the {ref}/reset-password.html[`elasticsearch-reset-password`]
+tool. This tool is available in the {es} `bin` directory of the Docker container.
+
+For example, to reset the password for the `elastic` user:
+
+[source,sh]
+----
+docker exec -it es-node01 /usr/share/elasticsearch/bin/elasticsearch-reset-password -u elastic
+----
+
+If you need to generate new enrollment tokens for {kib} or {es} nodes, run the
+{ref}/create-enrollment-token.html[`elasticsearch-create-enrollment-token`] tool.
+This tool is available in the {es} `bin` directory of the Docker container.
+
+For example, to generate a new enrollment token for {kib}:
+
+[source,sh]
+----
+docker exec -it es-node01 /usr/share/elasticsearch/bin/elasticsearch-create-enrollment-token -s kibana
+----
+
+[discrete]
=== Remove Docker containers
To remove the containers and their network, run:
@@ -98,8 +124,7 @@ docker rm es-node01
docker rm kib-01
----
-endif::[]
-[float]
+[discrete]
[[configuring-kibana-docker]]
=== Configure Kibana on Docker
@@ -108,7 +133,7 @@ conventional approach is to provide a `kibana.yml` file as described in
{kibana-ref}/settings.html[Configuring Kibana], but it's also possible to use
environment variables to define settings.
-[float]
+[discrete]
[[bind-mount-config]]
==== Bind-mounted configuration
@@ -127,7 +152,7 @@ services:
==== Persist the {kib} keystore
-By default, {kib] auto-generates a keystore file for secure settings at startup. To persist your {kibana-ref}/secure-settings.html[secure settings], use the `kibana-keystore` utility to bind-mount the parent directory of the keystore to the container. For example:
+By default, {kib} auto-generates a keystore file for secure settings at startup. To persist your {kibana-ref}/secure-settings.html[secure settings], use the `kibana-keystore` utility to bind-mount the parent directory of the keystore to the container. For example:
["source","sh",subs="attributes"]
----
@@ -135,7 +160,7 @@ docker run -it --rm -v full_path_to/config:/usr/share/kibana/config -v full_path
docker run -it --rm -v full_path_to/config:/usr/share/kibana/config -v full_path_to/data:/usr/share/kibana/data {docker-image} bin/kibana-keystore add test_keystore_setting
----
-[float]
+[discrete]
[[environment-variable-config]]
==== Environment variable configuration
@@ -179,7 +204,7 @@ services:
Since environment variables are translated to CLI arguments, they take
precedence over settings configured in `kibana.yml`.
-[float]
+[discrete]
[[docker-defaults]]
==== Docker defaults
The following settings have different default values when using the Docker
diff --git a/docs/setup/install/deb.asciidoc b/docs/setup/install/deb.asciidoc
index 3f600d7c2bdbc..8e8c43ff8a15d 100644
--- a/docs/setup/install/deb.asciidoc
+++ b/docs/setup/install/deb.asciidoc
@@ -188,9 +188,9 @@ locations for a Debian-based system:
| path.data
| logs
- | Logs files location
- | /var/log/kibana
- | path.logs
+ | Logs files location
+ | /var/log/kibana
+ | path.logs
| plugins
| Plugin files location. Each plugin will be contained in a subdirectory.
diff --git a/docs/setup/install/rpm.asciidoc b/docs/setup/install/rpm.asciidoc
index 329af9af0ccf7..0ef714c73b9ba 100644
--- a/docs/setup/install/rpm.asciidoc
+++ b/docs/setup/install/rpm.asciidoc
@@ -174,7 +174,6 @@ locations for an RPM-based system:
| Configuration files including `kibana.yml`
| /etc/kibana
| <>
- d|
| data
| The location of the data files written to disk by Kibana and its plugins
@@ -182,9 +181,9 @@ locations for an RPM-based system:
| path.data
| logs
- | Logs files location
- | /var/log/kibana
- | path.logs
+ | Logs files location
+ | /var/log/kibana
+ | path.logs
| plugins
| Plugin files location. Each plugin will be contained in a subdirectory.
diff --git a/docs/setup/install/targz.asciidoc b/docs/setup/install/targz.asciidoc
index d9849811a7455..1d8c61a6e9a07 100644
--- a/docs/setup/install/targz.asciidoc
+++ b/docs/setup/install/targz.asciidoc
@@ -125,7 +125,7 @@ important data later on.
| home
| Kibana home directory or `$KIBANA_HOME`
d| Directory created by unpacking the archive
- d|
+ |
| bin
| Binary scripts including `kibana` to start the Kibana server
@@ -137,7 +137,6 @@ important data later on.
| Configuration files including `kibana.yml`
| $KIBANA_HOME\config
| <>
- d|
| data
| The location of the data files written to disk by Kibana and its plugins
diff --git a/package.json b/package.json
index 374ccee71ec6a..6b7d6662eb70b 100644
--- a/package.json
+++ b/package.json
@@ -103,7 +103,7 @@
"@elastic/apm-rum": "^5.9.1",
"@elastic/apm-rum-react": "^1.3.1",
"@elastic/apm-synthtrace": "link:bazel-bin/packages/elastic-apm-synthtrace",
- "@elastic/charts": "40.0.0",
+ "@elastic/charts": "40.1.0",
"@elastic/datemath": "link:bazel-bin/packages/elastic-datemath",
"@elastic/elasticsearch": "npm:@elastic/elasticsearch-canary@^8.0.0-canary.35",
"@elastic/ems-client": "8.0.0",
@@ -197,7 +197,7 @@
"axios": "^0.21.1",
"base64-js": "^1.3.1",
"brace": "0.11.1",
- "broadcast-channel": "^4.5.0",
+ "broadcast-channel": "^4.7.0",
"chalk": "^4.1.0",
"cheerio": "^1.0.0-rc.10",
"chokidar": "^3.4.3",
@@ -531,7 +531,6 @@
"@types/file-saver": "^2.0.0",
"@types/flot": "^0.0.31",
"@types/geojson": "7946.0.7",
- "@types/getopts": "^2.0.1",
"@types/getos": "^3.0.0",
"@types/glob": "^7.1.2",
"@types/gulp": "^4.0.6",
@@ -568,6 +567,7 @@
"@types/kbn__config": "link:bazel-bin/packages/kbn-config/npm_module_types",
"@types/kbn__config-schema": "link:bazel-bin/packages/kbn-config-schema/npm_module_types",
"@types/kbn__crypto": "link:bazel-bin/packages/kbn-crypto/npm_module_types",
+ "@types/kbn__docs-utils": "link:bazel-bin/packages/kbn-docs-utils/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",
@@ -677,7 +677,7 @@
"babel-plugin-add-module-exports": "^1.0.4",
"babel-plugin-istanbul": "^6.1.1",
"babel-plugin-require-context-hook": "^1.0.0",
- "babel-plugin-styled-components": "^1.13.3",
+ "babel-plugin-styled-components": "^2.0.2",
"babel-plugin-transform-react-remove-prop-types": "^0.4.24",
"backport": "^5.6.6",
"callsites": "^3.1.0",
diff --git a/packages/BUILD.bazel b/packages/BUILD.bazel
index 96b1846147689..aa90c3c122171 100644
--- a/packages/BUILD.bazel
+++ b/packages/BUILD.bazel
@@ -86,6 +86,7 @@ filegroup(
"//packages/kbn-config:build_types",
"//packages/kbn-config-schema:build_types",
"//packages/kbn-crypto:build_types",
+ "//packages/kbn-docs-utils:build_types",
"//packages/kbn-i18n:build_types",
"//packages/kbn-i18n-react:build_types",
],
diff --git a/packages/kbn-apm-config-loader/src/init_apm.test.ts b/packages/kbn-apm-config-loader/src/init_apm.test.ts
index 95f0a15a448c8..cabab421519bd 100644
--- a/packages/kbn-apm-config-loader/src/init_apm.test.ts
+++ b/packages/kbn-apm-config-loader/src/init_apm.test.ts
@@ -12,13 +12,13 @@ import { initApm } from './init_apm';
import apm from 'elastic-apm-node';
describe('initApm', () => {
- let apmAddFilterSpy: jest.SpyInstance;
- let apmStartSpy: jest.SpyInstance;
+ let apmAddFilterMock: jest.Mock;
+ let apmStartMock: jest.Mock;
let getConfig: jest.Mock;
beforeEach(() => {
- apmAddFilterSpy = jest.spyOn(apm, 'addFilter').mockImplementation(() => undefined);
- apmStartSpy = jest.spyOn(apm, 'start').mockImplementation(() => undefined as any);
+ apmAddFilterMock = apm.addFilter as jest.Mock;
+ apmStartMock = apm.start as jest.Mock;
getConfig = jest.fn();
mockLoadConfiguration.mockImplementation(() => ({
@@ -27,7 +27,8 @@ describe('initApm', () => {
});
afterEach(() => {
- jest.restoreAllMocks();
+ apmAddFilterMock.mockReset();
+ apmStartMock.mockReset();
mockLoadConfiguration.mockReset();
});
@@ -48,8 +49,8 @@ describe('initApm', () => {
it('registers a filter using `addFilter`', () => {
initApm(['foo', 'bar'], 'rootDir', true, 'service-name');
- expect(apmAddFilterSpy).toHaveBeenCalledTimes(1);
- expect(apmAddFilterSpy).toHaveBeenCalledWith(expect.any(Function));
+ expect(apmAddFilterMock).toHaveBeenCalledTimes(1);
+ expect(apmAddFilterMock).toHaveBeenCalledWith(expect.any(Function));
});
it('starts apm with the config returned from `getConfig`', () => {
@@ -60,7 +61,7 @@ describe('initApm', () => {
initApm(['foo', 'bar'], 'rootDir', true, 'service-name');
- expect(apmStartSpy).toHaveBeenCalledTimes(1);
- expect(apmStartSpy).toHaveBeenCalledWith(config);
+ expect(apmStartMock).toHaveBeenCalledTimes(1);
+ expect(apmStartMock).toHaveBeenCalledWith(config);
});
});
diff --git a/packages/kbn-cli-dev-mode/BUILD.bazel b/packages/kbn-cli-dev-mode/BUILD.bazel
index 66e00706e9e58..dfb441dffc6ef 100644
--- a/packages/kbn-cli-dev-mode/BUILD.bazel
+++ b/packages/kbn-cli-dev-mode/BUILD.bazel
@@ -60,12 +60,12 @@ TYPES_DEPS = [
"@npm//chokidar",
"@npm//elastic-apm-node",
"@npm//execa",
+ "@npm//getopts",
"@npm//moment",
"@npm//rxjs",
"@npm//supertest",
"@npm//@types/hapi__h2o2",
"@npm//@types/hapi__hapi",
- "@npm//@types/getopts",
"@npm//@types/jest",
"@npm//@types/lodash",
"@npm//@types/node",
diff --git a/packages/kbn-cli-dev-mode/src/get_server_watch_paths.test.ts b/packages/kbn-cli-dev-mode/src/get_server_watch_paths.test.ts
index 9fa13b013f195..06ded8d8bf526 100644
--- a/packages/kbn-cli-dev-mode/src/get_server_watch_paths.test.ts
+++ b/packages/kbn-cli-dev-mode/src/get_server_watch_paths.test.ts
@@ -65,7 +65,7 @@ it('produces the right watch and ignore list', () => {
/x-pack/test/plugin_functional/plugins/resolver_test/target/**,
/x-pack/test/plugin_functional/plugins/resolver_test/scripts/**,
/x-pack/test/plugin_functional/plugins/resolver_test/docs/**,
- /x-pack/plugins/reporting/chromium,
+ /x-pack/plugins/screenshotting/chromium,
/x-pack/plugins/security_solution/cypress,
/x-pack/plugins/apm/scripts,
/x-pack/plugins/apm/ftr_e2e,
diff --git a/packages/kbn-cli-dev-mode/src/get_server_watch_paths.ts b/packages/kbn-cli-dev-mode/src/get_server_watch_paths.ts
index e1bd431d280a4..f075dc806b6ec 100644
--- a/packages/kbn-cli-dev-mode/src/get_server_watch_paths.ts
+++ b/packages/kbn-cli-dev-mode/src/get_server_watch_paths.ts
@@ -56,7 +56,7 @@ export function getServerWatchPaths({ pluginPaths, pluginScanDirs }: Options) {
/\.(md|sh|txt)$/,
/debug\.log$/,
...pluginInternalDirsIgnore,
- fromRoot('x-pack/plugins/reporting/chromium'),
+ fromRoot('x-pack/plugins/screenshotting/chromium'),
fromRoot('x-pack/plugins/security_solution/cypress'),
fromRoot('x-pack/plugins/apm/scripts'),
fromRoot('x-pack/plugins/apm/ftr_e2e'), // prevents restarts for APM cypress tests
diff --git a/packages/kbn-docs-utils/BUILD.bazel b/packages/kbn-docs-utils/BUILD.bazel
index 6bb37b3500152..37e5bb06377cc 100644
--- a/packages/kbn-docs-utils/BUILD.bazel
+++ b/packages/kbn-docs-utils/BUILD.bazel
@@ -1,9 +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-docs-utils"
PKG_REQUIRE_NAME = "@kbn/docs-utils"
+TYPES_PKG_REQUIRE_NAME = "@types/kbn__docs-utils"
SOURCE_FILES = glob(
[
@@ -77,7 +78,7 @@ ts_project(
js_library(
name = PKG_BASE_NAME,
srcs = NPM_MODULE_EXTRA_FILES,
- deps = RUNTIME_DEPS + [":target_node", ":tsc_types"],
+ deps = RUNTIME_DEPS + [":target_node"],
package_name = PKG_REQUIRE_NAME,
visibility = ["//visibility:public"],
)
@@ -96,3 +97,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-docs-utils/package.json b/packages/kbn-docs-utils/package.json
index dcff832583f59..84fc3ccb0cded 100644
--- a/packages/kbn-docs-utils/package.json
+++ b/packages/kbn-docs-utils/package.json
@@ -4,7 +4,6 @@
"license": "SSPL-1.0 OR Elastic License 2.0",
"private": "true",
"main": "target_node/index.js",
- "types": "target_types/index.d.ts",
"kibana": {
"devOnly": true
}
diff --git a/packages/kbn-optimizer/limits.yml b/packages/kbn-optimizer/limits.yml
index 41c4d3bdd1b35..1de3a8a1b3976 100644
--- a/packages/kbn-optimizer/limits.yml
+++ b/packages/kbn-optimizer/limits.yml
@@ -117,3 +117,4 @@ pageLoadAssetSize:
dataViewManagement: 5000
reporting: 57003
visTypeHeatmap: 25340
+ screenshotting: 17017
diff --git a/packages/kbn-storybook/src/lib/theme_switcher.tsx b/packages/kbn-storybook/src/lib/theme_switcher.tsx
index 3d6f7999545a0..8cc805ee2e494 100644
--- a/packages/kbn-storybook/src/lib/theme_switcher.tsx
+++ b/packages/kbn-storybook/src/lib/theme_switcher.tsx
@@ -6,7 +6,7 @@
* Side Public License, v 1.
*/
-import React from 'react';
+import React, { useCallback, useEffect } from 'react';
import { Icons, IconButton, TooltipLinkList, WithTooltip } from '@storybook/components';
import { useGlobals } from '@storybook/api';
@@ -17,14 +17,52 @@ type Link = ArrayItem['links']>;
const defaultTheme = 'v8.light';
export function ThemeSwitcher() {
- const [globals, updateGlobals] = useGlobals();
- const selectedTheme = globals.euiTheme;
+ const [{ euiTheme: selectedTheme }, updateGlobals] = useGlobals();
- if (!selectedTheme) {
- updateGlobals({ euiTheme: defaultTheme });
- }
+ const selectTheme = useCallback(
+ (themeId: string) => {
+ updateGlobals({ euiTheme: themeId });
+ },
+ [updateGlobals]
+ );
- function Menu({ onHide }: { onHide: () => void }) {
+ useEffect(() => {
+ if (!selectedTheme) {
+ selectTheme(defaultTheme);
+ }
+ }, [selectTheme, selectedTheme]);
+
+ return (
+ (
+
+ )}
+ >
+ {/* @ts-ignore Remove when @storybook has moved to @emotion v11 */}
+
+
+
+
+ );
+}
+
+const ThemeSwitcherTooltip = React.memo(
+ ({
+ onHide,
+ onChangeSelectedTheme,
+ selectedTheme,
+ }: {
+ onHide: () => void;
+ onChangeSelectedTheme: (themeId: string) => void;
+ selectedTheme: string;
+ }) => {
const links = [
{
id: 'v8.light',
@@ -38,8 +76,8 @@ export function ThemeSwitcher() {
(link): Link => ({
...link,
onClick: (_event, item) => {
- if (item.id !== selectedTheme) {
- updateGlobals({ euiTheme: item.id });
+ if (item.id != null && item.id !== selectedTheme) {
+ onChangeSelectedTheme(item.id);
}
onHide();
},
@@ -49,18 +87,4 @@ export function ThemeSwitcher() {
return ;
}
-
- return (
- }
- >
- {/* @ts-ignore Remove when @storybook has moved to @emotion v11 */}
-
-
-
-
- );
-}
+);
diff --git a/packages/kbn-test/BUILD.bazel b/packages/kbn-test/BUILD.bazel
index c42c33483703e..1d1d95d639861 100644
--- a/packages/kbn-test/BUILD.bazel
+++ b/packages/kbn-test/BUILD.bazel
@@ -76,6 +76,7 @@ TYPES_DEPS = [
"//packages/kbn-i18n-react:npm_module_types",
"//packages/kbn-utils",
"@npm//@elastic/elasticsearch",
+ "@npm//elastic-apm-node",
"@npm//del",
"@npm//form-data",
"@npm//jest",
diff --git a/packages/kbn-test/jest-preset.js b/packages/kbn-test/jest-preset.js
index db64f070b37d9..e2607100babc5 100644
--- a/packages/kbn-test/jest-preset.js
+++ b/packages/kbn-test/jest-preset.js
@@ -28,6 +28,7 @@ module.exports = {
moduleNameMapper: {
'@elastic/eui/lib/(.*)?': '/node_modules/@elastic/eui/test-env/$1',
'@elastic/eui$': '/node_modules/@elastic/eui/test-env',
+ 'elastic-apm-node': '/node_modules/@kbn/test/target_node/jest/mocks/apm_agent_mock.js',
'\\.module.(css|scss)$':
'/node_modules/@kbn/test/target_node/jest/mocks/css_module_mock.js',
'\\.(css|less|scss)$': '/node_modules/@kbn/test/target_node/jest/mocks/style_mock.js',
diff --git a/packages/kbn-test/src/jest/mocks/apm_agent_mock.ts b/packages/kbn-test/src/jest/mocks/apm_agent_mock.ts
new file mode 100644
index 0000000000000..1615f710504ad
--- /dev/null
+++ b/packages/kbn-test/src/jest/mocks/apm_agent_mock.ts
@@ -0,0 +1,63 @@
+/*
+ * Copyright 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 type { Agent } from 'elastic-apm-node';
+
+/**
+ * `elastic-apm-node` patches the runtime at import time
+ * causing memory leak with jest module sandbox, so it
+ * needs to be mocked for tests
+ */
+const agent: jest.Mocked = {
+ start: jest.fn().mockImplementation(() => agent),
+ isStarted: jest.fn().mockReturnValue(false),
+ getServiceName: jest.fn().mockReturnValue('mock-service'),
+ setFramework: jest.fn(),
+ addPatch: jest.fn(),
+ removePatch: jest.fn(),
+ clearPatches: jest.fn(),
+ lambda: jest.fn(),
+ handleUncaughtExceptions: jest.fn(),
+ captureError: jest.fn(),
+ currentTraceparent: null,
+ currentTraceIds: {},
+ startTransaction: jest.fn().mockReturnValue(null),
+ setTransactionName: jest.fn(),
+ endTransaction: jest.fn(),
+ currentTransaction: null,
+ startSpan: jest.fn(),
+ currentSpan: null,
+ setLabel: jest.fn().mockReturnValue(false),
+ addLabels: jest.fn().mockReturnValue(false),
+ setUserContext: jest.fn(),
+ setCustomContext: jest.fn(),
+ addFilter: jest.fn(),
+ addErrorFilter: jest.fn(),
+ addSpanFilter: jest.fn(),
+ addTransactionFilter: jest.fn(),
+ addMetadataFilter: jest.fn(),
+ flush: jest.fn(),
+ destroy: jest.fn(),
+ registerMetric: jest.fn(),
+ setTransactionOutcome: jest.fn(),
+ setSpanOutcome: jest.fn(),
+ middleware: {
+ connect: jest.fn().mockReturnValue(jest.fn()),
+ },
+ logger: {
+ fatal: jest.fn(),
+ error: jest.fn(),
+ warn: jest.fn(),
+ info: jest.fn(),
+ debug: jest.fn(),
+ trace: jest.fn(),
+ },
+};
+
+// eslint-disable-next-line import/no-default-export
+export default agent;
diff --git a/packages/kbn-test/src/kbn/users.ts b/packages/kbn-test/src/kbn/users.ts
index 230354089dcac..88480fde74ddc 100644
--- a/packages/kbn-test/src/kbn/users.ts
+++ b/packages/kbn-test/src/kbn/users.ts
@@ -14,7 +14,7 @@ export const kibanaTestUser = {
};
export const kibanaServerTestUser = {
- username: env.TEST_KIBANA_SERVER_USER || 'kibana',
+ username: env.TEST_KIBANA_SERVER_USER || 'kibana_system',
password: env.TEST_KIBANA_SERVER_PASS || 'changeme',
};
diff --git a/packages/kbn-typed-react-router-config/BUILD.bazel b/packages/kbn-typed-react-router-config/BUILD.bazel
index b347915ae3310..d759948a6c576 100644
--- a/packages/kbn-typed-react-router-config/BUILD.bazel
+++ b/packages/kbn-typed-react-router-config/BUILD.bazel
@@ -41,10 +41,10 @@ TYPES_DEPS = [
"@npm//query-string",
"@npm//utility-types",
"@npm//@types/jest",
- "@npm//@types/history",
"@npm//@types/node",
"@npm//@types/react-router-config",
"@npm//@types/react-router-dom",
+ "@npm//@types/history",
]
jsts_transpiler(
diff --git a/packages/kbn-typed-react-router-config/src/create_router.test.tsx b/packages/kbn-typed-react-router-config/src/create_router.test.tsx
index e82fcf791804e..ac337f8bb5b87 100644
--- a/packages/kbn-typed-react-router-config/src/create_router.test.tsx
+++ b/packages/kbn-typed-react-router-config/src/create_router.test.tsx
@@ -267,7 +267,6 @@ describe('createRouter', () => {
const matches = router.matchRoutes('/', history.location);
- // @ts-expect-error 4.3.5 upgrade - router doesn't seem able to merge properly when two routes match
expect(matches[1]?.match.params).toEqual({
query: {
rangeFrom: 'now-30m',
@@ -286,7 +285,6 @@ describe('createRouter', () => {
expect(matchedRoutes.length).toEqual(4);
- // @ts-expect-error 4.3.5 upgrade - router doesn't seem able to merge properly when two routes match
expect(matchedRoutes[matchedRoutes.length - 1].match).toEqual({
isExact: true,
params: {
diff --git a/packages/kbn-typed-react-router-config/src/create_router.ts b/packages/kbn-typed-react-router-config/src/create_router.ts
index 186f949d9c8e8..89ff4fc6b0c6c 100644
--- a/packages/kbn-typed-react-router-config/src/create_router.ts
+++ b/packages/kbn-typed-react-router-config/src/create_router.ts
@@ -23,7 +23,7 @@ function toReactRouterPath(path: string) {
return path.replace(/(?:{([^\/]+)})/g, ':$1');
}
-export function createRouter(routes: TRoute[]): Router {
+export function createRouter(routes: TRoutes): Router {
const routesByReactRouterConfig = new Map();
const reactRouterConfigsByRoute = new Map();
@@ -181,8 +181,10 @@ export function createRouter(routes: TRoute[]): Router {
+ return link(path, ...args);
+ },
getParams: (...args: any[]) => {
const matches = matchRoutes(...args);
return matches.length
@@ -195,13 +197,11 @@ export function createRouter(routes: TRoute[]): Router {
return matchRoutes(...args) as any;
},
- getRoutePath: (route: Route) => {
+ getRoutePath: (route) => {
return reactRouterConfigsByRoute.get(route)!.path as string;
},
getRoutesToMatch: (path: string) => {
- return getRoutesToMatch(path) as unknown as FlattenRoutesOf;
+ return getRoutesToMatch(path) as unknown as FlattenRoutesOf;
},
};
-
- return router;
}
diff --git a/packages/kbn-typed-react-router-config/src/types/index.ts b/packages/kbn-typed-react-router-config/src/types/index.ts
index 3c09b60054a0c..c1ae5afd816ee 100644
--- a/packages/kbn-typed-react-router-config/src/types/index.ts
+++ b/packages/kbn-typed-react-router-config/src/types/index.ts
@@ -115,7 +115,7 @@ export interface RouteMatch {
params: t.Type;
}
? t.TypeOf
- : AnyObj;
+ : {};
};
}
@@ -160,11 +160,10 @@ interface ReadonlyPlainRoute {
}
export type Route = PlainRoute | ReadonlyPlainRoute;
-type AnyObj = Record;
interface DefaultOutput {
- path: AnyObj;
- query: AnyObj;
+ path: {};
+ query: {};
}
type OutputOfRouteMatch = TRouteMatch extends {
@@ -191,21 +190,20 @@ type TypeOfRouteMatch = TRouteMatch extends {
route: { params: t.Type };
}
? t.TypeOf
- : AnyObj;
+ : {};
type TypeOfMatches = TRouteMatches extends [RouteMatch]
? TypeOfRouteMatch
: TRouteMatches extends [RouteMatch, ...infer TNextRouteMatches]
? TypeOfRouteMatch &
- (TNextRouteMatches extends RouteMatch[] ? TypeOfMatches : AnyObj)
- : AnyObj;
+ (TNextRouteMatches extends RouteMatch[] ? TypeOfMatches : {})
+ : {};
export type TypeOf<
TRoutes extends Route[],
TPath extends PathsOf,
TWithDefaultOutput extends boolean = true
-> = TypeOfMatches> &
- (TWithDefaultOutput extends true ? DefaultOutput : AnyObj);
+> = TypeOfMatches> & (TWithDefaultOutput extends true ? DefaultOutput : {});
export type TypeAsArgs = keyof TObject extends never
? []
@@ -278,7 +276,7 @@ type MapRoute = MaybeUnion<
>;
}
>
- : AnyObj
+ : {}
>;
type MapRoutes = TRoutes extends [Route]
@@ -343,7 +341,7 @@ type MapRoutes = TRoutes extends [Route]
MapRoute &
MapRoute &
MapRoute
- : AnyObj;
+ : {};
// const element = null as any;
diff --git a/src/core/public/doc_links/doc_links_service.ts b/src/core/public/doc_links/doc_links_service.ts
index 24c085ef64de3..692367cd0f580 100644
--- a/src/core/public/doc_links/doc_links_service.ts
+++ b/src/core/public/doc_links/doc_links_service.ts
@@ -113,6 +113,7 @@ export class DocLinksService {
usersAccess: `${ENTERPRISE_SEARCH_DOCS}users-access.html`,
},
workplaceSearch: {
+ apiKeys: `${WORKPLACE_SEARCH_DOCS}workplace-search-api-authentication.html`,
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`,
@@ -671,6 +672,7 @@ export interface DocLinksStart {
readonly usersAccess: string;
};
readonly workplaceSearch: {
+ readonly apiKeys: string;
readonly box: string;
readonly confluenceCloud: string;
readonly confluenceServer: string;
diff --git a/src/core/public/public.api.md b/src/core/public/public.api.md
index 30225acb3dd8d..08d41ab1301b0 100644
--- a/src/core/public/public.api.md
+++ b/src/core/public/public.api.md
@@ -571,6 +571,7 @@ export interface DocLinksStart {
readonly usersAccess: string;
};
readonly workplaceSearch: {
+ readonly apiKeys: string;
readonly box: string;
readonly confluenceCloud: string;
readonly confluenceServer: string;
diff --git a/src/dev/build/tasks/install_chromium.js b/src/dev/build/tasks/install_chromium.js
index ad60019ea81a4..2bcceb33fad00 100644
--- a/src/dev/build/tasks/install_chromium.js
+++ b/src/dev/build/tasks/install_chromium.js
@@ -6,10 +6,8 @@
* Side Public License, v 1.
*/
-import { first } from 'rxjs/operators';
-
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
-import { installBrowser } from '../../../../x-pack/plugins/reporting/server/browsers/install';
+import { install } from '../../../../x-pack/plugins/screenshotting/server/utils';
export const InstallChromium = {
description: 'Installing Chromium',
@@ -22,13 +20,23 @@ export const InstallChromium = {
// revert after https://github.com/elastic/kibana/issues/109949
if (target === 'darwin-arm64') continue;
- const { binaryPath$ } = installBrowser(
- log,
- build.resolvePathForPlatform(platform, 'x-pack/plugins/reporting/chromium'),
+ const logger = {
+ get: log.withType.bind(log),
+ debug: log.debug.bind(log),
+ info: log.info.bind(log),
+ warn: log.warning.bind(log),
+ trace: log.verbose.bind(log),
+ error: log.error.bind(log),
+ fatal: log.error.bind(log),
+ log: log.write.bind(log),
+ };
+
+ await install(
+ logger,
+ build.resolvePathForPlatform(platform, 'x-pack/plugins/screenshotting/chromium'),
platform.getName(),
platform.getArchitecture()
);
- await binaryPath$.pipe(first()).toPromise();
}
},
};
diff --git a/src/dev/code_coverage/ingest_coverage/__tests__/enumerate_patterns.test.js b/src/dev/code_coverage/ingest_coverage/__tests__/enumerate_patterns.test.js
index 05af7c2a154a4..57467d84f1f61 100644
--- a/src/dev/code_coverage/ingest_coverage/__tests__/enumerate_patterns.test.js
+++ b/src/dev/code_coverage/ingest_coverage/__tests__/enumerate_patterns.test.js
@@ -15,16 +15,17 @@ const log = new ToolingLog({
});
describe(`enumeratePatterns`, () => {
- it(`should resolve x-pack/plugins/reporting/server/browsers/extract/unzip.ts to kibana-reporting`, () => {
+ it(`should resolve x-pack/plugins/screenshotting/server/browsers/extract/unzip.ts to kibana-screenshotting`, () => {
const actual = enumeratePatterns(REPO_ROOT)(log)(
- new Map([['x-pack/plugins/reporting', ['kibana-reporting']]])
+ new Map([['x-pack/plugins/screenshotting', ['kibana-screenshotting']]])
);
- expect(
- actual[0].includes(
- 'x-pack/plugins/reporting/server/browsers/extract/unzip.ts kibana-reporting'
- )
- ).toBe(true);
+ expect(actual).toHaveProperty(
+ '0',
+ expect.arrayContaining([
+ 'x-pack/plugins/screenshotting/server/browsers/extract/unzip.ts kibana-screenshotting',
+ ])
+ );
});
it(`should resolve src/plugins/charts/common/static/color_maps/color_maps.ts to kibana-app`, () => {
const actual = enumeratePatterns(REPO_ROOT)(log)(
diff --git a/src/plugins/console/public/lib/mappings/mappings.js b/src/plugins/console/public/lib/mappings/mappings.js
index d4996f9fd8862..2a4ee6b2e346b 100644
--- a/src/plugins/console/public/lib/mappings/mappings.js
+++ b/src/plugins/console/public/lib/mappings/mappings.js
@@ -250,7 +250,10 @@ function retrieveSettings(settingsKey, settingsToRetrieve) {
// Fetch autocomplete info if setting is set to true, and if user has made changes.
if (settingsToRetrieve[settingsKey] === true) {
- return es.send('GET', settingKeyToPathMap[settingsKey], null, true);
+ // Use pretty=false in these request in order to compress the response by removing whitespace
+ const path = `${settingKeyToPathMap[settingsKey]}?pretty=false`;
+
+ return es.send('GET', path, null, true);
} else {
const settingsPromise = new $.Deferred();
if (settingsToRetrieve[settingsKey] === false) {
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 40f6f872535f9..e6404d6327ec3 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
@@ -65,6 +65,7 @@ beforeEach(async () => {
uiActions: {} as any,
uiSettings: uiSettingsServiceMock.createStartContract(),
http: coreStart.http,
+ theme: coreStart.theme,
presentationUtil: getStubPluginServices(),
screenshotMode: screenshotModePluginMock.createSetupContract(),
};
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 fc4c6b299284b..3c3872226ffb0 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
@@ -56,6 +56,7 @@ beforeEach(async () => {
uiActions: {} as any,
uiSettings: uiSettingsServiceMock.createStartContract(),
http: coreStart.http,
+ theme: coreStart.theme,
presentationUtil: getStubPluginServices(),
screenshotMode: screenshotModePluginMock.createSetupContract(),
};
diff --git a/src/plugins/dashboard/public/application/actions/copy_to_dashboard_action.tsx b/src/plugins/dashboard/public/application/actions/copy_to_dashboard_action.tsx
index f16486dd65e3c..3c9c1cbbba83e 100644
--- a/src/plugins/dashboard/public/application/actions/copy_to_dashboard_action.tsx
+++ b/src/plugins/dashboard/public/application/actions/copy_to_dashboard_action.tsx
@@ -7,7 +7,7 @@
*/
import React from 'react';
-import { OverlayStart } from '../../../../../core/public';
+import { CoreStart, OverlayStart } from '../../../../../core/public';
import { dashboardCopyToDashboardAction } from '../../dashboard_strings';
import { EmbeddableStateTransfer, IEmbeddable } from '../../services/embeddable';
import { toMountPoint } from '../../services/kibana_react';
@@ -37,6 +37,7 @@ export class CopyToDashboardAction implements Action
+ />,
+ { theme$: this.theme.theme$ }
),
{
maxWidth: 400,
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 b20a96c79aed6..f99b539ecb26c 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
@@ -48,6 +48,7 @@ beforeEach(async () => {
uiActions: {} as any,
uiSettings: uiSettingsServiceMock.createStartContract(),
http: coreMock.createStart().http,
+ theme: coreMock.createStart().theme,
presentationUtil: getStubPluginServices(),
screenshotMode: screenshotModePluginMock.createSetupContract(),
};
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 797765eda232d..c08a8d4af68dd 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
@@ -61,6 +61,7 @@ describe('Export CSV action', () => {
uiActions: {} as any,
uiSettings: uiSettingsServiceMock.createStartContract(),
http: coreStart.http,
+ theme: coreStart.theme,
presentationUtil: getStubPluginServices(),
screenshotMode: screenshotModePluginMock.createSetupContract(),
};
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 ab442bf839e37..92042f581fad4 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
@@ -62,6 +62,7 @@ beforeEach(async () => {
uiActions: {} as any,
uiSettings: uiSettingsServiceMock.createStartContract(),
http: coreStart.http,
+ theme: coreStart.theme,
presentationUtil: getStubPluginServices(),
screenshotMode: screenshotModePluginMock.createSetupContract(),
};
@@ -90,7 +91,7 @@ beforeEach(async () => {
});
test('Notification is incompatible with Error Embeddables', async () => {
- const action = new LibraryNotificationAction(unlinkAction);
+ const action = new LibraryNotificationAction(coreStart.theme, unlinkAction);
const errorEmbeddable = new ErrorEmbeddable(
'Wow what an awful error',
{ id: ' 404' },
@@ -100,19 +101,19 @@ test('Notification is incompatible with Error Embeddables', async () => {
});
test('Notification is shown when embeddable on dashboard has reference type input', async () => {
- const action = new LibraryNotificationAction(unlinkAction);
+ const action = new LibraryNotificationAction(coreStart.theme, unlinkAction);
embeddable.updateInput(await embeddable.getInputAsRefType());
expect(await action.isCompatible({ embeddable })).toBe(true);
});
test('Notification is not shown when embeddable input is by value', async () => {
- const action = new LibraryNotificationAction(unlinkAction);
+ const action = new LibraryNotificationAction(coreStart.theme, unlinkAction);
embeddable.updateInput(await embeddable.getInputAsValueType());
expect(await action.isCompatible({ embeddable })).toBe(false);
});
test('Notification is not shown when view mode is set to view', async () => {
- const action = new LibraryNotificationAction(unlinkAction);
+ const action = new LibraryNotificationAction(coreStart.theme, unlinkAction);
embeddable.updateInput(await embeddable.getInputAsRefType());
embeddable.updateInput({ viewMode: ViewMode.VIEW });
expect(await action.isCompatible({ embeddable })).toBe(false);
diff --git a/src/plugins/dashboard/public/application/actions/library_notification_action.tsx b/src/plugins/dashboard/public/application/actions/library_notification_action.tsx
index 4211dcf1443ed..b867f83985a6e 100644
--- a/src/plugins/dashboard/public/application/actions/library_notification_action.tsx
+++ b/src/plugins/dashboard/public/application/actions/library_notification_action.tsx
@@ -9,7 +9,8 @@
import React from 'react';
import { Action, IncompatibleActionError } from '../../services/ui_actions';
-import { reactToUiComponent } from '../../services/kibana_react';
+import { CoreStart } from '../../../../../core/public';
+import { KibanaThemeProvider, reactToUiComponent } from '../../services/kibana_react';
import {
IEmbeddable,
ViewMode,
@@ -32,7 +33,7 @@ export class LibraryNotificationAction implements Action {
const { embeddable } = context;
return (
-
+
+
+
);
};
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 de1a475fdbd18..a2a55404072eb 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
@@ -58,6 +58,7 @@ describe('LibraryNotificationPopover', () => {
uiActions: {} as any,
uiSettings: uiSettingsServiceMock.createStartContract(),
http: coreStart.http,
+ theme: coreStart.theme,
presentationUtil: getStubPluginServices(),
screenshotMode: screenshotModePluginMock.createSetupContract(),
};
diff --git a/src/plugins/dashboard/public/application/actions/open_replace_panel_flyout.tsx b/src/plugins/dashboard/public/application/actions/open_replace_panel_flyout.tsx
index cca72d10fac15..fa79b02d20dd5 100644
--- a/src/plugins/dashboard/public/application/actions/open_replace_panel_flyout.tsx
+++ b/src/plugins/dashboard/public/application/actions/open_replace_panel_flyout.tsx
@@ -47,7 +47,8 @@ export async function openReplacePanelFlyout(options: {
savedObjectsFinder={savedObjectFinder}
notifications={notifications}
getEmbeddableFactories={getEmbeddableFactories}
- />
+ />,
+ { theme$: core.theme.theme$ }
),
{
'data-test-subj': 'dashboardReplacePanel',
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 fe39f6112a7f3..9781736606607 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
@@ -48,6 +48,7 @@ beforeEach(async () => {
uiActions: {} as any,
uiSettings: uiSettingsServiceMock.createStartContract(),
http: coreStart.http,
+ theme: coreStart.theme,
presentationUtil: getStubPluginServices(),
screenshotMode: screenshotModePluginMock.createSetupContract(),
};
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 4f10f833f643c..f82b8d1bc7a87 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
@@ -57,6 +57,7 @@ beforeEach(async () => {
uiActions: {} as any,
uiSettings: uiSettingsServiceMock.createStartContract(),
http: coreStart.http,
+ theme: coreStart.theme,
presentationUtil: getStubPluginServices(),
screenshotMode: screenshotModePluginMock.createSetupContract(),
};
diff --git a/src/plugins/dashboard/public/application/dashboard_router.tsx b/src/plugins/dashboard/public/application/dashboard_router.tsx
index c74ac506e4809..ae16527b64440 100644
--- a/src/plugins/dashboard/public/application/dashboard_router.tsx
+++ b/src/plugins/dashboard/public/application/dashboard_router.tsx
@@ -20,7 +20,7 @@ import { DashboardListing } from './listing';
import { dashboardStateStore } from './state';
import { DashboardApp } from './dashboard_app';
import { DashboardNoMatch } from './listing/dashboard_no_match';
-import { KibanaContextProvider } from '../services/kibana_react';
+import { KibanaContextProvider, KibanaThemeProvider } from '../services/kibana_react';
import { addHelpMenuToAppChrome, DashboardSessionStorage } from './lib';
import { createDashboardListingFilterUrl } from '../dashboard_constants';
import { createDashboardEditUrl, DashboardConstants } from '../dashboard_constants';
@@ -226,26 +226,28 @@ export async function mountApp({
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
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 744d63c1ba04a..d5eef0c05129d 100644
--- a/src/plugins/dashboard/public/application/embeddable/dashboard_container.test.tsx
+++ b/src/plugins/dashboard/public/application/embeddable/dashboard_container.test.tsx
@@ -55,6 +55,7 @@ const options: DashboardContainerServices = {
uiActions: {} as any,
uiSettings: uiSettingsServiceMock.createStartContract(),
http: coreMock.createStart().http,
+ theme: coreMock.createStart().theme,
presentationUtil,
};
diff --git a/src/plugins/dashboard/public/application/embeddable/dashboard_container.tsx b/src/plugins/dashboard/public/application/embeddable/dashboard_container.tsx
index d7081bf020d85..3e259d4e26179 100644
--- a/src/plugins/dashboard/public/application/embeddable/dashboard_container.tsx
+++ b/src/plugins/dashboard/public/application/embeddable/dashboard_container.tsx
@@ -36,6 +36,7 @@ import {
KibanaContextProvider,
KibanaReactContext,
KibanaReactContextValue,
+ KibanaThemeProvider,
} from '../../services/kibana_react';
import { PLACEHOLDER_EMBEDDABLE } from './placeholder';
import { DashboardAppCapabilities, DashboardContainerInput } from '../../types';
@@ -60,6 +61,7 @@ export interface DashboardContainerServices {
uiSettings: IUiSettingsClient;
embeddable: EmbeddableStart;
uiActions: UiActionsStart;
+ theme: CoreStart['theme'];
http: CoreStart['http'];
}
@@ -259,9 +261,11 @@ export class DashboardContainer extends Container
-
-
-
+
+
+
+
+
,
dom
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 7518a36433d35..59f346caf4b0d 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
@@ -71,6 +71,7 @@ function prepare(props?: Partial) {
} as any,
uiSettings: uiSettingsServiceMock.createStartContract(),
http: coreMock.createStart().http,
+ theme: coreMock.createStart().theme,
presentationUtil,
screenshotMode: screenshotModePluginMock.createSetupContract(),
};
diff --git a/src/plugins/dashboard/public/application/embeddable/placeholder/placeholder_embeddable.tsx b/src/plugins/dashboard/public/application/embeddable/placeholder/placeholder_embeddable.tsx
index 97ca4a1332f24..598d6fc5eabb5 100644
--- a/src/plugins/dashboard/public/application/embeddable/placeholder/placeholder_embeddable.tsx
+++ b/src/plugins/dashboard/public/application/embeddable/placeholder/placeholder_embeddable.tsx
@@ -10,15 +10,25 @@ import React from 'react';
import ReactDOM from 'react-dom';
import { EuiLoadingChart } from '@elastic/eui';
import classNames from 'classnames';
+import { CoreStart } from 'src/core/public';
import { Embeddable, EmbeddableInput, IContainer } from '../../../services/embeddable';
+import { KibanaThemeProvider } from '../../../services/kibana_react';
export const PLACEHOLDER_EMBEDDABLE = 'placeholder';
+export interface PlaceholderEmbeddableServices {
+ theme: CoreStart['theme'];
+}
+
export class PlaceholderEmbeddable extends Embeddable {
public readonly type = PLACEHOLDER_EMBEDDABLE;
private node?: HTMLElement;
- constructor(initialInput: EmbeddableInput, parent?: IContainer) {
+ constructor(
+ initialInput: EmbeddableInput,
+ private readonly services: PlaceholderEmbeddableServices,
+ parent?: IContainer
+ ) {
super(initialInput, {}, parent);
this.input = initialInput;
}
@@ -30,9 +40,11 @@ export class PlaceholderEmbeddable extends Embeddable {
const classes = classNames('embPanel', 'embPanel-isLoading');
ReactDOM.render(
-
-
-
,
+
+
+
+
+ ,
node
);
}
diff --git a/src/plugins/dashboard/public/application/embeddable/placeholder/placeholder_embeddable_factory.ts b/src/plugins/dashboard/public/application/embeddable/placeholder/placeholder_embeddable_factory.ts
index 50cf85998913b..b0dce72ad77e3 100644
--- a/src/plugins/dashboard/public/application/embeddable/placeholder/placeholder_embeddable_factory.ts
+++ b/src/plugins/dashboard/public/application/embeddable/placeholder/placeholder_embeddable_factory.ts
@@ -13,11 +13,17 @@ import {
EmbeddableInput,
IContainer,
} from '../../../services/embeddable';
-import { PlaceholderEmbeddable, PLACEHOLDER_EMBEDDABLE } from './placeholder_embeddable';
+import {
+ PlaceholderEmbeddable,
+ PlaceholderEmbeddableServices,
+ PLACEHOLDER_EMBEDDABLE,
+} from './placeholder_embeddable';
export class PlaceholderEmbeddableFactory implements EmbeddableFactoryDefinition {
public readonly type = PLACEHOLDER_EMBEDDABLE;
+ constructor(private readonly getStartServices: () => Promise) {}
+
public async isEditable() {
return false;
}
@@ -27,7 +33,8 @@ export class PlaceholderEmbeddableFactory implements EmbeddableFactoryDefinition
}
public async create(initialInput: EmbeddableInput, parent?: IContainer) {
- return new PlaceholderEmbeddable(initialInput, parent);
+ const services = await this.getStartServices();
+ return new PlaceholderEmbeddable(initialInput, services, parent);
}
public getDisplayName() {
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 f0333cefd612f..d9de67ee9455d 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
@@ -49,6 +49,7 @@ function getProps(props?: Partial): {
application: applicationServiceMock.createStartContract(),
uiSettings: uiSettingsServiceMock.createStartContract(),
http: coreMock.createStart().http,
+ theme: coreMock.createStart().theme,
embeddable: {
getTriggerCompatibleActions: (() => []) as any,
getEmbeddablePanel: jest.fn(),
diff --git a/src/plugins/dashboard/public/application/listing/confirm_overlays.tsx b/src/plugins/dashboard/public/application/listing/confirm_overlays.tsx
index e3f7b32ef8223..f2792790f2f5d 100644
--- a/src/plugins/dashboard/public/application/listing/confirm_overlays.tsx
+++ b/src/plugins/dashboard/public/application/listing/confirm_overlays.tsx
@@ -20,7 +20,7 @@ import {
} from '@elastic/eui';
import React from 'react';
-import { OverlayStart } from '../../../../../core/public';
+import { CoreStart, OverlayStart } from '../../../../../core/public';
import { toMountPoint } from '../../services/kibana_react';
import { createConfirmStrings, discardConfirmStrings } from '../../dashboard_strings';
@@ -43,6 +43,7 @@ export const confirmDiscardUnsavedChanges = (overlays: OverlayStart, discardCall
export const confirmCreateWithUnsaved = (
overlays: OverlayStart,
+ theme: CoreStart['theme'],
startBlankCallback: () => void,
contineCallback: () => void
) => {
@@ -105,7 +106,8 @@ export const confirmCreateWithUnsaved = (
-
+ ,
+ { theme$: theme.theme$ }
),
{
'data-test-subj': 'dashboardCreateConfirmModal',
diff --git a/src/plugins/dashboard/public/application/listing/dashboard_listing.tsx b/src/plugins/dashboard/public/application/listing/dashboard_listing.tsx
index 605e5ec88565f..deb8671edb97d 100644
--- a/src/plugins/dashboard/public/application/listing/dashboard_listing.tsx
+++ b/src/plugins/dashboard/public/application/listing/dashboard_listing.tsx
@@ -119,6 +119,7 @@ export const DashboardListing = ({
} else {
confirmCreateWithUnsaved(
core.overlays,
+ core.theme,
() => {
dashboardSessionStorage.clearState();
redirectTo({ destination: 'dashboard' });
@@ -126,7 +127,7 @@ export const DashboardListing = ({
() => redirectTo({ destination: 'dashboard' })
);
}
- }, [dashboardSessionStorage, redirectTo, core.overlays]);
+ }, [dashboardSessionStorage, redirectTo, core.overlays, core.theme]);
const emptyPrompt = useMemo(() => {
if (!showWriteControls) {
diff --git a/src/plugins/dashboard/public/application/listing/dashboard_no_match.tsx b/src/plugins/dashboard/public/application/listing/dashboard_no_match.tsx
index 228a6994dcbb7..df7e9bc21e46d 100644
--- a/src/plugins/dashboard/public/application/listing/dashboard_no_match.tsx
+++ b/src/plugins/dashboard/public/application/listing/dashboard_no_match.tsx
@@ -45,7 +45,8 @@ export const DashboardNoMatch = ({ history }: { history: RouteComponentProps['hi
}}
/>
-
+ ,
+ { theme$: services.core.theme.theme$ }
)
);
diff --git a/src/plugins/dashboard/public/application/top_nav/dashboard_top_nav.tsx b/src/plugins/dashboard/public/application/top_nav/dashboard_top_nav.tsx
index 8e24e9ea595dc..bc5bb3aa4a566 100644
--- a/src/plugins/dashboard/public/application/top_nav/dashboard_top_nav.tsx
+++ b/src/plugins/dashboard/public/application/top_nav/dashboard_top_nav.tsx
@@ -110,7 +110,9 @@ export function DashboardTopNav({
} = useKibana().services;
const { version: kibanaVersion } = initializerContext.env.packageInfo;
const timefilter = data.query.timefilter.timefilter;
- const toasts = core.notifications.toasts;
+ const { notifications, theme } = core;
+ const { toasts } = notifications;
+ const { theme$ } = theme;
const dispatchDashboardStateChange = useDashboardDispatch();
const dashboardState = useDashboardSelector((state) => state.dashboardStateReducer);
@@ -367,7 +369,7 @@ export function DashboardTopNav({
});
return saveResult.id ? { id: saveResult.id } : { error: saveResult.error };
};
- showCloneModal(onClone, currentState.title);
+ showCloneModal({ onClone, title: currentState.title, theme$ });
}, [
dashboardSessionStorage,
savedObjectsTagging,
@@ -375,6 +377,7 @@ export function DashboardTopNav({
kibanaVersion,
redirectTo,
timefilter,
+ theme$,
toasts,
]);
@@ -395,9 +398,10 @@ export function DashboardTopNav({
onHidePanelTitlesChange: (isChecked: boolean) => {
dispatchDashboardStateChange(setHidePanelTitles(isChecked));
},
+ theme$,
});
},
- [dashboardAppState, dispatchDashboardStateChange]
+ [dashboardAppState, dispatchDashboardStateChange, theme$]
);
const showShare = useCallback(
diff --git a/src/plugins/dashboard/public/application/top_nav/show_clone_modal.tsx b/src/plugins/dashboard/public/application/top_nav/show_clone_modal.tsx
index 66803d0d7741e..5c7ec042bf1d9 100644
--- a/src/plugins/dashboard/public/application/top_nav/show_clone_modal.tsx
+++ b/src/plugins/dashboard/public/application/top_nav/show_clone_modal.tsx
@@ -10,16 +10,21 @@ import React from 'react';
import ReactDOM from 'react-dom';
import { i18n } from '@kbn/i18n';
import { I18nProvider } from '@kbn/i18n-react';
+import { CoreStart } from 'src/core/public';
import { DashboardCloneModal } from './clone_modal';
+import { KibanaThemeProvider } from '../../services/kibana_react';
-export function showCloneModal(
+export interface ShowCloneModalProps {
onClone: (
newTitle: string,
isTitleDuplicateConfirmed: boolean,
onTitleDuplicate: () => void
- ) => Promise<{ id?: string } | { error: Error }>,
- title: string
-) {
+ ) => Promise<{ id?: string } | { error: Error }>;
+ title: string;
+ theme$: CoreStart['theme']['theme$'];
+}
+
+export function showCloneModal({ onClone, title, theme$ }: ShowCloneModalProps) {
const container = document.createElement('div');
const closeModal = () => {
ReactDOM.unmountComponentAtNode(container);
@@ -44,14 +49,16 @@ export function showCloneModal(
document.body.appendChild(container);
const element = (
-
+
+
+
);
ReactDOM.render(element, container);
diff --git a/src/plugins/dashboard/public/application/top_nav/show_options_popover.tsx b/src/plugins/dashboard/public/application/top_nav/show_options_popover.tsx
index c9e10f83ff7ef..c53103075dcfb 100644
--- a/src/plugins/dashboard/public/application/top_nav/show_options_popover.tsx
+++ b/src/plugins/dashboard/public/application/top_nav/show_options_popover.tsx
@@ -10,8 +10,9 @@ import React from 'react';
import ReactDOM from 'react-dom';
import { I18nProvider } from '@kbn/i18n-react';
import { EuiWrappingPopover } from '@elastic/eui';
-
+import { CoreStart } from 'src/core/public';
import { OptionsMenu } from './options';
+import { KibanaThemeProvider } from '../../services/kibana_react';
let isOpen = false;
@@ -22,6 +23,17 @@ const onClose = () => {
isOpen = false;
};
+export interface ShowOptionsPopoverProps {
+ anchorElement: HTMLElement;
+ useMargins: boolean;
+ onUseMarginsChange: (useMargins: boolean) => void;
+ syncColors: boolean;
+ onSyncColorsChange: (syncColors: boolean) => void;
+ hidePanelTitles: boolean;
+ onHidePanelTitlesChange: (hideTitles: boolean) => void;
+ theme$: CoreStart['theme']['theme$'];
+}
+
export function showOptionsPopover({
anchorElement,
useMargins,
@@ -30,15 +42,8 @@ export function showOptionsPopover({
onHidePanelTitlesChange,
syncColors,
onSyncColorsChange,
-}: {
- anchorElement: HTMLElement;
- useMargins: boolean;
- onUseMarginsChange: (useMargins: boolean) => void;
- syncColors: boolean;
- onSyncColorsChange: (syncColors: boolean) => void;
- hidePanelTitles: boolean;
- onHidePanelTitlesChange: (hideTitles: boolean) => void;
-}) {
+ theme$,
+}: ShowOptionsPopoverProps) {
if (isOpen) {
onClose();
return;
@@ -49,16 +54,23 @@ export function showOptionsPopover({
document.body.appendChild(container);
const element = (
-
-
-
+
+
+
+
+
);
ReactDOM.render(element, container);
diff --git a/src/plugins/dashboard/public/plugin.tsx b/src/plugins/dashboard/public/plugin.tsx
index 9912aef943144..7f784d43c0cb7 100644
--- a/src/plugins/dashboard/public/plugin.tsx
+++ b/src/plugins/dashboard/public/plugin.tsx
@@ -193,6 +193,11 @@ export class DashboardPlugin
);
}
+ const getPlaceholderEmbeddableStartServices = async () => {
+ const [coreStart] = await core.getStartServices();
+ return { theme: coreStart.theme };
+ };
+
const getStartServices = async () => {
const [coreStart, deps] = await core.getStartServices();
@@ -203,13 +208,14 @@ export class DashboardPlugin
SavedObjectFinder: getSavedObjectFinder(coreStart.savedObjects, coreStart.uiSettings),
showWriteControls: Boolean(coreStart.application.capabilities.dashboard.showWriteControls),
notifications: coreStart.notifications,
+ screenshotMode: deps.screenshotMode,
application: coreStart.application,
uiSettings: coreStart.uiSettings,
overlays: coreStart.overlays,
embeddable: deps.embeddable,
uiActions: deps.uiActions,
inspector: deps.inspector,
- screenshotMode: deps.screenshotMode,
+ theme: coreStart.theme,
http: coreStart.http,
ExitFullScreenButton,
presentationUtil: deps.presentationUtil,
@@ -279,10 +285,12 @@ export class DashboardPlugin
dashboardContainerFactory.type,
dashboardContainerFactory
);
- });
- const placeholderFactory = new PlaceholderEmbeddableFactory();
- embeddable.registerEmbeddableFactory(placeholderFactory.type, placeholderFactory);
+ const placeholderFactory = new PlaceholderEmbeddableFactory(
+ getPlaceholderEmbeddableStartServices
+ );
+ embeddable.registerEmbeddableFactory(placeholderFactory.type, placeholderFactory);
+ });
this.stopUrlTracking = () => {
stopUrlTracker();
@@ -364,7 +372,7 @@ export class DashboardPlugin
}
public start(core: CoreStart, plugins: DashboardStartDependencies): DashboardStart {
- const { notifications, overlays, application } = core;
+ const { notifications, overlays, application, theme } = core;
const { uiActions, data, share, presentationUtil, embeddable } = plugins;
const dashboardCapabilities: Readonly = application.capabilities
@@ -406,11 +414,15 @@ export class DashboardPlugin
uiActions.registerAction(unlinkFromLibraryAction);
uiActions.attachAction(CONTEXT_MENU_TRIGGER, unlinkFromLibraryAction.id);
- const libraryNotificationAction = new LibraryNotificationAction(unlinkFromLibraryAction);
+ const libraryNotificationAction = new LibraryNotificationAction(
+ theme,
+ unlinkFromLibraryAction
+ );
uiActions.registerAction(libraryNotificationAction);
uiActions.attachAction(PANEL_NOTIFICATION_TRIGGER, libraryNotificationAction.id);
const copyToDashboardAction = new CopyToDashboardAction(
+ theme,
overlays,
embeddable.getStateTransfer(),
{
diff --git a/src/plugins/dashboard/public/services/kibana_react.ts b/src/plugins/dashboard/public/services/kibana_react.ts
index 4d5a3a5b57657..8cab64065824d 100644
--- a/src/plugins/dashboard/public/services/kibana_react.ts
+++ b/src/plugins/dashboard/public/services/kibana_react.ts
@@ -20,4 +20,5 @@ export {
reactToUiComponent,
ExitFullScreenButton,
KibanaContextProvider,
+ KibanaThemeProvider,
} from '../../../kibana_react/public';
diff --git a/src/plugins/expression_image/kibana.json b/src/plugins/expression_image/kibana.json
index 4f4b736d82d1a..7391b17bce779 100755
--- a/src/plugins/expression_image/kibana.json
+++ b/src/plugins/expression_image/kibana.json
@@ -10,5 +10,6 @@
"server": true,
"ui": true,
"requiredPlugins": ["expressions", "presentationUtil"],
- "optionalPlugins": []
+ "optionalPlugins": [],
+ "requiredBundles": ["kibanaReact"]
}
diff --git a/src/plugins/expression_image/public/expression_renderers/__stories__/image_renderer.stories.tsx b/src/plugins/expression_image/public/expression_renderers/__stories__/image_renderer.stories.tsx
index d75aa1a4263eb..dc54194d5d83f 100644
--- a/src/plugins/expression_image/public/expression_renderers/__stories__/image_renderer.stories.tsx
+++ b/src/plugins/expression_image/public/expression_renderers/__stories__/image_renderer.stories.tsx
@@ -9,7 +9,7 @@
import React from 'react';
import { storiesOf } from '@storybook/react';
import { Render, waitFor } from '../../../../presentation_util/public/__stories__';
-import { imageRenderer } from '../image_renderer';
+import { getImageRenderer } from '../image_renderer';
import { getElasticLogo } from '../../../../../../src/plugins/presentation_util/common/lib';
import { ImageMode } from '../../../common';
@@ -19,7 +19,7 @@ const Renderer = ({ elasticLogo }: { elasticLogo: string }) => {
mode: ImageMode.COVER,
};
- return ;
+ return ;
};
storiesOf('renderers/image', module).add(
diff --git a/src/plugins/expression_image/public/expression_renderers/image_renderer.tsx b/src/plugins/expression_image/public/expression_renderers/image_renderer.tsx
index 3d542a9978a83..a38649f13fb32 100644
--- a/src/plugins/expression_image/public/expression_renderers/image_renderer.tsx
+++ b/src/plugins/expression_image/public/expression_renderers/image_renderer.tsx
@@ -9,7 +9,11 @@ import React from 'react';
import { render, unmountComponentAtNode } from 'react-dom';
import { ExpressionRenderDefinition, IInterpreterRenderHandlers } from 'src/plugins/expressions';
import { i18n } from '@kbn/i18n';
-import { getElasticLogo, isValidUrl } from '../../../presentation_util/public';
+import { Observable } from 'rxjs';
+import { CoreTheme } from 'kibana/public';
+import { CoreSetup } from '../../../../core/public';
+import { KibanaThemeProvider } from '../../../kibana_react/public';
+import { getElasticLogo, isValidUrl, defaultTheme$ } from '../../../presentation_util/public';
import { ImageRendererConfig } from '../../common/types';
const strings = {
@@ -23,31 +27,41 @@ const strings = {
}),
};
-export const imageRenderer = (): ExpressionRenderDefinition => ({
- name: 'image',
- displayName: strings.getDisplayName(),
- help: strings.getHelpDescription(),
- reuseDomNode: true,
- render: async (
- domNode: HTMLElement,
- config: ImageRendererConfig,
- handlers: IInterpreterRenderHandlers
- ) => {
- const { elasticLogo } = await getElasticLogo();
- const dataurl = isValidUrl(config.dataurl ?? '') ? config.dataurl : elasticLogo;
+export const getImageRenderer =
+ (theme$: Observable = defaultTheme$) =>
+ (): ExpressionRenderDefinition => ({
+ name: 'image',
+ displayName: strings.getDisplayName(),
+ help: strings.getHelpDescription(),
+ reuseDomNode: true,
+ render: async (
+ domNode: HTMLElement,
+ config: ImageRendererConfig,
+ handlers: IInterpreterRenderHandlers
+ ) => {
+ const { elasticLogo } = await getElasticLogo();
+ const dataurl = isValidUrl(config.dataurl ?? '') ? config.dataurl : elasticLogo;
- const style = {
- height: '100%',
- backgroundImage: `url(${dataurl})`,
- backgroundRepeat: 'no-repeat',
- backgroundPosition: 'center center',
- backgroundSize: config.mode as string,
- };
+ const style = {
+ height: '100%',
+ backgroundImage: `url(${dataurl})`,
+ backgroundRepeat: 'no-repeat',
+ backgroundPosition: 'center center',
+ backgroundSize: config.mode as string,
+ };
- handlers.onDestroy(() => {
- unmountComponentAtNode(domNode);
- });
+ handlers.onDestroy(() => {
+ unmountComponentAtNode(domNode);
+ });
- render(, domNode, () => handlers.done());
- },
-});
+ render(
+
+
+ ,
+ domNode,
+ () => handlers.done()
+ );
+ },
+ });
+
+export const imageRendererFactory = (core: CoreSetup) => getImageRenderer(core.theme.theme$);
diff --git a/src/plugins/expression_image/public/expression_renderers/index.ts b/src/plugins/expression_image/public/expression_renderers/index.ts
index 96c274f05a7a9..6b4c4b03f7922 100644
--- a/src/plugins/expression_image/public/expression_renderers/index.ts
+++ b/src/plugins/expression_image/public/expression_renderers/index.ts
@@ -6,8 +6,4 @@
* Side Public License, v 1.
*/
-import { imageRenderer } from './image_renderer';
-
-export const renderers = [imageRenderer];
-
-export { imageRenderer };
+export { imageRendererFactory, getImageRenderer } from './image_renderer';
diff --git a/src/plugins/expression_image/public/index.ts b/src/plugins/expression_image/public/index.ts
index 661a12e7cf028..c379dd05dc221 100755
--- a/src/plugins/expression_image/public/index.ts
+++ b/src/plugins/expression_image/public/index.ts
@@ -6,9 +6,6 @@
* Side Public License, v 1.
*/
-// TODO: https://github.com/elastic/kibana/issues/110893
-/* eslint-disable @kbn/eslint/no_export_all */
-
import { ExpressionImagePlugin } from './plugin';
export type { ExpressionImagePluginSetup, ExpressionImagePluginStart } from './plugin';
@@ -17,4 +14,4 @@ export function plugin() {
return new ExpressionImagePlugin();
}
-export * from './expression_renderers';
+export { imageRendererFactory, getImageRenderer } from './expression_renderers';
diff --git a/src/plugins/expression_image/public/plugin.ts b/src/plugins/expression_image/public/plugin.ts
index 6e6c02248642f..ba7e2baded8d8 100755
--- a/src/plugins/expression_image/public/plugin.ts
+++ b/src/plugins/expression_image/public/plugin.ts
@@ -8,7 +8,7 @@
import { CoreSetup, CoreStart, Plugin } from '../../../core/public';
import { ExpressionsStart, ExpressionsSetup } from '../../expressions/public';
-import { imageRenderer } from './expression_renderers';
+import { imageRendererFactory } from './expression_renderers';
import { imageFunction } from '../common/expression_functions';
interface SetupDeps {
@@ -27,7 +27,7 @@ export class ExpressionImagePlugin
{
public setup(core: CoreSetup, { expressions }: SetupDeps): ExpressionImagePluginSetup {
expressions.registerFunction(imageFunction);
- expressions.registerRenderer(imageRenderer);
+ expressions.registerRenderer(imageRendererFactory(core));
}
public start(core: CoreStart): ExpressionImagePluginStart {}
diff --git a/src/plugins/expression_shape/common/index.ts b/src/plugins/expression_shape/common/index.ts
index 6019cda7a51bd..2a889e6de1bb3 100755
--- a/src/plugins/expression_shape/common/index.ts
+++ b/src/plugins/expression_shape/common/index.ts
@@ -6,10 +6,31 @@
* Side Public License, v 1.
*/
-// TODO: https://github.com/elastic/kibana/issues/110893
-/* eslint-disable @kbn/eslint/no_export_all */
+export {
+ PLUGIN_ID,
+ PLUGIN_NAME,
+ SVG,
+ CSS,
+ FONT_FAMILY,
+ FONT_WEIGHT,
+ BOOLEAN_TRUE,
+ BOOLEAN_FALSE,
+} from './constants';
-export * from './constants';
-export * from './types';
+export type {
+ Output,
+ ExpressionShapeFunction,
+ ProgressArguments,
+ ProgressOutput,
+ ExpressionProgressFunction,
+ OriginString,
+ ShapeRendererConfig,
+ NodeDimensions,
+ ParentNodeParams,
+ ViewBoxParams,
+ ProgressRendererConfig,
+} from './types';
+
+export { Progress, Shape } from './types';
export { getAvailableShapes, getAvailableProgressShapes } from './lib/available_shapes';
diff --git a/src/plugins/expression_shape/common/types/index.ts b/src/plugins/expression_shape/common/types/index.ts
index ec934e7affe88..ef45082ac2d96 100644
--- a/src/plugins/expression_shape/common/types/index.ts
+++ b/src/plugins/expression_shape/common/types/index.ts
@@ -5,5 +5,21 @@
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
-export * from './expression_functions';
-export * from './expression_renderers';
+
+export type {
+ Output,
+ ExpressionShapeFunction,
+ ProgressArguments,
+ ProgressOutput,
+ ExpressionProgressFunction,
+} from './expression_functions';
+export { Progress, Shape } from './expression_functions';
+
+export type {
+ OriginString,
+ ShapeRendererConfig,
+ NodeDimensions,
+ ParentNodeParams,
+ ViewBoxParams,
+ ProgressRendererConfig,
+} from './expression_renderers';
diff --git a/src/plugins/expression_shape/kibana.json b/src/plugins/expression_shape/kibana.json
index adf95689e271b..5d831f8e98f60 100755
--- a/src/plugins/expression_shape/kibana.json
+++ b/src/plugins/expression_shape/kibana.json
@@ -12,5 +12,5 @@
"extraPublicDirs": ["common"],
"requiredPlugins": ["expressions", "presentationUtil"],
"optionalPlugins": [],
- "requiredBundles": []
+ "requiredBundles": ["kibanaReact"]
}
diff --git a/src/plugins/expression_shape/public/expression_renderers/__stories__/progress_renderer.stories.tsx b/src/plugins/expression_shape/public/expression_renderers/__stories__/progress_renderer.stories.tsx
index dcf2daaafcfc1..862718f775c5e 100644
--- a/src/plugins/expression_shape/public/expression_renderers/__stories__/progress_renderer.stories.tsx
+++ b/src/plugins/expression_shape/public/expression_renderers/__stories__/progress_renderer.stories.tsx
@@ -9,7 +9,7 @@
import React from 'react';
import { storiesOf } from '@storybook/react';
import { Render } from '../../../../presentation_util/public/__stories__';
-import { progressRenderer } from '../progress_renderer';
+import { getProgressRenderer } from '../progress_renderer';
import { Progress } from '../../../common';
storiesOf('renderers/progress', module).add('default', () => {
@@ -29,5 +29,5 @@ storiesOf('renderers/progress', module).add('default', () => {
valueWeight: 15,
};
- return ;
+ return ;
});
diff --git a/src/plugins/expression_shape/public/expression_renderers/__stories__/shape_renderer.stories.tsx b/src/plugins/expression_shape/public/expression_renderers/__stories__/shape_renderer.stories.tsx
index 10ac3df88e81c..d7098e8378c60 100644
--- a/src/plugins/expression_shape/public/expression_renderers/__stories__/shape_renderer.stories.tsx
+++ b/src/plugins/expression_shape/public/expression_renderers/__stories__/shape_renderer.stories.tsx
@@ -8,7 +8,7 @@
import React from 'react';
import { storiesOf } from '@storybook/react';
-import { shapeRenderer as shape } from '../';
+import { getShapeRenderer } from '../';
import { Render } from '../../../../presentation_util/public/__stories__';
import { Shape } from '../../../common/types';
@@ -22,5 +22,5 @@ storiesOf('renderers/shape', module).add('default', () => {
maintainAspect: true,
};
- return ;
+ return ;
});
diff --git a/src/plugins/expression_shape/public/expression_renderers/index.ts b/src/plugins/expression_shape/public/expression_renderers/index.ts
index fc031c4a03c8a..59d98e7bd6f8f 100644
--- a/src/plugins/expression_shape/public/expression_renderers/index.ts
+++ b/src/plugins/expression_shape/public/expression_renderers/index.ts
@@ -6,9 +6,5 @@
* Side Public License, v 1.
*/
-import { shapeRenderer } from './shape_renderer';
-import { progressRenderer } from './progress_renderer';
-
-export const renderers = [shapeRenderer, progressRenderer];
-
-export { shapeRenderer, progressRenderer };
+export { getShapeRenderer, shapeRendererFactory } from './shape_renderer';
+export { getProgressRenderer, progressRendererFactory } from './progress_renderer';
diff --git a/src/plugins/expression_shape/public/expression_renderers/progress_renderer.tsx b/src/plugins/expression_shape/public/expression_renderers/progress_renderer.tsx
index 5f81ffcffd3d9..b618d24d26fb0 100644
--- a/src/plugins/expression_shape/public/expression_renderers/progress_renderer.tsx
+++ b/src/plugins/expression_shape/public/expression_renderers/progress_renderer.tsx
@@ -7,11 +7,16 @@
*/
import React from 'react';
import { render, unmountComponentAtNode } from 'react-dom';
+import { Observable } from 'rxjs';
+import { CoreTheme } from 'kibana/public';
import { ExpressionRenderDefinition, IInterpreterRenderHandlers } from 'src/plugins/expressions';
import { i18n } from '@kbn/i18n';
+import { I18nProvider } from '@kbn/i18n-react';
+import { KibanaThemeProvider } from '../../../kibana_react/public';
+import { CoreSetup } from '../../../../core/public';
import { ProgressRendererConfig } from '../../common/types';
import { LazyProgressComponent } from '../components/progress';
-import { withSuspense } from '../../../presentation_util/public';
+import { withSuspense, defaultTheme$ } from '../../../presentation_util/public';
const ProgressComponent = withSuspense(LazyProgressComponent);
@@ -26,23 +31,31 @@ const strings = {
}),
};
-export const progressRenderer = (): ExpressionRenderDefinition => ({
- name: 'progress',
- displayName: strings.getDisplayName(),
- help: strings.getHelpDescription(),
- reuseDomNode: true,
- render: async (
- domNode: HTMLElement,
- config: ProgressRendererConfig,
- handlers: IInterpreterRenderHandlers
- ) => {
- handlers.onDestroy(() => {
- unmountComponentAtNode(domNode);
- });
+export const getProgressRenderer =
+ (theme$: Observable = defaultTheme$) =>
+ (): ExpressionRenderDefinition => ({
+ name: 'progress',
+ displayName: strings.getDisplayName(),
+ help: strings.getHelpDescription(),
+ reuseDomNode: true,
+ render: async (
+ domNode: HTMLElement,
+ config: ProgressRendererConfig,
+ handlers: IInterpreterRenderHandlers
+ ) => {
+ handlers.onDestroy(() => {
+ unmountComponentAtNode(domNode);
+ });
- render(
- ,
- domNode
- );
- },
-});
+ render(
+
+
+
+
+ ,
+ domNode
+ );
+ },
+ });
+
+export const progressRendererFactory = (core: CoreSetup) => getProgressRenderer(core.theme.theme$);
diff --git a/src/plugins/expression_shape/public/expression_renderers/shape_renderer.tsx b/src/plugins/expression_shape/public/expression_renderers/shape_renderer.tsx
index d6fc7c4d27107..fb2a32884d03b 100644
--- a/src/plugins/expression_shape/public/expression_renderers/shape_renderer.tsx
+++ b/src/plugins/expression_shape/public/expression_renderers/shape_renderer.tsx
@@ -7,10 +7,14 @@
*/
import React from 'react';
import { render, unmountComponentAtNode } from 'react-dom';
+import { Observable } from 'rxjs';
+import { CoreTheme } from 'kibana/public';
import { I18nProvider } from '@kbn/i18n-react';
import { ExpressionRenderDefinition, IInterpreterRenderHandlers } from 'src/plugins/expressions';
import { i18n } from '@kbn/i18n';
-import { withSuspense } from '../../../presentation_util/public';
+import { CoreSetup } from '../../../../core/public';
+import { KibanaThemeProvider } from '../../../kibana_react/public';
+import { withSuspense, defaultTheme$ } from '../../../presentation_util/public';
import { ShapeRendererConfig } from '../../common/types';
import { LazyShapeComponent } from '../components/shape';
@@ -27,25 +31,31 @@ const strings = {
const ShapeComponent = withSuspense(LazyShapeComponent);
-export const shapeRenderer = (): ExpressionRenderDefinition => ({
- name: 'shape',
- displayName: strings.getDisplayName(),
- help: strings.getHelpDescription(),
- reuseDomNode: true,
- render: async (
- domNode: HTMLElement,
- config: ShapeRendererConfig,
- handlers: IInterpreterRenderHandlers
- ) => {
- handlers.onDestroy(() => {
- unmountComponentAtNode(domNode);
- });
+export const getShapeRenderer =
+ (theme$: Observable = defaultTheme$) =>
+ (): ExpressionRenderDefinition => ({
+ name: 'shape',
+ displayName: strings.getDisplayName(),
+ help: strings.getHelpDescription(),
+ reuseDomNode: true,
+ render: async (
+ domNode: HTMLElement,
+ config: ShapeRendererConfig,
+ handlers: IInterpreterRenderHandlers
+ ) => {
+ handlers.onDestroy(() => {
+ unmountComponentAtNode(domNode);
+ });
- render(
-
-
- ,
- domNode
- );
- },
-});
+ render(
+
+
+
+
+ ,
+ domNode
+ );
+ },
+ });
+
+export const shapeRendererFactory = (core: CoreSetup) => getShapeRenderer(core.theme.theme$);
diff --git a/src/plugins/expression_shape/public/index.ts b/src/plugins/expression_shape/public/index.ts
index 21276d3fb4df9..be260c4c8c80b 100755
--- a/src/plugins/expression_shape/public/index.ts
+++ b/src/plugins/expression_shape/public/index.ts
@@ -6,9 +6,6 @@
* Side Public License, v 1.
*/
-// TODO: https://github.com/elastic/kibana/issues/110893
-/* eslint-disable @kbn/eslint/no_export_all */
-
import { ExpressionShapePlugin } from './plugin';
export type { ExpressionShapePluginSetup, ExpressionShapePluginStart } from './plugin';
@@ -17,10 +14,50 @@ export function plugin() {
return new ExpressionShapePlugin();
}
-export * from './expression_renderers';
+export {
+ getShapeRenderer,
+ shapeRendererFactory,
+ getProgressRenderer,
+ progressRendererFactory,
+} from './expression_renderers';
+
export { LazyShapeDrawer } from './components/shape';
export { LazyProgressDrawer } from './components/progress';
export { getDefaultShapeData } from './components/reusable';
-export * from './components/shape/types';
-export * from './components/reusable/types';
-export * from '../common/types';
+
+export type {
+ ShapeProps,
+ ShapeAttributes,
+ ShapeContentAttributes,
+ SvgConfig,
+ SvgTextAttributes,
+ CircleParams,
+ RectParams,
+ PathParams,
+ PolygonParams,
+ SpecificShapeContentAttributes,
+ ShapeDrawerProps,
+ ShapeDrawerComponentProps,
+ ShapeRef,
+ ShapeType,
+} from './components/reusable/types';
+
+export { SvgElementTypes } from './components/reusable/types';
+
+export type {
+ Output,
+ ExpressionShapeFunction,
+ ProgressArguments,
+ ProgressOutput,
+ ExpressionProgressFunction,
+ OriginString,
+ ShapeRendererConfig,
+ NodeDimensions,
+ ParentNodeParams,
+ ViewBoxParams,
+ ProgressRendererConfig,
+} from '../common/types';
+
+export { Progress, Shape } from '../common/types';
+
+export type { ShapeComponentProps, Dimensions } from './components/shape/types';
diff --git a/src/plugins/expression_shape/public/plugin.ts b/src/plugins/expression_shape/public/plugin.ts
index 9403bce0af728..5728b92e97f94 100755
--- a/src/plugins/expression_shape/public/plugin.ts
+++ b/src/plugins/expression_shape/public/plugin.ts
@@ -8,7 +8,7 @@
import { CoreSetup, CoreStart, Plugin } from '../../../core/public';
import { ExpressionsStart, ExpressionsSetup } from '../../expressions/public';
-import { shapeRenderer, progressRenderer } from './expression_renderers';
+import { shapeRendererFactory, progressRendererFactory } from './expression_renderers';
import { shapeFunction, progressFunction } from '../common/expression_functions';
interface SetupDeps {
@@ -28,8 +28,8 @@ export class ExpressionShapePlugin
public setup(core: CoreSetup, { expressions }: SetupDeps): ExpressionShapePluginSetup {
expressions.registerFunction(shapeFunction);
expressions.registerFunction(progressFunction);
- expressions.registerRenderer(shapeRenderer);
- expressions.registerRenderer(progressRenderer);
+ expressions.registerRenderer(shapeRendererFactory(core));
+ expressions.registerRenderer(progressRendererFactory(core));
}
public start(core: CoreStart): ExpressionShapePluginStart {}
diff --git a/src/plugins/presentation_util/public/components/controls/control_group/component/control_frame_component.tsx b/src/plugins/presentation_util/public/components/controls/control_group/component/control_frame_component.tsx
index f94d2f8fee0dc..bdc3b2978f888 100644
--- a/src/plugins/presentation_util/public/components/controls/control_group/component/control_frame_component.tsx
+++ b/src/plugins/presentation_util/public/components/controls/control_group/component/control_frame_component.tsx
@@ -70,6 +70,7 @@ export const ControlFrame = ({ customPrepend, enableActions, embeddableId }: Con
openConfirm(ControlGroupStrings.management.deleteControls.getSubtitle(), {
@@ -131,6 +132,7 @@ export const ControlFrame = ({ customPrepend, enableActions, embeddableId }: Con
<>
{embeddable && enableActions && floatingActions}
{
direction="row"
responsive={false}
alignItems="center"
+ data-test-subj="controls-group"
+ data-shared-items-count={idsInOrder.length}
>
{
aria-label={ControlGroupStrings.management.getManageButtonTitle()}
iconType="gear"
color="text"
- data-test-subj="inputControlsSortingButton"
+ data-test-subj="controls-sorting-button"
onClick={() => {
const flyoutInstance = openFlyout(
forwardAllContext(
@@ -198,7 +200,7 @@ export const ControlGroup = () => {
) : (
<>
-
+
{ControlGroupStrings.emptyState.getCallToAction()}
diff --git a/src/plugins/presentation_util/public/components/controls/control_group/component/control_group_sortable_item.tsx b/src/plugins/presentation_util/public/components/controls/control_group/component/control_group_sortable_item.tsx
index 0ef9c4b7f115a..f4c28e840556a 100644
--- a/src/plugins/presentation_util/public/components/controls/control_group/component/control_group_sortable_item.tsx
+++ b/src/plugins/presentation_util/public/components/controls/control_group/component/control_group_sortable_item.tsx
@@ -76,6 +76,9 @@ const SortableControlInner = forwardRef<
return (
-
+
{ControlTypeEditor && (
@@ -105,6 +105,7 @@ export const ControlEditor = ({
)}
{
@@ -147,6 +148,7 @@ export const ControlEditor = ({
{
onCancel();
@@ -158,6 +160,7 @@ export const ControlEditor = ({
{
if (getControlTypes().length > 1) {
setIsControlTypePopoverOpen(!isControlTypePopoverOpen);
@@ -132,15 +125,17 @@ export const CreateControlButton = ({ isIconButton }: { isIconButton: boolean })
createNewControl(getControlTypes()[0]);
};
+ const commonButtonProps = {
+ onClick: onCreateButtonClick,
+ color: 'primary' as EuiButtonIconColor,
+ 'data-test-subj': 'controls-create-button',
+ 'aria-label': ControlGroupStrings.management.getManageButtonTitle(),
+ };
+
const createControlButton = isIconButton ? (
-
+
) : (
-
+
{ControlGroupStrings.emptyState.getAddControlButtonTitle()}
);
@@ -153,6 +148,7 @@ export const CreateControlButton = ({ isIconButton }: { isIconButton: boolean })
{
setIsControlTypePopoverOpen(false);
createNewControl(type);
@@ -169,6 +165,7 @@ export const CreateControlButton = ({ isIconButton }: { isIconButton: boolean })
isOpen={isControlTypePopoverOpen}
panelPaddingSize="none"
anchorPosition="downLeft"
+ data-test-subj="control-type-picker"
closePopover={() => setIsControlTypePopoverOpen(false)}
>
diff --git a/src/plugins/presentation_util/public/components/controls/control_group/editor/edit_control.tsx b/src/plugins/presentation_util/public/components/controls/control_group/editor/edit_control.tsx
index 549d3c51b6e34..eb628049f7c93 100644
--- a/src/plugins/presentation_util/public/components/controls/control_group/editor/edit_control.tsx
+++ b/src/plugins/presentation_util/public/components/controls/control_group/editor/edit_control.tsx
@@ -132,6 +132,7 @@ export const EditControlButton = ({ embeddableId }: { embeddableId: string }) =>
return (
editControl()}
diff --git a/src/plugins/presentation_util/public/components/controls/control_group/editor/editor_constants.ts b/src/plugins/presentation_util/public/components/controls/control_group/editor/editor_constants.ts
index 812f794efc8c3..814e2a08cd931 100644
--- a/src/plugins/presentation_util/public/components/controls/control_group/editor/editor_constants.ts
+++ b/src/plugins/presentation_util/public/components/controls/control_group/editor/editor_constants.ts
@@ -14,18 +14,22 @@ export const DEFAULT_CONTROL_WIDTH: ControlWidth = 'auto';
export const CONTROL_WIDTH_OPTIONS = [
{
id: `auto`,
+ 'data-test-subj': 'control-editor-width-auto',
label: ControlGroupStrings.management.controlWidth.getAutoWidthTitle(),
},
{
id: `small`,
+ 'data-test-subj': 'control-editor-width-small',
label: ControlGroupStrings.management.controlWidth.getSmallWidthTitle(),
},
{
id: `medium`,
+ 'data-test-subj': 'control-editor-width-medium',
label: ControlGroupStrings.management.controlWidth.getMediumWidthTitle(),
},
{
id: `large`,
+ 'data-test-subj': 'control-editor-width-large',
label: ControlGroupStrings.management.controlWidth.getLargeWidthTitle(),
},
];
diff --git a/src/plugins/presentation_util/public/components/controls/control_types/index.ts b/src/plugins/presentation_util/public/components/controls/control_types/index.ts
new file mode 100644
index 0000000000000..141e9f9b4d55f
--- /dev/null
+++ b/src/plugins/presentation_util/public/components/controls/control_types/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 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 * from './options_list';
diff --git a/src/plugins/presentation_util/public/components/controls/control_types/options_list/options_list_component.tsx b/src/plugins/presentation_util/public/components/controls/control_types/options_list/options_list_component.tsx
index 43026a67eb946..1c79d1ce3e9b0 100644
--- a/src/plugins/presentation_util/public/components/controls/control_types/options_list/options_list_component.tsx
+++ b/src/plugins/presentation_util/public/components/controls/control_types/options_list/options_list_component.tsx
@@ -44,7 +44,9 @@ export const OptionsListComponent = ({
actions: { replaceSelection },
} = useReduxEmbeddableContext();
const dispatch = useEmbeddableDispatch();
- const { controlStyle, selectedOptions, singleSelect } = useEmbeddableSelector((state) => state);
+ const { controlStyle, selectedOptions, singleSelect, id } = useEmbeddableSelector(
+ (state) => state
+ );
// useStateObservable to get component state from Embeddable
const { availableOptions, loading } = useStateObservable(
@@ -90,6 +92,7 @@ export const OptionsListComponent = ({
'optionsList--filterBtnSingle': controlStyle !== 'twoLine',
'optionsList--filterBtnPlaceholder': !selectedOptionsCount,
})}
+ data-test-subj={`optionsList-control-${id}`}
onClick={() => setIsPopoverOpen((openState) => !openState)}
isSelected={isPopoverOpen}
numActiveFilters={selectedOptionsCount}
diff --git a/src/plugins/presentation_util/public/components/controls/control_types/options_list/options_list_popover_component.tsx b/src/plugins/presentation_util/public/components/controls/control_types/options_list/options_list_popover_component.tsx
index a84d0460e9299..4aae049a5d446 100644
--- a/src/plugins/presentation_util/public/components/controls/control_types/options_list/options_list_popover_component.tsx
+++ b/src/plugins/presentation_util/public/components/controls/control_types/options_list/options_list_popover_component.tsx
@@ -63,6 +63,7 @@ export const OptionsListPopover = ({
disabled={showOnlySelected}
onChange={(event) => updateSearchString(event.target.value)}
value={searchString}
+ data-test-subj="optionsList-control-search-input"
/>
@@ -74,6 +75,7 @@ export const OptionsListPopover = ({
size="s"
color="danger"
iconType="eraser"
+ data-test-subj="optionsList-control-clear-all-selections"
aria-label={OptionsListStrings.popover.getClearAllSelectionsButtonTitle()}
onClick={() => dispatch(clearSelections({}))}
/>
@@ -102,11 +104,16 @@ export const OptionsListPopover = ({
-
+
{!showOnlySelected && (
<>
{availableOptions?.map((availableOption, index) => (
{
diff --git a/src/plugins/presentation_util/public/components/controls/index.ts b/src/plugins/presentation_util/public/components/controls/index.ts
index dbea24336699d..c110bc348498d 100644
--- a/src/plugins/presentation_util/public/components/controls/index.ts
+++ b/src/plugins/presentation_util/public/components/controls/index.ts
@@ -7,4 +7,5 @@
*/
export * from './control_group';
+export * from './control_types';
export * from './types';
diff --git a/src/plugins/presentation_util/public/components/data_view_picker/data_view_picker.tsx b/src/plugins/presentation_util/public/components/data_view_picker/data_view_picker.tsx
index 7b285944840c8..2911ae7a1e687 100644
--- a/src/plugins/presentation_util/public/components/data_view_picker/data_view_picker.tsx
+++ b/src/plugins/presentation_util/public/components/data_view_picker/data_view_picker.tsx
@@ -47,6 +47,7 @@ export function DataViewPicker({
return (
setPopoverIsOpen(!isPopoverOpen)}
fullWidth
{...colorProp}
@@ -68,7 +69,7 @@ export function DataViewPicker({
ownFocus
>
-
+
{i18n.translate('presentationUtil.dataViewPicker.changeDataViewTitle', {
defaultMessage: 'Data view',
})}
@@ -86,6 +87,7 @@ export function DataViewPicker({
key: id,
label: title,
value: id,
+ 'data-test-subj': `data-view-picker-${title}`,
checked: id === selectedDataViewId ? 'on' : undefined,
}))}
onChange={(choices) => {
diff --git a/src/plugins/presentation_util/public/components/field_picker/field_picker.tsx b/src/plugins/presentation_util/public/components/field_picker/field_picker.tsx
index fd83eeb0c8895..ebfbb24e7c390 100644
--- a/src/plugins/presentation_util/public/components/field_picker/field_picker.tsx
+++ b/src/plugins/presentation_util/public/components/field_picker/field_picker.tsx
@@ -88,6 +88,7 @@ export const FieldPicker = ({
return (
onSearchChange(event.currentTarget.value)}
placeholder={searchPlaceholder}
diff --git a/src/plugins/vis_types/xy/public/config/get_config.ts b/src/plugins/vis_types/xy/public/config/get_config.ts
index bd79b915be917..76fe1b21a74d6 100644
--- a/src/plugins/vis_types/xy/public/config/get_config.ts
+++ b/src/plugins/vis_types/xy/public/config/get_config.ts
@@ -134,8 +134,6 @@ const shouldEnableHistogramMode = (
}
return bars.every(({ valueAxis: groupId, mode }) => {
- const yAxisScale = yAxes.find(({ groupId: axisGroupId }) => axisGroupId === groupId)?.scale;
-
- return mode === 'stacked' || yAxisScale?.mode === 'percentage';
+ return mode === 'stacked';
});
};
diff --git a/src/plugins/vis_types/xy/public/editor/components/options/metrics_axes/__snapshots__/chart_options.test.tsx.snap b/src/plugins/vis_types/xy/public/editor/components/options/metrics_axes/__snapshots__/chart_options.test.tsx.snap
index 59a7cf966df91..56f35ae021173 100644
--- a/src/plugins/vis_types/xy/public/editor/components/options/metrics_axes/__snapshots__/chart_options.test.tsx.snap
+++ b/src/plugins/vis_types/xy/public/editor/components/options/metrics_axes/__snapshots__/chart_options.test.tsx.snap
@@ -54,7 +54,6 @@ exports[`ChartOptions component should init with the default set of props 1`] =
{
expect(setParamByIndex).toBeCalledWith('seriesParams', 0, paramName, ChartMode.Normal);
});
-
- it('should set "stacked" mode and disabled control if the referenced axis is "percentage"', () => {
- defaultProps.valueAxes[0].scale.mode = AxisMode.Percentage;
- defaultProps.chart.mode = ChartMode.Normal;
- const paramName = 'mode';
- const comp = mount();
-
- expect(setParamByIndex).toBeCalledWith('seriesParams', 0, paramName, ChartMode.Stacked);
- expect(comp.find({ paramName }).prop('disabled')).toBeTruthy();
- });
});
diff --git a/src/plugins/vis_types/xy/public/editor/components/options/metrics_axes/chart_options.tsx b/src/plugins/vis_types/xy/public/editor/components/options/metrics_axes/chart_options.tsx
index 04013969fb4fa..f1643746cd84e 100644
--- a/src/plugins/vis_types/xy/public/editor/components/options/metrics_axes/chart_options.tsx
+++ b/src/plugins/vis_types/xy/public/editor/components/options/metrics_axes/chart_options.tsx
@@ -6,14 +6,14 @@
* Side Public License, v 1.
*/
-import React, { useMemo, useCallback, useEffect, useState } from 'react';
+import React, { useMemo, useCallback } from 'react';
import { i18n } from '@kbn/i18n';
import { EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui';
import { SelectOption } from '../../../../../../../vis_default_editor/public';
-import { SeriesParam, ValueAxis, ChartMode, AxisMode } from '../../../../types';
+import { SeriesParam, ValueAxis } from '../../../../types';
import { LineOptions } from './line_options';
import { PointOptions } from './point_options';
import { SetParamByIndex, ChangeValueAxis } from '.';
@@ -39,7 +39,6 @@ function ChartOptions({
changeValueAxis,
setParamByIndex,
}: ChartOptionsParams) {
- const [disabledMode, setDisabledMode] = useState(false);
const setChart: SetChart = useCallback(
(paramName, value) => {
setParamByIndex('seriesParams', index, paramName, value);
@@ -70,20 +69,6 @@ function ChartOptions({
[valueAxes]
);
- useEffect(() => {
- const valueAxisToMetric = valueAxes.find((valueAxis) => valueAxis.id === chart.valueAxis);
- if (valueAxisToMetric) {
- if (valueAxisToMetric.scale.mode === AxisMode.Percentage) {
- setDisabledMode(true);
- if (chart.mode !== ChartMode.Stacked) {
- setChart('mode', ChartMode.Stacked);
- }
- } else if (disabledMode) {
- setDisabledMode(false);
- }
- }
- }, [valueAxes, chart, disabledMode, setChart, setDisabledMode]);
-
return (
<>
diff --git a/src/plugins/vis_types/xy/public/utils/render_all_series.test.mocks.ts b/src/plugins/vis_types/xy/public/utils/render_all_series.test.mocks.ts
index c14e313b1e7a4..4c51d8cad64e4 100644
--- a/src/plugins/vis_types/xy/public/utils/render_all_series.test.mocks.ts
+++ b/src/plugins/vis_types/xy/public/utils/render_all_series.test.mocks.ts
@@ -109,7 +109,7 @@ export const getVisConfig = (): VisConfig => {
show: false,
},
scale: {
- mode: AxisMode.Normal,
+ mode: AxisMode.Percentage,
type: 'linear',
},
domain: {
diff --git a/src/plugins/vis_types/xy/public/utils/render_all_series.test.tsx b/src/plugins/vis_types/xy/public/utils/render_all_series.test.tsx
index 47b103003b3ed..6f56eff3c2a92 100644
--- a/src/plugins/vis_types/xy/public/utils/render_all_series.test.tsx
+++ b/src/plugins/vis_types/xy/public/utils/render_all_series.test.tsx
@@ -95,6 +95,43 @@ describe('renderAllSeries', function () {
expect(wrapper.find(BarSeries).length).toBe(1);
});
+ it('renders percentage data for percentage mode', () => {
+ const barSeriesParams = [{ ...defaultSeriesParams[0], type: 'histogram', mode: 'percentage' }];
+ const config = getVisConfig();
+
+ const renderBarSeries = renderAllSeries(
+ config,
+ barSeriesParams as SeriesParam[],
+ defaultData,
+ jest.fn(),
+ jest.fn(),
+ 'Europe/Athens',
+ 'col-0-2',
+ []
+ );
+ const wrapper = shallow({renderBarSeries}
);
+ expect(wrapper.find(BarSeries).length).toBe(1);
+ expect(wrapper.find(BarSeries).prop('stackMode')).toEqual('percentage');
+ expect(wrapper.find(BarSeries).prop('data')).toEqual([
+ {
+ 'col-0-2': 1610960220000,
+ 'col-1-3': 1,
+ },
+ {
+ 'col-0-2': 1610961300000,
+ 'col-1-3': 1,
+ },
+ {
+ 'col-0-2': 1610961900000,
+ 'col-1-3': 1,
+ },
+ {
+ 'col-0-2': 1610962980000,
+ 'col-1-3': 1,
+ },
+ ]);
+ });
+
it('renders the correct yAccessors for not percentile aggs', () => {
const renderSeries = getAllSeries(getVisConfig(), defaultSeriesParams, defaultData);
const wrapper = shallow({renderSeries}
);
diff --git a/src/plugins/vis_types/xy/public/utils/render_all_series.tsx b/src/plugins/vis_types/xy/public/utils/render_all_series.tsx
index c248b3b86e42a..4d71cf454cfd6 100644
--- a/src/plugins/vis_types/xy/public/utils/render_all_series.tsx
+++ b/src/plugins/vis_types/xy/public/utils/render_all_series.tsx
@@ -19,6 +19,7 @@ import {
AccessorFn,
ColorVariant,
LabelOverflowConstraint,
+ computeRatioByGroups,
} from '@elastic/charts';
import { DatatableRow } from '../../../../expressions/public';
@@ -90,7 +91,24 @@ export const renderAllSeries = (
const id = `${type}-${yAccessors[0]}`;
const yAxisScale = yAxes.find(({ groupId: axisGroupId }) => axisGroupId === groupId)?.scale;
- const isStacked = mode === 'stacked' || yAxisScale?.mode === 'percentage';
+ // compute percentage mode data
+ const splitChartAccessor = aspects.splitColumn?.accessor || aspects.splitRow?.accessor;
+ const groupAccessors = [String(xAccessor)];
+ if (splitChartAccessor) {
+ groupAccessors.push(splitChartAccessor);
+ }
+ let computedData = data;
+ if (yAxisScale?.mode === 'percentage') {
+ yAccessors.forEach((accessor) => {
+ computedData = computeRatioByGroups(
+ computedData,
+ groupAccessors,
+ (d) => d[accessor],
+ accessor
+ );
+ });
+ }
+ const isStacked = mode === 'stacked';
const stackMode = yAxisScale?.mode === 'normal' ? undefined : yAxisScale?.mode;
// needed to seperate stacked and non-stacked bars into unique pseudo groups
const pseudoGroupId = isStacked ? `__pseudo_stacked_group-${groupId}__` : groupId;
@@ -113,7 +131,7 @@ export const renderAllSeries = (
xAccessor={xAccessor}
yAccessors={yAccessors}
splitSeriesAccessors={splitSeriesAccessors}
- data={data}
+ data={computedData}
timeZone={timeZone}
stackAccessors={isStacked ? ['__any_value__'] : undefined}
enableHistogramMode={enableHistogramMode}
@@ -153,7 +171,7 @@ export const renderAllSeries = (
markSizeAccessor={markSizeAccessor}
markFormat={aspects.z?.formatter}
splitSeriesAccessors={splitSeriesAccessors}
- data={data}
+ data={computedData}
stackAccessors={isStacked ? ['__any_value__'] : undefined}
displayValueSettings={{
showValueLabel,
diff --git a/test/functional/apps/dashboard/dashboard_controls_integration.ts b/test/functional/apps/dashboard/dashboard_controls_integration.ts
new file mode 100644
index 0000000000000..789d66fab6c86
--- /dev/null
+++ b/test/functional/apps/dashboard/dashboard_controls_integration.ts
@@ -0,0 +1,216 @@
+/*
+ * Copyright 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 { FtrProviderContext } from '../../ftr_provider_context';
+
+export default function ({ getService, getPageObjects }: FtrProviderContext) {
+ const retry = getService('retry');
+ const security = getService('security');
+ const queryBar = getService('queryBar');
+ const pieChart = getService('pieChart');
+ const filterBar = getService('filterBar');
+ const esArchiver = getService('esArchiver');
+ const testSubjects = getService('testSubjects');
+ const kibanaServer = getService('kibanaServer');
+ const dashboardAddPanel = getService('dashboardAddPanel');
+ const { dashboardControls, timePicker, common, dashboard, header } = getPageObjects([
+ 'dashboardControls',
+ 'timePicker',
+ 'dashboard',
+ 'common',
+ 'header',
+ ]);
+
+ describe('Dashboard controls integration', () => {
+ before(async () => {
+ await esArchiver.load('test/functional/fixtures/es_archiver/dashboard/current/kibana');
+ await security.testUser.setRoles(['kibana_admin', 'test_logstash_reader', 'animals']);
+ await kibanaServer.uiSettings.replace({
+ defaultIndex: '0bf35f60-3dc9-11e8-8660-4d65aa086b3c',
+ });
+ await common.navigateToApp('dashboard');
+ await dashboardControls.enableControlsLab();
+ await common.navigateToApp('dashboard');
+ await dashboard.preserveCrossAppState();
+ await dashboard.gotoDashboardLandingPage();
+ await dashboard.clickNewDashboard();
+ await timePicker.setDefaultDataRange();
+ });
+
+ it('shows the empty control callout on a new dashboard', async () => {
+ await testSubjects.existOrFail('controls-empty');
+ });
+
+ describe('Options List Control creation and editing experience', async () => {
+ it('can add a new options list control from a blank state', async () => {
+ await dashboardControls.createOptionsListControl({ fieldName: 'machine.os.raw' });
+ expect(await dashboardControls.getControlsCount()).to.be(1);
+ });
+
+ it('can add a second options list control with a non-default data view', async () => {
+ await dashboardControls.createOptionsListControl({
+ dataViewTitle: 'animals-*',
+ fieldName: 'sound.keyword',
+ });
+ expect(await dashboardControls.getControlsCount()).to.be(2);
+
+ // data views should be properly propagated from the control group to the dashboard
+ expect(await filterBar.getIndexPatterns()).to.be('logstash-*,animals-*');
+ });
+
+ it('renames an existing control', async () => {
+ const secondId = (await dashboardControls.getAllControlIds())[1];
+
+ const newTitle = 'wow! Animal sounds?';
+ await dashboardControls.editExistingControl(secondId);
+ await dashboardControls.controlEditorSetTitle(newTitle);
+ await dashboardControls.controlEditorSave();
+ expect(await dashboardControls.doesControlTitleExist(newTitle)).to.be(true);
+ });
+
+ it('can change the data view and field of an existing options list', async () => {
+ const firstId = (await dashboardControls.getAllControlIds())[0];
+ await dashboardControls.editExistingControl(firstId);
+
+ await dashboardControls.optionsListEditorSetDataView('animals-*');
+ await dashboardControls.optionsListEditorSetfield('animal.keyword');
+ await dashboardControls.controlEditorSave();
+
+ // when creating a new filter, the ability to select a data view should be removed, because the dashboard now only has one data view
+ await testSubjects.click('addFilter');
+ await testSubjects.missingOrFail('filterIndexPatternsSelect');
+ await filterBar.ensureFieldEditorModalIsClosed();
+ });
+
+ it('deletes an existing control', async () => {
+ const firstId = (await dashboardControls.getAllControlIds())[0];
+
+ await dashboardControls.removeExistingControl(firstId);
+ expect(await dashboardControls.getControlsCount()).to.be(1);
+ });
+
+ after(async () => {
+ const controlIds = await dashboardControls.getAllControlIds();
+ for (const controlId of controlIds) {
+ await dashboardControls.removeExistingControl(controlId);
+ }
+ });
+ });
+
+ describe('Interact with options list on dashboard', async () => {
+ before(async () => {
+ await dashboardAddPanel.addVisualization('Rendering-Test:-animal-sounds-pie');
+
+ await dashboardControls.createOptionsListControl({
+ dataViewTitle: 'animals-*',
+ fieldName: 'sound.keyword',
+ title: 'Animal Sounds',
+ });
+ });
+
+ it('Shows available options in options list', async () => {
+ const controlIds = await dashboardControls.getAllControlIds();
+ await dashboardControls.optionsListOpenPopover(controlIds[0]);
+ await retry.try(async () => {
+ expect(await dashboardControls.optionsListPopoverGetAvailableOptionsCount()).to.be(8);
+ });
+ await dashboardControls.optionsListEnsurePopoverIsClosed(controlIds[0]);
+ });
+
+ it('Can search options list for available options', async () => {
+ const controlIds = await dashboardControls.getAllControlIds();
+ await dashboardControls.optionsListOpenPopover(controlIds[0]);
+ await dashboardControls.optionsListPopoverSearchForOption('meo');
+ await retry.try(async () => {
+ expect(await dashboardControls.optionsListPopoverGetAvailableOptionsCount()).to.be(1);
+ expect(await dashboardControls.optionsListPopoverGetAvailableOptions()).to.eql(['meow']);
+ });
+ await dashboardControls.optionsListPopoverClearSearch();
+ await dashboardControls.optionsListEnsurePopoverIsClosed(controlIds[0]);
+ });
+
+ it('Applies dashboard query to options list control', async () => {
+ await queryBar.setQuery('isDog : true ');
+ await queryBar.submitQuery();
+ await dashboard.waitForRenderComplete();
+ await header.waitUntilLoadingHasFinished();
+
+ const controlIds = await dashboardControls.getAllControlIds();
+ await dashboardControls.optionsListOpenPopover(controlIds[0]);
+ await retry.try(async () => {
+ expect(await dashboardControls.optionsListPopoverGetAvailableOptionsCount()).to.be(5);
+ expect(await dashboardControls.optionsListPopoverGetAvailableOptions()).to.eql([
+ 'ruff',
+ 'bark',
+ 'grrr',
+ 'bow ow ow',
+ 'grr',
+ ]);
+ });
+
+ await queryBar.setQuery('');
+ await queryBar.submitQuery();
+ });
+
+ it('Applies dashboard filters to options list control', async () => {
+ await filterBar.addFilter('sound.keyword', 'is one of', ['bark', 'bow ow ow', 'ruff']);
+ await dashboard.waitForRenderComplete();
+ await header.waitUntilLoadingHasFinished();
+
+ const controlIds = await dashboardControls.getAllControlIds();
+ await dashboardControls.optionsListOpenPopover(controlIds[0]);
+ await retry.try(async () => {
+ expect(await dashboardControls.optionsListPopoverGetAvailableOptionsCount()).to.be(3);
+ expect(await dashboardControls.optionsListPopoverGetAvailableOptions()).to.eql([
+ 'ruff',
+ 'bark',
+ 'bow ow ow',
+ ]);
+ });
+
+ await filterBar.removeAllFilters();
+ });
+
+ it('Can select multiple available options', async () => {
+ const controlIds = await dashboardControls.getAllControlIds();
+ await dashboardControls.optionsListOpenPopover(controlIds[0]);
+ await dashboardControls.optionsListPopoverSelectOption('hiss');
+ await dashboardControls.optionsListPopoverSelectOption('grr');
+ await dashboardControls.optionsListEnsurePopoverIsClosed(controlIds[0]);
+ });
+
+ it('Selected options appear in control', async () => {
+ const controlIds = await dashboardControls.getAllControlIds();
+ const selectionString = await dashboardControls.optionsListGetSelectionsString(
+ controlIds[0]
+ );
+ expect(selectionString).to.be('hiss, grr');
+ });
+
+ it('Applies options list control options to dashboard', async () => {
+ expect(await pieChart.getPieSliceCount()).to.be(2);
+ });
+
+ it('Applies options list control options to dashboard by default on open', async () => {
+ await dashboard.gotoDashboardLandingPage();
+ await header.waitUntilLoadingHasFinished();
+ await dashboard.clickUnsavedChangesContinueEditing('New Dashboard');
+ await header.waitUntilLoadingHasFinished();
+ expect(await pieChart.getPieSliceCount()).to.be(2);
+
+ const controlIds = await dashboardControls.getAllControlIds();
+ const selectionString = await dashboardControls.optionsListGetSelectionsString(
+ controlIds[0]
+ );
+ expect(selectionString).to.be('hiss, grr');
+ });
+ });
+ });
+}
diff --git a/test/functional/apps/dashboard/dashboard_filtering.ts b/test/functional/apps/dashboard/dashboard_filtering.ts
index 73a53281df16d..796e8e35f0d49 100644
--- a/test/functional/apps/dashboard/dashboard_filtering.ts
+++ b/test/functional/apps/dashboard/dashboard_filtering.ts
@@ -67,7 +67,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
await security.testUser.restoreDefaults();
});
- describe('adding a filter that excludes all data', () => {
+ // FLAKY: https://github.com/elastic/kibana/issues/120195
+ describe.skip('adding a filter that excludes all data', () => {
before(async () => {
await populateDashboard();
await addFilterAndRefresh();
diff --git a/test/functional/apps/dashboard/index.ts b/test/functional/apps/dashboard/index.ts
index c9a62447f223a..73a8754982e4f 100644
--- a/test/functional/apps/dashboard/index.ts
+++ b/test/functional/apps/dashboard/index.ts
@@ -72,6 +72,7 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) {
loadTestFile(require.resolve('./full_screen_mode'));
loadTestFile(require.resolve('./dashboard_filter_bar'));
loadTestFile(require.resolve('./dashboard_filtering'));
+ loadTestFile(require.resolve('./dashboard_controls_integration'));
loadTestFile(require.resolve('./panel_expand_toggle'));
loadTestFile(require.resolve('./dashboard_grid'));
loadTestFile(require.resolve('./view_edit'));
diff --git a/test/functional/page_objects/dashboard_page_controls.ts b/test/functional/page_objects/dashboard_page_controls.ts
new file mode 100644
index 0000000000000..2603608eebee9
--- /dev/null
+++ b/test/functional/page_objects/dashboard_page_controls.ts
@@ -0,0 +1,254 @@
+/*
+ * Copyright 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 { WebElementWrapper } from 'test/functional/services/lib/web_element_wrapper';
+import { OPTIONS_LIST_CONTROL } from '../../../src/plugins/presentation_util/common/controls/';
+import { ControlWidth } from '../../../src/plugins/presentation_util/public/components/controls';
+
+import { FtrService } from '../ftr_provider_context';
+
+export class DashboardPageControls extends FtrService {
+ private readonly log = this.ctx.getService('log');
+ private readonly find = this.ctx.getService('find');
+ private readonly retry = this.ctx.getService('retry');
+ private readonly common = this.ctx.getPageObject('common');
+ private readonly header = this.ctx.getPageObject('header');
+ private readonly settings = this.ctx.getPageObject('settings');
+ private readonly testSubjects = this.ctx.getService('testSubjects');
+
+ /* -----------------------------------------------------------
+ General controls functions
+ ----------------------------------------------------------- */
+
+ public async enableControlsLab() {
+ await this.header.clickStackManagement();
+ await this.settings.clickKibanaSettings();
+ await this.settings.toggleAdvancedSettingCheckbox('labs:dashboard:dashboardControls');
+ }
+
+ public async expectControlsEmpty() {
+ await this.testSubjects.existOrFail('controls-empty');
+ }
+
+ public async getAllControlIds() {
+ const controlFrames = await this.testSubjects.findAll('control-frame');
+ const ids = await Promise.all(
+ controlFrames.map(async (controlFrame) => await controlFrame.getAttribute('data-control-id'))
+ );
+ this.log.debug('Got all control ids:', ids);
+ return ids;
+ }
+
+ public async getAllControlTitles() {
+ const titleObjects = await this.testSubjects.findAll('control-frame-title');
+ const titles = await Promise.all(
+ titleObjects.map(async (title) => (await title.getVisibleText()).split('\n')[0])
+ );
+ this.log.debug('Got all control titles:', titles);
+ return titles;
+ }
+
+ public async doesControlTitleExist(title: string) {
+ const titles = await this.getAllControlTitles();
+ return Boolean(titles.find((currentTitle) => currentTitle.indexOf(title)));
+ }
+
+ public async getControlsCount() {
+ const allTitles = await this.getAllControlTitles();
+ return allTitles.length;
+ }
+
+ public async openCreateControlFlyout(type: string) {
+ this.log.debug(`Opening flyout for ${type} control`);
+ await this.testSubjects.click('controls-create-button');
+ if (await this.testSubjects.exists('control-type-picker')) {
+ await this.testSubjects.click(`create-${type}-control`);
+ }
+ await this.retry.try(async () => {
+ await this.testSubjects.existOrFail('control-editor-flyout');
+ });
+ }
+
+ /* -----------------------------------------------------------
+ Individual controls functions
+ ----------------------------------------------------------- */
+
+ // Control Frame functions
+ public async getControlElementById(controlId: string): Promise {
+ const errorText = `Control frame ${controlId} could not be found`;
+ let controlElement: WebElementWrapper | undefined;
+ await this.retry.try(async () => {
+ const controlFrames = await this.testSubjects.findAll('control-frame');
+ const framesWithIds = await Promise.all(
+ controlFrames.map(async (frame) => {
+ const id = await frame.getAttribute('data-control-id');
+ return { id, element: frame };
+ })
+ );
+ const foundControlFrame = framesWithIds.find(({ id }) => id === controlId);
+ if (!foundControlFrame) throw new Error(errorText);
+ controlElement = foundControlFrame.element;
+ });
+ if (!controlElement) throw new Error(errorText);
+ return controlElement;
+ }
+
+ public async hoverOverExistingControl(controlId: string) {
+ const elementToHover = await this.getControlElementById(controlId);
+ await this.retry.try(async () => {
+ await elementToHover.moveMouseTo();
+ await this.testSubjects.existOrFail(`control-action-${controlId}-edit`);
+ });
+ }
+
+ public async editExistingControl(controlId: string) {
+ this.log.debug(`Opening control editor for control: ${controlId}`);
+ await this.hoverOverExistingControl(controlId);
+ await this.testSubjects.click(`control-action-${controlId}-edit`);
+ }
+
+ public async removeExistingControl(controlId: string) {
+ this.log.debug(`Removing control: ${controlId}`);
+ await this.hoverOverExistingControl(controlId);
+ await this.testSubjects.click(`control-action-${controlId}-delete`);
+ await this.common.clickConfirmOnModal();
+ }
+
+ // Options list functions
+ public async optionsListGetSelectionsString(controlId: string) {
+ this.log.debug(`Getting selections string for Options List: ${controlId}`);
+ const controlElement = await this.getControlElementById(controlId);
+ return (await controlElement.getVisibleText()).split('\n')[1];
+ }
+
+ public async optionsListOpenPopover(controlId: string) {
+ this.log.debug(`Opening popover for Options List: ${controlId}`);
+ await this.testSubjects.click(`optionsList-control-${controlId}`);
+ await this.retry.try(async () => {
+ await this.testSubjects.existOrFail(`optionsList-control-available-options`);
+ });
+ }
+
+ public async optionsListEnsurePopoverIsClosed(controlId: string) {
+ this.log.debug(`Opening popover for Options List: ${controlId}`);
+ await this.testSubjects.click(`optionsList-control-${controlId}`);
+ await this.testSubjects.waitForDeleted(`optionsList-control-available-options`);
+ }
+
+ public async optionsListPopoverAssertOpen() {
+ await this.retry.try(async () => {
+ if (!(await this.testSubjects.exists(`optionsList-control-available-options`))) {
+ throw new Error('options list popover must be open before calling selectOption');
+ }
+ });
+ }
+
+ public async optionsListPopoverGetAvailableOptionsCount() {
+ this.log.debug(`getting available options count from options list`);
+ const availableOptions = await this.testSubjects.find(`optionsList-control-available-options`);
+ return +(await availableOptions.getAttribute('data-option-count'));
+ }
+
+ public async optionsListPopoverGetAvailableOptions() {
+ this.log.debug(`getting available options count from options list`);
+ const availableOptions = await this.testSubjects.find(`optionsList-control-available-options`);
+ return (await availableOptions.getVisibleText()).split('\n');
+ }
+
+ public async optionsListPopoverSearchForOption(search: string) {
+ this.log.debug(`searching for ${search} in options list`);
+ await this.optionsListPopoverAssertOpen();
+ await this.testSubjects.setValue(`optionsList-control-search-input`, search);
+ }
+
+ public async optionsListPopoverClearSearch() {
+ this.log.debug(`clearing search from options list`);
+ await this.optionsListPopoverAssertOpen();
+ await this.find.clickByCssSelector('.euiFormControlLayoutClearButton');
+ }
+
+ public async optionsListPopoverSelectOption(availableOption: string) {
+ this.log.debug(`selecting ${availableOption} from options list`);
+ await this.optionsListPopoverAssertOpen();
+ await this.testSubjects.click(`optionsList-control-selection-${availableOption}`);
+ }
+
+ public async optionsListPopoverClearSelections() {
+ this.log.debug(`clearing all selections from options list`);
+ await this.optionsListPopoverAssertOpen();
+ await this.testSubjects.click(`optionsList-control-clear-all-selections`);
+ }
+
+ /* -----------------------------------------------------------
+ Control editor flyout
+ ----------------------------------------------------------- */
+
+ // Generic control editor functions
+ public async controlEditorSetTitle(title: string) {
+ this.log.debug(`Setting control title to ${title}`);
+ await this.testSubjects.setValue('control-editor-title-input', title);
+ }
+
+ public async controlEditorSetWidth(width: ControlWidth) {
+ this.log.debug(`Setting control width to ${width}`);
+ await this.testSubjects.click(`control-editor-width-${width}`);
+ }
+
+ public async controlEditorSave() {
+ this.log.debug(`Saving changes in control editor`);
+ await this.testSubjects.click(`control-editor-save`);
+ }
+
+ public async controlEditorCancel() {
+ this.log.debug(`Canceling changes in control editor`);
+ await this.testSubjects.click(`control-editor-cancel`);
+ }
+
+ // Options List editor functions
+ public async createOptionsListControl({
+ dataViewTitle,
+ fieldName,
+ width,
+ title,
+ }: {
+ title?: string;
+ fieldName: string;
+ width?: ControlWidth;
+ dataViewTitle?: string;
+ }) {
+ this.log.debug(`Creating options list control ${title ?? fieldName}`);
+ await this.openCreateControlFlyout(OPTIONS_LIST_CONTROL);
+
+ if (dataViewTitle) await this.optionsListEditorSetDataView(dataViewTitle);
+ if (fieldName) await this.optionsListEditorSetfield(fieldName);
+ if (title) await this.controlEditorSetTitle(title);
+ if (width) await this.controlEditorSetWidth(width);
+
+ await this.controlEditorSave();
+ }
+
+ public async optionsListEditorSetDataView(dataViewTitle: string) {
+ this.log.debug(`Setting options list data view to ${dataViewTitle}`);
+ await this.testSubjects.click('open-data-view-picker');
+ await this.retry.try(async () => {
+ await this.testSubjects.existOrFail('data-view-picker-title');
+ });
+ await this.testSubjects.click(`data-view-picker-${dataViewTitle}`);
+ }
+
+ public async optionsListEditorSetfield(fieldName: string, shouldSearch: boolean = false) {
+ this.log.debug(`Setting options list field to ${fieldName}`);
+ if (shouldSearch) {
+ await this.testSubjects.setValue('field-search-input', fieldName);
+ }
+ await this.retry.try(async () => {
+ await this.testSubjects.existOrFail(`field-picker-select-${fieldName}`);
+ });
+ await this.testSubjects.click(`field-picker-select-${fieldName}`);
+ }
+}
diff --git a/test/functional/page_objects/index.ts b/test/functional/page_objects/index.ts
index cda2c7de44d3b..826c4b78d1d0f 100644
--- a/test/functional/page_objects/index.ts
+++ b/test/functional/page_objects/index.ts
@@ -30,12 +30,14 @@ import { VegaChartPageObject } from './vega_chart_page';
import { SavedObjectsPageObject } from './management/saved_objects_page';
import { LegacyDataTableVisPageObject } from './legacy/data_table_vis';
import { IndexPatternFieldEditorPageObject } from './management/indexpattern_field_editor_page';
+import { DashboardPageControls } from './dashboard_page_controls';
export const pageObjects = {
common: CommonPageObject,
console: ConsolePageObject,
context: ContextPageObject,
dashboard: DashboardPageObject,
+ dashboardControls: DashboardPageControls,
discover: DiscoverPageObject,
error: ErrorPageObject,
header: HeaderPageObject,
diff --git a/x-pack/.gitignore b/x-pack/.gitignore
index 9a02a9e552b40..0e0e9aba84467 100644
--- a/x-pack/.gitignore
+++ b/x-pack/.gitignore
@@ -6,7 +6,7 @@
/test/functional/apps/**/reports/session
/test/reporting/configs/failure_debug/
/plugins/reporting/.chromium/
-/plugins/reporting/chromium/
+/plugins/screenshotting/chromium/
/plugins/reporting/.phantom/
/.aws-config.json
/.env
diff --git a/x-pack/.i18nrc.json b/x-pack/.i18nrc.json
index b51363f1b7006..aac29086fe53d 100644
--- a/x-pack/.i18nrc.json
+++ b/x-pack/.i18nrc.json
@@ -46,6 +46,7 @@
"xpack.reporting": ["plugins/reporting"],
"xpack.rollupJobs": ["plugins/rollup"],
"xpack.runtimeFields": "plugins/runtime_fields",
+ "xpack.screenshotting": "plugins/screenshotting",
"xpack.searchProfiler": "plugins/searchprofiler",
"xpack.security": "plugins/security",
"xpack.server": "legacy/server",
diff --git a/x-pack/examples/reporting_example/kibana.json b/x-pack/examples/reporting_example/kibana.json
index 716c6ea29c2a0..94780f1df0b36 100644
--- a/x-pack/examples/reporting_example/kibana.json
+++ b/x-pack/examples/reporting_example/kibana.json
@@ -10,5 +10,6 @@
},
"description": "Example integration code for applications to feature reports.",
"optionalPlugins": [],
- "requiredPlugins": ["reporting", "developerExamples", "navigation", "screenshotMode", "share"]
+ "requiredPlugins": ["reporting", "developerExamples", "navigation", "screenshotMode", "share"],
+ "requiredBundles": ["screenshotting"]
}
diff --git a/x-pack/examples/reporting_example/public/containers/main.tsx b/x-pack/examples/reporting_example/public/containers/main.tsx
index c6723c9839197..5f6cd816e9db3 100644
--- a/x-pack/examples/reporting_example/public/containers/main.tsx
+++ b/x-pack/examples/reporting_example/public/containers/main.tsx
@@ -39,7 +39,8 @@ import type {
JobParamsPDFV2,
JobParamsPNGV2,
} from '../../../../plugins/reporting/public';
-import { constants, ReportingStart } from '../../../../plugins/reporting/public';
+import { LayoutTypes } from '../../../../plugins/screenshotting/public';
+import { ReportingStart } from '../../../../plugins/reporting/public';
import { REPORTING_EXAMPLE_LOCATOR_ID } from '../../common';
@@ -87,7 +88,7 @@ export const Main = ({ basename, reporting, screenshotMode }: ReportingExampleAp
const getPDFJobParamsDefault = (): JobAppParamsPDF => {
return {
layout: {
- id: constants.LAYOUT_TYPES.PRESERVE_LAYOUT,
+ id: LayoutTypes.PRESERVE_LAYOUT,
},
relativeUrls: ['/app/reportingExample#/intended-visualization'],
objectType: 'develeloperExample',
@@ -99,7 +100,7 @@ export const Main = ({ basename, reporting, screenshotMode }: ReportingExampleAp
return {
version: '8.0.0',
layout: {
- id: constants.LAYOUT_TYPES.PRESERVE_LAYOUT,
+ id: LayoutTypes.PRESERVE_LAYOUT,
},
locatorParams: [
{ id: REPORTING_EXAMPLE_LOCATOR_ID, version: '0.5.0', params: { myTestState: {} } },
@@ -114,7 +115,7 @@ export const Main = ({ basename, reporting, screenshotMode }: ReportingExampleAp
return {
version: '8.0.0',
layout: {
- id: constants.LAYOUT_TYPES.PRESERVE_LAYOUT,
+ id: LayoutTypes.PRESERVE_LAYOUT,
},
locatorParams: {
id: REPORTING_EXAMPLE_LOCATOR_ID,
@@ -131,7 +132,7 @@ export const Main = ({ basename, reporting, screenshotMode }: ReportingExampleAp
return {
version: '8.0.0',
layout: {
- id: constants.LAYOUT_TYPES.PRESERVE_LAYOUT,
+ id: LayoutTypes.PRESERVE_LAYOUT,
},
locatorParams: {
id: REPORTING_EXAMPLE_LOCATOR_ID,
@@ -148,7 +149,7 @@ export const Main = ({ basename, reporting, screenshotMode }: ReportingExampleAp
return {
version: '8.0.0',
layout: {
- id: print ? constants.LAYOUT_TYPES.PRINT : constants.LAYOUT_TYPES.PRESERVE_LAYOUT,
+ id: print ? LayoutTypes.PRINT : LayoutTypes.PRESERVE_LAYOUT,
dimensions: {
// Magic numbers based on height of components not rendered on this screen :(
height: 2400,
diff --git a/x-pack/plugins/alerting/server/saved_objects/migrations.test.ts b/x-pack/plugins/alerting/server/saved_objects/migrations.test.ts
index 649c5c1526377..481edb07cedb9 100644
--- a/x-pack/plugins/alerting/server/saved_objects/migrations.test.ts
+++ b/x-pack/plugins/alerting/server/saved_objects/migrations.test.ts
@@ -2055,6 +2055,62 @@ describe('successful migrations', () => {
undefined
);
});
+
+ describe('Metrics Inventory Threshold rule', () => {
+ test('Migrates incorrect action group spelling', () => {
+ const migration800 = getMigrations(encryptedSavedObjectsSetup, isPreconfigured)['8.0.0'];
+
+ const actions = [
+ {
+ group: 'metrics.invenotry_threshold.fired',
+ params: {
+ level: 'info',
+ message:
+ '""{{alertName}} - {{context.group}} is in a state of {{context.alertState}} Reason: {{context.reason}}""',
+ },
+ actionRef: 'action_0',
+ actionTypeId: '.server-log',
+ },
+ ];
+
+ const alert = getMockData({ alertTypeId: 'metrics.alert.inventory.threshold', actions });
+
+ expect(migration800(alert, migrationContext)).toMatchObject({
+ ...alert,
+ attributes: {
+ ...alert.attributes,
+ actions: [{ ...actions[0], group: 'metrics.inventory_threshold.fired' }],
+ },
+ });
+ });
+
+ test('Works with the correct action group spelling', () => {
+ const migration800 = getMigrations(encryptedSavedObjectsSetup, isPreconfigured)['8.0.0'];
+
+ const actions = [
+ {
+ group: 'metrics.inventory_threshold.fired',
+ params: {
+ level: 'info',
+ message:
+ '""{{alertName}} - {{context.group}} is in a state of {{context.alertState}} Reason: {{context.reason}}""',
+ },
+ actionRef: 'action_0',
+ actionTypeId: '.server-log',
+ },
+ ];
+
+ const alert = getMockData({ alertTypeId: 'metrics.alert.inventory.threshold', actions });
+
+ expect(migration800(alert, migrationContext)).toMatchObject({
+ ...alert,
+ attributes: {
+ ...alert.attributes,
+ actions: [{ ...actions[0], group: 'metrics.inventory_threshold.fired' }],
+ },
+ });
+ });
+ });
});
});
diff --git a/x-pack/plugins/alerting/server/saved_objects/migrations.ts b/x-pack/plugins/alerting/server/saved_objects/migrations.ts
index 9dd3bac7f37a2..201c78ed2340d 100644
--- a/x-pack/plugins/alerting/server/saved_objects/migrations.ts
+++ b/x-pack/plugins/alerting/server/saved_objects/migrations.ts
@@ -129,7 +129,11 @@ export function getMigrations(
const migrationRules800 = createEsoMigration(
encryptedSavedObjects,
(doc: SavedObjectUnsanitizedDoc): doc is SavedObjectUnsanitizedDoc => true,
- pipeMigrations(addThreatIndicatorPathToThreatMatchRules, addRACRuleTypes)
+ pipeMigrations(
+ addThreatIndicatorPathToThreatMatchRules,
+ addRACRuleTypes,
+ fixInventoryThresholdGroupId
+ )
);
return {
@@ -751,6 +755,42 @@ function removePreconfiguredConnectorsFromReferences(
return doc;
}
+// This fixes an issue whereby metrics.alert.inventory.threshold rules had the
+// group for actions incorrectly spelt as metrics.invenotry_threshold.fired vs metrics.inventory_threshold.fired
+function fixInventoryThresholdGroupId(
+ doc: SavedObjectUnsanitizedDoc
+): SavedObjectUnsanitizedDoc {
+ if (doc.attributes.alertTypeId === 'metrics.alert.inventory.threshold') {
+ const {
+ attributes: { actions },
+ } = doc;
+
+ const updatedActions = actions
+ ? actions.map((action) => {
+ // Wrong spelling
+ if (action.group === 'metrics.invenotry_threshold.fired') {
+ return {
+ ...action,
+ group: 'metrics.inventory_threshold.fired',
+ };
+ } else {
+ return action;
+ }
+ })
+ : [];
+
+ return {
+ ...doc,
+ attributes: {
+ ...doc.attributes,
+ actions: updatedActions,
+ },
+ };
+ } else {
+ return doc;
+ }
+}
+
function getCorrespondingAction(
actions: SavedObjectAttribute,
connectorRef: string
diff --git a/x-pack/plugins/apm/common/anomaly_detection/apm_ml_job.ts b/x-pack/plugins/apm/common/anomaly_detection/apm_ml_job.ts
new file mode 100644
index 0000000000000..ab630decb70c8
--- /dev/null
+++ b/x-pack/plugins/apm/common/anomaly_detection/apm_ml_job.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 { DATAFEED_STATE, JOB_STATE } from '../../../ml/common';
+import { Environment } from '../environment_rt';
+
+export interface ApmMlJob {
+ environment: Environment;
+ version: number;
+ jobId: string;
+ jobState?: JOB_STATE;
+ datafeedId?: string;
+ datafeedState?: DATAFEED_STATE;
+}
diff --git a/x-pack/plugins/apm/common/anomaly_detection/get_anomaly_detection_setup_state.ts b/x-pack/plugins/apm/common/anomaly_detection/get_anomaly_detection_setup_state.ts
new file mode 100644
index 0000000000000..9ca8ddbe437fe
--- /dev/null
+++ b/x-pack/plugins/apm/common/anomaly_detection/get_anomaly_detection_setup_state.ts
@@ -0,0 +1,78 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+// eslint-disable-next-line @kbn/eslint/no-restricted-paths
+import { FETCH_STATUS } from '../../public/hooks/use_fetcher';
+// eslint-disable-next-line @kbn/eslint/no-restricted-paths
+import type { APIReturnType } from '../../public/services/rest/createCallApmApi';
+import { ENVIRONMENT_ALL } from '../environment_filter_values';
+
+export enum AnomalyDetectionSetupState {
+ Loading = 'pending',
+ Failure = 'failure',
+ Unknown = 'unknown',
+ NoJobs = 'noJobs',
+ NoJobsForEnvironment = 'noJobsForEnvironment',
+ LegacyJobs = 'legacyJobs',
+ UpgradeableJobs = 'upgradeableJobs',
+ UpToDate = 'upToDate',
+}
+
+export function getAnomalyDetectionSetupState({
+ environment,
+ jobs,
+ fetchStatus,
+ isAuthorized,
+}: {
+ environment: string;
+ jobs: APIReturnType<'GET /internal/apm/settings/anomaly-detection/jobs'>['jobs'];
+ fetchStatus: FETCH_STATUS;
+ isAuthorized: boolean;
+}): AnomalyDetectionSetupState {
+ if (!isAuthorized) {
+ return AnomalyDetectionSetupState.Unknown;
+ }
+
+ if (fetchStatus === FETCH_STATUS.LOADING) {
+ return AnomalyDetectionSetupState.Loading;
+ }
+
+ if (fetchStatus === FETCH_STATUS.FAILURE) {
+ return AnomalyDetectionSetupState.Failure;
+ }
+
+ if (fetchStatus !== FETCH_STATUS.SUCCESS) {
+ return AnomalyDetectionSetupState.Unknown;
+ }
+
+ const jobsForEnvironment =
+ environment === ENVIRONMENT_ALL.value
+ ? jobs
+ : jobs.filter((job) => job.environment === environment);
+
+ const hasV1Jobs = jobs.some((job) => job.version === 1);
+ const hasV2Jobs = jobsForEnvironment.some((job) => job.version === 2);
+ const hasV3Jobs = jobsForEnvironment.some((job) => job.version === 3);
+ const hasAnyJobs = jobs.length > 0;
+
+ if (hasV3Jobs) {
+ return AnomalyDetectionSetupState.UpToDate;
+ }
+
+ if (hasV2Jobs) {
+ return AnomalyDetectionSetupState.UpgradeableJobs;
+ }
+
+ if (hasV1Jobs) {
+ return AnomalyDetectionSetupState.LegacyJobs;
+ }
+
+ if (hasAnyJobs) {
+ return AnomalyDetectionSetupState.NoJobsForEnvironment;
+ }
+
+ return AnomalyDetectionSetupState.NoJobs;
+}
diff --git a/x-pack/plugins/apm/common/environment_rt.ts b/x-pack/plugins/apm/common/environment_rt.ts
index 4598ffa6f6681..67d1a6ce6fa64 100644
--- a/x-pack/plugins/apm/common/environment_rt.ts
+++ b/x-pack/plugins/apm/common/environment_rt.ts
@@ -11,12 +11,14 @@ import {
ENVIRONMENT_NOT_DEFINED,
} from './environment_filter_values';
+export const environmentStringRt = t.union([
+ t.literal(ENVIRONMENT_NOT_DEFINED.value),
+ t.literal(ENVIRONMENT_ALL.value),
+ nonEmptyStringRt,
+]);
+
export const environmentRt = t.type({
- environment: t.union([
- t.literal(ENVIRONMENT_NOT_DEFINED.value),
- t.literal(ENVIRONMENT_ALL.value),
- nonEmptyStringRt,
- ]),
+ environment: environmentStringRt,
});
export type Environment = t.TypeOf['environment'];
diff --git a/x-pack/plugins/apm/dev_docs/testing.md b/x-pack/plugins/apm/dev_docs/testing.md
index 2a7533402ecca..f6a8298ef9d0c 100644
--- a/x-pack/plugins/apm/dev_docs/testing.md
+++ b/x-pack/plugins/apm/dev_docs/testing.md
@@ -23,17 +23,44 @@ API tests are separated in two suites:
- a basic license test suite [default]
- a trial license test suite (the equivalent of gold+)
+### Run tests with [--trial] license
+
```
node scripts/test/api [--trial] [--help]
```
+The above command will initiate an Elasticsearch instance on http://localhost:9220 and a kibana instance on http://localhost:5620 and will run the api test against these environments.
+Once the tests finish, the instances will be terminated.
+
+### Start test server
+
+```
+node scripts/test/api --server
+```
+Start Elasticsearch and Kibana instances.
+
+### Run all tests
+
+```
+node scripts/test/api --runner
+```
+Run all tests. The test server needs to be running, see [Start Test Server](#start-test-server).
+
+### Update snapshots (from Kibana root)
+
+To update snapshots append `--updateSnapshots` to the `functional_test_runner` command
+
+```
+node scripts/functional_test_runner --config x-pack/test/apm_api_integration/[basic | trial]/config.ts --quiet --updateSnapshots
+```
+The test server needs to be running, see [Start Test Server](#start-test-server).
+
The API tests are located in [`x-pack/test/apm_api_integration/`](/x-pack/test/apm_api_integration/).
**API Test tips**
- For data generation in API tests have a look at the [elastic-apm-synthtrace](../../../../packages/elastic-apm-synthtrace/README.md) package
- For debugging access Elasticsearch on http://localhost:9220 and Kibana on http://localhost:5620 (`elastic` / `changeme`)
-- To update snapshots append `--updateSnapshots` to the functional_test_runner command
---
diff --git a/x-pack/plugins/apm/public/components/app/Settings/anomaly_detection/index.tsx b/x-pack/plugins/apm/public/components/app/Settings/anomaly_detection/index.tsx
index 8e1064a71647f..7fd40cc4a1663 100644
--- a/x-pack/plugins/apm/public/components/app/Settings/anomaly_detection/index.tsx
+++ b/x-pack/plugins/apm/public/components/app/Settings/anomaly_detection/index.tsx
@@ -11,19 +11,14 @@ import { ML_ERRORS } from '../../../../../common/anomaly_detection';
import { useApmPluginContext } from '../../../../context/apm_plugin/use_apm_plugin_context';
import { JobsList } from './jobs_list';
import { AddEnvironments } from './add_environments';
-import { useFetcher } from '../../../../hooks/use_fetcher';
import { LicensePrompt } from '../../../shared/license_prompt';
import { useLicenseContext } from '../../../../context/license/use_license_context';
import { APIReturnType } from '../../../../services/rest/createCallApmApi';
+import { useAnomalyDetectionJobsContext } from '../../../../context/anomaly_detection_jobs/use_anomaly_detection_jobs_context';
export type AnomalyDetectionApiResponse =
APIReturnType<'GET /internal/apm/settings/anomaly-detection/jobs'>;
-const DEFAULT_VALUE: AnomalyDetectionApiResponse = {
- jobs: [],
- hasLegacyJobs: false,
-};
-
export function AnomalyDetection() {
const plugin = useApmPluginContext();
const canGetJobs = !!plugin.core.application.capabilities.ml?.canGetJobs;
@@ -33,20 +28,14 @@ export function AnomalyDetection() {
const [viewAddEnvironments, setViewAddEnvironments] = useState(false);
const {
- refetch,
- data = DEFAULT_VALUE,
- status,
- } = useFetcher(
- (callApmApi) => {
- if (canGetJobs) {
- return callApmApi({
- endpoint: `GET /internal/apm/settings/anomaly-detection/jobs`,
- });
- }
- },
- [canGetJobs],
- { preservePreviousData: false, showToastOnError: false }
- );
+ anomalyDetectionJobsStatus,
+ anomalyDetectionJobsRefetch,
+ anomalyDetectionJobsData = {
+ jobs: [],
+ hasLegacyJobs: false,
+ } as AnomalyDetectionApiResponse,
+ anomalyDetectionSetupState,
+ } = useAnomalyDetectionJobsContext();
if (!hasValidLicense) {
return (
@@ -71,9 +60,11 @@ export function AnomalyDetection() {
<>
{viewAddEnvironments ? (
environment)}
+ currentEnvironments={anomalyDetectionJobsData.jobs.map(
+ ({ environment }) => environment
+ )}
onCreateJobSuccess={() => {
- refetch();
+ anomalyDetectionJobsRefetch();
setViewAddEnvironments(false);
}}
onCancel={() => {
@@ -82,11 +73,15 @@ export function AnomalyDetection() {
/>
) : (
{
setViewAddEnvironments(true);
}}
+ onUpdateComplete={() => {
+ anomalyDetectionJobsRefetch();
+ }}
/>
)}
>
diff --git a/x-pack/plugins/apm/public/components/app/Settings/anomaly_detection/jobs_list.tsx b/x-pack/plugins/apm/public/components/app/Settings/anomaly_detection/jobs_list.tsx
index 2e199d1d726fb..1faab4092361d 100644
--- a/x-pack/plugins/apm/public/components/app/Settings/anomaly_detection/jobs_list.tsx
+++ b/x-pack/plugins/apm/public/components/app/Settings/anomaly_detection/jobs_list.tsx
@@ -5,27 +5,35 @@
* 2.0.
*/
+import { EuiSwitch } from '@elastic/eui';
import {
EuiButton,
EuiFlexGroup,
EuiFlexItem,
+ EuiIcon,
EuiSpacer,
EuiText,
EuiTitle,
+ EuiToolTip,
RIGHT_ALIGNMENT,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n-react';
-import React from 'react';
+import React, { useState } from 'react';
+import { MLJobsAwaitingNodeWarning } from '../../../../../../ml/public';
+import { AnomalyDetectionSetupState } from '../../../../../common/anomaly_detection/get_anomaly_detection_setup_state';
import { getEnvironmentLabel } from '../../../../../common/environment_filter_values';
+import { useApmPluginContext } from '../../../../context/apm_plugin/use_apm_plugin_context';
import { FETCH_STATUS } from '../../../../hooks/use_fetcher';
+import { useMlManageJobsHref } from '../../../../hooks/use_ml_manage_jobs_href';
+import { callApmApi } from '../../../../services/rest/createCallApmApi';
import { MLExplorerLink } from '../../../shared/Links/MachineLearningLinks/MLExplorerLink';
import { MLManageJobsLink } from '../../../shared/Links/MachineLearningLinks/MLManageJobsLink';
import { LoadingStatePrompt } from '../../../shared/LoadingStatePrompt';
import { ITableColumn, ManagedTable } from '../../../shared/managed_table';
+import { MLCallout, shouldDisplayMlCallout } from '../../../shared/ml_callout';
import { AnomalyDetectionApiResponse } from './index';
-import { LegacyJobsCallout } from './legacy_jobs_callout';
-import { MLJobsAwaitingNodeWarning } from '../../../../../../ml/public';
+import { JobsListStatus } from './jobs_list_status';
type Jobs = AnomalyDetectionApiResponse['jobs'];
@@ -36,7 +44,24 @@ const columns: Array> = [
'xpack.apm.settings.anomalyDetection.jobList.environmentColumnLabel',
{ defaultMessage: 'Environment' }
),
- render: getEnvironmentLabel,
+ width: '100%',
+ render: (_, { environment, jobId, jobState, datafeedState, version }) => {
+ return (
+
+
+ {getEnvironmentLabel(environment)}
+
+
+
+
+
+ );
+ },
},
{
field: 'job_id',
@@ -45,30 +70,79 @@ const columns: Array> = [
'xpack.apm.settings.anomalyDetection.jobList.actionColumnLabel',
{ defaultMessage: 'Action' }
),
- render: (_, { job_id: jobId }) => (
-
- {i18n.translate(
- 'xpack.apm.settings.anomalyDetection.jobList.mlJobLinkText',
- {
- defaultMessage: 'View job in ML',
- }
- )}
-
- ),
+ render: (_, { jobId }) => {
+ return (
+
+
+
+ {/* setting the key to remount the element as a workaround for https://github.com/elastic/kibana/issues/119951*/}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+ },
},
];
interface Props {
data: AnomalyDetectionApiResponse;
+ setupState: AnomalyDetectionSetupState;
status: FETCH_STATUS;
onAddEnvironments: () => void;
+ onUpdateComplete: () => void;
}
-export function JobsList({ data, status, onAddEnvironments }: Props) {
- const { jobs, hasLegacyJobs } = data;
+
+export function JobsList({
+ data,
+ status,
+ onAddEnvironments,
+ setupState,
+ onUpdateComplete,
+}: Props) {
+ const { core } = useApmPluginContext();
+
+ const { jobs } = data;
+
+ // default to showing legacy jobs if not up to date
+ const [showLegacyJobs, setShowLegacyJobs] = useState(
+ setupState !== AnomalyDetectionSetupState.UpToDate
+ );
+
+ const mlManageJobsHref = useMlManageJobsHref();
+
+ const displayMlCallout = shouldDisplayMlCallout(setupState);
+
+ const filteredJobs = showLegacyJobs
+ ? jobs
+ : jobs.filter((job) => job.version >= 3);
return (
<>
- j.job_id)} />
+ j.jobId)} />
+ {displayMlCallout && (
+ <>
+ {
+ onAddEnvironments();
+ }}
+ onUpgradeClick={() => {
+ if (setupState === AnomalyDetectionSetupState.UpgradeableJobs) {
+ return callApmApi({
+ endpoint:
+ 'POST /internal/apm/settings/anomaly-detection/update_to_v3',
+ signal: null,
+ }).then(() => {
+ core.notifications.toasts.addSuccess({
+ title: i18n.translate(
+ 'xpack.apm.jobsList.updateCompletedToastTitle',
+ {
+ defaultMessage: 'Anomaly detection jobs created!',
+ }
+ ),
+ text: i18n.translate(
+ 'xpack.apm.jobsList.updateCompletedToastText',
+ {
+ defaultMessage:
+ 'Your new anomaly detection jobs have been created successfully. You will start to see anomaly detection results in the app within minutes. The old jobs have been closed but the results are still available within Machine Learning.',
+ }
+ ),
+ });
+ onUpdateComplete();
+ });
+ }
+ }}
+ anomalyDetectionSetupState={setupState}
+ />
+
+ >
+ )}
-
+
@@ -103,12 +215,36 @@ export function JobsList({ data, status, onAddEnvironments }: Props) {
+
+ {
+ setShowLegacyJobs(e.target.checked);
+ }}
+ label={i18n.translate(
+ 'xpack.apm.settings.anomalyDetection.jobList.showLegacyJobsCheckboxText',
+ {
+ defaultMessage: 'Show legacy jobs',
+ }
+ )}
+ />
+
+
+
+ {i18n.translate(
+ 'xpack.apm.settings.anomalyDetection.jobList.manageMlJobsButtonText',
+ {
+ defaultMessage: 'Manage jobs',
+ }
+ )}
+
+
{i18n.translate(
'xpack.apm.settings.anomalyDetection.jobList.addEnvironments',
{
- defaultMessage: 'Create ML Job',
+ defaultMessage: 'Create job',
}
)}
@@ -120,11 +256,10 @@ export function JobsList({ data, status, onAddEnvironments }: Props) {
-
- {hasLegacyJobs && }
>
);
}
diff --git a/x-pack/plugins/apm/public/components/app/Settings/anomaly_detection/jobs_list_status.tsx b/x-pack/plugins/apm/public/components/app/Settings/anomaly_detection/jobs_list_status.tsx
new file mode 100644
index 0000000000000..6145e9f9ca7da
--- /dev/null
+++ b/x-pack/plugins/apm/public/components/app/Settings/anomaly_detection/jobs_list_status.tsx
@@ -0,0 +1,102 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+import { EuiBadge, EuiFlexGroup, EuiFlexItem, EuiToolTip } from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
+import React from 'react';
+import { DATAFEED_STATE, JOB_STATE } from '../../../../../../ml/common';
+import { MLManageJobsLink } from '../../../shared/Links/MachineLearningLinks/MLManageJobsLink';
+
+export function JobsListStatus({
+ jobId,
+ jobState,
+ datafeedState,
+ version,
+}: {
+ jobId: string;
+ jobState?: JOB_STATE;
+ datafeedState?: DATAFEED_STATE;
+ version: number;
+}) {
+ const jobIsOk =
+ jobState === JOB_STATE.OPENED || jobState === JOB_STATE.OPENING;
+
+ const datafeedIsOk =
+ datafeedState === DATAFEED_STATE.STARTED ||
+ datafeedState === DATAFEED_STATE.STARTING;
+
+ const isClosed =
+ jobState === JOB_STATE.CLOSED || jobState === JOB_STATE.CLOSING;
+
+ const isLegacy = version < 3;
+
+ const statuses: React.ReactElement[] = [];
+
+ if (jobIsOk && datafeedIsOk) {
+ statuses.push(
+
+ {i18n.translate(
+ 'xpack.apm.settings.anomalyDetection.jobList.okStatusLabel',
+ { defaultMessage: 'OK' }
+ )}
+
+ );
+ } else if (!isClosed) {
+ statuses.push(
+
+
+
+ {i18n.translate(
+ 'xpack.apm.settings.anomalyDetection.jobList.warningStatusBadgeLabel',
+ { defaultMessage: 'Warning' }
+ )}
+
+
+
+ );
+ }
+
+ if (isClosed) {
+ statuses.push(
+
+ {i18n.translate(
+ 'xpack.apm.settings.anomalyDetection.jobList.closedStatusLabel',
+ { defaultMessage: 'Closed' }
+ )}
+
+ );
+ }
+
+ if (isLegacy) {
+ statuses.push(
+
+ {' '}
+ {i18n.translate(
+ 'xpack.apm.settings.anomalyDetection.jobList.legacyStatusLabel',
+ { defaultMessage: 'Legacy' }
+ )}
+
+ );
+ }
+
+ return (
+
+ {statuses.map((status, idx) => (
+
+ {status}
+
+ ))}
+
+ );
+}
diff --git a/x-pack/plugins/apm/public/components/app/Settings/anomaly_detection/legacy_jobs_callout.tsx b/x-pack/plugins/apm/public/components/app/Settings/anomaly_detection/legacy_jobs_callout.tsx
deleted file mode 100644
index 0d3da5c9f97ad..0000000000000
--- a/x-pack/plugins/apm/public/components/app/Settings/anomaly_detection/legacy_jobs_callout.tsx
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { EuiCallOut, EuiButton } from '@elastic/eui';
-import React from 'react';
-import { i18n } from '@kbn/i18n';
-import { useApmPluginContext } from '../../../../context/apm_plugin/use_apm_plugin_context';
-import { useMlHref } from '../../../../../../ml/public';
-
-export function LegacyJobsCallout() {
- const {
- core,
- plugins: { ml },
- } = useApmPluginContext();
- const mlADLink = useMlHref(ml, core.http.basePath.get(), {
- page: 'jobs',
- pageState: {
- jobId: 'high_mean_response_time',
- },
- });
-
- return (
-
-
- {i18n.translate(
- 'xpack.apm.settings.anomaly_detection.legacy_jobs.body',
- {
- defaultMessage:
- 'We have discovered legacy Machine Learning jobs from our previous integration which are no longer being used in the APM app',
- }
- )}
-
-
- {i18n.translate(
- 'xpack.apm.settings.anomaly_detection.legacy_jobs.button',
- { defaultMessage: 'Review jobs' }
- )}
-
-
- );
-}
diff --git a/x-pack/plugins/apm/public/components/app/service_inventory/index.tsx b/x-pack/plugins/apm/public/components/app/service_inventory/index.tsx
index 6ca632eac4f2e..1994d3641ee53 100644
--- a/x-pack/plugins/apm/public/components/app/service_inventory/index.tsx
+++ b/x-pack/plugins/apm/public/components/app/service_inventory/index.tsx
@@ -26,7 +26,7 @@ import { useUpgradeAssistantHref } from '../../shared/Links/kibana';
import { SearchBar } from '../../shared/search_bar';
import { getTimeRangeComparison } from '../../shared/time_comparison/get_time_range_comparison';
import { ServiceList } from './service_list';
-import { MLCallout } from './service_list/MLCallout';
+import { MLCallout, shouldDisplayMlCallout } from '../../shared/ml_callout';
const initialData = {
requestId: '',
@@ -159,26 +159,19 @@ function useServicesFetcher() {
}
export function ServiceInventory() {
- const { core } = useApmPluginContext();
-
const { mainStatisticsData, mainStatisticsStatus, comparisonData } =
useServicesFetcher();
- const { anomalyDetectionJobsData, anomalyDetectionJobsStatus } =
- useAnomalyDetectionJobsContext();
+ const { anomalyDetectionSetupState } = useAnomalyDetectionJobsContext();
const [userHasDismissedCallout, setUserHasDismissedCallout] = useLocalStorage(
- 'apm.userHasDismissedServiceInventoryMlCallout',
+ `apm.userHasDismissedServiceInventoryMlCallout.${anomalyDetectionSetupState}`,
false
);
- const canCreateJob = !!core.application.capabilities.ml?.canCreateJob;
-
const displayMlCallout =
- anomalyDetectionJobsStatus === FETCH_STATUS.SUCCESS &&
- !anomalyDetectionJobsData?.jobs.length &&
- canCreateJob &&
- !userHasDismissedCallout;
+ !userHasDismissedCallout &&
+ shouldDisplayMlCallout(anomalyDetectionSetupState);
const isLoading = mainStatisticsStatus === FETCH_STATUS.LOADING;
const isFailure = mainStatisticsStatus === FETCH_STATUS.FAILURE;
@@ -198,10 +191,14 @@ export function ServiceInventory() {
return (
<>
-
+
{displayMlCallout && (
- setUserHasDismissedCallout(true)} />
+ setUserHasDismissedCallout(true)}
+ />
)}
diff --git a/x-pack/plugins/apm/public/components/app/service_inventory/service_inventory.stories.tsx b/x-pack/plugins/apm/public/components/app/service_inventory/service_inventory.stories.tsx
index 0a4adc07e1a98..bececfb545ba9 100644
--- a/x-pack/plugins/apm/public/components/app/service_inventory/service_inventory.stories.tsx
+++ b/x-pack/plugins/apm/public/components/app/service_inventory/service_inventory.stories.tsx
@@ -10,6 +10,7 @@ import React from 'react';
import { MemoryRouter } from 'react-router-dom';
import { CoreStart } from '../../../../../../../src/core/public';
import { createKibanaReactContext } from '../../../../../../../src/plugins/kibana_react/public';
+import { AnomalyDetectionSetupState } from '../../../../common/anomaly_detection/get_anomaly_detection_setup_state';
import { TimeRangeComparisonEnum } from '../../../../common/runtime_types/comparison_type_rt';
import { AnomalyDetectionJobsContext } from '../../../context/anomaly_detection_jobs/anomaly_detection_jobs_context';
import { ApmPluginContextValue } from '../../../context/apm_plugin/apm_plugin_context';
@@ -45,6 +46,7 @@ const stories: Meta<{}> = {
anomalyDetectionJobsData: { jobs: [], hasLegacyJobs: false },
anomalyDetectionJobsStatus: FETCH_STATUS.SUCCESS,
anomalyDetectionJobsRefetch: () => {},
+ anomalyDetectionSetupState: AnomalyDetectionSetupState.NoJobs,
};
return (
diff --git a/x-pack/plugins/apm/public/components/app/service_inventory/service_list/MLCallout.tsx b/x-pack/plugins/apm/public/components/app/service_inventory/service_list/MLCallout.tsx
deleted file mode 100644
index 91625af7062cc..0000000000000
--- a/x-pack/plugins/apm/public/components/app/service_inventory/service_list/MLCallout.tsx
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import React from 'react';
-import { EuiCallOut } from '@elastic/eui';
-import { i18n } from '@kbn/i18n';
-import { EuiButton } from '@elastic/eui';
-import { EuiFlexItem } from '@elastic/eui';
-import { EuiFlexGrid } from '@elastic/eui';
-import { EuiButtonEmpty } from '@elastic/eui';
-import { APMLink } from '../../../shared/Links/apm/APMLink';
-
-export function MLCallout({ onDismiss }: { onDismiss: () => void }) {
- return (
-
-
- {i18n.translate('xpack.apm.serviceOverview.mlNudgeMessage.content', {
- defaultMessage: `Pinpoint anomalous transactions and see the health of upstream and downstream services with APM's anomaly detection integration. Get started in just a few minutes.`,
- })}
-
-
-
-
-
- {i18n.translate(
- 'xpack.apm.serviceOverview.mlNudgeMessage.learnMoreButton',
- {
- defaultMessage: `Get started`,
- }
- )}
-
-
-
-
- onDismiss()}>
- {i18n.translate(
- 'xpack.apm.serviceOverview.mlNudgeMessage.dismissButton',
- {
- defaultMessage: `Dismiss`,
- }
- )}
-
-
-
-
- );
-}
diff --git a/x-pack/plugins/apm/public/components/shared/Links/MachineLearningLinks/MLManageJobsLink.tsx b/x-pack/plugins/apm/public/components/shared/Links/MachineLearningLinks/MLManageJobsLink.tsx
index eb7b531121753..4e2a7f477b666 100644
--- a/x-pack/plugins/apm/public/components/shared/Links/MachineLearningLinks/MLManageJobsLink.tsx
+++ b/x-pack/plugins/apm/public/components/shared/Links/MachineLearningLinks/MLManageJobsLink.tsx
@@ -7,47 +7,17 @@
import { EuiLink } from '@elastic/eui';
import React from 'react';
-import { UI_SETTINGS } from '../../../../../../../../src/plugins/data/common';
-import { useApmPluginContext } from '../../../../context/apm_plugin/use_apm_plugin_context';
-import { useMlHref, ML_PAGES } from '../../../../../../ml/public';
-import { useLegacyUrlParams } from '../../../../context/url_params_context/use_url_params';
-import { TimePickerRefreshInterval } from '../../DatePicker/typings';
+import { useMlManageJobsHref } from '../../../../hooks/use_ml_manage_jobs_href';
interface Props {
children?: React.ReactNode;
external?: boolean;
+ jobId?: string;
}
-export function MLManageJobsLink({ children, external }: Props) {
- const {
- core,
- plugins: { ml },
- } = useApmPluginContext();
-
- const { urlParams } = useLegacyUrlParams();
-
- const timePickerRefreshIntervalDefaults =
- core.uiSettings.get(
- UI_SETTINGS.TIMEPICKER_REFRESH_INTERVAL_DEFAULTS
- );
-
- const {
- // hardcoding a custom default of 1 hour since the default kibana timerange of 15 minutes is shorter than the ML interval
- rangeFrom = 'now-1h',
- rangeTo = 'now',
- refreshInterval = timePickerRefreshIntervalDefaults.value,
- refreshPaused = timePickerRefreshIntervalDefaults.pause,
- } = urlParams;
-
- const mlADLink = useMlHref(ml, core.http.basePath.get(), {
- page: ML_PAGES.ANOMALY_DETECTION_JOBS_MANAGE,
- pageState: {
- groupIds: ['apm'],
- globalState: {
- time: { from: rangeFrom, to: rangeTo },
- refreshInterval: { pause: refreshPaused, value: refreshInterval },
- },
- },
+export function MLManageJobsLink({ children, external, jobId }: Props) {
+ const mlADLink = useMlManageJobsHref({
+ jobId,
});
return (
diff --git a/x-pack/plugins/apm/public/components/shared/apm_header_action_menu/anomaly_detection_setup_link.test.tsx b/x-pack/plugins/apm/public/components/shared/apm_header_action_menu/anomaly_detection_setup_link.test.tsx
index 0520cfa39a743..e47c4853827de 100644
--- a/x-pack/plugins/apm/public/components/shared/apm_header_action_menu/anomaly_detection_setup_link.test.tsx
+++ b/x-pack/plugins/apm/public/components/shared/apm_header_action_menu/anomaly_detection_setup_link.test.tsx
@@ -5,28 +5,55 @@
* 2.0.
*/
+import { fireEvent, render, waitFor } from '@testing-library/react';
+import { createMemoryHistory } from 'history';
import React from 'react';
-import { render, fireEvent, waitFor } from '@testing-library/react';
-import { MissingJobsAlert } from './anomaly_detection_setup_link';
+import { EuiThemeProvider } from '../../../../../../../src/plugins/kibana_react/common';
+import { ApmMlJob } from '../../../../common/anomaly_detection/apm_ml_job';
+import { getAnomalyDetectionSetupState } from '../../../../common/anomaly_detection/get_anomaly_detection_setup_state';
+import { ENVIRONMENT_ALL } from '../../../../common/environment_filter_values';
import * as hooks from '../../../context/anomaly_detection_jobs/use_anomaly_detection_jobs_context';
+import { MockApmPluginContextWrapper } from '../../../context/apm_plugin/mock_apm_plugin_context';
import { FETCH_STATUS } from '../../../hooks/use_fetcher';
+import { AnomalyDetectionSetupLink } from './anomaly_detection_setup_link';
async function renderTooltipAnchor({
jobs,
environment,
}: {
- jobs: Array<{ job_id: string; environment: string }>;
+ jobs: ApmMlJob[];
environment?: string;
}) {
// mock api response
jest.spyOn(hooks, 'useAnomalyDetectionJobsContext').mockReturnValue({
- anomalyDetectionJobsData: { jobs, hasLegacyJobs: false },
+ anomalyDetectionJobsData: {
+ jobs,
+ hasLegacyJobs: jobs.some((job) => job.version <= 2),
+ },
anomalyDetectionJobsStatus: FETCH_STATUS.SUCCESS,
anomalyDetectionJobsRefetch: () => {},
+ anomalyDetectionSetupState: getAnomalyDetectionSetupState({
+ environment: environment ?? ENVIRONMENT_ALL.value,
+ fetchStatus: FETCH_STATUS.SUCCESS,
+ isAuthorized: true,
+ jobs,
+ }),
+ });
+
+ const history = createMemoryHistory({
+ initialEntries: [
+ `/services?environment=${
+ environment || ENVIRONMENT_ALL.value
+ }&rangeFrom=now-15m&rangeTo=now`,
+ ],
});
const { baseElement, container } = render(
-
+
+
+
+
+
);
// hover tooltip anchor if it exists
@@ -65,7 +92,13 @@ describe('MissingJobsAlert', () => {
describe('when no jobs exists for the selected environment', () => {
it('shows a warning', async () => {
const { toolTipAnchor, toolTipText } = await renderTooltipAnchor({
- jobs: [{ environment: 'production', job_id: 'my_job_id' }],
+ jobs: [
+ {
+ environment: 'production',
+ jobId: 'my_job_id',
+ version: 3,
+ } as ApmMlJob,
+ ],
environment: 'staging',
});
@@ -79,7 +112,13 @@ describe('MissingJobsAlert', () => {
describe('when a job exists for the selected environment', () => {
it('does not show a warning', async () => {
const { toolTipAnchor, toolTipText } = await renderTooltipAnchor({
- jobs: [{ environment: 'production', job_id: 'my_job_id' }],
+ jobs: [
+ {
+ environment: 'production',
+ jobId: 'my_job_id',
+ version: 3,
+ } as ApmMlJob,
+ ],
environment: 'production',
});
@@ -91,7 +130,13 @@ describe('MissingJobsAlert', () => {
describe('when at least one job exists and no environment is selected', () => {
it('does not show a warning', async () => {
const { toolTipAnchor, toolTipText } = await renderTooltipAnchor({
- jobs: [{ environment: 'production', job_id: 'my_job_id' }],
+ jobs: [
+ {
+ environment: 'production',
+ jobId: 'my_job_id',
+ version: 3,
+ } as ApmMlJob,
+ ],
});
expect(toolTipAnchor).not.toBeInTheDocument();
@@ -102,7 +147,54 @@ describe('MissingJobsAlert', () => {
describe('when at least one job exists and all environments are selected', () => {
it('does not show a warning', async () => {
const { toolTipAnchor, toolTipText } = await renderTooltipAnchor({
- jobs: [{ environment: 'ENVIRONMENT_ALL', job_id: 'my_job_id' }],
+ jobs: [
+ {
+ environment: 'ENVIRONMENT_ALL',
+ jobId: 'my_job_id',
+ version: 3,
+ } as ApmMlJob,
+ ],
+ });
+
+ expect(toolTipAnchor).not.toBeInTheDocument();
+ expect(toolTipText).toBe(undefined);
+ });
+ });
+
+ describe('when at least one legacy job exists', () => {
+ it('displays a nudge to upgrade', async () => {
+ const { toolTipAnchor, toolTipText } = await renderTooltipAnchor({
+ jobs: [
+ {
+ environment: 'ENVIRONMENT_ALL',
+ jobId: 'my_job_id',
+ version: 2,
+ } as ApmMlJob,
+ ],
+ });
+
+ expect(toolTipAnchor).toBeInTheDocument();
+ expect(toolTipText).toBe(
+ 'Updates available for existing anomaly detection jobs.'
+ );
+ });
+ });
+
+ describe('when both legacy and modern jobs exist', () => {
+ it('does not show a tooltip', async () => {
+ const { toolTipAnchor, toolTipText } = await renderTooltipAnchor({
+ jobs: [
+ {
+ environment: 'ENVIRONMENT_ALL',
+ jobId: 'my_job_id',
+ version: 2,
+ } as ApmMlJob,
+ {
+ environment: 'ENVIRONMENT_ALL',
+ jobId: 'my_job_id_2',
+ version: 3,
+ } as ApmMlJob,
+ ],
});
expect(toolTipAnchor).not.toBeInTheDocument();
diff --git a/x-pack/plugins/apm/public/components/shared/apm_header_action_menu/anomaly_detection_setup_link.tsx b/x-pack/plugins/apm/public/components/shared/apm_header_action_menu/anomaly_detection_setup_link.tsx
index 4891ca896076a..e1bda5475acc4 100644
--- a/x-pack/plugins/apm/public/components/shared/apm_header_action_menu/anomaly_detection_setup_link.tsx
+++ b/x-pack/plugins/apm/public/components/shared/apm_header_action_menu/anomaly_detection_setup_link.tsx
@@ -5,32 +5,22 @@
* 2.0.
*/
-import {
- EuiHeaderLink,
- EuiIcon,
- EuiLoadingSpinner,
- EuiToolTip,
-} from '@elastic/eui';
+import { EuiLoadingSpinner } from '@elastic/eui';
+import { IconType } from '@elastic/eui';
+import { EuiHeaderLink, EuiIcon, EuiToolTip } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import React from 'react';
+import { AnomalyDetectionSetupState } from '../../../../common/anomaly_detection/get_anomaly_detection_setup_state';
import {
ENVIRONMENT_ALL,
getEnvironmentLabel,
} from '../../../../common/environment_filter_values';
import { useAnomalyDetectionJobsContext } from '../../../context/anomaly_detection_jobs/use_anomaly_detection_jobs_context';
import { useApmPluginContext } from '../../../context/apm_plugin/use_apm_plugin_context';
-import { useLicenseContext } from '../../../context/license/use_license_context';
import { useApmParams } from '../../../hooks/use_apm_params';
-import { FETCH_STATUS } from '../../../hooks/use_fetcher';
import { useTheme } from '../../../hooks/use_theme';
-import { APIReturnType } from '../../../services/rest/createCallApmApi';
import { getLegacyApmHref } from '../Links/apm/APMLink';
-export type AnomalyDetectionApiResponse =
- APIReturnType<'GET /internal/apm/settings/anomaly-detection/jobs'>;
-
-const DEFAULT_DATA = { jobs: [], hasLegacyJobs: false };
-
export function AnomalyDetectionSetupLink() {
const { query } = useApmParams('/*');
@@ -38,71 +28,86 @@ export function AnomalyDetectionSetupLink() {
('environment' in query && query.environment) || ENVIRONMENT_ALL.value;
const { core } = useApmPluginContext();
- const canGetJobs = !!core.application.capabilities.ml?.canGetJobs;
- const license = useLicenseContext();
- const hasValidLicense = license?.isActive && license?.hasAtLeast('platinum');
+
const { basePath } = core.http;
const theme = useTheme();
- return (
+ const { anomalyDetectionSetupState } = useAnomalyDetectionJobsContext();
+
+ let tooltipText: string = '';
+ let color: 'warning' | 'text' | 'success' | 'danger' = 'text';
+ let icon: IconType | undefined;
+
+ if (anomalyDetectionSetupState === AnomalyDetectionSetupState.Failure) {
+ color = 'warning';
+ tooltipText = i18n.translate(
+ 'xpack.apm.anomalyDetectionSetup.jobFetchFailureText',
+ {
+ defaultMessage: 'Could not determine state of anomaly detection setup.',
+ }
+ );
+ icon = 'alert';
+ } else if (
+ anomalyDetectionSetupState === AnomalyDetectionSetupState.NoJobs ||
+ anomalyDetectionSetupState ===
+ AnomalyDetectionSetupState.NoJobsForEnvironment
+ ) {
+ color = 'warning';
+ tooltipText = getNoJobsMessage(anomalyDetectionSetupState, environment);
+ icon = 'alert';
+ } else if (
+ anomalyDetectionSetupState === AnomalyDetectionSetupState.UpgradeableJobs
+ ) {
+ color = 'success';
+ tooltipText = i18n.translate(
+ 'xpack.apm.anomalyDetectionSetup.upgradeableJobsText',
+ {
+ defaultMessage:
+ 'Updates available for existing anomaly detection jobs.',
+ }
+ );
+ icon = 'wrench';
+ }
+
+ let pre: React.ReactElement | null = null;
+
+ if (anomalyDetectionSetupState === AnomalyDetectionSetupState.Loading) {
+ pre = ;
+ } else if (icon) {
+ pre = ;
+ }
+
+ const element = (
- {canGetJobs && hasValidLicense ? (
-
- ) : (
-
- )}
+ {pre}
{ANOMALY_DETECTION_LINK_LABEL}
);
-}
-
-export function MissingJobsAlert({ environment }: { environment?: string }) {
- const {
- anomalyDetectionJobsData = DEFAULT_DATA,
- anomalyDetectionJobsStatus,
- } = useAnomalyDetectionJobsContext();
- const defaultIcon = ;
-
- if (anomalyDetectionJobsStatus === FETCH_STATUS.LOADING) {
- return ;
- }
-
- if (anomalyDetectionJobsStatus !== FETCH_STATUS.SUCCESS) {
- return defaultIcon;
- }
-
- const isEnvironmentSelected =
- environment && environment !== ENVIRONMENT_ALL.value;
-
- // there are jobs for at least one environment
- if (!isEnvironmentSelected && anomalyDetectionJobsData.jobs.length > 0) {
- return defaultIcon;
- }
-
- // there are jobs for the selected environment
- if (
- isEnvironmentSelected &&
- anomalyDetectionJobsData.jobs.some((job) => environment === job.environment)
- ) {
- return defaultIcon;
- }
-
- return (
-
-
+ const wrappedElement = tooltipText ? (
+
+ {element}
+ ) : (
+ element
);
+
+ return wrappedElement;
}
-function getTooltipText(environment?: string) {
- if (!environment || environment === ENVIRONMENT_ALL.value) {
+function getNoJobsMessage(
+ state:
+ | AnomalyDetectionSetupState.NoJobs
+ | AnomalyDetectionSetupState.NoJobsForEnvironment,
+ environment: string
+) {
+ if (state === AnomalyDetectionSetupState.NoJobs) {
return i18n.translate('xpack.apm.anomalyDetectionSetup.notEnabledText', {
defaultMessage: `Anomaly detection is not yet enabled. Click to continue setup.`,
});
diff --git a/x-pack/plugins/apm/public/components/shared/managed_table/index.tsx b/x-pack/plugins/apm/public/components/shared/managed_table/index.tsx
index 30ca3e79f6d7b..03ae13c06c613 100644
--- a/x-pack/plugins/apm/public/components/shared/managed_table/index.tsx
+++ b/x-pack/plugins/apm/public/components/shared/managed_table/index.tsx
@@ -44,6 +44,7 @@ interface Props {
pagination?: boolean;
isLoading?: boolean;
error?: boolean;
+ tableLayout?: 'auto' | 'fixed';
}
function defaultSortFn(
@@ -70,6 +71,7 @@ function UnoptimizedManagedTable(props: Props) {
pagination = true,
isLoading = false,
error = false,
+ tableLayout,
} = props;
const {
@@ -141,6 +143,7 @@ function UnoptimizedManagedTable(props: Props) {
// @ts-expect-error TS thinks pagination should be non-nullable, but it's not
void;
+ onUpgradeClick?: () => any;
+ onCreateJobClick?: () => void;
+ isOnSettingsPage: boolean;
+ append?: React.ReactElement;
+}) {
+ const [loading, setLoading] = useState(false);
+
+ const mlManageJobsHref = useMlManageJobsHref();
+
+ let properties:
+ | {
+ primaryAction: React.ReactNode | undefined;
+ color: 'primary' | 'success' | 'danger' | 'warning';
+ title: string;
+ icon: string;
+ text: string;
+ }
+ | undefined;
+
+ const getLearnMoreLink = (color: 'primary' | 'success') => (
+
+
+ {i18n.translate('xpack.apm.mlCallout.learnMoreButton', {
+ defaultMessage: `Learn more`,
+ })}
+
+
+ );
+
+ switch (anomalyDetectionSetupState) {
+ case AnomalyDetectionSetupState.NoJobs:
+ properties = {
+ title: i18n.translate('xpack.apm.mlCallout.noJobsCalloutTitle', {
+ defaultMessage:
+ 'Enable anomaly detection to add health status indicators to your services',
+ }),
+ text: i18n.translate('xpack.apm.mlCallout.noJobsCalloutText', {
+ defaultMessage: `Pinpoint anomalous transactions and see the health of upstream and downstream services with APM's anomaly detection integration. Get started in just a few minutes.`,
+ }),
+ icon: 'iInCircle',
+ color: 'primary',
+ primaryAction: isOnSettingsPage ? (
+ {
+ onCreateJobClick?.();
+ }}
+ >
+ {i18n.translate('xpack.apm.mlCallout.noJobsCalloutButtonText', {
+ defaultMessage: 'Create ML Job',
+ })}
+
+ ) : (
+ getLearnMoreLink('primary')
+ ),
+ };
+ break;
+
+ case AnomalyDetectionSetupState.UpgradeableJobs:
+ properties = {
+ title: i18n.translate(
+ 'xpack.apm.mlCallout.updateAvailableCalloutTitle',
+ { defaultMessage: 'Updates available' }
+ ),
+ text: i18n.translate('xpack.apm.mlCallout.updateAvailableCalloutText', {
+ defaultMessage:
+ 'We have updated the anomaly detection jobs that provide insights into degraded performance and added detectors for throughput and failed transaction rate. If you choose to upgrade, we will create the new jobs and close the existing legacy jobs. The data shown in the APM app will automatically switch to the new.',
+ }),
+ color: 'success',
+ icon: 'wrench',
+ primaryAction: isOnSettingsPage ? (
+ {
+ setLoading(true);
+ Promise.resolve(onUpgradeClick?.()).finally(() => {
+ setLoading(false);
+ });
+ }}
+ >
+ {i18n.translate(
+ 'xpack.apm.mlCallout.updateAvailableCalloutButtonText',
+ {
+ defaultMessage: 'Update jobs',
+ }
+ )}
+
+ ) : (
+ getLearnMoreLink('success')
+ ),
+ };
+ break;
+
+ case AnomalyDetectionSetupState.LegacyJobs:
+ properties = {
+ title: i18n.translate('xpack.apm.mlCallout.legacyJobsCalloutTitle', {
+ defaultMessage: 'Legacy ML jobs are no longer used in APM app',
+ }),
+ text: i18n.translate('xpack.apm.mlCallout.legacyJobsCalloutText', {
+ defaultMessage:
+ 'We have discovered legacy Machine Learning jobs from our previous integration which are no longer being used in the APM app',
+ }),
+ icon: 'iInCircle',
+ color: 'primary',
+ primaryAction: (
+
+ {i18n.translate(
+ 'xpack.apm.settings.anomaly_detection.legacy_jobs.button',
+ { defaultMessage: 'Review jobs' }
+ )}
+
+ ),
+ };
+ break;
+ }
+
+ if (!properties) {
+ return null;
+ }
+
+ const dismissable = !isOnSettingsPage;
+
+ const hasAnyActions = properties.primaryAction || dismissable;
+
+ const actions = hasAnyActions ? (
+
+ {properties.primaryAction && (
+ {properties.primaryAction}
+ )}
+ {dismissable && (
+
+
+ {i18n.translate('xpack.apm.mlCallout.dismissButton', {
+ defaultMessage: `Dismiss`,
+ })}
+
+
+ )}
+
+ ) : null;
+
+ return (
+
+ {properties.text}
+ {actions}
+
+ );
+}
diff --git a/x-pack/plugins/apm/public/context/anomaly_detection_jobs/anomaly_detection_jobs_context.tsx b/x-pack/plugins/apm/public/context/anomaly_detection_jobs/anomaly_detection_jobs_context.tsx
index bf9f2941fa2fb..3b9cea7b88998 100644
--- a/x-pack/plugins/apm/public/context/anomaly_detection_jobs/anomaly_detection_jobs_context.tsx
+++ b/x-pack/plugins/apm/public/context/anomaly_detection_jobs/anomaly_detection_jobs_context.tsx
@@ -5,14 +5,23 @@
* 2.0.
*/
-import React, { createContext, ReactChild, useState } from 'react';
+import React, { createContext, ReactChild } from 'react';
+import {
+ AnomalyDetectionSetupState,
+ getAnomalyDetectionSetupState,
+} from '../../../common/anomaly_detection/get_anomaly_detection_setup_state';
+import { ENVIRONMENT_ALL } from '../../../common/environment_filter_values';
+import { useApmParams } from '../../hooks/use_apm_params';
import { FETCH_STATUS, useFetcher } from '../../hooks/use_fetcher';
import { APIReturnType } from '../../services/rest/createCallApmApi';
+import { useApmPluginContext } from '../apm_plugin/use_apm_plugin_context';
+import { useLicenseContext } from '../license/use_license_context';
export interface AnomalyDetectionJobsContextValue {
anomalyDetectionJobsData?: APIReturnType<'GET /internal/apm/settings/anomaly-detection/jobs'>;
anomalyDetectionJobsStatus: FETCH_STATUS;
anomalyDetectionJobsRefetch: () => void;
+ anomalyDetectionSetupState: AnomalyDetectionSetupState;
}
export const AnomalyDetectionJobsContext = createContext(
@@ -24,24 +33,45 @@ export function AnomalyDetectionJobsContextProvider({
}: {
children: ReactChild;
}) {
- const [fetchId, setFetchId] = useState(0);
- const refetch = () => setFetchId((id) => id + 1);
+ const { core } = useApmPluginContext();
+ const canGetJobs = !!core.application.capabilities.ml?.canGetJobs;
+ const license = useLicenseContext();
+ const hasValidLicense = license?.isActive && license?.hasAtLeast('platinum');
- const { data, status } = useFetcher(
- (callApmApi) =>
- callApmApi({
+ const isAuthorized = !!(canGetJobs && hasValidLicense);
+
+ const { data, status, refetch } = useFetcher(
+ (callApmApi) => {
+ if (!isAuthorized) {
+ return;
+ }
+ return callApmApi({
endpoint: `GET /internal/apm/settings/anomaly-detection/jobs`,
- }),
- [fetchId], // eslint-disable-line react-hooks/exhaustive-deps
+ });
+ },
+ [isAuthorized],
{ showToastOnError: false }
);
+ const { query } = useApmParams('/*');
+
+ const environment =
+ ('environment' in query && query.environment) || ENVIRONMENT_ALL.value;
+
+ const anomalyDetectionSetupState = getAnomalyDetectionSetupState({
+ environment,
+ fetchStatus: status,
+ jobs: data?.jobs ?? [],
+ isAuthorized,
+ });
+
return (
{children}
diff --git a/x-pack/plugins/apm/public/hooks/use_ml_manage_jobs_href.ts b/x-pack/plugins/apm/public/hooks/use_ml_manage_jobs_href.ts
new file mode 100644
index 0000000000000..cc187c6cf619a
--- /dev/null
+++ b/x-pack/plugins/apm/public/hooks/use_ml_manage_jobs_href.ts
@@ -0,0 +1,48 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { UI_SETTINGS } from '../../../../../src/plugins/data/public';
+import { ML_PAGES, useMlHref } from '../../../ml/public';
+import { TimePickerRefreshInterval } from '../components/shared/DatePicker/typings';
+import { useApmPluginContext } from '../context/apm_plugin/use_apm_plugin_context';
+import { useLegacyUrlParams } from '../context/url_params_context/use_url_params';
+
+export function useMlManageJobsHref({ jobId }: { jobId?: string } = {}) {
+ const {
+ core,
+ plugins: { ml },
+ } = useApmPluginContext();
+
+ const { urlParams } = useLegacyUrlParams();
+
+ const timePickerRefreshIntervalDefaults =
+ core.uiSettings.get(
+ UI_SETTINGS.TIMEPICKER_REFRESH_INTERVAL_DEFAULTS
+ );
+
+ const {
+ // hardcoding a custom default of 1 hour since the default kibana timerange of 15 minutes is shorter than the ML interval
+ rangeFrom = 'now-1h',
+ rangeTo = 'now',
+ refreshInterval = timePickerRefreshIntervalDefaults.value,
+ refreshPaused = timePickerRefreshIntervalDefaults.pause,
+ } = urlParams;
+
+ const mlADLink = useMlHref(ml, core.http.basePath.get(), {
+ page: ML_PAGES.ANOMALY_DETECTION_JOBS_MANAGE,
+ pageState: {
+ groupIds: ['apm'],
+ jobId,
+ globalState: {
+ time: { from: rangeFrom, to: rangeTo },
+ refreshInterval: { pause: refreshPaused, value: refreshInterval },
+ },
+ },
+ });
+
+ return mlADLink;
+}
diff --git a/x-pack/plugins/apm/server/deprecations/index.ts b/x-pack/plugins/apm/server/deprecations/index.ts
index 06d04eb037d73..6c6567440f267 100644
--- a/x-pack/plugins/apm/server/deprecations/index.ts
+++ b/x-pack/plugins/apm/server/deprecations/index.ts
@@ -51,7 +51,7 @@ export function getDeprecations({
}),
message: i18n.translate('xpack.apm.deprecations.message', {
defaultMessage:
- 'Running the APM Server binary directly is considered a legacy option and is deprecated since 7.16. Switch to APM Server managed by an Elastic Agent instead. Read our documentation to learn more.',
+ 'Running the APM Server binary directly is considered a legacy option and will be deprecated and removed in the future.',
}),
documentationUrl: `https://www.elastic.co/guide/en/apm/server/${docBranch}/apm-integration.html`,
level: 'warning',
@@ -68,7 +68,7 @@ export function getDeprecations({
}),
i18n.translate('xpack.apm.deprecations.steps.switch', {
defaultMessage:
- 'Click "Switch to data streams". You will be guided through the process',
+ 'Click "Switch to Elastic Agent". You will be guided through the process',
}),
],
},
diff --git a/x-pack/plugins/apm/server/lib/anomaly_detection/apm_ml_jobs_query.ts b/x-pack/plugins/apm/server/lib/anomaly_detection/apm_ml_jobs_query.ts
index 4eec3b39f3739..2720dbdecfe1c 100644
--- a/x-pack/plugins/apm/server/lib/anomaly_detection/apm_ml_jobs_query.ts
+++ b/x-pack/plugins/apm/server/lib/anomaly_detection/apm_ml_jobs_query.ts
@@ -4,12 +4,10 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
-import {
- MlJob,
- QueryDslQueryContainer,
-} from '@elastic/elasticsearch/lib/api/types';
+import { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types';
+import { ApmMlJob } from '../../../common/anomaly_detection/apm_ml_job';
-export function apmMlJobsQuery(jobs: MlJob[]) {
+export function apmMlJobsQuery(jobs: ApmMlJob[]) {
if (!jobs.length) {
throw new Error('At least one ML job should be given');
}
@@ -17,7 +15,7 @@ export function apmMlJobsQuery(jobs: MlJob[]) {
return [
{
terms: {
- job_id: jobs.map((job) => job.job_id),
+ job_id: jobs.map((job) => job.jobId),
},
},
] as QueryDslQueryContainer[];
diff --git a/x-pack/plugins/apm/server/lib/anomaly_detection/create_anomaly_detection_jobs.ts b/x-pack/plugins/apm/server/lib/anomaly_detection/create_anomaly_detection_jobs.ts
index 7277a12c2bf14..d855adee4a9ba 100644
--- a/x-pack/plugins/apm/server/lib/anomaly_detection/create_anomaly_detection_jobs.ts
+++ b/x-pack/plugins/apm/server/lib/anomaly_detection/create_anomaly_detection_jobs.ts
@@ -15,6 +15,7 @@ import {
METRICSET_NAME,
PROCESSOR_EVENT,
} from '../../../common/elasticsearch_fieldnames';
+import { Environment } from '../../../common/environment_rt';
import { ProcessorEvent } from '../../../common/processor_event';
import { environmentQuery } from '../../../common/utils/environment_query';
import { withApmSpan } from '../../utils/with_apm_span';
@@ -24,7 +25,7 @@ import { getAnomalyDetectionJobs } from './get_anomaly_detection_jobs';
export async function createAnomalyDetectionJobs(
setup: Setup,
- environments: string[],
+ environments: Environment[],
logger: Logger
) {
const { ml, indices } = setup;
@@ -33,13 +34,6 @@ export async function createAnomalyDetectionJobs(
throw Boom.notImplemented(ML_ERRORS.ML_NOT_AVAILABLE);
}
- const mlCapabilities = await withApmSpan('get_ml_capabilities', () =>
- ml.mlSystem.mlCapabilities()
- );
- if (!mlCapabilities.mlFeatureEnabledInSpace) {
- throw Boom.forbidden(ML_ERRORS.ML_NOT_AVAILABLE_IN_SPACE);
- }
-
const uniqueMlJobEnvs = await getUniqueMlJobEnvs(setup, environments, logger);
if (uniqueMlJobEnvs.length === 0) {
return [];
@@ -56,6 +50,7 @@ export async function createAnomalyDetectionJobs(
createAnomalyDetectionJob({ ml, environment, dataViewName })
)
);
+
const jobResponses = responses.flatMap((response) => response.jobs);
const failedJobs = jobResponses.filter(({ success }) => !success);
@@ -116,12 +111,15 @@ async function createAnomalyDetectionJob({
async function getUniqueMlJobEnvs(
setup: Setup,
- environments: string[],
+ environments: Environment[],
logger: Logger
) {
// skip creation of duplicate ML jobs
- const jobs = await getAnomalyDetectionJobs(setup, logger);
- const existingMlJobEnvs = jobs.map(({ environment }) => environment);
+ const jobs = await getAnomalyDetectionJobs(setup);
+ const existingMlJobEnvs = jobs
+ .filter((job) => job.version === 3)
+ .map(({ environment }) => environment);
+
const requestedExistingMlJobEnvs = environments.filter((env) =>
existingMlJobEnvs.includes(env)
);
diff --git a/x-pack/plugins/apm/server/lib/anomaly_detection/get_anomaly_detection_jobs.ts b/x-pack/plugins/apm/server/lib/anomaly_detection/get_anomaly_detection_jobs.ts
index 75b2e8289c7a8..9047ae9ed90d0 100644
--- a/x-pack/plugins/apm/server/lib/anomaly_detection/get_anomaly_detection_jobs.ts
+++ b/x-pack/plugins/apm/server/lib/anomaly_detection/get_anomaly_detection_jobs.ts
@@ -4,41 +4,17 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
-
-import { Logger } from 'kibana/server';
import Boom from '@hapi/boom';
import { ML_ERRORS } from '../../../common/anomaly_detection';
import { Setup } from '../helpers/setup_request';
import { getMlJobsWithAPMGroup } from './get_ml_jobs_with_apm_group';
-import { withApmSpan } from '../../utils/with_apm_span';
-export function getAnomalyDetectionJobs(setup: Setup, logger: Logger) {
+export function getAnomalyDetectionJobs(setup: Setup) {
const { ml } = setup;
if (!ml) {
throw Boom.notImplemented(ML_ERRORS.ML_NOT_AVAILABLE);
}
- return withApmSpan('get_anomaly_detection_jobs', async () => {
- const mlCapabilities = await withApmSpan('get_ml_capabilities', () =>
- ml.mlSystem.mlCapabilities()
- );
-
- if (!mlCapabilities.mlFeatureEnabledInSpace) {
- throw Boom.forbidden(ML_ERRORS.ML_NOT_AVAILABLE_IN_SPACE);
- }
-
- const response = await getMlJobsWithAPMGroup(ml.anomalyDetectors);
- return response.jobs
- .filter(
- (job) => (job.custom_settings?.job_tags?.apm_ml_version ?? 0) >= 2
- )
- .map((job) => {
- const environment = job.custom_settings?.job_tags?.environment ?? '';
- return {
- job_id: job.job_id,
- environment,
- };
- });
- });
+ return getMlJobsWithAPMGroup(ml.anomalyDetectors);
}
diff --git a/x-pack/plugins/apm/server/lib/anomaly_detection/get_anomaly_timeseries.ts b/x-pack/plugins/apm/server/lib/anomaly_detection/get_anomaly_timeseries.ts
index 77ffef9801a86..37279d3320585 100644
--- a/x-pack/plugins/apm/server/lib/anomaly_detection/get_anomaly_timeseries.ts
+++ b/x-pack/plugins/apm/server/lib/anomaly_detection/get_anomaly_timeseries.ts
@@ -46,9 +46,7 @@ export async function getAnomalyTimeseries({
end,
});
- const { jobs: mlJobs } = await getMlJobsWithAPMGroup(
- mlSetup.anomalyDetectors
- );
+ const mlJobs = await getMlJobsWithAPMGroup(mlSetup.anomalyDetectors);
if (!mlJobs.length) {
return [];
@@ -148,7 +146,7 @@ export async function getAnomalyTimeseries({
}
);
- const jobsById = keyBy(mlJobs, (job) => job.job_id);
+ const jobsById = keyBy(mlJobs, (job) => job.jobId);
function divide(value: number | null, divider: number) {
if (value === null) {
@@ -176,9 +174,9 @@ export async function getAnomalyTimeseries({
jobId,
type,
serviceName: bucket.key.serviceName as string,
- environment: job.custom_settings!.job_tags!.environment as string,
+ environment: job.environment,
transactionType: bucket.key.transactionType as string,
- version: Number(job.custom_settings!.job_tags!.apm_ml_version),
+ version: job.version,
anomalies: bucket.timeseries.buckets.map((dateBucket) => ({
x: dateBucket.key as number,
y:
diff --git a/x-pack/plugins/apm/server/lib/anomaly_detection/get_ml_jobs_with_apm_group.ts b/x-pack/plugins/apm/server/lib/anomaly_detection/get_ml_jobs_with_apm_group.ts
index bcea8f1ed6b26..1f989ba17fe7c 100644
--- a/x-pack/plugins/apm/server/lib/anomaly_detection/get_ml_jobs_with_apm_group.ts
+++ b/x-pack/plugins/apm/server/lib/anomaly_detection/get_ml_jobs_with_apm_group.ts
@@ -6,23 +6,63 @@
*/
import { MlPluginSetup } from '../../../../ml/server';
+import { ApmMlJob } from '../../../common/anomaly_detection/apm_ml_job';
+import { Environment } from '../../../common/environment_rt';
import { withApmSpan } from '../../utils/with_apm_span';
import { APM_ML_JOB_GROUP } from './constants';
// returns ml jobs containing "apm" group
// workaround: the ML api returns 404 when no jobs are found. This is handled so instead of throwing an empty response is returned
+
+function catch404(e: any) {
+ if (e.statusCode === 404) {
+ return [];
+ }
+
+ throw e;
+}
+
export function getMlJobsWithAPMGroup(
anomalyDetectors: ReturnType
-) {
+): Promise {
return withApmSpan('get_ml_jobs_with_apm_group', async () => {
try {
- return await anomalyDetectors.jobs(APM_ML_JOB_GROUP);
- } catch (e) {
- if (e.statusCode === 404) {
- return { count: 0, jobs: [] };
- }
+ const [jobs, allJobStats, allDatafeedStats] = await Promise.all([
+ anomalyDetectors
+ .jobs(APM_ML_JOB_GROUP)
+ .then((response) => response.jobs),
+ anomalyDetectors
+ .jobStats(APM_ML_JOB_GROUP)
+ .then((response) => response.jobs)
+ .catch(catch404),
+ anomalyDetectors
+ .datafeedStats(`datafeed-${APM_ML_JOB_GROUP}*`)
+ .then((response) => response.datafeeds)
+ .catch(catch404),
+ ]);
+
+ return jobs.map((job): ApmMlJob => {
+ const jobStats = allJobStats.find(
+ (stats) => stats.job_id === job.job_id
+ );
- throw e;
+ const datafeedStats = allDatafeedStats.find(
+ (stats) => stats.datafeed_id === job.datafeed_config?.datafeed_id
+ );
+
+ return {
+ environment: String(
+ job.custom_settings?.job_tags?.environment
+ ) as Environment,
+ jobId: job.job_id,
+ jobState: jobStats?.state as ApmMlJob['jobState'],
+ version: Number(job.custom_settings?.job_tags?.apm_ml_version ?? 1),
+ datafeedId: datafeedStats?.datafeed_id,
+ datafeedState: datafeedStats?.state as ApmMlJob['datafeedState'],
+ };
+ });
+ } catch (e) {
+ return catch404(e) as ApmMlJob[];
}
});
}
diff --git a/x-pack/plugins/apm/server/lib/anomaly_detection/has_legacy_jobs.ts b/x-pack/plugins/apm/server/lib/anomaly_detection/has_legacy_jobs.ts
deleted file mode 100644
index c189d24efc23a..0000000000000
--- a/x-pack/plugins/apm/server/lib/anomaly_detection/has_legacy_jobs.ts
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import Boom from '@hapi/boom';
-import { ML_ERRORS } from '../../../common/anomaly_detection';
-import { withApmSpan } from '../../utils/with_apm_span';
-import { Setup } from '../helpers/setup_request';
-import { getMlJobsWithAPMGroup } from './get_ml_jobs_with_apm_group';
-
-// Determine whether there are any legacy ml jobs.
-// A legacy ML job has a job id that ends with "high_mean_response_time" and created_by=ml-module-apm-transaction
-export function hasLegacyJobs(setup: Setup) {
- const { ml } = setup;
-
- if (!ml) {
- throw Boom.notImplemented(ML_ERRORS.ML_NOT_AVAILABLE);
- }
-
- return withApmSpan('has_legacy_jobs', async () => {
- const mlCapabilities = await withApmSpan('get_ml_capabilities', () =>
- ml.mlSystem.mlCapabilities()
- );
- if (!mlCapabilities.mlFeatureEnabledInSpace) {
- throw Boom.forbidden(ML_ERRORS.ML_NOT_AVAILABLE_IN_SPACE);
- }
-
- const response = await getMlJobsWithAPMGroup(ml.anomalyDetectors);
- return response.jobs.some(
- (job) =>
- job.job_id.endsWith('high_mean_response_time') &&
- job.custom_settings?.created_by === 'ml-module-apm-transaction'
- );
- });
-}
diff --git a/x-pack/plugins/apm/server/routes/alerts/register_transaction_duration_anomaly_alert_type.test.ts b/x-pack/plugins/apm/server/routes/alerts/register_transaction_duration_anomaly_alert_type.test.ts
index fa4125c54126d..889fe3c16596e 100644
--- a/x-pack/plugins/apm/server/routes/alerts/register_transaction_duration_anomaly_alert_type.test.ts
+++ b/x-pack/plugins/apm/server/routes/alerts/register_transaction_duration_anomaly_alert_type.test.ts
@@ -6,9 +6,10 @@
*/
import { registerTransactionDurationAnomalyAlertType } from './register_transaction_duration_anomaly_alert_type';
import { ANOMALY_SEVERITY } from '../../../common/ml_constants';
-import { Job, MlPluginSetup } from '../../../../ml/server';
+import { MlPluginSetup } from '../../../../ml/server';
import * as GetServiceAnomalies from '../service_map/get_service_anomalies';
import { createRuleTypeMocks } from './test_utils';
+import { ApmMlJob } from '../../../common/anomaly_detection/apm_ml_job';
describe('Transaction duration anomaly alert', () => {
afterEach(() => {
@@ -65,14 +66,14 @@ describe('Transaction duration anomaly alert', () => {
jest.spyOn(GetServiceAnomalies, 'getMLJobs').mockReturnValue(
Promise.resolve([
{
- job_id: '1',
- custom_settings: { job_tags: { environment: 'development' } },
+ jobId: '1',
+ environment: 'development',
},
{
- job_id: '2',
- custom_settings: { job_tags: { environment: 'production' } },
+ jobId: '2',
+ environment: 'production',
},
- ] as unknown as Job[])
+ ] as unknown as ApmMlJob[])
);
const { services, dependencies, executor } = createRuleTypeMocks();
@@ -118,14 +119,14 @@ describe('Transaction duration anomaly alert', () => {
jest.spyOn(GetServiceAnomalies, 'getMLJobs').mockReturnValue(
Promise.resolve([
{
- job_id: '1',
- custom_settings: { job_tags: { environment: 'development' } },
+ jobId: '1',
+ environment: 'development',
},
{
- job_id: '2',
- custom_settings: { job_tags: { environment: 'production' } },
+ jobId: '2',
+ environment: 'production',
},
- ] as unknown as Job[])
+ ] as unknown as ApmMlJob[])
);
const { services, dependencies, executor, scheduleActions } =
diff --git a/x-pack/plugins/apm/server/routes/alerts/register_transaction_duration_anomaly_alert_type.ts b/x-pack/plugins/apm/server/routes/alerts/register_transaction_duration_anomaly_alert_type.ts
index dead149cd7761..5216d485bc31e 100644
--- a/x-pack/plugins/apm/server/routes/alerts/register_transaction_duration_anomaly_alert_type.ts
+++ b/x-pack/plugins/apm/server/routes/alerts/register_transaction_duration_anomaly_alert_type.ts
@@ -126,7 +126,7 @@ export function registerTransactionDurationAnomalyAlertType({
return {};
}
- const jobIds = mlJobs.map((job) => job.job_id);
+ const jobIds = mlJobs.map((job) => job.jobId);
const anomalySearchParams = {
body: {
size: 0,
@@ -190,7 +190,7 @@ export function registerTransactionDurationAnomalyAlertType({
.map((bucket) => {
const latest = bucket.latest_score.top[0].metrics;
- const job = mlJobs.find((j) => j.job_id === latest.job_id);
+ const job = mlJobs.find((j) => j.jobId === latest.job_id);
if (!job) {
logger.warn(
@@ -202,7 +202,7 @@ export function registerTransactionDurationAnomalyAlertType({
return {
serviceName: latest.partition_field_value as string,
transactionType: latest.by_field_value as string,
- environment: job.custom_settings!.job_tags!.environment,
+ environment: job.environment,
score: latest.record_score as number,
};
})
diff --git a/x-pack/plugins/apm/server/routes/service_map/get_service_anomalies.ts b/x-pack/plugins/apm/server/routes/service_map/get_service_anomalies.ts
index 0a0a92760decd..792bc0463aa15 100644
--- a/x-pack/plugins/apm/server/routes/service_map/get_service_anomalies.ts
+++ b/x-pack/plugins/apm/server/routes/service_map/get_service_anomalies.ts
@@ -162,17 +162,13 @@ export async function getMLJobs(
anomalyDetectors: ReturnType,
environment: string
) {
- const response = await getMlJobsWithAPMGroup(anomalyDetectors);
+ const jobs = await getMlJobsWithAPMGroup(anomalyDetectors);
// to filter out legacy jobs we are filtering by the existence of `apm_ml_version` in `custom_settings`
// and checking that it is compatable.
- const mlJobs = response.jobs.filter(
- (job) => (job.custom_settings?.job_tags?.apm_ml_version ?? 0) >= 2
- );
+ const mlJobs = jobs.filter((job) => job.version >= 2);
if (environment !== ENVIRONMENT_ALL.value) {
- const matchingMLJob = mlJobs.find(
- (job) => job.custom_settings?.job_tags?.environment === environment
- );
+ const matchingMLJob = mlJobs.find((job) => job.environment === environment);
if (!matchingMLJob) {
return [];
}
@@ -186,5 +182,5 @@ export async function getMLJobIds(
environment: string
) {
const mlJobs = await getMLJobs(anomalyDetectors, environment);
- return mlJobs.map((job) => job.job_id);
+ return mlJobs.map((job) => job.jobId);
}
diff --git a/x-pack/plugins/apm/server/routes/settings/anomaly_detection/route.ts b/x-pack/plugins/apm/server/routes/settings/anomaly_detection/route.ts
index a924a9214977d..35089acf38688 100644
--- a/x-pack/plugins/apm/server/routes/settings/anomaly_detection/route.ts
+++ b/x-pack/plugins/apm/server/routes/settings/anomaly_detection/route.ts
@@ -11,15 +11,15 @@ import { maxSuggestions } from '../../../../../observability/common';
import { isActivePlatinumLicense } from '../../../../common/license_check';
import { ML_ERRORS } from '../../../../common/anomaly_detection';
import { createApmServerRoute } from '../../apm_routes/create_apm_server_route';
-import { getAnomalyDetectionJobs } from '../../../lib/anomaly_detection/get_anomaly_detection_jobs';
import { createAnomalyDetectionJobs } from '../../../lib/anomaly_detection/create_anomaly_detection_jobs';
import { setupRequest } from '../../../lib/helpers/setup_request';
import { getAllEnvironments } from '../../environments/get_all_environments';
-import { hasLegacyJobs } from '../../../lib/anomaly_detection/has_legacy_jobs';
import { getSearchAggregatedTransactions } from '../../../lib/helpers/transactions';
import { notifyFeatureUsage } from '../../../feature';
-import { withApmSpan } from '../../../utils/with_apm_span';
import { createApmServerRouteRepository } from '../../apm_routes/create_apm_server_route_repository';
+import { updateToV3 } from './update_to_v3';
+import { environmentStringRt } from '../../../../common/environment_rt';
+import { getMlJobsWithAPMGroup } from '../../../lib/anomaly_detection/get_ml_jobs_with_apm_group';
// get ML anomaly detection jobs for each environment
const anomalyDetectionJobsRoute = createApmServerRoute({
@@ -29,22 +29,21 @@ const anomalyDetectionJobsRoute = createApmServerRoute({
},
handler: async (resources) => {
const setup = await setupRequest(resources);
- const { context, logger } = resources;
+ const { context } = resources;
if (!isActivePlatinumLicense(context.licensing.license)) {
throw Boom.forbidden(ML_ERRORS.INVALID_LICENSE);
}
- const [jobs, legacyJobs] = await withApmSpan('get_available_ml_jobs', () =>
- Promise.all([
- getAnomalyDetectionJobs(setup, logger),
- hasLegacyJobs(setup),
- ])
- );
+ if (!setup.ml) {
+ throw Boom.forbidden(ML_ERRORS.ML_NOT_AVAILABLE);
+ }
+
+ const jobs = await getMlJobsWithAPMGroup(setup.ml?.anomalyDetectors);
return {
jobs,
- hasLegacyJobs: legacyJobs,
+ hasLegacyJobs: jobs.some((job) => job.version === 1),
};
},
});
@@ -57,7 +56,7 @@ const createAnomalyDetectionJobsRoute = createApmServerRoute({
},
params: t.type({
body: t.type({
- environments: t.array(t.string),
+ environments: t.array(environmentStringRt),
}),
}),
handler: async (resources) => {
@@ -107,7 +106,35 @@ const anomalyDetectionEnvironmentsRoute = createApmServerRoute({
},
});
+const anomalyDetectionUpdateToV3Route = createApmServerRoute({
+ endpoint: 'POST /internal/apm/settings/anomaly-detection/update_to_v3',
+ options: {
+ tags: [
+ 'access:apm',
+ 'access:apm_write',
+ 'access:ml:canCreateJob',
+ 'access:ml:canGetJobs',
+ 'access:ml:canCloseJob',
+ ],
+ },
+ handler: async (resources) => {
+ const [setup, esClient] = await Promise.all([
+ setupRequest(resources),
+ resources.core
+ .start()
+ .then((start) => start.elasticsearch.client.asInternalUser),
+ ]);
+
+ const { logger } = resources;
+
+ return {
+ update: await updateToV3({ setup, logger, esClient }),
+ };
+ },
+});
+
export const anomalyDetectionRouteRepository = createApmServerRouteRepository()
.add(anomalyDetectionJobsRoute)
.add(createAnomalyDetectionJobsRoute)
- .add(anomalyDetectionEnvironmentsRoute);
+ .add(anomalyDetectionEnvironmentsRoute)
+ .add(anomalyDetectionUpdateToV3Route);
diff --git a/x-pack/plugins/apm/server/routes/settings/anomaly_detection/update_to_v3.ts b/x-pack/plugins/apm/server/routes/settings/anomaly_detection/update_to_v3.ts
new file mode 100644
index 0000000000000..b23a28648482e
--- /dev/null
+++ b/x-pack/plugins/apm/server/routes/settings/anomaly_detection/update_to_v3.ts
@@ -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 { Logger } from '@kbn/logging';
+import { uniq } from 'lodash';
+import pLimit from 'p-limit';
+import { ElasticsearchClient } from '../../../../../../../src/core/server';
+import { JOB_STATE } from '../../../../../ml/common';
+import { createAnomalyDetectionJobs } from '../../../lib/anomaly_detection/create_anomaly_detection_jobs';
+import { getAnomalyDetectionJobs } from '../../../lib/anomaly_detection/get_anomaly_detection_jobs';
+import { Setup } from '../../../lib/helpers/setup_request';
+import { withApmSpan } from '../../../utils/with_apm_span';
+
+export async function updateToV3({
+ logger,
+ setup,
+ esClient,
+}: {
+ logger: Logger;
+ setup: Setup;
+ esClient: ElasticsearchClient;
+}) {
+ const allJobs = await getAnomalyDetectionJobs(setup);
+
+ const v2Jobs = allJobs.filter((job) => job.version === 2);
+
+ const activeV2Jobs = v2Jobs.filter(
+ (job) =>
+ job.jobState === JOB_STATE.OPENED || job.jobState === JOB_STATE.OPENING
+ );
+
+ const environments = uniq(v2Jobs.map((job) => job.environment));
+
+ const limiter = pLimit(3);
+
+ if (!v2Jobs.length) {
+ return true;
+ }
+
+ if (activeV2Jobs.length) {
+ await withApmSpan('anomaly_detection_stop_v2_jobs', () =>
+ Promise.all(
+ activeV2Jobs.map((job) =>
+ limiter(() => {
+ return esClient.ml.closeJob({
+ job_id: job.jobId,
+ });
+ })
+ )
+ )
+ );
+ }
+
+ await createAnomalyDetectionJobs(setup, environments, logger);
+
+ return true;
+}
diff --git a/x-pack/plugins/apm/server/routes/typings.ts b/x-pack/plugins/apm/server/routes/typings.ts
index a089c7bf3968a..6ec196b2a9b8c 100644
--- a/x-pack/plugins/apm/server/routes/typings.ts
+++ b/x-pack/plugins/apm/server/routes/typings.ts
@@ -37,6 +37,7 @@ export interface APMRouteCreateOptions {
| 'access:apm_write'
| 'access:ml:canGetJobs'
| 'access:ml:canCreateJob'
+ | 'access:ml:canCloseJob'
>;
body?: { accepts: Array<'application/json' | 'multipart/form-data'> };
disableTelemetry?: boolean;
diff --git a/x-pack/plugins/canvas/canvas_plugin_src/renderers/external.ts b/x-pack/plugins/canvas/canvas_plugin_src/renderers/external.ts
index 569669032cb0b..9b79c50a92098 100644
--- a/x-pack/plugins/canvas/canvas_plugin_src/renderers/external.ts
+++ b/x-pack/plugins/canvas/canvas_plugin_src/renderers/external.ts
@@ -5,7 +5,7 @@
* 2.0.
*/
-import { imageRenderer } from '../../../../../src/plugins/expression_image/public';
+import { imageRendererFactory } from '../../../../../src/plugins/expression_image/public';
import { metricRendererFactory } from '../../../../../src/plugins/expression_metric/public';
import {
errorRendererFactory,
@@ -14,15 +14,18 @@ import {
import { revealImageRendererFactory } from '../../../../../src/plugins/expression_reveal_image/public';
import { repeatImageRendererFactory } from '../../../../../src/plugins/expression_repeat_image/public';
import {
- shapeRenderer,
- progressRenderer,
+ shapeRendererFactory,
+ progressRendererFactory,
} from '../../../../../src/plugins/expression_shape/public';
-export const renderFunctions = [imageRenderer, shapeRenderer, progressRenderer];
+export const renderFunctions = [];
export const renderFunctionFactories = [
debugRendererFactory,
errorRendererFactory,
+ imageRendererFactory,
+ shapeRendererFactory,
+ progressRendererFactory,
revealImageRendererFactory,
repeatImageRendererFactory,
metricRendererFactory,
diff --git a/x-pack/plugins/canvas/shareable_runtime/supported_renderers.js b/x-pack/plugins/canvas/shareable_runtime/supported_renderers.js
index 01b8cc98ba5ec..f8aebc04efe5c 100644
--- a/x-pack/plugins/canvas/shareable_runtime/supported_renderers.js
+++ b/x-pack/plugins/canvas/shareable_runtime/supported_renderers.js
@@ -10,7 +10,7 @@ import { pie } from '../canvas_plugin_src/renderers/pie';
import { plot } from '../canvas_plugin_src/renderers/plot';
import { getTableRenderer } from '../canvas_plugin_src/renderers/table';
import { getTextRenderer } from '../canvas_plugin_src/renderers/text';
-import { imageRenderer as image } from '../../../../src/plugins/expression_image/public';
+import { getImageRenderer } from '../../../../src/plugins/expression_image/public';
import {
getErrorRenderer,
getDebugRenderer,
@@ -18,8 +18,8 @@ import {
import { getRevealImageRenderer } from '../../../../src/plugins/expression_reveal_image/public';
import { getRepeatImageRenderer } from '../../../../src/plugins/expression_repeat_image/public';
import {
- shapeRenderer as shape,
- progressRenderer as progress,
+ getShapeRenderer,
+ getProgressRenderer,
} from '../../../../src/plugins/expression_shape/public';
import { getMetricRenderer } from '../../../../src/plugins/expression_metric/public';
@@ -31,6 +31,9 @@ const renderFunctionsFactories = [
getTableRenderer,
getErrorRenderer,
getDebugRenderer,
+ getImageRenderer,
+ getShapeRenderer,
+ getProgressRenderer,
getRevealImageRenderer,
getRepeatImageRenderer,
getMetricRenderer,
@@ -41,13 +44,6 @@ const renderFunctionsFactories = [
* a renderer is not listed here, but is used by the Shared Workpad, it will
* not render. This includes any plugins.
*/
-export const renderFunctions = [
- image,
- pie,
- plot,
- progress,
- shape,
- ...renderFunctionsFactories.map(unboxFactory),
-];
+export const renderFunctions = [pie, plot, ...renderFunctionsFactories.map(unboxFactory)];
export const renderFunctionNames = [...renderFunctions.map((fn) => fn().name)];
diff --git a/x-pack/plugins/cases/common/index.ts b/x-pack/plugins/cases/common/index.ts
index d38b1a779981c..41bba2ee2194d 100644
--- a/x-pack/plugins/cases/common/index.ts
+++ b/x-pack/plugins/cases/common/index.ts
@@ -5,11 +5,28 @@
* 2.0.
*/
-// TODO: https://github.com/elastic/kibana/issues/110896
-/* eslint-disable @kbn/eslint/no_export_all */
-
-export * from './constants';
-export * from './api';
-export * from './ui/types';
-export * from './utils/connectors_api';
-export * from './utils/user_actions';
+// Careful of exporting anything from this file as any file(s) you export here will cause your page bundle size to increase.
+// If you're using functions/types/etc... internally or within integration tests it's best to import directly from their paths
+// than expose the functions/types/etc... here. You should _only_ expose functions/types/etc... that need to be shared with other plugins here.
+
+// When you do have to add things here you might want to consider creating a package such as kbn-cases-constants to share with
+// other plugins instead as packages are easier to break down and you do not have to carry the cost of extra plugin weight on
+// first download since the other plugins/areas of your code can directly pull from the package in their async imports.
+// For example, constants below could eventually be in a "kbn-cases-constants" instead.
+// See: https://docs.elastic.dev/kibana-dev-docs/key-concepts/platform-intro#public-plugin-api
+
+export { CASES_URL, SECURITY_SOLUTION_OWNER, ENABLE_CASE_CONNECTOR } from './constants';
+
+export { CommentType, CaseStatuses, getCasesFromAlertsUrl, throwErrors } from './api';
+
+export type {
+ SubCase,
+ Case,
+ Ecs,
+ CasesContextValue,
+ CaseViewRefreshPropInterface,
+} from './ui/types';
+
+export { StatusAll } from './ui/types';
+
+export { getCreateConnectorUrl, getAllConnectorsUrl } from './utils/connectors_api';
diff --git a/x-pack/plugins/cases/common/utils/connectors_api.ts b/x-pack/plugins/cases/common/utils/connectors_api.ts
index f9f85bbfb0127..3ab8f856d925e 100644
--- a/x-pack/plugins/cases/common/utils/connectors_api.ts
+++ b/x-pack/plugins/cases/common/utils/connectors_api.ts
@@ -9,7 +9,7 @@
* Actions and connectors API endpoint helpers
*/
-import { ACTION_URL, ACTION_TYPES_URL, CONNECTORS_URL } from '../../common';
+import { ACTION_URL, ACTION_TYPES_URL, CONNECTORS_URL } from '../../common/constants';
/**
*
diff --git a/x-pack/plugins/cases/public/common/lib/kibana/hooks.ts b/x-pack/plugins/cases/public/common/lib/kibana/hooks.ts
index 49d03d44a3a4f..08eb2ebf3df7a 100644
--- a/x-pack/plugins/cases/public/common/lib/kibana/hooks.ts
+++ b/x-pack/plugins/cases/public/common/lib/kibana/hooks.ts
@@ -10,7 +10,7 @@ import moment from 'moment-timezone';
import { useCallback, useEffect, useState } from 'react';
import { i18n } from '@kbn/i18n';
-import { DEFAULT_DATE_FORMAT, DEFAULT_DATE_FORMAT_TZ } from '../../../../common';
+import { DEFAULT_DATE_FORMAT, DEFAULT_DATE_FORMAT_TZ } from '../../../../common/constants';
import { AuthenticatedUser } from '../../../../../security/common/model';
import { convertToCamelCase } from '../../../containers/utils';
import { StartServices } from '../../../types';
diff --git a/x-pack/plugins/cases/public/common/mock/register_connectors.ts b/x-pack/plugins/cases/public/common/mock/register_connectors.ts
index 42e7cd4a85e40..b86968e4bf801 100644
--- a/x-pack/plugins/cases/public/common/mock/register_connectors.ts
+++ b/x-pack/plugins/cases/public/common/mock/register_connectors.ts
@@ -8,7 +8,7 @@
import { TriggersAndActionsUIPublicPluginStart } from '../../../../triggers_actions_ui/public';
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
import { actionTypeRegistryMock } from '../../../../triggers_actions_ui/public/application/action_type_registry.mock';
-import { CaseActionConnector } from '../../../common';
+import { CaseActionConnector } from '../../../common/ui/types';
const getUniqueActionTypeIds = (connectors: CaseActionConnector[]) =>
new Set(connectors.map((connector) => connector.actionTypeId));
diff --git a/x-pack/plugins/cases/public/common/mock/test_providers.tsx b/x-pack/plugins/cases/public/common/mock/test_providers.tsx
index d016dce48a24e..c076ca28c9318 100644
--- a/x-pack/plugins/cases/public/common/mock/test_providers.tsx
+++ b/x-pack/plugins/cases/public/common/mock/test_providers.tsx
@@ -10,7 +10,8 @@ import { merge } from 'lodash';
import { euiDarkVars } from '@kbn/ui-shared-deps-src/theme';
import { I18nProvider } from '@kbn/i18n-react';
import { ThemeProvider } from 'styled-components';
-import { CasesContextValue, DEFAULT_FEATURES, SECURITY_SOLUTION_OWNER } from '../../../common';
+import { DEFAULT_FEATURES, SECURITY_SOLUTION_OWNER } from '../../../common/constants';
+import { CasesContextValue } from '../../../common/ui/types';
import { CasesProvider } from '../../components/cases_context';
import { createKibanaContextProviderMock } from '../lib/kibana/kibana_react.mock';
import { FieldHook } from '../shared_imports';
diff --git a/x-pack/plugins/cases/public/common/user_actions/parsers.test.ts b/x-pack/plugins/cases/public/common/user_actions/parsers.test.ts
index c6d13cc41686c..e2d24bf19f3d4 100644
--- a/x-pack/plugins/cases/public/common/user_actions/parsers.test.ts
+++ b/x-pack/plugins/cases/public/common/user_actions/parsers.test.ts
@@ -5,7 +5,7 @@
* 2.0.
*/
-import { ConnectorTypes, noneConnectorId } from '../../../common';
+import { ConnectorTypes, noneConnectorId } from '../../../common/api';
import { parseStringAsConnector, parseStringAsExternalService } from './parsers';
describe('user actions utility functions', () => {
diff --git a/x-pack/plugins/cases/public/common/user_actions/parsers.ts b/x-pack/plugins/cases/public/common/user_actions/parsers.ts
index dfea22443aa51..0384a97124c54 100644
--- a/x-pack/plugins/cases/public/common/user_actions/parsers.ts
+++ b/x-pack/plugins/cases/public/common/user_actions/parsers.ts
@@ -12,7 +12,7 @@ import {
noneConnectorId,
CaseFullExternalService,
CaseUserActionExternalServiceRt,
-} from '../../../common';
+} from '../../../common/api';
export const parseStringAsConnector = (
id: string | null,
diff --git a/x-pack/plugins/cases/public/components/add_comment/index.test.tsx b/x-pack/plugins/cases/public/components/add_comment/index.test.tsx
index c15722a3ec354..f1167504628c4 100644
--- a/x-pack/plugins/cases/public/components/add_comment/index.test.tsx
+++ b/x-pack/plugins/cases/public/components/add_comment/index.test.tsx
@@ -12,7 +12,8 @@ import { noop } from 'lodash/fp';
import { TestProviders } from '../../common/mock';
-import { CommentRequest, CommentType, SECURITY_SOLUTION_OWNER } from '../../../common';
+import { CommentRequest, CommentType } from '../../../common/api';
+import { SECURITY_SOLUTION_OWNER } from '../../../common/constants';
import { usePostComment } from '../../containers/use_post_comment';
import { AddComment, AddCommentProps, AddCommentRefObject } from '.';
import { CasesTimelineIntegrationProvider } from '../timeline_context';
diff --git a/x-pack/plugins/cases/public/components/add_comment/index.tsx b/x-pack/plugins/cases/public/components/add_comment/index.tsx
index 37f21e0949288..83bd187e7863a 100644
--- a/x-pack/plugins/cases/public/components/add_comment/index.tsx
+++ b/x-pack/plugins/cases/public/components/add_comment/index.tsx
@@ -17,7 +17,7 @@ import { EuiButton, EuiFlexItem, EuiFlexGroup, EuiLoadingSpinner } from '@elasti
import styled from 'styled-components';
import { isEmpty } from 'lodash';
-import { CommentType } from '../../../common';
+import { CommentType } from '../../../common/api';
import { usePostComment } from '../../containers/use_post_comment';
import { Case } from '../../containers/types';
import { EuiMarkdownEditorRef, MarkdownEditorForm } from '../markdown_editor';
diff --git a/x-pack/plugins/cases/public/components/add_comment/schema.tsx b/x-pack/plugins/cases/public/components/add_comment/schema.tsx
index 9693219dd5196..3e32c8a938b68 100644
--- a/x-pack/plugins/cases/public/components/add_comment/schema.tsx
+++ b/x-pack/plugins/cases/public/components/add_comment/schema.tsx
@@ -5,7 +5,7 @@
* 2.0.
*/
-import { CommentRequestUserType } from '../../../common';
+import { CommentRequestUserType } from '../../../common/api';
import { FIELD_TYPES, fieldValidators, FormSchema } from '../../common/shared_imports';
import * as i18n from './translations';
diff --git a/x-pack/plugins/cases/public/components/all_cases/all_cases_list.test.tsx b/x-pack/plugins/cases/public/components/all_cases/all_cases_list.test.tsx
index bf02202ff83b2..85e33402ebe45 100644
--- a/x-pack/plugins/cases/public/components/all_cases/all_cases_list.test.tsx
+++ b/x-pack/plugins/cases/public/components/all_cases/all_cases_list.test.tsx
@@ -20,7 +20,9 @@ import {
connectorsMock,
} from '../../containers/mock';
-import { CaseStatuses, CaseType, SECURITY_SOLUTION_OWNER, StatusAll } from '../../../common';
+import { StatusAll } from '../../../common/ui/types';
+import { CaseStatuses, CaseType } from '../../../common/api';
+import { SECURITY_SOLUTION_OWNER } from '../../../common/constants';
import { getEmptyTagValue } from '../empty_value';
import { useDeleteCases } from '../../containers/use_delete_cases';
import { useGetCases } from '../../containers/use_get_cases';
diff --git a/x-pack/plugins/cases/public/components/all_cases/all_cases_list.tsx b/x-pack/plugins/cases/public/components/all_cases/all_cases_list.tsx
index 58c17695d0dfe..b3631155f1b6e 100644
--- a/x-pack/plugins/cases/public/components/all_cases/all_cases_list.tsx
+++ b/x-pack/plugins/cases/public/components/all_cases/all_cases_list.tsx
@@ -13,15 +13,12 @@ import classnames from 'classnames';
import {
Case,
- CaseStatuses,
- CaseType,
- CommentRequestAlertType,
CaseStatusWithAllStatus,
FilterOptions,
SortFieldCase,
SubCase,
- caseStatuses,
-} from '../../../common';
+} from '../../../common/ui/types';
+import { CaseStatuses, CaseType, CommentRequestAlertType, caseStatuses } from '../../../common/api';
import { SELECTABLE_MESSAGE_COLLECTIONS } from '../../common/translations';
import { useGetCases } from '../../containers/use_get_cases';
import { usePostComment } from '../../containers/use_post_comment';
diff --git a/x-pack/plugins/cases/public/components/all_cases/columns.tsx b/x-pack/plugins/cases/public/components/all_cases/columns.tsx
index c30ddd199fc49..684b9644a7879 100644
--- a/x-pack/plugins/cases/public/components/all_cases/columns.tsx
+++ b/x-pack/plugins/cases/public/components/all_cases/columns.tsx
@@ -22,16 +22,14 @@ import {
import { RIGHT_ALIGNMENT } from '@elastic/eui/lib/services';
import styled from 'styled-components';
+import { Case, DeleteCase, SubCase } from '../../../common/ui/types';
import {
CaseStatuses,
CaseType,
CommentType,
CommentRequestAlertType,
- DeleteCase,
- Case,
- SubCase,
ActionConnector,
-} from '../../../common';
+} from '../../../common/api';
import { getEmptyTagValue } from '../empty_value';
import { FormattedRelativePreferenceDate } from '../formatted_date';
import { CaseDetailsLink } from '../links';
diff --git a/x-pack/plugins/cases/public/components/all_cases/count.tsx b/x-pack/plugins/cases/public/components/all_cases/count.tsx
index eb33cf1069a9b..1f6e71c377ee6 100644
--- a/x-pack/plugins/cases/public/components/all_cases/count.tsx
+++ b/x-pack/plugins/cases/public/components/all_cases/count.tsx
@@ -7,7 +7,7 @@
import React, { FunctionComponent, useEffect } from 'react';
import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
-import { CaseStatuses } from '../../../common';
+import { CaseStatuses } from '../../../common/api';
import { Stats } from '../status';
import { useGetCasesStatus } from '../../containers/use_get_cases_status';
diff --git a/x-pack/plugins/cases/public/components/all_cases/expanded_row.tsx b/x-pack/plugins/cases/public/components/all_cases/expanded_row.tsx
index 2b43fbf63095e..4719c2ce3db82 100644
--- a/x-pack/plugins/cases/public/components/all_cases/expanded_row.tsx
+++ b/x-pack/plugins/cases/public/components/all_cases/expanded_row.tsx
@@ -10,7 +10,7 @@ import { EuiBasicTable } from '@elastic/eui';
import styled from 'styled-components';
import { Case, SubCase } from '../../containers/types';
import { CasesColumns } from './columns';
-import { AssociationType } from '../../../common';
+import { AssociationType } from '../../../common/api';
type ExpandedRowMap = Record | {};
diff --git a/x-pack/plugins/cases/public/components/all_cases/helpers.ts b/x-pack/plugins/cases/public/components/all_cases/helpers.ts
index ca5b2e422c15c..f84f19d3030ae 100644
--- a/x-pack/plugins/cases/public/components/all_cases/helpers.ts
+++ b/x-pack/plugins/cases/public/components/all_cases/helpers.ts
@@ -6,7 +6,7 @@
*/
import { filter } from 'lodash/fp';
-import { AssociationType, CaseStatuses, CaseType } from '../../../common';
+import { AssociationType, CaseStatuses, CaseType } from '../../../common/api';
import { Case, SubCase } from '../../containers/types';
import { statuses } from '../status';
diff --git a/x-pack/plugins/cases/public/components/all_cases/index.test.tsx b/x-pack/plugins/cases/public/components/all_cases/index.test.tsx
index 681bb65870c1e..9decb3a58f831 100644
--- a/x-pack/plugins/cases/public/components/all_cases/index.test.tsx
+++ b/x-pack/plugins/cases/public/components/all_cases/index.test.tsx
@@ -16,7 +16,7 @@ import { useGetReporters } from '../../containers/use_get_reporters';
import { useGetActionLicense } from '../../containers/use_get_action_license';
import { useConnectors } from '../../containers/configure/use_connectors';
import { useKibana } from '../../common/lib/kibana';
-import { CaseStatuses } from '../../../common';
+import { CaseStatuses } from '../../../common/api';
import { casesStatus, connectorsMock, useGetCasesMockState } from '../../containers/mock';
import { registerConnectorsToMockActionRegistry } from '../../common/mock/register_connectors';
import { useGetCases } from '../../containers/use_get_cases';
diff --git a/x-pack/plugins/cases/public/components/all_cases/selector_modal/all_cases_selector_modal.test.tsx b/x-pack/plugins/cases/public/components/all_cases/selector_modal/all_cases_selector_modal.test.tsx
index fb062fe101db5..33eddeccb59b2 100644
--- a/x-pack/plugins/cases/public/components/all_cases/selector_modal/all_cases_selector_modal.test.tsx
+++ b/x-pack/plugins/cases/public/components/all_cases/selector_modal/all_cases_selector_modal.test.tsx
@@ -11,7 +11,7 @@ import { mount } from 'enzyme';
import { AllCasesSelectorModal } from '.';
import { TestProviders } from '../../../common/mock';
import { AllCasesList } from '../all_cases_list';
-import { SECURITY_SOLUTION_OWNER } from '../../../../common';
+import { SECURITY_SOLUTION_OWNER } from '../../../../common/constants';
jest.mock('../../../methods');
jest.mock('../all_cases_list');
diff --git a/x-pack/plugins/cases/public/components/all_cases/selector_modal/all_cases_selector_modal.tsx b/x-pack/plugins/cases/public/components/all_cases/selector_modal/all_cases_selector_modal.tsx
index 227dd88c6f5a2..5db6531d8e140 100644
--- a/x-pack/plugins/cases/public/components/all_cases/selector_modal/all_cases_selector_modal.tsx
+++ b/x-pack/plugins/cases/public/components/all_cases/selector_modal/all_cases_selector_modal.tsx
@@ -15,12 +15,8 @@ import {
EuiModalHeaderTitle,
} from '@elastic/eui';
import styled from 'styled-components';
-import {
- Case,
- CaseStatusWithAllStatus,
- CommentRequestAlertType,
- SubCase,
-} from '../../../../common';
+import { Case, SubCase, CaseStatusWithAllStatus } from '../../../../common/ui/types';
+import { CommentRequestAlertType } from '../../../../common/api';
import * as i18n from '../../../common/translations';
import { AllCasesList } from '../all_cases_list';
export interface AllCasesSelectorModalProps {
diff --git a/x-pack/plugins/cases/public/components/all_cases/status_filter.test.tsx b/x-pack/plugins/cases/public/components/all_cases/status_filter.test.tsx
index 5d975c51c6569..5471c03a6f181 100644
--- a/x-pack/plugins/cases/public/components/all_cases/status_filter.test.tsx
+++ b/x-pack/plugins/cases/public/components/all_cases/status_filter.test.tsx
@@ -9,7 +9,8 @@ import React from 'react';
import { mount } from 'enzyme';
import { waitFor } from '@testing-library/react';
-import { CaseStatuses, StatusAll } from '../../../common';
+import { StatusAll } from '../../../common/ui/types';
+import { CaseStatuses } from '../../../common/api';
import { StatusFilter } from './status_filter';
const stats = {
diff --git a/x-pack/plugins/cases/public/components/all_cases/status_filter.tsx b/x-pack/plugins/cases/public/components/all_cases/status_filter.tsx
index bb54fbe410951..71359c2e50582 100644
--- a/x-pack/plugins/cases/public/components/all_cases/status_filter.tsx
+++ b/x-pack/plugins/cases/public/components/all_cases/status_filter.tsx
@@ -8,7 +8,7 @@
import React, { memo } from 'react';
import { EuiSuperSelect, EuiSuperSelectOption, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import { Status, statuses } from '../status';
-import { CaseStatusWithAllStatus, StatusAll } from '../../../common';
+import { CaseStatusWithAllStatus, StatusAll } from '../../../common/ui/types';
interface Props {
stats: Record;
diff --git a/x-pack/plugins/cases/public/components/all_cases/table.tsx b/x-pack/plugins/cases/public/components/all_cases/table.tsx
index 40d61007f9056..94a44add3402f 100644
--- a/x-pack/plugins/cases/public/components/all_cases/table.tsx
+++ b/x-pack/plugins/cases/public/components/all_cases/table.tsx
@@ -18,7 +18,7 @@ import styled from 'styled-components';
import { CasesTableUtilityBar } from './utility_bar';
import { LinkButton } from '../links';
-import { AllCases, Case, FilterOptions } from '../../../common';
+import { AllCases, Case, FilterOptions } from '../../../common/ui/types';
import * as i18n from './translations';
import { useCreateCaseNavigation } from '../../common/navigation';
diff --git a/x-pack/plugins/cases/public/components/all_cases/table_filters.test.tsx b/x-pack/plugins/cases/public/components/all_cases/table_filters.test.tsx
index f71009a37b747..2d14ffe5738ca 100644
--- a/x-pack/plugins/cases/public/components/all_cases/table_filters.test.tsx
+++ b/x-pack/plugins/cases/public/components/all_cases/table_filters.test.tsx
@@ -8,7 +8,7 @@
import React from 'react';
import { mount } from 'enzyme';
-import { CaseStatuses } from '../../../common';
+import { CaseStatuses } from '../../../common/api';
import { TestProviders } from '../../common/mock';
import { useGetTags } from '../../containers/use_get_tags';
import { useGetReporters } from '../../containers/use_get_reporters';
diff --git a/x-pack/plugins/cases/public/components/all_cases/table_filters.tsx b/x-pack/plugins/cases/public/components/all_cases/table_filters.tsx
index 47ab4cb210778..e1ed709e0d93f 100644
--- a/x-pack/plugins/cases/public/components/all_cases/table_filters.tsx
+++ b/x-pack/plugins/cases/public/components/all_cases/table_filters.tsx
@@ -10,7 +10,8 @@ import { isEqual } from 'lodash/fp';
import styled from 'styled-components';
import { EuiFlexGroup, EuiFlexItem, EuiFieldSearch, EuiFilterGroup } from '@elastic/eui';
-import { CaseStatuses, CaseStatusWithAllStatus, StatusAll } from '../../../common';
+import { StatusAll, CaseStatusWithAllStatus } from '../../../common/ui/types';
+import { CaseStatuses } from '../../../common/api';
import { FilterOptions } from '../../containers/types';
import { useGetTags } from '../../containers/use_get_tags';
import { useGetReporters } from '../../containers/use_get_reporters';
diff --git a/x-pack/plugins/cases/public/components/all_cases/utility_bar.tsx b/x-pack/plugins/cases/public/components/all_cases/utility_bar.tsx
index 26430482bc067..b6ab44517bb66 100644
--- a/x-pack/plugins/cases/public/components/all_cases/utility_bar.tsx
+++ b/x-pack/plugins/cases/public/components/all_cases/utility_bar.tsx
@@ -15,7 +15,7 @@ import {
UtilityBarText,
} from '../utility_bar';
import * as i18n from './translations';
-import { AllCases, Case, DeleteCase, FilterOptions } from '../../../common';
+import { AllCases, Case, DeleteCase, FilterOptions } from '../../../common/ui/types';
import { getBulkItems } from '../bulk_actions';
import { isSelectedCasesIncludeCollections } from './helpers';
import { useDeleteCases } from '../../containers/use_delete_cases';
diff --git a/x-pack/plugins/cases/public/components/app/types.ts b/x-pack/plugins/cases/public/components/app/types.ts
index 9c825ad95618a..ebe174c095fa7 100644
--- a/x-pack/plugins/cases/public/components/app/types.ts
+++ b/x-pack/plugins/cases/public/components/app/types.ts
@@ -6,7 +6,7 @@
*/
import { MutableRefObject } from 'react';
-import { Ecs, CaseViewRefreshPropInterface } from '../../../common';
+import { Ecs, CaseViewRefreshPropInterface } from '../../../common/ui/types';
import { CasesNavigation } from '../links';
import { CasesTimelineIntegration } from '../timeline_context';
diff --git a/x-pack/plugins/cases/public/components/bulk_actions/index.tsx b/x-pack/plugins/cases/public/components/bulk_actions/index.tsx
index 751a45a706ef7..c8dbe2adaca0b 100644
--- a/x-pack/plugins/cases/public/components/bulk_actions/index.tsx
+++ b/x-pack/plugins/cases/public/components/bulk_actions/index.tsx
@@ -8,7 +8,8 @@
import React from 'react';
import { EuiContextMenuItem } from '@elastic/eui';
-import { CaseStatuses, CaseStatusWithAllStatus } from '../../../common';
+import { CaseStatusWithAllStatus } from '../../../common/ui/types';
+import { CaseStatuses } from '../../../common/api';
import { statuses } from '../status';
import * as i18n from './translations';
import { Case } from '../../containers/types';
diff --git a/x-pack/plugins/cases/public/components/case_action_bar/actions.tsx b/x-pack/plugins/cases/public/components/case_action_bar/actions.tsx
index ada2b61c816db..4cad00535d165 100644
--- a/x-pack/plugins/cases/public/components/case_action_bar/actions.tsx
+++ b/x-pack/plugins/cases/public/components/case_action_bar/actions.tsx
@@ -11,7 +11,7 @@ import * as i18n from '../case_view/translations';
import { useDeleteCases } from '../../containers/use_delete_cases';
import { ConfirmDeleteCaseModal } from '../confirm_delete_case';
import { PropertyActions } from '../property_actions';
-import { Case } from '../../../common';
+import { Case } from '../../../common/ui/types';
import { CaseService } from '../../containers/use_get_case_user_actions';
import { useAllCasesNavigation } from '../../common/navigation';
diff --git a/x-pack/plugins/cases/public/components/case_action_bar/helpers.test.ts b/x-pack/plugins/cases/public/components/case_action_bar/helpers.test.ts
index ed5832d19b4da..f04ef94405db8 100644
--- a/x-pack/plugins/cases/public/components/case_action_bar/helpers.test.ts
+++ b/x-pack/plugins/cases/public/components/case_action_bar/helpers.test.ts
@@ -5,7 +5,7 @@
* 2.0.
*/
-import { CaseStatuses } from '../../../common';
+import { CaseStatuses } from '../../../common/api';
import { basicCase } from '../../containers/mock';
import { getStatusDate, getStatusTitle } from './helpers';
diff --git a/x-pack/plugins/cases/public/components/case_action_bar/helpers.ts b/x-pack/plugins/cases/public/components/case_action_bar/helpers.ts
index 35cfdae3abe21..b26c33b0fd009 100644
--- a/x-pack/plugins/cases/public/components/case_action_bar/helpers.ts
+++ b/x-pack/plugins/cases/public/components/case_action_bar/helpers.ts
@@ -5,7 +5,7 @@
* 2.0.
*/
-import { CaseStatuses } from '../../../common';
+import { CaseStatuses } from '../../../common/api';
import { Case } from '../../containers/types';
import { statuses } from '../status';
diff --git a/x-pack/plugins/cases/public/components/case_action_bar/index.tsx b/x-pack/plugins/cases/public/components/case_action_bar/index.tsx
index 9b326f3216084..ac81dfea2fd93 100644
--- a/x-pack/plugins/cases/public/components/case_action_bar/index.tsx
+++ b/x-pack/plugins/cases/public/components/case_action_bar/index.tsx
@@ -16,7 +16,8 @@ import {
EuiFlexItem,
EuiIconTip,
} from '@elastic/eui';
-import { Case, CaseStatuses, CaseType } from '../../../common';
+import { Case } from '../../../common/ui/types';
+import { CaseStatuses, CaseType } from '../../../common/api';
import * as i18n from '../case_view/translations';
import { FormattedRelativePreferenceDate } from '../formatted_date';
import { Actions } from './actions';
diff --git a/x-pack/plugins/cases/public/components/case_action_bar/status_context_menu.test.tsx b/x-pack/plugins/cases/public/components/case_action_bar/status_context_menu.test.tsx
index 93ecf4df997d2..4a67eada2e00d 100644
--- a/x-pack/plugins/cases/public/components/case_action_bar/status_context_menu.test.tsx
+++ b/x-pack/plugins/cases/public/components/case_action_bar/status_context_menu.test.tsx
@@ -8,7 +8,7 @@
import React from 'react';
import { mount } from 'enzyme';
-import { CaseStatuses } from '../../../common';
+import { CaseStatuses } from '../../../common/api';
import { StatusContextMenu } from './status_context_menu';
describe('SyncAlertsSwitch', () => {
diff --git a/x-pack/plugins/cases/public/components/case_action_bar/status_context_menu.tsx b/x-pack/plugins/cases/public/components/case_action_bar/status_context_menu.tsx
index ab86f589bfdd0..193ef4a708e38 100644
--- a/x-pack/plugins/cases/public/components/case_action_bar/status_context_menu.tsx
+++ b/x-pack/plugins/cases/public/components/case_action_bar/status_context_menu.tsx
@@ -7,7 +7,7 @@
import React, { memo, useCallback, useMemo, useState } from 'react';
import { EuiPopover, EuiContextMenuPanel, EuiContextMenuItem } from '@elastic/eui';
-import { caseStatuses, CaseStatuses } from '../../../common';
+import { caseStatuses, CaseStatuses } from '../../../common/api';
import { Status } from '../status';
import { CHANGE_STATUS } from '../all_cases/translations';
diff --git a/x-pack/plugins/cases/public/components/case_view/helpers.test.tsx b/x-pack/plugins/cases/public/components/case_view/helpers.test.tsx
index bf5a9fe5d0a22..e398c5edad145 100644
--- a/x-pack/plugins/cases/public/components/case_view/helpers.test.tsx
+++ b/x-pack/plugins/cases/public/components/case_view/helpers.test.tsx
@@ -5,7 +5,8 @@
* 2.0.
*/
-import { AssociationType, CommentType, SECURITY_SOLUTION_OWNER } from '../../../common';
+import { AssociationType, CommentType } from '../../../common/api';
+import { SECURITY_SOLUTION_OWNER } from '../../../common/constants';
import { Comment } from '../../containers/types';
import { getManualAlertIdsWithNoRuleId } from './helpers';
diff --git a/x-pack/plugins/cases/public/components/case_view/helpers.ts b/x-pack/plugins/cases/public/components/case_view/helpers.ts
index ab26b132e0489..7f3924ef2564c 100644
--- a/x-pack/plugins/cases/public/components/case_view/helpers.ts
+++ b/x-pack/plugins/cases/public/components/case_view/helpers.ts
@@ -6,7 +6,7 @@
*/
import { isEmpty } from 'lodash';
-import { CommentType } from '../../../common';
+import { CommentType } from '../../../common/api';
import { Comment } from '../../containers/types';
export const getManualAlertIdsWithNoRuleId = (comments: Comment[]): string[] => {
diff --git a/x-pack/plugins/cases/public/components/case_view/index.test.tsx b/x-pack/plugins/cases/public/components/case_view/index.test.tsx
index aaf4928703896..daa3ad4416200 100644
--- a/x-pack/plugins/cases/public/components/case_view/index.test.tsx
+++ b/x-pack/plugins/cases/public/components/case_view/index.test.tsx
@@ -27,7 +27,7 @@ import { useGetCaseUserActions } from '../../containers/use_get_case_user_action
import { useConnectors } from '../../containers/configure/use_connectors';
import { connectorsMock } from '../../containers/configure/mock';
import { usePostPushToService } from '../../containers/use_post_push_to_service';
-import { CaseType, ConnectorTypes } from '../../../common';
+import { CaseType, ConnectorTypes } from '../../../common/api';
import { useKibana } from '../../common/lib/kibana';
jest.mock('../../containers/use_update_case');
diff --git a/x-pack/plugins/cases/public/components/case_view/index.tsx b/x-pack/plugins/cases/public/components/case_view/index.tsx
index 2b78c31242ba6..c436547c9e2bd 100644
--- a/x-pack/plugins/cases/public/components/case_view/index.tsx
+++ b/x-pack/plugins/cases/public/components/case_view/index.tsx
@@ -9,15 +9,8 @@ import React, { useCallback, useEffect, useMemo, useState, useRef, MutableRefObj
import styled from 'styled-components';
import { EuiFlexGroup, EuiFlexItem, EuiLoadingContent, EuiLoadingSpinner } from '@elastic/eui';
-import {
- CaseStatuses,
- CaseAttributes,
- CaseType,
- Case,
- CaseConnector,
- Ecs,
- CaseViewRefreshPropInterface,
-} from '../../../common';
+import { Case, Ecs, CaseViewRefreshPropInterface } from '../../../common/ui/types';
+import { CaseStatuses, CaseAttributes, CaseType, CaseConnector } from '../../../common/api';
import { HeaderPage } from '../header_page';
import { EditableTitle } from '../header_page/editable_title';
import { TagList } from '../tag_list';
diff --git a/x-pack/plugins/cases/public/components/cases_context/index.tsx b/x-pack/plugins/cases/public/components/cases_context/index.tsx
index 588bda245b044..ecc5719cb81b7 100644
--- a/x-pack/plugins/cases/public/components/cases_context/index.tsx
+++ b/x-pack/plugins/cases/public/components/cases_context/index.tsx
@@ -7,7 +7,8 @@
import React, { useState, useEffect } from 'react';
import { merge } from 'lodash';
-import { CasesContextValue, DEFAULT_FEATURES } from '../../../common';
+import { CasesContextValue } from '../../../common/ui/types';
+import { DEFAULT_FEATURES } from '../../../common/constants';
import { DEFAULT_BASE_PATH } from '../../common/navigation';
import { useApplication } from './use_application';
diff --git a/x-pack/plugins/cases/public/components/configure_cases/__mock__/index.tsx b/x-pack/plugins/cases/public/components/configure_cases/__mock__/index.tsx
index 983e32ba508fb..49ac373724336 100644
--- a/x-pack/plugins/cases/public/components/configure_cases/__mock__/index.tsx
+++ b/x-pack/plugins/cases/public/components/configure_cases/__mock__/index.tsx
@@ -5,7 +5,7 @@
* 2.0.
*/
-import { ActionTypeConnector, ConnectorTypes } from '../../../../common';
+import { ActionTypeConnector, ConnectorTypes } from '../../../../common/api';
import { ActionConnector } from '../../../containers/configure/types';
import { UseConnectorsResponse } from '../../../containers/configure/use_connectors';
import { ReturnUseCaseConfigure } from '../../../containers/configure/use_configure';
diff --git a/x-pack/plugins/cases/public/components/configure_cases/connectors.test.tsx b/x-pack/plugins/cases/public/components/configure_cases/connectors.test.tsx
index 9bbddfae2f9bd..7a6bca518ac3e 100644
--- a/x-pack/plugins/cases/public/components/configure_cases/connectors.test.tsx
+++ b/x-pack/plugins/cases/public/components/configure_cases/connectors.test.tsx
@@ -13,7 +13,7 @@ import { Connectors, Props } from './connectors';
import { TestProviders } from '../../common/mock';
import { ConnectorsDropdown } from './connectors_dropdown';
import { connectors, actionTypes } from './__mock__';
-import { ConnectorTypes } from '../../../common';
+import { ConnectorTypes } from '../../../common/api';
import { useKibana } from '../../common/lib/kibana';
import { registerConnectorsToMockActionRegistry } from '../../common/mock/register_connectors';
diff --git a/x-pack/plugins/cases/public/components/configure_cases/connectors.tsx b/x-pack/plugins/cases/public/components/configure_cases/connectors.tsx
index b7bf7c322f76e..11026acde2bf6 100644
--- a/x-pack/plugins/cases/public/components/configure_cases/connectors.tsx
+++ b/x-pack/plugins/cases/public/components/configure_cases/connectors.tsx
@@ -21,7 +21,7 @@ import * as i18n from './translations';
import { ActionConnector, CaseConnectorMapping } from '../../containers/configure/types';
import { Mapping } from './mapping';
-import { ActionTypeConnector, ConnectorTypes } from '../../../common';
+import { ActionTypeConnector, ConnectorTypes } from '../../../common/api';
import { DeprecatedCallout } from '../connectors/deprecated_callout';
import { isDeprecatedConnector } from '../utils';
diff --git a/x-pack/plugins/cases/public/components/configure_cases/connectors_dropdown.tsx b/x-pack/plugins/cases/public/components/configure_cases/connectors_dropdown.tsx
index c7ce3c5b3c4b6..af518e3c773b6 100644
--- a/x-pack/plugins/cases/public/components/configure_cases/connectors_dropdown.tsx
+++ b/x-pack/plugins/cases/public/components/configure_cases/connectors_dropdown.tsx
@@ -9,7 +9,7 @@ import React, { useMemo } from 'react';
import { EuiFlexGroup, EuiFlexItem, EuiIcon, EuiIconTip, EuiSuperSelect } from '@elastic/eui';
import styled from 'styled-components';
-import { ConnectorTypes } from '../../../common';
+import { ConnectorTypes } from '../../../common/api';
import { ActionConnector } from '../../containers/configure/types';
import * as i18n from './translations';
import { useKibana } from '../../common/lib/kibana';
diff --git a/x-pack/plugins/cases/public/components/configure_cases/index.test.tsx b/x-pack/plugins/cases/public/components/configure_cases/index.test.tsx
index 55983df8f347d..918252369c26b 100644
--- a/x-pack/plugins/cases/public/components/configure_cases/index.test.tsx
+++ b/x-pack/plugins/cases/public/components/configure_cases/index.test.tsx
@@ -26,7 +26,7 @@ import {
useConnectorsResponse,
useActionTypesResponse,
} from './__mock__';
-import { ConnectorTypes } from '../../../common';
+import { ConnectorTypes } from '../../../common/api';
import { actionTypeRegistryMock } from '../../../../triggers_actions_ui/public/application/action_type_registry.mock';
jest.mock('../../common/lib/kibana');
diff --git a/x-pack/plugins/cases/public/components/configure_cases/index.tsx b/x-pack/plugins/cases/public/components/configure_cases/index.tsx
index 6b19fd911d10d..44c1979aa0fda 100644
--- a/x-pack/plugins/cases/public/components/configure_cases/index.tsx
+++ b/x-pack/plugins/cases/public/components/configure_cases/index.tsx
@@ -11,7 +11,7 @@ import styled, { css } from 'styled-components';
import { FormattedMessage } from '@kbn/i18n-react';
import { EuiCallOut, EuiLink } from '@elastic/eui';
-import { SUPPORTED_CONNECTORS } from '../../../common';
+import { SUPPORTED_CONNECTORS } from '../../../common/constants';
import { useKibana } from '../../common/lib/kibana';
import { useConnectors } from '../../containers/configure/use_connectors';
import { useActionTypes } from '../../containers/configure/use_action_types';
diff --git a/x-pack/plugins/cases/public/components/configure_cases/utils.ts b/x-pack/plugins/cases/public/components/configure_cases/utils.ts
index 6597417b5068a..d7de06e9c5aee 100644
--- a/x-pack/plugins/cases/public/components/configure_cases/utils.ts
+++ b/x-pack/plugins/cases/public/components/configure_cases/utils.ts
@@ -5,7 +5,7 @@
* 2.0.
*/
-import { ConnectorTypeFields, ConnectorTypes } from '../../../common';
+import { ConnectorTypeFields, ConnectorTypes } from '../../../common/api';
import {
CaseField,
ActionType,
diff --git a/x-pack/plugins/cases/public/components/connector_selector/form.tsx b/x-pack/plugins/cases/public/components/connector_selector/form.tsx
index 71a65ae030d9d..05db3474fdb99 100644
--- a/x-pack/plugins/cases/public/components/connector_selector/form.tsx
+++ b/x-pack/plugins/cases/public/components/connector_selector/form.tsx
@@ -12,7 +12,7 @@ import styled from 'styled-components';
import { FieldHook, getFieldValidityAndErrorMessage } from '../../common/shared_imports';
import { ConnectorsDropdown } from '../configure_cases/connectors_dropdown';
-import { ActionConnector } from '../../../common';
+import { ActionConnector } from '../../../common/api';
interface ConnectorSelectorProps {
connectors: ActionConnector[];
diff --git a/x-pack/plugins/cases/public/components/connectors/card.test.tsx b/x-pack/plugins/cases/public/components/connectors/card.test.tsx
index 384442814ffef..7a07e87a1da4c 100644
--- a/x-pack/plugins/cases/public/components/connectors/card.test.tsx
+++ b/x-pack/plugins/cases/public/components/connectors/card.test.tsx
@@ -8,7 +8,7 @@
import React from 'react';
import { mount } from 'enzyme';
-import { ConnectorTypes } from '../../../common';
+import { ConnectorTypes } from '../../../common/api';
import { useKibana } from '../../common/lib/kibana';
import { connectors } from '../configure_cases/__mock__';
import { ConnectorCard } from './card';
diff --git a/x-pack/plugins/cases/public/components/connectors/card.tsx b/x-pack/plugins/cases/public/components/connectors/card.tsx
index ec4b52c54f707..9870c77fda743 100644
--- a/x-pack/plugins/cases/public/components/connectors/card.tsx
+++ b/x-pack/plugins/cases/public/components/connectors/card.tsx
@@ -9,7 +9,7 @@ import React, { memo, useMemo } from 'react';
import { EuiCard, EuiFlexGroup, EuiFlexItem, EuiIcon, EuiLoadingSpinner } from '@elastic/eui';
import styled from 'styled-components';
-import { ConnectorTypes } from '../../../common';
+import { ConnectorTypes } from '../../../common/api';
import { useKibana } from '../../common/lib/kibana';
import { getConnectorIcon } from '../utils';
diff --git a/x-pack/plugins/cases/public/components/connectors/case/alert_fields.tsx b/x-pack/plugins/cases/public/components/connectors/case/alert_fields.tsx
index a330ae339b338..7cd9b5f6a367c 100644
--- a/x-pack/plugins/cases/public/components/connectors/case/alert_fields.tsx
+++ b/x-pack/plugins/cases/public/components/connectors/case/alert_fields.tsx
@@ -12,7 +12,7 @@ import styled from 'styled-components';
import { EuiCallOut, EuiSpacer } from '@elastic/eui';
import { ActionParamsProps } from '../../../../../triggers_actions_ui/public/types';
-import { CommentType } from '../../../../common';
+import { CommentType } from '../../../../common/api';
import { CaseActionParams } from './types';
import { ExistingCase } from './existing_case';
diff --git a/x-pack/plugins/cases/public/components/connectors/fields_form.tsx b/x-pack/plugins/cases/public/components/connectors/fields_form.tsx
index 062695fa41cc2..56c56436c08c7 100644
--- a/x-pack/plugins/cases/public/components/connectors/fields_form.tsx
+++ b/x-pack/plugins/cases/public/components/connectors/fields_form.tsx
@@ -11,7 +11,7 @@ import { EuiFlexGroup, EuiFlexItem, EuiLoadingSpinner } from '@elastic/eui';
import { CaseActionConnector } from '../types';
import { ConnectorFieldsProps } from './types';
import { getCaseConnectors } from '.';
-import { ConnectorTypeFields } from '../../../common';
+import { ConnectorTypeFields } from '../../../common/api';
interface Props extends Omit, 'connector'> {
connector: CaseActionConnector | null;
diff --git a/x-pack/plugins/cases/public/components/connectors/index.ts b/x-pack/plugins/cases/public/components/connectors/index.ts
index 3aa10c56dd8e9..0d5e33a818d3a 100644
--- a/x-pack/plugins/cases/public/components/connectors/index.ts
+++ b/x-pack/plugins/cases/public/components/connectors/index.ts
@@ -17,7 +17,7 @@ import {
ServiceNowSIRFieldsType,
ResilientFieldsType,
SwimlaneFieldsType,
-} from '../../../common';
+} from '../../../common/api';
export { getActionType as getCaseConnectorUi } from './case';
diff --git a/x-pack/plugins/cases/public/components/connectors/jira/case_fields.tsx b/x-pack/plugins/cases/public/components/connectors/jira/case_fields.tsx
index 6aff81f380015..b9326a08330cd 100644
--- a/x-pack/plugins/cases/public/components/connectors/jira/case_fields.tsx
+++ b/x-pack/plugins/cases/public/components/connectors/jira/case_fields.tsx
@@ -10,7 +10,7 @@ import { map } from 'lodash/fp';
import { EuiFormRow, EuiSelect, EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui';
import * as i18n from './translations';
-import { ConnectorTypes, JiraFieldsType } from '../../../../common';
+import { ConnectorTypes, JiraFieldsType } from '../../../../common/api';
import { useKibana } from '../../../common/lib/kibana';
import { ConnectorFieldsProps } from '../types';
import { useGetIssueTypes } from './use_get_issue_types';
diff --git a/x-pack/plugins/cases/public/components/connectors/jira/index.ts b/x-pack/plugins/cases/public/components/connectors/jira/index.ts
index d59d20177c14d..afb53ffcb87cf 100644
--- a/x-pack/plugins/cases/public/components/connectors/jira/index.ts
+++ b/x-pack/plugins/cases/public/components/connectors/jira/index.ts
@@ -8,7 +8,7 @@
import { lazy } from 'react';
import { CaseConnector } from '../types';
-import { ConnectorTypes, JiraFieldsType } from '../../../../common';
+import { ConnectorTypes, JiraFieldsType } from '../../../../common/api';
import * as i18n from './translations';
export * from './types';
diff --git a/x-pack/plugins/cases/public/components/connectors/jira/search_issues.tsx b/x-pack/plugins/cases/public/components/connectors/jira/search_issues.tsx
index 61866d126dfd7..a9ed87fa81346 100644
--- a/x-pack/plugins/cases/public/components/connectors/jira/search_issues.tsx
+++ b/x-pack/plugins/cases/public/components/connectors/jira/search_issues.tsx
@@ -9,7 +9,7 @@ import React, { useMemo, useEffect, useCallback, useState, memo } from 'react';
import { EuiComboBox, EuiComboBoxOptionOption } from '@elastic/eui';
import { useKibana } from '../../../common/lib/kibana';
-import { ActionConnector } from '../../../../common';
+import { ActionConnector } from '../../../../common/api';
import { useGetIssues } from './use_get_issues';
import { useGetSingleIssue } from './use_get_single_issue';
import * as i18n from './translations';
diff --git a/x-pack/plugins/cases/public/components/connectors/jira/use_get_fields_by_issue_type.tsx b/x-pack/plugins/cases/public/components/connectors/jira/use_get_fields_by_issue_type.tsx
index d762c9d3aaf20..f3d14f02ca1f3 100644
--- a/x-pack/plugins/cases/public/components/connectors/jira/use_get_fields_by_issue_type.tsx
+++ b/x-pack/plugins/cases/public/components/connectors/jira/use_get_fields_by_issue_type.tsx
@@ -7,7 +7,7 @@
import { useState, useEffect, useRef } from 'react';
import { HttpSetup, IToasts } from 'kibana/public';
-import { ActionConnector } from '../../../../common';
+import { ActionConnector } from '../../../../common/api';
import { getFieldsByIssueType } from './api';
import { Fields } from './types';
import * as i18n from './translations';
diff --git a/x-pack/plugins/cases/public/components/connectors/jira/use_get_issue_types.tsx b/x-pack/plugins/cases/public/components/connectors/jira/use_get_issue_types.tsx
index 6f409f1ddef8d..6322b59527e4e 100644
--- a/x-pack/plugins/cases/public/components/connectors/jira/use_get_issue_types.tsx
+++ b/x-pack/plugins/cases/public/components/connectors/jira/use_get_issue_types.tsx
@@ -7,7 +7,7 @@
import { useState, useEffect, useRef } from 'react';
import { HttpSetup, IToasts } from 'kibana/public';
-import { ActionConnector } from '../../../../common';
+import { ActionConnector } from '../../../../common/api';
import { getIssueTypes } from './api';
import { IssueTypes } from './types';
import * as i18n from './translations';
diff --git a/x-pack/plugins/cases/public/components/connectors/jira/use_get_issues.tsx b/x-pack/plugins/cases/public/components/connectors/jira/use_get_issues.tsx
index e4b6f5e4dea01..f4ab31c9daa2d 100644
--- a/x-pack/plugins/cases/public/components/connectors/jira/use_get_issues.tsx
+++ b/x-pack/plugins/cases/public/components/connectors/jira/use_get_issues.tsx
@@ -8,7 +8,7 @@
import { isEmpty, debounce } from 'lodash/fp';
import { useState, useEffect, useRef } from 'react';
import { HttpSetup, ToastsApi } from 'kibana/public';
-import { ActionConnector } from '../../../../common';
+import { ActionConnector } from '../../../../common/api';
import { getIssues } from './api';
import { Issues } from './types';
import * as i18n from './translations';
diff --git a/x-pack/plugins/cases/public/components/connectors/jira/use_get_single_issue.tsx b/x-pack/plugins/cases/public/components/connectors/jira/use_get_single_issue.tsx
index e26940a40d39f..857b07e41d2f2 100644
--- a/x-pack/plugins/cases/public/components/connectors/jira/use_get_single_issue.tsx
+++ b/x-pack/plugins/cases/public/components/connectors/jira/use_get_single_issue.tsx
@@ -7,7 +7,7 @@
import { useState, useEffect, useRef } from 'react';
import { HttpSetup, ToastsApi } from 'kibana/public';
-import { ActionConnector } from '../../../../common';
+import { ActionConnector } from '../../../../common/api';
import { getIssue } from './api';
import { Issue } from './types';
import * as i18n from './translations';
diff --git a/x-pack/plugins/cases/public/components/connectors/mock.ts b/x-pack/plugins/cases/public/components/connectors/mock.ts
index 663b397e6f4fe..2882622b29269 100644
--- a/x-pack/plugins/cases/public/components/connectors/mock.ts
+++ b/x-pack/plugins/cases/public/components/connectors/mock.ts
@@ -5,7 +5,7 @@
* 2.0.
*/
-import { SwimlaneConnectorType } from '../../../common';
+import { SwimlaneConnectorType } from '../../../common/api';
export const connector = {
id: '123',
diff --git a/x-pack/plugins/cases/public/components/connectors/resilient/case_fields.tsx b/x-pack/plugins/cases/public/components/connectors/resilient/case_fields.tsx
index 44f06f92093dd..9dc76fb48cf17 100644
--- a/x-pack/plugins/cases/public/components/connectors/resilient/case_fields.tsx
+++ b/x-pack/plugins/cases/public/components/connectors/resilient/case_fields.tsx
@@ -21,7 +21,7 @@ import { useGetIncidentTypes } from './use_get_incident_types';
import { useGetSeverity } from './use_get_severity';
import * as i18n from './translations';
-import { ConnectorTypes, ResilientFieldsType } from '../../../../common';
+import { ConnectorTypes, ResilientFieldsType } from '../../../../common/api';
import { ConnectorCard } from '../card';
const ResilientFieldsComponent: React.FunctionComponent> =
diff --git a/x-pack/plugins/cases/public/components/connectors/resilient/index.ts b/x-pack/plugins/cases/public/components/connectors/resilient/index.ts
index 8a429c0dea091..0da7448e62a65 100644
--- a/x-pack/plugins/cases/public/components/connectors/resilient/index.ts
+++ b/x-pack/plugins/cases/public/components/connectors/resilient/index.ts
@@ -8,7 +8,7 @@
import { lazy } from 'react';
import { CaseConnector } from '../types';
-import { ConnectorTypes, ResilientFieldsType } from '../../../../common';
+import { ConnectorTypes, ResilientFieldsType } from '../../../../common/api';
import * as i18n from './translations';
export * from './types';
diff --git a/x-pack/plugins/cases/public/components/connectors/resilient/use_get_incident_types.tsx b/x-pack/plugins/cases/public/components/connectors/resilient/use_get_incident_types.tsx
index 530b56de8796d..588e2ee715a88 100644
--- a/x-pack/plugins/cases/public/components/connectors/resilient/use_get_incident_types.tsx
+++ b/x-pack/plugins/cases/public/components/connectors/resilient/use_get_incident_types.tsx
@@ -7,7 +7,7 @@
import { useState, useEffect, useRef } from 'react';
import { HttpSetup, ToastsApi } from 'kibana/public';
-import { ActionConnector } from '../../../../common';
+import { ActionConnector } from '../../../../common/api';
import { getIncidentTypes } from './api';
import * as i18n from './translations';
diff --git a/x-pack/plugins/cases/public/components/connectors/resilient/use_get_severity.tsx b/x-pack/plugins/cases/public/components/connectors/resilient/use_get_severity.tsx
index 8753e3926ffe5..1d647ca1848fe 100644
--- a/x-pack/plugins/cases/public/components/connectors/resilient/use_get_severity.tsx
+++ b/x-pack/plugins/cases/public/components/connectors/resilient/use_get_severity.tsx
@@ -7,7 +7,7 @@
import { useState, useEffect, useRef } from 'react';
import { HttpSetup, ToastsApi } from 'kibana/public';
-import { ActionConnector } from '../../../../common';
+import { ActionConnector } from '../../../../common/api';
import { getSeverity } from './api';
import * as i18n from './translations';
diff --git a/x-pack/plugins/cases/public/components/connectors/servicenow/index.ts b/x-pack/plugins/cases/public/components/connectors/servicenow/index.ts
index 88afd902ccf60..1c466d08e9bcb 100644
--- a/x-pack/plugins/cases/public/components/connectors/servicenow/index.ts
+++ b/x-pack/plugins/cases/public/components/connectors/servicenow/index.ts
@@ -12,7 +12,7 @@ import {
ConnectorTypes,
ServiceNowITSMFieldsType,
ServiceNowSIRFieldsType,
-} from '../../../../common';
+} from '../../../../common/api';
import * as i18n from './translations';
export const getServiceNowITSMCaseConnector = (): CaseConnector => ({
diff --git a/x-pack/plugins/cases/public/components/connectors/servicenow/servicenow_itsm_case_fields.tsx b/x-pack/plugins/cases/public/components/connectors/servicenow/servicenow_itsm_case_fields.tsx
index e24b25065a1c8..521b8609b4eac 100644
--- a/x-pack/plugins/cases/public/components/connectors/servicenow/servicenow_itsm_case_fields.tsx
+++ b/x-pack/plugins/cases/public/components/connectors/servicenow/servicenow_itsm_case_fields.tsx
@@ -10,7 +10,7 @@ import { EuiFormRow, EuiSelect, EuiSpacer, EuiFlexGroup, EuiFlexItem } from '@el
import * as i18n from './translations';
import { ConnectorFieldsProps } from '../types';
-import { ConnectorTypes, ServiceNowITSMFieldsType } from '../../../../common';
+import { ConnectorTypes, ServiceNowITSMFieldsType } from '../../../../common/api';
import { useKibana } from '../../../common/lib/kibana';
import { ConnectorCard } from '../card';
import { useGetChoices } from './use_get_choices';
diff --git a/x-pack/plugins/cases/public/components/connectors/servicenow/servicenow_sir_case_fields.tsx b/x-pack/plugins/cases/public/components/connectors/servicenow/servicenow_sir_case_fields.tsx
index d502b7382664b..095393adb77cb 100644
--- a/x-pack/plugins/cases/public/components/connectors/servicenow/servicenow_sir_case_fields.tsx
+++ b/x-pack/plugins/cases/public/components/connectors/servicenow/servicenow_sir_case_fields.tsx
@@ -8,7 +8,7 @@
import React, { useCallback, useEffect, useMemo, useState, useRef } from 'react';
import { EuiFormRow, EuiSelect, EuiFlexGroup, EuiFlexItem, EuiCheckbox } from '@elastic/eui';
-import { ConnectorTypes, ServiceNowSIRFieldsType } from '../../../../common';
+import { ConnectorTypes, ServiceNowSIRFieldsType } from '../../../../common/api';
import { useKibana } from '../../../common/lib/kibana';
import { ConnectorFieldsProps } from '../types';
import { ConnectorCard } from '../card';
diff --git a/x-pack/plugins/cases/public/components/connectors/servicenow/use_get_choices.test.tsx b/x-pack/plugins/cases/public/components/connectors/servicenow/use_get_choices.test.tsx
index 9f88da9f35eb5..950b17d6f784f 100644
--- a/x-pack/plugins/cases/public/components/connectors/servicenow/use_get_choices.test.tsx
+++ b/x-pack/plugins/cases/public/components/connectors/servicenow/use_get_choices.test.tsx
@@ -8,7 +8,7 @@
import { renderHook } from '@testing-library/react-hooks';
import { useKibana } from '../../../common/lib/kibana';
-import { ActionConnector } from '../../../../common';
+import { ActionConnector } from '../../../../common/api';
import { choices } from '../mock';
import { useGetChoices, UseGetChoices, UseGetChoicesProps } from './use_get_choices';
import * as api from './api';
diff --git a/x-pack/plugins/cases/public/components/connectors/servicenow/use_get_choices.tsx b/x-pack/plugins/cases/public/components/connectors/servicenow/use_get_choices.tsx
index 2c6181dd08eb1..fa8e648a0981e 100644
--- a/x-pack/plugins/cases/public/components/connectors/servicenow/use_get_choices.tsx
+++ b/x-pack/plugins/cases/public/components/connectors/servicenow/use_get_choices.tsx
@@ -7,7 +7,7 @@
import { useState, useEffect, useRef } from 'react';
import { HttpSetup, IToasts } from 'kibana/public';
-import { ActionConnector } from '../../../../common';
+import { ActionConnector } from '../../../../common/api';
import { getChoices } from './api';
import { Choice } from './types';
import * as i18n from './translations';
diff --git a/x-pack/plugins/cases/public/components/connectors/swimlane/case_fields.test.tsx b/x-pack/plugins/cases/public/components/connectors/swimlane/case_fields.test.tsx
index 1a035d92611bd..cca74b83ddb80 100644
--- a/x-pack/plugins/cases/public/components/connectors/swimlane/case_fields.test.tsx
+++ b/x-pack/plugins/cases/public/components/connectors/swimlane/case_fields.test.tsx
@@ -8,7 +8,7 @@
import React from 'react';
import { render, screen } from '@testing-library/react';
-import { SwimlaneConnectorType } from '../../../../common';
+import { SwimlaneConnectorType } from '../../../../common/api';
import Fields from './case_fields';
import * as i18n from './translations';
import { swimlaneConnector as connector } from '../mock';
diff --git a/x-pack/plugins/cases/public/components/connectors/swimlane/case_fields.tsx b/x-pack/plugins/cases/public/components/connectors/swimlane/case_fields.tsx
index b6370504edbb6..a7e584f7c22e2 100644
--- a/x-pack/plugins/cases/public/components/connectors/swimlane/case_fields.tsx
+++ b/x-pack/plugins/cases/public/components/connectors/swimlane/case_fields.tsx
@@ -9,7 +9,7 @@ import React, { useMemo } from 'react';
import { EuiCallOut } from '@elastic/eui';
import * as i18n from './translations';
-import { ConnectorTypes, SwimlaneFieldsType } from '../../../../common';
+import { ConnectorTypes, SwimlaneFieldsType } from '../../../../common/api';
import { ConnectorFieldsProps } from '../types';
import { ConnectorCard } from '../card';
import { connectorValidator } from './validator';
diff --git a/x-pack/plugins/cases/public/components/connectors/swimlane/index.ts b/x-pack/plugins/cases/public/components/connectors/swimlane/index.ts
index bd2eaae9e0174..394b93b961004 100644
--- a/x-pack/plugins/cases/public/components/connectors/swimlane/index.ts
+++ b/x-pack/plugins/cases/public/components/connectors/swimlane/index.ts
@@ -8,7 +8,7 @@
import { lazy } from 'react';
import { CaseConnector } from '../types';
-import { ConnectorTypes, SwimlaneFieldsType } from '../../../../common';
+import { ConnectorTypes, SwimlaneFieldsType } from '../../../../common/api';
import * as i18n from './translations';
export const getCaseConnector = (): CaseConnector => {
diff --git a/x-pack/plugins/cases/public/components/connectors/swimlane/validator.test.ts b/x-pack/plugins/cases/public/components/connectors/swimlane/validator.test.ts
index 552d988c26330..c8cb142232972 100644
--- a/x-pack/plugins/cases/public/components/connectors/swimlane/validator.test.ts
+++ b/x-pack/plugins/cases/public/components/connectors/swimlane/validator.test.ts
@@ -5,7 +5,7 @@
* 2.0.
*/
-import { SwimlaneConnectorType } from '../../../../common';
+import { SwimlaneConnectorType } from '../../../../common/api';
import { swimlaneConnector as connector } from '../mock';
import { isAnyRequiredFieldNotSet, connectorValidator } from './validator';
diff --git a/x-pack/plugins/cases/public/components/connectors/swimlane/validator.ts b/x-pack/plugins/cases/public/components/connectors/swimlane/validator.ts
index 4ead75e5854f9..90d9946d4adb8 100644
--- a/x-pack/plugins/cases/public/components/connectors/swimlane/validator.ts
+++ b/x-pack/plugins/cases/public/components/connectors/swimlane/validator.ts
@@ -5,7 +5,7 @@
* 2.0.
*/
-import { SwimlaneConnectorType } from '../../../../common';
+import { SwimlaneConnectorType } from '../../../../common/api';
import { ValidationConfig } from '../../../common/shared_imports';
import { CaseActionConnector } from '../../types';
diff --git a/x-pack/plugins/cases/public/components/connectors/types.ts b/x-pack/plugins/cases/public/components/connectors/types.ts
index 8bc978152b796..66e5d519ac752 100644
--- a/x-pack/plugins/cases/public/components/connectors/types.ts
+++ b/x-pack/plugins/cases/public/components/connectors/types.ts
@@ -12,10 +12,10 @@ import {
ActionType as ThirdPartySupportedActions,
CaseField,
ConnectorTypeFields,
-} from '../../../common';
+} from '../../../common/api';
import { CaseActionConnector } from '../types';
-export type { ThirdPartyField as AllThirdPartyFields } from '../../../common';
+export type { ThirdPartyField as AllThirdPartyFields } from '../../../common/api';
export interface ThirdPartyField {
label: string;
diff --git a/x-pack/plugins/cases/public/components/create/connector.tsx b/x-pack/plugins/cases/public/components/create/connector.tsx
index 84695d4011f55..aa0eb024a3b0d 100644
--- a/x-pack/plugins/cases/public/components/create/connector.tsx
+++ b/x-pack/plugins/cases/public/components/create/connector.tsx
@@ -8,7 +8,7 @@
import React, { memo, useCallback, useMemo, useEffect } from 'react';
import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
-import { ConnectorTypes, ActionConnector } from '../../../common';
+import { ConnectorTypes, ActionConnector } from '../../../common/api';
import {
UseField,
useFormData,
diff --git a/x-pack/plugins/cases/public/components/create/flyout/create_case_flyout.tsx b/x-pack/plugins/cases/public/components/create/flyout/create_case_flyout.tsx
index e77f72929ecd8..eeebcb29ed2a9 100644
--- a/x-pack/plugins/cases/public/components/create/flyout/create_case_flyout.tsx
+++ b/x-pack/plugins/cases/public/components/create/flyout/create_case_flyout.tsx
@@ -10,7 +10,7 @@ import styled, { createGlobalStyle } from 'styled-components';
import { EuiFlyout, EuiFlyoutHeader, EuiTitle, EuiFlyoutBody } from '@elastic/eui';
import * as i18n from '../translations';
-import { Case } from '../../../../common';
+import { Case } from '../../../../common/ui/types';
import { CreateCaseForm } from '../form';
export interface CreateCaseFlyoutProps {
diff --git a/x-pack/plugins/cases/public/components/create/form.tsx b/x-pack/plugins/cases/public/components/create/form.tsx
index 2c775cb5fd86d..396c72fa54c0d 100644
--- a/x-pack/plugins/cases/public/components/create/form.tsx
+++ b/x-pack/plugins/cases/public/components/create/form.tsx
@@ -23,7 +23,7 @@ import { Tags } from './tags';
import { Connector } from './connector';
import * as i18n from './translations';
import { SyncAlertsToggle } from './sync_alerts_toggle';
-import { ActionConnector, CaseType } from '../../../common';
+import { ActionConnector, CaseType } from '../../../common/api';
import { Case } from '../../containers/types';
import { CasesTimelineIntegration, CasesTimelineIntegrationProvider } from '../timeline_context';
import { InsertTimeline } from '../insert_timeline';
diff --git a/x-pack/plugins/cases/public/components/create/form_context.test.tsx b/x-pack/plugins/cases/public/components/create/form_context.test.tsx
index 15ffa5376e418..6e406386b48ef 100644
--- a/x-pack/plugins/cases/public/components/create/form_context.test.tsx
+++ b/x-pack/plugins/cases/public/components/create/form_context.test.tsx
@@ -10,7 +10,7 @@ import { mount, ReactWrapper } from 'enzyme';
import { act, waitFor } from '@testing-library/react';
import { EuiComboBox, EuiComboBoxOptionOption } from '@elastic/eui';
-import { ConnectorTypes } from '../../../common';
+import { ConnectorTypes } from '../../../common/api';
import { useKibana } from '../../common/lib/kibana';
import { TestProviders } from '../../common/mock';
import { usePostCase } from '../../containers/use_post_case';
diff --git a/x-pack/plugins/cases/public/components/create/form_context.tsx b/x-pack/plugins/cases/public/components/create/form_context.tsx
index a513056ba31a5..b76a4640507be 100644
--- a/x-pack/plugins/cases/public/components/create/form_context.tsx
+++ b/x-pack/plugins/cases/public/components/create/form_context.tsx
@@ -14,7 +14,7 @@ import { usePostPushToService } from '../../containers/use_post_push_to_service'
import { useConnectors } from '../../containers/configure/use_connectors';
import { Case } from '../../containers/types';
-import { CaseType } from '../../../common';
+import { CaseType } from '../../../common/api';
import { UsePostComment, usePostComment } from '../../containers/use_post_comment';
import { useCasesContext } from '../cases_context/use_cases_context';
import { useCasesFeatures } from '../cases_context/use_cases_features';
diff --git a/x-pack/plugins/cases/public/components/create/mock.ts b/x-pack/plugins/cases/public/components/create/mock.ts
index fb00f114f480c..321194826e484 100644
--- a/x-pack/plugins/cases/public/components/create/mock.ts
+++ b/x-pack/plugins/cases/public/components/create/mock.ts
@@ -5,12 +5,8 @@
* 2.0.
*/
-import {
- CasePostRequest,
- CaseType,
- ConnectorTypes,
- SECURITY_SOLUTION_OWNER,
-} from '../../../common';
+import { CasePostRequest, CaseType, ConnectorTypes } from '../../../common/api';
+import { SECURITY_SOLUTION_OWNER } from '../../../common/constants';
import { choices } from '../connectors/mock';
export const sampleTags = ['coke', 'pepsi'];
diff --git a/x-pack/plugins/cases/public/components/create/schema.tsx b/x-pack/plugins/cases/public/components/create/schema.tsx
index 57cf2f63a3fd2..435ebefd943ca 100644
--- a/x-pack/plugins/cases/public/components/create/schema.tsx
+++ b/x-pack/plugins/cases/public/components/create/schema.tsx
@@ -5,7 +5,8 @@
* 2.0.
*/
-import { CasePostRequest, ConnectorTypeFields, MAX_TITLE_LENGTH } from '../../../common';
+import { CasePostRequest, ConnectorTypeFields } from '../../../common/api';
+import { MAX_TITLE_LENGTH } from '../../../common/constants';
import {
FIELD_TYPES,
fieldValidators,
diff --git a/x-pack/plugins/cases/public/components/edit_connector/helpers.test.ts b/x-pack/plugins/cases/public/components/edit_connector/helpers.test.ts
index e20d6b37258bc..e1cc8cefcafb8 100644
--- a/x-pack/plugins/cases/public/components/edit_connector/helpers.test.ts
+++ b/x-pack/plugins/cases/public/components/edit_connector/helpers.test.ts
@@ -5,7 +5,7 @@
* 2.0.
*/
-import { CaseUserActionConnector, ConnectorTypes } from '../../../common';
+import { CaseUserActionConnector, ConnectorTypes } from '../../../common/api';
import { CaseUserActions } from '../../containers/types';
import { getConnectorFieldsFromUserActions } from './helpers';
diff --git a/x-pack/plugins/cases/public/components/edit_connector/helpers.ts b/x-pack/plugins/cases/public/components/edit_connector/helpers.ts
index b97035c458aca..c6027bb7b570e 100644
--- a/x-pack/plugins/cases/public/components/edit_connector/helpers.ts
+++ b/x-pack/plugins/cases/public/components/edit_connector/helpers.ts
@@ -5,7 +5,7 @@
* 2.0.
*/
-import { ConnectorTypeFields } from '../../../common';
+import { ConnectorTypeFields } from '../../../common/api';
import { CaseUserActions } from '../../containers/types';
import { parseStringAsConnector } from '../../common/user_actions';
diff --git a/x-pack/plugins/cases/public/components/edit_connector/index.tsx b/x-pack/plugins/cases/public/components/edit_connector/index.tsx
index 25cb17cdd8c98..efee35a8ba134 100644
--- a/x-pack/plugins/cases/public/components/edit_connector/index.tsx
+++ b/x-pack/plugins/cases/public/components/edit_connector/index.tsx
@@ -21,7 +21,8 @@ import styled from 'styled-components';
import { isEmpty, noop } from 'lodash/fp';
import { FieldConfig, Form, UseField, useForm } from '../../common/shared_imports';
-import { ActionConnector, Case, ConnectorTypeFields } from '../../../common';
+import { Case } from '../../../common/ui/types';
+import { ActionConnector, ConnectorTypeFields } from '../../../common/api';
import { ConnectorSelector } from '../connector_selector/form';
import { ConnectorFieldsForm } from '../connectors/fields_form';
import { CaseUserActions } from '../../containers/types';
diff --git a/x-pack/plugins/cases/public/components/header_page/editable_title.tsx b/x-pack/plugins/cases/public/components/header_page/editable_title.tsx
index 954ee0b031e51..43b210b5a5afb 100644
--- a/x-pack/plugins/cases/public/components/header_page/editable_title.tsx
+++ b/x-pack/plugins/cases/public/components/header_page/editable_title.tsx
@@ -19,7 +19,7 @@ import {
EuiFormRow,
} from '@elastic/eui';
-import { MAX_TITLE_LENGTH } from '../../../common';
+import { MAX_TITLE_LENGTH } from '../../../common/constants';
import * as i18n from './translations';
import { Title } from './title';
diff --git a/x-pack/plugins/cases/public/components/status/button.test.tsx b/x-pack/plugins/cases/public/components/status/button.test.tsx
index a4d4a53ff4a62..32df83b4b2ddf 100644
--- a/x-pack/plugins/cases/public/components/status/button.test.tsx
+++ b/x-pack/plugins/cases/public/components/status/button.test.tsx
@@ -8,7 +8,7 @@
import React from 'react';
import { mount } from 'enzyme';
-import { CaseStatuses } from '../../../common';
+import { CaseStatuses } from '../../../common/api';
import { StatusActionButton } from './button';
describe('StatusActionButton', () => {
diff --git a/x-pack/plugins/cases/public/components/status/button.tsx b/x-pack/plugins/cases/public/components/status/button.tsx
index 675d83c759bc7..c9dcd509c1002 100644
--- a/x-pack/plugins/cases/public/components/status/button.tsx
+++ b/x-pack/plugins/cases/public/components/status/button.tsx
@@ -8,7 +8,7 @@
import React, { memo, useCallback, useMemo } from 'react';
import { EuiButton } from '@elastic/eui';
-import { CaseStatuses, caseStatuses } from '../../../common';
+import { CaseStatuses, caseStatuses } from '../../../common/api';
import { statuses } from './config';
interface Props {
diff --git a/x-pack/plugins/cases/public/components/status/config.ts b/x-pack/plugins/cases/public/components/status/config.ts
index 0202507aa3721..6c5ff18ad977a 100644
--- a/x-pack/plugins/cases/public/components/status/config.ts
+++ b/x-pack/plugins/cases/public/components/status/config.ts
@@ -4,7 +4,8 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
-import { CaseStatuses, StatusAll } from '../../../common';
+import { StatusAll } from '../../../common/ui/types';
+import { CaseStatuses } from '../../../common/api';
import * as i18n from './translations';
import { AllCaseStatus, Statuses } from './types';
diff --git a/x-pack/plugins/cases/public/components/status/stats.test.tsx b/x-pack/plugins/cases/public/components/status/stats.test.tsx
index b2da828da77b0..ea0f54bf8055b 100644
--- a/x-pack/plugins/cases/public/components/status/stats.test.tsx
+++ b/x-pack/plugins/cases/public/components/status/stats.test.tsx
@@ -8,7 +8,7 @@
import React from 'react';
import { mount } from 'enzyme';
-import { CaseStatuses } from '../../../common';
+import { CaseStatuses } from '../../../common/api';
import { Stats } from './stats';
describe('Stats', () => {
diff --git a/x-pack/plugins/cases/public/components/status/stats.tsx b/x-pack/plugins/cases/public/components/status/stats.tsx
index 071ea43746fdc..98720ad75a656 100644
--- a/x-pack/plugins/cases/public/components/status/stats.tsx
+++ b/x-pack/plugins/cases/public/components/status/stats.tsx
@@ -7,7 +7,7 @@
import React, { memo, useMemo } from 'react';
import { EuiDescriptionList, EuiLoadingSpinner } from '@elastic/eui';
-import { CaseStatuses } from '../../../common';
+import { CaseStatuses } from '../../../common/api';
import { statuses } from './config';
export interface Props {
diff --git a/x-pack/plugins/cases/public/components/status/status.test.tsx b/x-pack/plugins/cases/public/components/status/status.test.tsx
index a685256741c43..9ea71dfd52393 100644
--- a/x-pack/plugins/cases/public/components/status/status.test.tsx
+++ b/x-pack/plugins/cases/public/components/status/status.test.tsx
@@ -8,7 +8,7 @@
import React from 'react';
import { mount } from 'enzyme';
-import { CaseStatuses } from '../../../common';
+import { CaseStatuses } from '../../../common/api';
import { Status } from './status';
describe('Stats', () => {
diff --git a/x-pack/plugins/cases/public/components/status/status.tsx b/x-pack/plugins/cases/public/components/status/status.tsx
index 3c186313a151a..47c30a7761264 100644
--- a/x-pack/plugins/cases/public/components/status/status.tsx
+++ b/x-pack/plugins/cases/public/components/status/status.tsx
@@ -11,7 +11,7 @@ import { EuiBadge } from '@elastic/eui';
import { allCaseStatus, statuses } from './config';
import * as i18n from './translations';
-import { CaseStatusWithAllStatus, StatusAll } from '../../../common';
+import { CaseStatusWithAllStatus, StatusAll } from '../../../common/ui/types';
interface Props {
disabled?: boolean;
diff --git a/x-pack/plugins/cases/public/components/status/types.ts b/x-pack/plugins/cases/public/components/status/types.ts
index f8115b8d692b3..0b4a1184633e1 100644
--- a/x-pack/plugins/cases/public/components/status/types.ts
+++ b/x-pack/plugins/cases/public/components/status/types.ts
@@ -6,7 +6,8 @@
*/
import { EuiIconType } from '@elastic/eui/src/components/icon/icon';
-import { CaseStatuses, StatusAllType } from '../../../common';
+import { StatusAllType } from '../../../common/ui/types';
+import { CaseStatuses } from '../../../common/api';
export type AllCaseStatus = Record;
diff --git a/x-pack/plugins/cases/public/components/types.ts b/x-pack/plugins/cases/public/components/types.ts
index 71c846eb922d7..6d72a74fa5d81 100644
--- a/x-pack/plugins/cases/public/components/types.ts
+++ b/x-pack/plugins/cases/public/components/types.ts
@@ -5,4 +5,4 @@
* 2.0.
*/
-export type { CaseActionConnector } from '../../common';
+export type { CaseActionConnector } from '../../common/ui/types';
diff --git a/x-pack/plugins/cases/public/components/use_create_case_modal/create_case_modal.test.tsx b/x-pack/plugins/cases/public/components/use_create_case_modal/create_case_modal.test.tsx
index 7c88d9425bd7e..9ee4a78b7d817 100644
--- a/x-pack/plugins/cases/public/components/use_create_case_modal/create_case_modal.test.tsx
+++ b/x-pack/plugins/cases/public/components/use_create_case_modal/create_case_modal.test.tsx
@@ -10,7 +10,7 @@ import { mount } from 'enzyme';
import { CreateCaseModal } from './create_case_modal';
import { TestProviders } from '../../common/mock';
-import { SECURITY_SOLUTION_OWNER } from '../../../common';
+import { SECURITY_SOLUTION_OWNER } from '../../../common/constants';
import { CreateCase } from '../create';
jest.mock('../create', () => ({
diff --git a/x-pack/plugins/cases/public/components/use_create_case_modal/index.tsx b/x-pack/plugins/cases/public/components/use_create_case_modal/index.tsx
index 2e9f3559d98d0..afae43b462a5b 100644
--- a/x-pack/plugins/cases/public/components/use_create_case_modal/index.tsx
+++ b/x-pack/plugins/cases/public/components/use_create_case_modal/index.tsx
@@ -6,7 +6,7 @@
*/
import React, { useState, useCallback, useMemo } from 'react';
-import { Case } from '../../../common';
+import { Case } from '../../../common/ui/types';
import { CreateCaseModal } from './create_case_modal';
export interface UseCreateCaseModalProps {
diff --git a/x-pack/plugins/cases/public/components/use_push_to_service/index.test.tsx b/x-pack/plugins/cases/public/components/use_push_to_service/index.test.tsx
index 19c0bf58de7bf..cf81b5195a961 100644
--- a/x-pack/plugins/cases/public/components/use_push_to_service/index.test.tsx
+++ b/x-pack/plugins/cases/public/components/use_push_to_service/index.test.tsx
@@ -12,7 +12,7 @@ import { render, screen } from '@testing-library/react';
import '../../common/mock/match_media';
import { usePushToService, ReturnUsePushToService, UsePushToService } from '.';
import { TestProviders } from '../../common/mock';
-import { CaseStatuses, ConnectorTypes } from '../../../common';
+import { CaseStatuses, ConnectorTypes } from '../../../common/api';
import { usePostPushToService } from '../../containers/use_post_push_to_service';
import { basicPush, actionLicenses } from '../../containers/mock';
import { useGetActionLicense } from '../../containers/use_get_action_license';
diff --git a/x-pack/plugins/cases/public/components/use_push_to_service/index.tsx b/x-pack/plugins/cases/public/components/use_push_to_service/index.tsx
index c1ba6f1fbeb25..b079a9d3d1b3d 100644
--- a/x-pack/plugins/cases/public/components/use_push_to_service/index.tsx
+++ b/x-pack/plugins/cases/public/components/use_push_to_service/index.tsx
@@ -19,7 +19,8 @@ import {
getCaseClosedInfo,
} from './helpers';
import * as i18n from './translations';
-import { Case, CaseConnector, ActionConnector, CaseStatuses } from '../../../common';
+import { Case } from '../../../common/ui/types';
+import { CaseConnector, ActionConnector, CaseStatuses } from '../../../common/api';
import { CaseServices } from '../../containers/use_get_case_user_actions';
import { ErrorMessage } from './callout/types';
diff --git a/x-pack/plugins/cases/public/components/user_action_tree/helpers.test.tsx b/x-pack/plugins/cases/public/components/user_action_tree/helpers.test.tsx
index 841f0d36bbf17..5c0d64857d0cb 100644
--- a/x-pack/plugins/cases/public/components/user_action_tree/helpers.test.tsx
+++ b/x-pack/plugins/cases/public/components/user_action_tree/helpers.test.tsx
@@ -8,7 +8,7 @@
import React from 'react';
import { mount } from 'enzyme';
-import { CaseStatuses, ConnectorTypes } from '../../../common';
+import { CaseStatuses, ConnectorTypes } from '../../../common/api';
import { basicPush, getUserAction } from '../../containers/mock';
import {
getLabelTitle,
diff --git a/x-pack/plugins/cases/public/components/user_action_tree/helpers.tsx b/x-pack/plugins/cases/public/components/user_action_tree/helpers.tsx
index a7b4624835882..6dd4032d7cdce 100644
--- a/x-pack/plugins/cases/public/components/user_action_tree/helpers.tsx
+++ b/x-pack/plugins/cases/public/components/user_action_tree/helpers.tsx
@@ -16,15 +16,15 @@ import {
import React, { useContext } from 'react';
import classNames from 'classnames';
import { ThemeContext } from 'styled-components';
+import { Comment } from '../../../common/ui/types';
import {
CaseFullExternalService,
ActionConnector,
CaseStatuses,
CommentType,
- Comment,
CommentRequestActionsType,
noneConnectorId,
-} from '../../../common';
+} from '../../../common/api';
import { CaseUserActions } from '../../containers/types';
import { CaseServices } from '../../containers/use_get_case_user_actions';
import { parseStringAsConnector, parseStringAsExternalService } from '../../common/user_actions';
diff --git a/x-pack/plugins/cases/public/components/user_action_tree/index.test.tsx b/x-pack/plugins/cases/public/components/user_action_tree/index.test.tsx
index e9cd556706646..94d0ca413192a 100644
--- a/x-pack/plugins/cases/public/components/user_action_tree/index.test.tsx
+++ b/x-pack/plugins/cases/public/components/user_action_tree/index.test.tsx
@@ -22,7 +22,7 @@ import {
} from '../../containers/mock';
import { UserActionTree } from '.';
import { TestProviders } from '../../common/mock';
-import { Ecs } from '../../../common';
+import { Ecs } from '../../../common/ui/types';
const fetchUserActions = jest.fn();
const onUpdateField = jest.fn();
diff --git a/x-pack/plugins/cases/public/components/user_action_tree/index.tsx b/x-pack/plugins/cases/public/components/user_action_tree/index.tsx
index 6197303a8d7ce..b5b76f36013c5 100644
--- a/x-pack/plugins/cases/public/components/user_action_tree/index.tsx
+++ b/x-pack/plugins/cases/public/components/user_action_tree/index.tsx
@@ -25,16 +25,14 @@ import * as i18n from './translations';
import { useUpdateComment } from '../../containers/use_update_comment';
import { useCurrentUser } from '../../common/lib/kibana';
import { AddComment, AddCommentRefObject } from '../add_comment';
+import { Case, CaseUserActions, Ecs } from '../../../common/ui/types';
import {
ActionConnector,
ActionsCommentRequestRt,
AlertCommentRequestRt,
- Case,
- CaseUserActions,
CommentType,
ContextTypeUserRt,
- Ecs,
-} from '../../../common';
+} from '../../../common/api';
import { CaseServices } from '../../containers/use_get_case_user_actions';
import { parseStringAsExternalService } from '../../common/user_actions';
import { OnUpdateFields } from '../case_view';
diff --git a/x-pack/plugins/cases/public/components/user_action_tree/user_action_alert_comment_event.test.tsx b/x-pack/plugins/cases/public/components/user_action_tree/user_action_alert_comment_event.test.tsx
index 73a61ed3afd5f..858b54038286d 100644
--- a/x-pack/plugins/cases/public/components/user_action_tree/user_action_alert_comment_event.test.tsx
+++ b/x-pack/plugins/cases/public/components/user_action_tree/user_action_alert_comment_event.test.tsx
@@ -11,7 +11,7 @@ import { mount } from 'enzyme';
import { TestProviders } from '../../common/mock';
import { useKibana } from '../../common/lib/kibana';
import { AlertCommentEvent } from './user_action_alert_comment_event';
-import { CommentType } from '../../../common';
+import { CommentType } from '../../../common/api';
const props = {
alertId: 'alert-id-1',
diff --git a/x-pack/plugins/cases/public/components/user_action_tree/user_action_alert_comment_event.tsx b/x-pack/plugins/cases/public/components/user_action_tree/user_action_alert_comment_event.tsx
index 8f405caa153f1..4236691a16bb2 100644
--- a/x-pack/plugins/cases/public/components/user_action_tree/user_action_alert_comment_event.tsx
+++ b/x-pack/plugins/cases/public/components/user_action_tree/user_action_alert_comment_event.tsx
@@ -10,7 +10,7 @@ import { isEmpty } from 'lodash';
import { EuiText, EuiLoadingSpinner } from '@elastic/eui';
import * as i18n from './translations';
-import { CommentType } from '../../../common';
+import { CommentType } from '../../../common/api';
import { LinkAnchor } from '../links';
import { RuleDetailsNavigation } from './helpers';
diff --git a/x-pack/plugins/cases/public/components/utils.ts b/x-pack/plugins/cases/public/components/utils.ts
index 82d2682e65fad..1fafe5afe6990 100644
--- a/x-pack/plugins/cases/public/components/utils.ts
+++ b/x-pack/plugins/cases/public/components/utils.ts
@@ -6,7 +6,7 @@
*/
import { IconType } from '@elastic/eui';
-import { ConnectorTypes } from '../../common';
+import { ConnectorTypes } from '../../common/api';
import { FieldConfig, ValidationConfig } from '../common/shared_imports';
import { StartPlugins } from '../types';
import { connectorValidator as swimlaneConnectorValidator } from './connectors/swimlane/validator';
diff --git a/x-pack/plugins/cases/public/containers/__mocks__/api.ts b/x-pack/plugins/cases/public/containers/__mocks__/api.ts
index 96e75a96ca115..843a9d81d8013 100644
--- a/x-pack/plugins/cases/public/containers/__mocks__/api.ts
+++ b/x-pack/plugins/cases/public/containers/__mocks__/api.ts
@@ -28,14 +28,14 @@ import {
respReporters,
tags,
} from '../mock';
+import { ResolvedCase } from '../../../common/ui/types';
import {
CasePatchRequest,
CasePostRequest,
CommentRequest,
User,
CaseStatuses,
- ResolvedCase,
-} from '../../../common';
+} from '../../../common/api';
export const getCase = async (
caseId: string,
diff --git a/x-pack/plugins/cases/public/containers/api.test.tsx b/x-pack/plugins/cases/public/containers/api.test.tsx
index 654ade308ed44..c83f5601da64b 100644
--- a/x-pack/plugins/cases/public/containers/api.test.tsx
+++ b/x-pack/plugins/cases/public/containers/api.test.tsx
@@ -7,13 +7,8 @@
import { KibanaServices } from '../common/lib/kibana';
-import {
- CASES_URL,
- ConnectorTypes,
- CommentType,
- CaseStatuses,
- SECURITY_SOLUTION_OWNER,
-} from '../../common';
+import { ConnectorTypes, CommentType, CaseStatuses } from '../../common/api';
+import { CASES_URL, SECURITY_SOLUTION_OWNER } from '../../common/constants';
import {
deleteCases,
diff --git a/x-pack/plugins/cases/public/containers/api.ts b/x-pack/plugins/cases/public/containers/api.ts
index 14f617b19db52..81bd6b39be5fd 100644
--- a/x-pack/plugins/cases/public/containers/api.ts
+++ b/x-pack/plugins/cases/public/containers/api.ts
@@ -7,15 +7,12 @@
import { assign, omit } from 'lodash';
+import { StatusAll, ResolvedCase } from '../../common/ui/types';
import {
- CASE_REPORTERS_URL,
- CASE_STATUS_URL,
- CASE_TAGS_URL,
CasePatchRequest,
CasePostRequest,
CaseResponse,
CaseResolveResponse,
- CASES_URL,
CasesFindResponse,
CasesResponse,
CasesStatusResponse,
@@ -29,16 +26,19 @@ import {
getCaseUserActionUrl,
getSubCaseDetailsUrl,
getSubCaseUserActionUrl,
- StatusAll,
- SUB_CASE_DETAILS_URL,
- SUB_CASES_PATCH_DEL_URL,
SubCasePatchRequest,
SubCaseResponse,
SubCasesResponse,
User,
- ResolvedCase,
-} from '../../common';
-
+} from '../../common/api';
+import {
+ CASE_REPORTERS_URL,
+ CASE_STATUS_URL,
+ CASE_TAGS_URL,
+ CASES_URL,
+ SUB_CASE_DETAILS_URL,
+ SUB_CASES_PATCH_DEL_URL,
+} from '../../common/constants';
import { getAllConnectorTypesUrl } from '../../common/utils/connectors_api';
import { KibanaServices } from '../common/lib/kibana';
diff --git a/x-pack/plugins/cases/public/containers/configure/__mocks__/api.ts b/x-pack/plugins/cases/public/containers/configure/__mocks__/api.ts
index ea4b92706b4d1..10cfde0c5ef9c 100644
--- a/x-pack/plugins/cases/public/containers/configure/__mocks__/api.ts
+++ b/x-pack/plugins/cases/public/containers/configure/__mocks__/api.ts
@@ -10,7 +10,7 @@ import {
CasesConfigureRequest,
ActionConnector,
ActionTypeConnector,
-} from '../../../../common';
+} from '../../../../common/api';
import { ApiProps } from '../../types';
import { CaseConfigure } from '../types';
diff --git a/x-pack/plugins/cases/public/containers/configure/api.test.ts b/x-pack/plugins/cases/public/containers/configure/api.test.ts
index 141b5e3711ceb..a315a455ec2a2 100644
--- a/x-pack/plugins/cases/public/containers/configure/api.test.ts
+++ b/x-pack/plugins/cases/public/containers/configure/api.test.ts
@@ -19,7 +19,8 @@ import {
caseConfigurationResposeMock,
caseConfigurationCamelCaseResponseMock,
} from './mock';
-import { ConnectorTypes, SECURITY_SOLUTION_OWNER } from '../../../common';
+import { ConnectorTypes } from '../../../common/api';
+import { SECURITY_SOLUTION_OWNER } from '../../../common/constants';
import { KibanaServices } from '../../common/lib/kibana';
const abortCtrl = new AbortController();
diff --git a/x-pack/plugins/cases/public/containers/configure/api.ts b/x-pack/plugins/cases/public/containers/configure/api.ts
index 1fd358e4dae9d..32202afc34881 100644
--- a/x-pack/plugins/cases/public/containers/configure/api.ts
+++ b/x-pack/plugins/cases/public/containers/configure/api.ts
@@ -10,14 +10,13 @@ import { getAllConnectorTypesUrl } from '../../../common/utils/connectors_api';
import {
ActionConnector,
ActionTypeConnector,
- CASE_CONFIGURE_CONNECTORS_URL,
- CASE_CONFIGURE_URL,
CasesConfigurePatch,
CasesConfigureRequest,
CasesConfigureResponse,
CasesConfigurationsResponse,
getCaseConfigurationDetailsUrl,
-} from '../../../common';
+} from '../../../common/api';
+import { CASE_CONFIGURE_CONNECTORS_URL, CASE_CONFIGURE_URL } from '../../../common/constants';
import { KibanaServices } from '../../common/lib/kibana';
import { ApiProps } from '../types';
diff --git a/x-pack/plugins/cases/public/containers/configure/mock.ts b/x-pack/plugins/cases/public/containers/configure/mock.ts
index a5483e524e92d..bbcf420324c83 100644
--- a/x-pack/plugins/cases/public/containers/configure/mock.ts
+++ b/x-pack/plugins/cases/public/containers/configure/mock.ts
@@ -11,8 +11,8 @@ import {
CasesConfigureResponse,
CasesConfigureRequest,
ConnectorTypes,
- SECURITY_SOLUTION_OWNER,
-} from '../../../common';
+} from '../../../common/api';
+import { SECURITY_SOLUTION_OWNER } from '../../../common/constants';
import { CaseConfigure, CaseConnectorMapping } from './types';
export const mappings: CaseConnectorMapping[] = [
diff --git a/x-pack/plugins/cases/public/containers/configure/types.ts b/x-pack/plugins/cases/public/containers/configure/types.ts
index 5ee09add196bd..55401a2fbfd2c 100644
--- a/x-pack/plugins/cases/public/containers/configure/types.ts
+++ b/x-pack/plugins/cases/public/containers/configure/types.ts
@@ -15,7 +15,7 @@ import {
CasesConfigure,
ClosureType,
ThirdPartyField,
-} from '../../../common';
+} from '../../../common/api';
export type {
ActionConnector,
diff --git a/x-pack/plugins/cases/public/containers/configure/use_configure.test.tsx b/x-pack/plugins/cases/public/containers/configure/use_configure.test.tsx
index 1814020de8465..1c9139b913617 100644
--- a/x-pack/plugins/cases/public/containers/configure/use_configure.test.tsx
+++ b/x-pack/plugins/cases/public/containers/configure/use_configure.test.tsx
@@ -15,7 +15,7 @@ import {
} from './use_configure';
import { mappings, caseConfigurationCamelCaseResponseMock } from './mock';
import * as api from './api';
-import { ConnectorTypes } from '../../../common';
+import { ConnectorTypes } from '../../../common/api';
import { TestProviders } from '../../common/mock';
const mockErrorToast = jest.fn();
diff --git a/x-pack/plugins/cases/public/containers/configure/use_configure.tsx b/x-pack/plugins/cases/public/containers/configure/use_configure.tsx
index afac625c7682e..21c6e9e0b388e 100644
--- a/x-pack/plugins/cases/public/containers/configure/use_configure.tsx
+++ b/x-pack/plugins/cases/public/containers/configure/use_configure.tsx
@@ -10,7 +10,7 @@ import { getCaseConfigure, patchCaseConfigure, postCaseConfigure } from './api';
import * as i18n from './translations';
import { ClosureType, CaseConfigure, CaseConnector, CaseConnectorMapping } from './types';
-import { ConnectorTypes } from '../../../common';
+import { ConnectorTypes } from '../../../common/api';
import { useToasts } from '../../common/lib/kibana';
import { useCasesContext } from '../../components/cases_context/use_cases_context';
diff --git a/x-pack/plugins/cases/public/containers/mock.ts b/x-pack/plugins/cases/public/containers/mock.ts
index f7d1daabd60ea..92fa8caa3ac5b 100644
--- a/x-pack/plugins/cases/public/containers/mock.ts
+++ b/x-pack/plugins/cases/public/containers/mock.ts
@@ -7,6 +7,8 @@
import { ActionLicense, AllCases, Case, CasesStatus, CaseUserActions, Comment } from './types';
+import { isCreateConnector, isPush, isUpdateConnector } from '../../common/utils/user_actions';
+import { ResolvedCase } from '../../common/ui/types';
import {
AssociationType,
CaseUserActionConnector,
@@ -20,14 +22,10 @@ import {
CommentResponse,
CommentType,
ConnectorTypes,
- ResolvedCase,
- isCreateConnector,
- isPush,
- isUpdateConnector,
- SECURITY_SOLUTION_OWNER,
UserAction,
UserActionField,
-} from '../../common';
+} from '../../common/api';
+import { SECURITY_SOLUTION_OWNER } from '../../common/constants';
import { UseGetCasesState, DEFAULT_FILTER_OPTIONS, DEFAULT_QUERY_PARAMS } from './use_get_cases';
export { connectorsMock } from './configure/mock';
diff --git a/x-pack/plugins/cases/public/containers/use_bulk_update_case.test.tsx b/x-pack/plugins/cases/public/containers/use_bulk_update_case.test.tsx
index 67f202e6adbad..d00b361828a6e 100644
--- a/x-pack/plugins/cases/public/containers/use_bulk_update_case.test.tsx
+++ b/x-pack/plugins/cases/public/containers/use_bulk_update_case.test.tsx
@@ -6,7 +6,7 @@
*/
import { renderHook, act } from '@testing-library/react-hooks';
-import { CaseStatuses } from '../../common';
+import { CaseStatuses } from '../../common/api';
import { useUpdateCases, UseUpdateCases } from './use_bulk_update_case';
import { basicCase } from './mock';
import * as api from './api';
diff --git a/x-pack/plugins/cases/public/containers/use_bulk_update_case.tsx b/x-pack/plugins/cases/public/containers/use_bulk_update_case.tsx
index 449ca0ab77f13..715b0c611c3b8 100644
--- a/x-pack/plugins/cases/public/containers/use_bulk_update_case.tsx
+++ b/x-pack/plugins/cases/public/containers/use_bulk_update_case.tsx
@@ -6,7 +6,7 @@
*/
import { useCallback, useReducer, useRef, useEffect } from 'react';
-import { CaseStatuses } from '../../common';
+import { CaseStatuses } from '../../common/api';
import * as i18n from './translations';
import { patchCasesStatus } from './api';
import { BulkUpdateStatus, Case } from './types';
diff --git a/x-pack/plugins/cases/public/containers/use_delete_cases.test.tsx b/x-pack/plugins/cases/public/containers/use_delete_cases.test.tsx
index 691af580b333a..307dc0941e398 100644
--- a/x-pack/plugins/cases/public/containers/use_delete_cases.test.tsx
+++ b/x-pack/plugins/cases/public/containers/use_delete_cases.test.tsx
@@ -7,7 +7,7 @@
import { renderHook, act } from '@testing-library/react-hooks';
-import { CaseType } from '../../common';
+import { CaseType } from '../../common/api';
import { useDeleteCases, UseDeleteCase } from './use_delete_cases';
import * as api from './api';
diff --git a/x-pack/plugins/cases/public/containers/use_get_action_license.tsx b/x-pack/plugins/cases/public/containers/use_get_action_license.tsx
index e4ea6d05011a7..7618f8c06d9ae 100644
--- a/x-pack/plugins/cases/public/containers/use_get_action_license.tsx
+++ b/x-pack/plugins/cases/public/containers/use_get_action_license.tsx
@@ -11,7 +11,7 @@ import { useToasts } from '../common/lib/kibana';
import { getActionLicense } from './api';
import * as i18n from './translations';
import { ActionLicense } from './types';
-import { ConnectorTypes } from '../../common';
+import { ConnectorTypes } from '../../common/api';
export interface ActionLicenseState {
actionLicense: ActionLicense | null;
diff --git a/x-pack/plugins/cases/public/containers/use_get_case_user_actions.tsx b/x-pack/plugins/cases/public/containers/use_get_case_user_actions.tsx
index 36d600c3f1c9d..d3864097f5fee 100644
--- a/x-pack/plugins/cases/public/containers/use_get_case_user_actions.tsx
+++ b/x-pack/plugins/cases/public/containers/use_get_case_user_actions.tsx
@@ -9,13 +9,8 @@ import { isEmpty, uniqBy } from 'lodash/fp';
import { useCallback, useEffect, useState, useRef } from 'react';
import deepEqual from 'fast-deep-equal';
-import {
- CaseFullExternalService,
- CaseConnector,
- CaseExternalService,
- CaseUserActions,
- ElasticUser,
-} from '../../common';
+import { ElasticUser, CaseUserActions, CaseExternalService } from '../../common/ui/types';
+import { CaseFullExternalService, CaseConnector } from '../../common/api';
import { getCaseUserActions, getSubCaseUserActions } from './api';
import * as i18n from './translations';
import { convertToCamelCase } from './utils';
diff --git a/x-pack/plugins/cases/public/containers/use_get_cases.test.tsx b/x-pack/plugins/cases/public/containers/use_get_cases.test.tsx
index 97de7a9073269..99fbf48665138 100644
--- a/x-pack/plugins/cases/public/containers/use_get_cases.test.tsx
+++ b/x-pack/plugins/cases/public/containers/use_get_cases.test.tsx
@@ -7,7 +7,8 @@
import React from 'react';
import { renderHook, act } from '@testing-library/react-hooks';
-import { CaseStatuses, SECURITY_SOLUTION_OWNER } from '../../common';
+import { CaseStatuses } from '../../common/api';
+import { SECURITY_SOLUTION_OWNER } from '../../common/constants';
import {
DEFAULT_FILTER_OPTIONS,
DEFAULT_QUERY_PARAMS,
diff --git a/x-pack/plugins/cases/public/containers/use_get_cases_status.test.tsx b/x-pack/plugins/cases/public/containers/use_get_cases_status.test.tsx
index 93da0ecbd14ae..2e3e42255145d 100644
--- a/x-pack/plugins/cases/public/containers/use_get_cases_status.test.tsx
+++ b/x-pack/plugins/cases/public/containers/use_get_cases_status.test.tsx
@@ -11,7 +11,7 @@ import { useGetCasesStatus, UseGetCasesStatus } from './use_get_cases_status';
import { casesStatus } from './mock';
import * as api from './api';
import { TestProviders } from '../common/mock';
-import { SECURITY_SOLUTION_OWNER } from '../../common';
+import { SECURITY_SOLUTION_OWNER } from '../../common/constants';
jest.mock('./api');
jest.mock('../common/lib/kibana');
diff --git a/x-pack/plugins/cases/public/containers/use_get_reporters.test.tsx b/x-pack/plugins/cases/public/containers/use_get_reporters.test.tsx
index 21f9352a7cbc0..38d47d3aa9cbb 100644
--- a/x-pack/plugins/cases/public/containers/use_get_reporters.test.tsx
+++ b/x-pack/plugins/cases/public/containers/use_get_reporters.test.tsx
@@ -11,7 +11,7 @@ import { useGetReporters, UseGetReporters } from './use_get_reporters';
import { reporters, respReporters } from './mock';
import * as api from './api';
import { TestProviders } from '../common/mock';
-import { SECURITY_SOLUTION_OWNER } from '../../common';
+import { SECURITY_SOLUTION_OWNER } from '../../common/constants';
jest.mock('./api');
jest.mock('../common/lib/kibana');
diff --git a/x-pack/plugins/cases/public/containers/use_get_reporters.tsx b/x-pack/plugins/cases/public/containers/use_get_reporters.tsx
index 881933419d60b..ce8aa4b961c23 100644
--- a/x-pack/plugins/cases/public/containers/use_get_reporters.tsx
+++ b/x-pack/plugins/cases/public/containers/use_get_reporters.tsx
@@ -8,7 +8,7 @@
import { useCallback, useEffect, useState, useRef } from 'react';
import { isEmpty } from 'lodash/fp';
-import { User } from '../../common';
+import { User } from '../../common/api';
import { getReporters } from './api';
import * as i18n from './translations';
import { useToasts } from '../common/lib/kibana';
diff --git a/x-pack/plugins/cases/public/containers/use_get_tags.test.tsx b/x-pack/plugins/cases/public/containers/use_get_tags.test.tsx
index b2bf4737356cc..2607129e5655d 100644
--- a/x-pack/plugins/cases/public/containers/use_get_tags.test.tsx
+++ b/x-pack/plugins/cases/public/containers/use_get_tags.test.tsx
@@ -11,7 +11,7 @@ import { useGetTags, UseGetTags } from './use_get_tags';
import { tags } from './mock';
import * as api from './api';
import { TestProviders } from '../common/mock';
-import { SECURITY_SOLUTION_OWNER } from '../../common';
+import { SECURITY_SOLUTION_OWNER } from '../../common/constants';
jest.mock('./api');
jest.mock('../common/lib/kibana');
diff --git a/x-pack/plugins/cases/public/containers/use_post_case.test.tsx b/x-pack/plugins/cases/public/containers/use_post_case.test.tsx
index d2b638b4c846f..5d5b6ced44afc 100644
--- a/x-pack/plugins/cases/public/containers/use_post_case.test.tsx
+++ b/x-pack/plugins/cases/public/containers/use_post_case.test.tsx
@@ -8,7 +8,8 @@
import { renderHook, act } from '@testing-library/react-hooks';
import { usePostCase, UsePostCase } from './use_post_case';
import * as api from './api';
-import { ConnectorTypes, SECURITY_SOLUTION_OWNER } from '../../common';
+import { ConnectorTypes } from '../../common/api';
+import { SECURITY_SOLUTION_OWNER } from '../../common/constants';
import { basicCasePost } from './mock';
jest.mock('./api');
diff --git a/x-pack/plugins/cases/public/containers/use_post_case.tsx b/x-pack/plugins/cases/public/containers/use_post_case.tsx
index f13c250b96a3e..dc23c503b333b 100644
--- a/x-pack/plugins/cases/public/containers/use_post_case.tsx
+++ b/x-pack/plugins/cases/public/containers/use_post_case.tsx
@@ -6,7 +6,7 @@
*/
import { useReducer, useCallback, useRef, useEffect } from 'react';
-import { CasePostRequest } from '../../common';
+import { CasePostRequest } from '../../common/api';
import { postCase } from './api';
import * as i18n from './translations';
import { Case } from './types';
diff --git a/x-pack/plugins/cases/public/containers/use_post_comment.test.tsx b/x-pack/plugins/cases/public/containers/use_post_comment.test.tsx
index 8a86d9becdfde..dd9d73cff9bae 100644
--- a/x-pack/plugins/cases/public/containers/use_post_comment.test.tsx
+++ b/x-pack/plugins/cases/public/containers/use_post_comment.test.tsx
@@ -7,7 +7,8 @@
import { renderHook, act } from '@testing-library/react-hooks';
-import { CommentType, SECURITY_SOLUTION_OWNER } from '../../common';
+import { CommentType } from '../../common/api';
+import { SECURITY_SOLUTION_OWNER } from '../../common/constants';
import { usePostComment, UsePostComment } from './use_post_comment';
import { basicCaseId, basicSubCaseId } from './mock';
import * as api from './api';
diff --git a/x-pack/plugins/cases/public/containers/use_post_comment.tsx b/x-pack/plugins/cases/public/containers/use_post_comment.tsx
index 2d4437826092a..d796c5035ff9d 100644
--- a/x-pack/plugins/cases/public/containers/use_post_comment.tsx
+++ b/x-pack/plugins/cases/public/containers/use_post_comment.tsx
@@ -6,7 +6,7 @@
*/
import { useReducer, useCallback, useRef, useEffect } from 'react';
-import { CommentRequest } from '../../common';
+import { CommentRequest } from '../../common/api';
import { postComment } from './api';
import * as i18n from './translations';
diff --git a/x-pack/plugins/cases/public/containers/use_post_push_to_service.test.tsx b/x-pack/plugins/cases/public/containers/use_post_push_to_service.test.tsx
index 18e3c4be493b8..dedde459ad557 100644
--- a/x-pack/plugins/cases/public/containers/use_post_push_to_service.test.tsx
+++ b/x-pack/plugins/cases/public/containers/use_post_push_to_service.test.tsx
@@ -9,7 +9,7 @@ import { renderHook, act } from '@testing-library/react-hooks';
import { usePostPushToService, UsePostPushToService } from './use_post_push_to_service';
import { pushedCase } from './mock';
import * as api from './api';
-import { CaseConnector, ConnectorTypes } from '../../common';
+import { CaseConnector, ConnectorTypes } from '../../common/api';
jest.mock('./api');
jest.mock('../common/lib/kibana');
diff --git a/x-pack/plugins/cases/public/containers/use_post_push_to_service.tsx b/x-pack/plugins/cases/public/containers/use_post_push_to_service.tsx
index f4cf5b012e84f..90f1fbe212a02 100644
--- a/x-pack/plugins/cases/public/containers/use_post_push_to_service.tsx
+++ b/x-pack/plugins/cases/public/containers/use_post_push_to_service.tsx
@@ -6,7 +6,7 @@
*/
import { useReducer, useCallback, useRef, useEffect } from 'react';
-import { CaseConnector } from '../../common';
+import { CaseConnector } from '../../common/api';
import { pushCase } from './api';
import * as i18n from './translations';
diff --git a/x-pack/plugins/cases/public/containers/use_update_case.tsx b/x-pack/plugins/cases/public/containers/use_update_case.tsx
index afdc33bcc25e4..42e861d300341 100644
--- a/x-pack/plugins/cases/public/containers/use_update_case.tsx
+++ b/x-pack/plugins/cases/public/containers/use_update_case.tsx
@@ -9,7 +9,8 @@ import { useReducer, useCallback, useRef, useEffect } from 'react';
import { useToasts } from '../common/lib/kibana';
import { patchCase, patchSubCase } from './api';
-import { UpdateKey, UpdateByKey, CaseStatuses } from '../../common';
+import { UpdateKey, UpdateByKey } from '../../common/ui/types';
+import { CaseStatuses } from '../../common/api';
import * as i18n from './translations';
import { createUpdateSuccessToaster } from './utils';
diff --git a/x-pack/plugins/cases/public/containers/use_update_comment.test.tsx b/x-pack/plugins/cases/public/containers/use_update_comment.test.tsx
index 14cc4dfab3599..836ec10e608a6 100644
--- a/x-pack/plugins/cases/public/containers/use_update_comment.test.tsx
+++ b/x-pack/plugins/cases/public/containers/use_update_comment.test.tsx
@@ -11,7 +11,7 @@ import { useUpdateComment, UseUpdateComment } from './use_update_comment';
import { basicCase, basicCaseCommentPatch, basicSubCaseId } from './mock';
import * as api from './api';
import { TestProviders } from '../common/mock';
-import { SECURITY_SOLUTION_OWNER } from '../../common';
+import { SECURITY_SOLUTION_OWNER } from '../../common/constants';
jest.mock('./api');
jest.mock('../common/lib/kibana');
diff --git a/x-pack/plugins/cases/public/containers/utils.ts b/x-pack/plugins/cases/public/containers/utils.ts
index 458899e5f53c9..938724a632dcb 100644
--- a/x-pack/plugins/cases/public/containers/utils.ts
+++ b/x-pack/plugins/cases/public/containers/utils.ts
@@ -32,7 +32,7 @@ import {
CasePatchRequest,
CaseResolveResponse,
CaseResolveResponseRt,
-} from '../../common';
+} from '../../common/api';
import { AllCases, Case, UpdateByKey } from './types';
import * as i18n from './translations';
diff --git a/x-pack/plugins/cases/public/plugin.ts b/x-pack/plugins/cases/public/plugin.ts
index b6b9643ea5856..48371f65b49e1 100644
--- a/x-pack/plugins/cases/public/plugin.ts
+++ b/x-pack/plugins/cases/public/plugin.ts
@@ -15,7 +15,8 @@ import {
getAllCasesSelectorModalLazy,
getCreateCaseFlyoutLazy,
} from './methods';
-import { CasesUiConfigType, ENABLE_CASE_CONNECTOR } from '../common';
+import { CasesUiConfigType } from '../common/ui/types';
+import { ENABLE_CASE_CONNECTOR } from '../common/constants';
/**
* @public
diff --git a/x-pack/plugins/cases/server/authorization/authorization.ts b/x-pack/plugins/cases/server/authorization/authorization.ts
index 7a474ff4db402..453d5c5783f8c 100644
--- a/x-pack/plugins/cases/server/authorization/authorization.ts
+++ b/x-pack/plugins/cases/server/authorization/authorization.ts
@@ -12,7 +12,7 @@ import { PluginStartContract as FeaturesPluginStart } from '../../../features/se
import { AuthFilterHelpers, GetSpaceFn, OwnerEntity } from './types';
import { getOwnersFilter } from './utils';
import { AuthorizationAuditLogger, OperationDetails } from '.';
-import { createCaseError } from '../common';
+import { createCaseError } from '../common/error';
/**
* This class handles ensuring that the user making a request has the correct permissions
diff --git a/x-pack/plugins/cases/server/authorization/index.ts b/x-pack/plugins/cases/server/authorization/index.ts
index c5208177554b5..057e85b460c2e 100644
--- a/x-pack/plugins/cases/server/authorization/index.ts
+++ b/x-pack/plugins/cases/server/authorization/index.ts
@@ -11,7 +11,7 @@ import {
CASE_CONFIGURE_SAVED_OBJECT,
CASE_SAVED_OBJECT,
CASE_USER_ACTION_SAVED_OBJECT,
-} from '../../common';
+} from '../../common/constants';
import { Verbs, ReadOperations, WriteOperations, OperationDetails } from './types';
export * from './authorization';
diff --git a/x-pack/plugins/cases/server/authorization/utils.test.ts b/x-pack/plugins/cases/server/authorization/utils.test.ts
index 2afffbbb768b8..7717edfc909ef 100644
--- a/x-pack/plugins/cases/server/authorization/utils.test.ts
+++ b/x-pack/plugins/cases/server/authorization/utils.test.ts
@@ -6,7 +6,7 @@
*/
import { nodeBuilder } from '@kbn/es-query';
-import { OWNER_FIELD } from '../../common';
+import { OWNER_FIELD } from '../../common/api';
import {
combineFilterWithAuthorizationFilter,
ensureFieldIsSafeForQuery,
diff --git a/x-pack/plugins/cases/server/authorization/utils.ts b/x-pack/plugins/cases/server/authorization/utils.ts
index f3a8512548430..ac88f96fb4e14 100644
--- a/x-pack/plugins/cases/server/authorization/utils.ts
+++ b/x-pack/plugins/cases/server/authorization/utils.ts
@@ -7,7 +7,7 @@
import { remove, uniq } from 'lodash';
import { nodeBuilder, KueryNode } from '@kbn/es-query';
-import { OWNER_FIELD } from '../../common';
+import { OWNER_FIELD } from '../../common/api';
export const getOwnersFilter = (
savedObjectType: string,
diff --git a/x-pack/plugins/cases/server/client/alerts/get.ts b/x-pack/plugins/cases/server/client/alerts/get.ts
index d95c4f536abc3..831ab0b05ed17 100644
--- a/x-pack/plugins/cases/server/client/alerts/get.ts
+++ b/x-pack/plugins/cases/server/client/alerts/get.ts
@@ -7,7 +7,7 @@
import { CasesClientGetAlertsResponse } from './types';
import { CasesClientArgs } from '..';
-import { AlertInfo } from '../../common';
+import { AlertInfo } from '../../common/types';
export const getAlerts = async (
alertsInfo: AlertInfo[],
diff --git a/x-pack/plugins/cases/server/client/alerts/types.ts b/x-pack/plugins/cases/server/client/alerts/types.ts
index 95cd9ae33bff9..cba18cc26e8b7 100644
--- a/x-pack/plugins/cases/server/client/alerts/types.ts
+++ b/x-pack/plugins/cases/server/client/alerts/types.ts
@@ -6,7 +6,7 @@
*/
import { CaseStatuses } from '../../../common/api';
-import { AlertInfo } from '../../common';
+import { AlertInfo } from '../../common/types';
interface Alert {
id: string;
diff --git a/x-pack/plugins/cases/server/client/attachments/add.ts b/x-pack/plugins/cases/server/client/attachments/add.ts
index 84dbc8921f0e4..88b4f493d694c 100644
--- a/x-pack/plugins/cases/server/client/attachments/add.ts
+++ b/x-pack/plugins/cases/server/client/attachments/add.ts
@@ -21,31 +21,30 @@ import { LensServerPluginSetup } from '../../../../lens/server';
import {
AlertCommentRequestRt,
- CASE_COMMENT_SAVED_OBJECT,
CaseResponse,
CaseStatuses,
CaseType,
CommentRequest,
CommentRequestRt,
CommentType,
- ENABLE_CASE_CONNECTOR,
- MAX_GENERATED_ALERTS_PER_SUB_CASE,
SubCaseAttributes,
throwErrors,
User,
-} from '../../../common';
+} from '../../../common/api';
+import {
+ CASE_COMMENT_SAVED_OBJECT,
+ ENABLE_CASE_CONNECTOR,
+ MAX_GENERATED_ALERTS_PER_SUB_CASE,
+} from '../../../common/constants';
import {
buildCaseUserActionItem,
buildCommentUserActionItem,
} from '../../services/user_actions/helpers';
import { AttachmentService, CasesService, CaseUserActionService } from '../../services';
-import {
- createCaseError,
- CommentableCase,
- createAlertUpdateRequest,
- isCommentRequestTypeGenAlert,
-} from '../../common';
+import { CommentableCase } from '../../common/models';
+import { createCaseError } from '../../common/error';
+import { createAlertUpdateRequest, isCommentRequestTypeGenAlert } from '../../common/utils';
import { CasesClientArgs, CasesClientInternal } from '..';
import { decodeCommentRequest } from '../utils';
diff --git a/x-pack/plugins/cases/server/client/attachments/client.ts b/x-pack/plugins/cases/server/client/attachments/client.ts
index a07633c0dd38c..d71496b764824 100644
--- a/x-pack/plugins/cases/server/client/attachments/client.ts
+++ b/x-pack/plugins/cases/server/client/attachments/client.ts
@@ -5,7 +5,7 @@
* 2.0.
*/
-import { AlertResponse, CommentResponse } from '../../../common';
+import { AlertResponse, CommentResponse } from '../../../common/api';
import { CasesClient } from '../client';
import { CasesClientInternal } from '../client_internal';
diff --git a/x-pack/plugins/cases/server/client/attachments/delete.ts b/x-pack/plugins/cases/server/client/attachments/delete.ts
index 89d097c5334b7..17fcd2235a034 100644
--- a/x-pack/plugins/cases/server/client/attachments/delete.ts
+++ b/x-pack/plugins/cases/server/client/attachments/delete.ts
@@ -9,16 +9,16 @@ import Boom from '@hapi/boom';
import pMap from 'p-map';
import { SavedObject } from 'kibana/public';
+import { AssociationType, CommentAttributes } from '../../../common/api';
import {
- AssociationType,
CASE_SAVED_OBJECT,
- CommentAttributes,
MAX_CONCURRENT_SEARCHES,
SUB_CASE_SAVED_OBJECT,
-} from '../../../common';
+} from '../../../common/constants';
import { CasesClientArgs } from '../types';
import { buildCommentUserActionItem } from '../../services/user_actions/helpers';
-import { createCaseError, checkEnabledCaseConnectorOrThrow } from '../../common';
+import { createCaseError } from '../../common/error';
+import { checkEnabledCaseConnectorOrThrow } from '../../common/utils';
import { Operations } from '../../authorization';
/**
diff --git a/x-pack/plugins/cases/server/client/attachments/get.ts b/x-pack/plugins/cases/server/client/attachments/get.ts
index 9a06c5142600a..bbef0fe0ce348 100644
--- a/x-pack/plugins/cases/server/client/attachments/get.ts
+++ b/x-pack/plugins/cases/server/client/attachments/get.ts
@@ -18,18 +18,18 @@ import {
CommentResponseRt,
CommentsResponse,
CommentsResponseRt,
- ENABLE_CASE_CONNECTOR,
FindQueryParams,
-} from '../../../common';
+} from '../../../common/api';
+import { ENABLE_CASE_CONNECTOR } from '../../../common/constants';
import {
- createCaseError,
checkEnabledCaseConnectorOrThrow,
defaultSortField,
transformComments,
flattenCommentSavedObject,
flattenCommentSavedObjects,
getIDsAndIndicesAsArrays,
-} from '../../common';
+} from '../../common/utils';
+import { createCaseError } from '../../common/error';
import { defaultPage, defaultPerPage } from '../../routes/api';
import { CasesClientArgs } from '../types';
import { combineFilters, stringToKueryNode } from '../utils';
diff --git a/x-pack/plugins/cases/server/client/attachments/update.ts b/x-pack/plugins/cases/server/client/attachments/update.ts
index b5e9e6c372355..a74e6c140bf4e 100644
--- a/x-pack/plugins/cases/server/client/attachments/update.ts
+++ b/x-pack/plugins/cases/server/client/attachments/update.ts
@@ -10,15 +10,12 @@ import Boom from '@hapi/boom';
import { SavedObjectsClientContract, Logger } from 'kibana/server';
import { LensServerPluginSetup } from '../../../../lens/server';
-import { checkEnabledCaseConnectorOrThrow, CommentableCase, createCaseError } from '../../common';
+import { CommentableCase } from '../../common/models';
+import { createCaseError } from '../../common/error';
+import { checkEnabledCaseConnectorOrThrow } from '../../common/utils';
import { buildCommentUserActionItem } from '../../services/user_actions/helpers';
-import {
- CASE_SAVED_OBJECT,
- SUB_CASE_SAVED_OBJECT,
- CaseResponse,
- CommentPatchRequest,
- CommentRequest,
-} from '../../../common';
+import { CaseResponse, CommentPatchRequest, CommentRequest } from '../../../common/api';
+import { CASE_SAVED_OBJECT, SUB_CASE_SAVED_OBJECT } from '../../../common/constants';
import { AttachmentService, CasesService } from '../../services';
import { CasesClientArgs } from '..';
import { decodeCommentRequest } from '../utils';
diff --git a/x-pack/plugins/cases/server/client/cases/client.ts b/x-pack/plugins/cases/server/client/cases/client.ts
index 09386431200ed..b2673eef33dd5 100644
--- a/x-pack/plugins/cases/server/client/cases/client.ts
+++ b/x-pack/plugins/cases/server/client/cases/client.ts
@@ -13,7 +13,7 @@ import {
AllTagsFindRequest,
AllReportersFindRequest,
CasesByAlertId,
-} from '../../../common';
+} from '../../../common/api';
import { CasesClient } from '../client';
import { CasesClientInternal } from '../client_internal';
import {
diff --git a/x-pack/plugins/cases/server/client/cases/create.ts b/x-pack/plugins/cases/server/client/cases/create.ts
index 488bc523f7796..8a668b7c5db09 100644
--- a/x-pack/plugins/cases/server/client/cases/create.ts
+++ b/x-pack/plugins/cases/server/client/cases/create.ts
@@ -21,13 +21,13 @@ import {
CasePostRequest,
CaseType,
OWNER_FIELD,
- ENABLE_CASE_CONNECTOR,
- MAX_TITLE_LENGTH,
-} from '../../../common';
+} from '../../../common/api';
+import { ENABLE_CASE_CONNECTOR, MAX_TITLE_LENGTH } from '../../../common/constants';
import { buildCaseUserActionItem } from '../../services/user_actions/helpers';
import { Operations } from '../../authorization';
-import { createCaseError, flattenCaseSavedObject, transformNewCase } from '../../common';
+import { createCaseError } from '../../common/error';
+import { flattenCaseSavedObject, transformNewCase } from '../../common/utils';
import { CasesClientArgs } from '..';
/**
diff --git a/x-pack/plugins/cases/server/client/cases/delete.ts b/x-pack/plugins/cases/server/client/cases/delete.ts
index 4333535f17a24..cbe481dd07098 100644
--- a/x-pack/plugins/cases/server/client/cases/delete.ts
+++ b/x-pack/plugins/cases/server/client/cases/delete.ts
@@ -8,15 +8,10 @@
import pMap from 'p-map';
import { Boom } from '@hapi/boom';
import { SavedObject, SavedObjectsClientContract, SavedObjectsFindResponse } from 'kibana/server';
-import {
- CommentAttributes,
- ENABLE_CASE_CONNECTOR,
- MAX_CONCURRENT_SEARCHES,
- OWNER_FIELD,
- SubCaseAttributes,
-} from '../../../common';
+import { CommentAttributes, SubCaseAttributes, OWNER_FIELD } from '../../../common/api';
+import { ENABLE_CASE_CONNECTOR, MAX_CONCURRENT_SEARCHES } from '../../../common/constants';
import { CasesClientArgs } from '..';
-import { createCaseError } from '../../common';
+import { createCaseError } from '../../common/error';
import { AttachmentService, CasesService } from '../../services';
import { buildCaseUserActionItem } from '../../services/user_actions/helpers';
import { Operations, OwnerEntity } from '../../authorization';
diff --git a/x-pack/plugins/cases/server/client/cases/find.ts b/x-pack/plugins/cases/server/client/cases/find.ts
index 282ff956b7a6f..4257dfce6d5e3 100644
--- a/x-pack/plugins/cases/server/client/cases/find.ts
+++ b/x-pack/plugins/cases/server/client/cases/find.ts
@@ -18,9 +18,10 @@ import {
caseStatuses,
CasesFindResponseRt,
excess,
-} from '../../../common';
+} from '../../../common/api';
-import { createCaseError, transformCases } from '../../common';
+import { createCaseError } from '../../common/error';
+import { transformCases } from '../../common/utils';
import { constructQueryOptions } from '../utils';
import { includeFieldsRequiredForAuthentication } from '../../authorization/utils';
import { Operations } from '../../authorization';
diff --git a/x-pack/plugins/cases/server/client/cases/get.ts b/x-pack/plugins/cases/server/client/cases/get.ts
index 653df1efd2daa..b388abb58e449 100644
--- a/x-pack/plugins/cases/server/client/cases/get.ts
+++ b/x-pack/plugins/cases/server/client/cases/get.ts
@@ -25,12 +25,13 @@ import {
AllReportersFindRequest,
CasesByAlertIDRequest,
CasesByAlertIDRequestRt,
- ENABLE_CASE_CONNECTOR,
CasesByAlertId,
CasesByAlertIdRt,
CaseAttributes,
-} from '../../../common';
-import { countAlertsForID, createCaseError, flattenCaseSavedObject } from '../../common';
+} from '../../../common/api';
+import { ENABLE_CASE_CONNECTOR } from '../../../common/constants';
+import { createCaseError } from '../../common/error';
+import { countAlertsForID, flattenCaseSavedObject } from '../../common/utils';
import { CasesClientArgs } from '..';
import { Operations } from '../../authorization';
import { combineAuthorizedAndOwnerFilter } from '../utils';
diff --git a/x-pack/plugins/cases/server/client/cases/mock.ts b/x-pack/plugins/cases/server/client/cases/mock.ts
index 22520cea11014..5f677bdbf4a73 100644
--- a/x-pack/plugins/cases/server/client/cases/mock.ts
+++ b/x-pack/plugins/cases/server/client/cases/mock.ts
@@ -12,8 +12,8 @@ import {
CaseUserActionsResponse,
AssociationType,
CommentResponseAlertsType,
- SECURITY_SOLUTION_OWNER,
-} from '../../../common';
+} from '../../../common/api';
+import { SECURITY_SOLUTION_OWNER } from '../../../common/constants';
import { BasicParams } from './types';
diff --git a/x-pack/plugins/cases/server/client/cases/push.ts b/x-pack/plugins/cases/server/client/cases/push.ts
index 953f8b88c990b..c05705dd03586 100644
--- a/x-pack/plugins/cases/server/client/cases/push.ts
+++ b/x-pack/plugins/cases/server/client/cases/push.ts
@@ -15,14 +15,15 @@ import {
CaseStatuses,
ExternalServiceResponse,
CaseType,
- ENABLE_CASE_CONNECTOR,
CasesConfigureAttributes,
CaseAttributes,
-} from '../../../common';
+} from '../../../common/api';
+import { ENABLE_CASE_CONNECTOR } from '../../../common/constants';
import { buildCaseUserActionItem } from '../../services/user_actions/helpers';
import { createIncident, getCommentContextFromAttributes } from './utils';
-import { createCaseError, flattenCaseSavedObject, getAlertInfoFromComments } from '../../common';
+import { createCaseError } from '../../common/error';
+import { flattenCaseSavedObject, getAlertInfoFromComments } from '../../common/utils';
import { CasesClient, CasesClientArgs, CasesClientInternal } from '..';
import { Operations } from '../../authorization';
import { casesConnectors } from '../../connectors';
diff --git a/x-pack/plugins/cases/server/client/cases/types.ts b/x-pack/plugins/cases/server/client/cases/types.ts
index fb400675136ef..f1d56e7132bd1 100644
--- a/x-pack/plugins/cases/server/client/cases/types.ts
+++ b/x-pack/plugins/cases/server/client/cases/types.ts
@@ -19,7 +19,7 @@ import {
PushToServiceApiParamsSIR as ServiceNowSIRPushToServiceApiParams,
ServiceNowITSMIncident,
} from '../../../../actions/server/builtin_action_types/servicenow/types';
-import { CaseResponse, ConnectorMappingsAttributes } from '../../../common';
+import { CaseResponse, ConnectorMappingsAttributes } from '../../../common/api';
export type Incident = JiraIncident | ResilientIncident | ServiceNowITSMIncident;
export type PushToServiceApiParams =
diff --git a/x-pack/plugins/cases/server/client/cases/update.ts b/x-pack/plugins/cases/server/client/cases/update.ts
index 455665dc7012c..786ba28343490 100644
--- a/x-pack/plugins/cases/server/client/cases/update.ts
+++ b/x-pack/plugins/cases/server/client/cases/update.ts
@@ -22,8 +22,6 @@ import { nodeBuilder } from '@kbn/es-query';
import {
AssociationType,
- CASE_COMMENT_SAVED_OBJECT,
- CASE_SAVED_OBJECT,
CasePatchRequest,
CasesPatchRequest,
CasesPatchRequestRt,
@@ -33,24 +31,28 @@ import {
CaseType,
CommentAttributes,
CommentType,
- ENABLE_CASE_CONNECTOR,
excess,
+ throwErrors,
+ CaseAttributes,
+} from '../../../common/api';
+import {
+ CASE_COMMENT_SAVED_OBJECT,
+ CASE_SAVED_OBJECT,
+ ENABLE_CASE_CONNECTOR,
MAX_CONCURRENT_SEARCHES,
SUB_CASE_SAVED_OBJECT,
- throwErrors,
MAX_TITLE_LENGTH,
- CaseAttributes,
-} from '../../../common';
+} from '../../../common/constants';
import { buildCaseUserActions } from '../../services/user_actions/helpers';
import { getCaseToUpdate } from '../utils';
import { AlertService, CasesService } from '../../services';
+import { createCaseError } from '../../common/error';
import {
createAlertUpdateRequest,
- createCaseError,
flattenCaseSavedObject,
isCommentRequestTypeAlertOrGenAlert,
-} from '../../common';
+} from '../../common/utils';
import { UpdateAlertRequest } from '../alerts/types';
import { CasesClientArgs } from '..';
import { Operations, OwnerEntity } from '../../authorization';
diff --git a/x-pack/plugins/cases/server/client/cases/utils.test.ts b/x-pack/plugins/cases/server/client/cases/utils.test.ts
index 315e9966d347b..3c9e8e7255e76 100644
--- a/x-pack/plugins/cases/server/client/cases/utils.test.ts
+++ b/x-pack/plugins/cases/server/client/cases/utils.test.ts
@@ -31,8 +31,8 @@ import {
transformers,
transformFields,
} from './utils';
-import { flattenCaseSavedObject } from '../../common';
-import { SECURITY_SOLUTION_OWNER } from '../../../common';
+import { flattenCaseSavedObject } from '../../common/utils';
+import { SECURITY_SOLUTION_OWNER } from '../../../common/constants';
import { casesConnectors } from '../../connectors';
const formatComment = {
diff --git a/x-pack/plugins/cases/server/client/cases/utils.ts b/x-pack/plugins/cases/server/client/cases/utils.ts
index f5cf2fe4b3f51..e992c6e25fb4e 100644
--- a/x-pack/plugins/cases/server/client/cases/utils.ts
+++ b/x-pack/plugins/cases/server/client/cases/utils.ts
@@ -7,6 +7,7 @@
import { i18n } from '@kbn/i18n';
import { flow } from 'lodash';
+import { isPush } from '../../../common/utils/user_actions';
import {
ActionConnector,
CaseFullExternalService,
@@ -21,8 +22,7 @@ import {
CommentRequestAlertType,
CommentRequestActionsType,
CaseUserActionResponse,
- isPush,
-} from '../../../common';
+} from '../../../common/api';
import { ActionsClient } from '../../../../actions/server';
import { CasesClientGetAlertsResponse } from '../../client/alerts/types';
import {
diff --git a/x-pack/plugins/cases/server/client/client.ts b/x-pack/plugins/cases/server/client/client.ts
index bd4e36bb7c177..f85667bee7bc3 100644
--- a/x-pack/plugins/cases/server/client/client.ts
+++ b/x-pack/plugins/cases/server/client/client.ts
@@ -11,7 +11,7 @@ import { AttachmentsSubClient, createAttachmentsSubClient } from './attachments/
import { UserActionsSubClient, createUserActionsSubClient } from './user_actions/client';
import { CasesClientInternal, createCasesClientInternal } from './client_internal';
import { createSubCasesClient, SubCasesClient } from './sub_cases/client';
-import { ENABLE_CASE_CONNECTOR } from '../../common';
+import { ENABLE_CASE_CONNECTOR } from '../../common/constants';
import { ConfigureSubClient, createConfigurationSubClient } from './configure/client';
import { createStatsSubClient, StatsSubClient } from './stats/client';
import { createMetricsSubClient, MetricsSubClient } from './metrics/client';
diff --git a/x-pack/plugins/cases/server/client/configure/client.ts b/x-pack/plugins/cases/server/client/configure/client.ts
index 791fcc70947db..c7b94df879142 100644
--- a/x-pack/plugins/cases/server/client/configure/client.ts
+++ b/x-pack/plugins/cases/server/client/configure/client.ts
@@ -30,11 +30,10 @@ import {
excess,
GetConfigureFindRequest,
GetConfigureFindRequestRt,
- MAX_CONCURRENT_SEARCHES,
- SUPPORTED_CONNECTORS,
throwErrors,
-} from '../../../common';
-import { createCaseError } from '../../common';
+} from '../../../common/api';
+import { MAX_CONCURRENT_SEARCHES, SUPPORTED_CONNECTORS } from '../../../common/constants';
+import { createCaseError } from '../../common/error';
import { CasesClientInternal } from '../client_internal';
import { CasesClientArgs } from '../types';
import { getMappings } from './get_mappings';
diff --git a/x-pack/plugins/cases/server/client/configure/create_mappings.ts b/x-pack/plugins/cases/server/client/configure/create_mappings.ts
index 2e9280b968d20..bb4c32ae57071 100644
--- a/x-pack/plugins/cases/server/client/configure/create_mappings.ts
+++ b/x-pack/plugins/cases/server/client/configure/create_mappings.ts
@@ -5,9 +5,9 @@
* 2.0.
*/
-import { ConnectorMappingsAttributes } from '../../../common';
+import { ConnectorMappingsAttributes } from '../../../common/api';
import { ACTION_SAVED_OBJECT_TYPE } from '../../../../actions/server';
-import { createCaseError } from '../../common';
+import { createCaseError } from '../../common/error';
import { CasesClientArgs } from '..';
import { CreateMappingsArgs } from './types';
import { casesConnectors } from '../../connectors';
diff --git a/x-pack/plugins/cases/server/client/configure/get_mappings.ts b/x-pack/plugins/cases/server/client/configure/get_mappings.ts
index c080159488cf2..2fa0e8454bacf 100644
--- a/x-pack/plugins/cases/server/client/configure/get_mappings.ts
+++ b/x-pack/plugins/cases/server/client/configure/get_mappings.ts
@@ -6,9 +6,9 @@
*/
import { SavedObjectsFindResponse } from 'kibana/server';
-import { ConnectorMappings } from '../../../common';
+import { ConnectorMappings } from '../../../common/api';
import { ACTION_SAVED_OBJECT_TYPE } from '../../../../actions/server';
-import { createCaseError } from '../../common';
+import { createCaseError } from '../../common/error';
import { CasesClientArgs } from '..';
import { MappingsArgs } from './types';
diff --git a/x-pack/plugins/cases/server/client/configure/types.ts b/x-pack/plugins/cases/server/client/configure/types.ts
index aca3436c59082..c7ac49c9e94aa 100644
--- a/x-pack/plugins/cases/server/client/configure/types.ts
+++ b/x-pack/plugins/cases/server/client/configure/types.ts
@@ -5,7 +5,7 @@
* 2.0.
*/
-import { CaseConnector } from '../../../common';
+import { CaseConnector } from '../../../common/api';
export interface MappingsArgs {
connector: CaseConnector;
diff --git a/x-pack/plugins/cases/server/client/configure/update_mappings.ts b/x-pack/plugins/cases/server/client/configure/update_mappings.ts
index 43fe527facd52..3d529e51e7561 100644
--- a/x-pack/plugins/cases/server/client/configure/update_mappings.ts
+++ b/x-pack/plugins/cases/server/client/configure/update_mappings.ts
@@ -5,9 +5,9 @@
* 2.0.
*/
-import { ConnectorMappingsAttributes } from '../../../common';
+import { ConnectorMappingsAttributes } from '../../../common/api';
import { ACTION_SAVED_OBJECT_TYPE } from '../../../../actions/server';
-import { createCaseError } from '../../common';
+import { createCaseError } from '../../common/error';
import { CasesClientArgs } from '..';
import { UpdateMappingsArgs } from './types';
import { casesConnectors } from '../../connectors';
diff --git a/x-pack/plugins/cases/server/client/factory.ts b/x-pack/plugins/cases/server/client/factory.ts
index 4f506b5e0b4f7..d657f1a3f4f48 100644
--- a/x-pack/plugins/cases/server/client/factory.ts
+++ b/x-pack/plugins/cases/server/client/factory.ts
@@ -12,7 +12,7 @@ import {
ElasticsearchClient,
} from 'kibana/server';
import { SecurityPluginSetup, SecurityPluginStart } from '../../../security/server';
-import { SAVED_OBJECT_TYPES } from '../../common';
+import { SAVED_OBJECT_TYPES } from '../../common/constants';
import { Authorization } from '../authorization/authorization';
import { GetSpaceFn } from '../authorization/types';
import {
diff --git a/x-pack/plugins/cases/server/client/metrics/alert_details.ts b/x-pack/plugins/cases/server/client/metrics/alert_details.ts
index 4bdd820d99ef6..5d25ab5dc1226 100644
--- a/x-pack/plugins/cases/server/client/metrics/alert_details.ts
+++ b/x-pack/plugins/cases/server/client/metrics/alert_details.ts
@@ -5,7 +5,7 @@
* 2.0.
*/
-import { CaseMetricsResponse } from '../../../common';
+import { CaseMetricsResponse } from '../../../common/api';
import { MetricsHandler } from './types';
export class AlertDetails implements MetricsHandler {
diff --git a/x-pack/plugins/cases/server/client/metrics/alerts_count.ts b/x-pack/plugins/cases/server/client/metrics/alerts_count.ts
index 11e2d32db7ca2..aa0e945bc5fcf 100644
--- a/x-pack/plugins/cases/server/client/metrics/alerts_count.ts
+++ b/x-pack/plugins/cases/server/client/metrics/alerts_count.ts
@@ -5,7 +5,7 @@
* 2.0.
*/
-import { CaseMetricsResponse } from '../../../common';
+import { CaseMetricsResponse } from '../../../common/api';
import { MetricsHandler } from './types';
export class AlertsCount implements MetricsHandler {
diff --git a/x-pack/plugins/cases/server/client/metrics/client.ts b/x-pack/plugins/cases/server/client/metrics/client.ts
index 527ce527d0cc2..c5420213f3f97 100644
--- a/x-pack/plugins/cases/server/client/metrics/client.ts
+++ b/x-pack/plugins/cases/server/client/metrics/client.ts
@@ -5,7 +5,7 @@
* 2.0.
*/
-import { CaseMetricsResponse } from '../../../common';
+import { CaseMetricsResponse } from '../../../common/api';
import { CasesClient } from '../client';
import { CasesClientArgs } from '../types';
diff --git a/x-pack/plugins/cases/server/client/metrics/connectors.ts b/x-pack/plugins/cases/server/client/metrics/connectors.ts
index 6ad5fcc056ee5..727b5576b4fa2 100644
--- a/x-pack/plugins/cases/server/client/metrics/connectors.ts
+++ b/x-pack/plugins/cases/server/client/metrics/connectors.ts
@@ -5,7 +5,7 @@
* 2.0.
*/
-import { CaseMetricsResponse } from '../../../common';
+import { CaseMetricsResponse } from '../../../common/api';
import { MetricsHandler } from './types';
export class Connectors implements MetricsHandler {
diff --git a/x-pack/plugins/cases/server/client/metrics/get_case_metrics.test.ts b/x-pack/plugins/cases/server/client/metrics/get_case_metrics.test.ts
index 072525d080f0a..cd3c9204e3c03 100644
--- a/x-pack/plugins/cases/server/client/metrics/get_case_metrics.test.ts
+++ b/x-pack/plugins/cases/server/client/metrics/get_case_metrics.test.ts
@@ -6,7 +6,7 @@
*/
import { getCaseMetrics } from './get_case_metrics';
-import { CaseAttributes, CaseResponse } from '../../../common';
+import { CaseAttributes, CaseResponse } from '../../../common/api';
import { createCasesClientMock } from '../mocks';
import { CasesClientArgs } from '../types';
import { createAuthorizationMock } from '../../authorization/mock';
diff --git a/x-pack/plugins/cases/server/client/metrics/get_case_metrics.ts b/x-pack/plugins/cases/server/client/metrics/get_case_metrics.ts
index a64325da8453e..74628ebd8c9ee 100644
--- a/x-pack/plugins/cases/server/client/metrics/get_case_metrics.ts
+++ b/x-pack/plugins/cases/server/client/metrics/get_case_metrics.ts
@@ -6,9 +6,9 @@
*/
import { merge } from 'lodash';
-import { CaseMetricsResponseRt, CaseMetricsResponse } from '../../../common';
+import { CaseMetricsResponseRt, CaseMetricsResponse } from '../../../common/api';
import { Operations } from '../../authorization';
-import { createCaseError } from '../../common';
+import { createCaseError } from '../../common/error';
import { CasesClient } from '../client';
import { CasesClientArgs } from '../types';
import { AlertsCount } from './alerts_count';
diff --git a/x-pack/plugins/cases/server/client/metrics/lifespan.ts b/x-pack/plugins/cases/server/client/metrics/lifespan.ts
index ed1470738b366..5302b610c7aa0 100644
--- a/x-pack/plugins/cases/server/client/metrics/lifespan.ts
+++ b/x-pack/plugins/cases/server/client/metrics/lifespan.ts
@@ -5,7 +5,7 @@
* 2.0.
*/
-import { CaseMetricsResponse } from '../../../common';
+import { CaseMetricsResponse } from '../../../common/api';
import { CasesClient } from '../client';
import { MetricsHandler } from './types';
diff --git a/x-pack/plugins/cases/server/client/metrics/types.ts b/x-pack/plugins/cases/server/client/metrics/types.ts
index 82038f76feaa2..7dd3b22821538 100644
--- a/x-pack/plugins/cases/server/client/metrics/types.ts
+++ b/x-pack/plugins/cases/server/client/metrics/types.ts
@@ -5,7 +5,7 @@
* 2.0.
*/
-import { CaseMetricsResponse } from '../../../common';
+import { CaseMetricsResponse } from '../../../common/api';
export interface MetricsHandler {
getFeatures(): Set;
diff --git a/x-pack/plugins/cases/server/client/stats/client.ts b/x-pack/plugins/cases/server/client/stats/client.ts
index b13a15c799f9d..a7716ba7a3da2 100644
--- a/x-pack/plugins/cases/server/client/stats/client.ts
+++ b/x-pack/plugins/cases/server/client/stats/client.ts
@@ -19,9 +19,9 @@ import {
throwErrors,
excess,
CasesStatusRequestRt,
-} from '../../../common';
+} from '../../../common/api';
import { Operations } from '../../authorization';
-import { createCaseError } from '../../common';
+import { createCaseError } from '../../common/error';
import { constructQueryOptions } from '../utils';
/**
diff --git a/x-pack/plugins/cases/server/client/sub_cases/client.ts b/x-pack/plugins/cases/server/client/sub_cases/client.ts
index 9b0395bbcb3b6..7350b6d6cab79 100644
--- a/x-pack/plugins/cases/server/client/sub_cases/client.ts
+++ b/x-pack/plugins/cases/server/client/sub_cases/client.ts
@@ -10,24 +10,19 @@ import Boom from '@hapi/boom';
import { SavedObject } from 'kibana/server';
import {
- CASE_SAVED_OBJECT,
caseStatuses,
CommentAttributes,
- MAX_CONCURRENT_SEARCHES,
SubCaseResponse,
SubCaseResponseRt,
SubCasesFindRequest,
SubCasesFindResponse,
SubCasesFindResponseRt,
SubCasesPatchRequest,
-} from '../../../common';
+} from '../../../common/api';
+import { CASE_SAVED_OBJECT, MAX_CONCURRENT_SEARCHES } from '../../../common/constants';
import { CasesClientArgs } from '..';
-import {
- countAlertsForID,
- createCaseError,
- flattenSubCaseSavedObject,
- transformSubCases,
-} from '../../common';
+import { createCaseError } from '../../common/error';
+import { countAlertsForID, flattenSubCaseSavedObject, transformSubCases } from '../../common/utils';
import { buildCaseUserActionItem } from '../../services/user_actions/helpers';
import { constructQueryOptions } from '../utils';
import { defaultPage, defaultPerPage } from '../../routes/api';
diff --git a/x-pack/plugins/cases/server/client/sub_cases/update.ts b/x-pack/plugins/cases/server/client/sub_cases/update.ts
index 3f602f7979d1f..80c516ad0ac34 100644
--- a/x-pack/plugins/cases/server/client/sub_cases/update.ts
+++ b/x-pack/plugins/cases/server/client/sub_cases/update.ts
@@ -19,12 +19,10 @@ import {
import { nodeBuilder } from '@kbn/es-query';
import { AlertService, CasesService } from '../../services';
import {
- CASE_COMMENT_SAVED_OBJECT,
CaseStatuses,
CommentAttributes,
CommentType,
excess,
- SUB_CASE_SAVED_OBJECT,
SubCaseAttributes,
SubCasePatchRequest,
SubCaseResponse,
@@ -35,15 +33,16 @@ import {
throwErrors,
User,
CaseAttributes,
-} from '../../../common';
+} from '../../../common/api';
+import { CASE_COMMENT_SAVED_OBJECT, SUB_CASE_SAVED_OBJECT } from '../../../common/constants';
import { getCaseToUpdate } from '../utils';
import { buildSubCaseUserActions } from '../../services/user_actions/helpers';
+import { createCaseError } from '../../common/error';
import {
createAlertUpdateRequest,
- createCaseError,
isCommentRequestTypeAlertOrGenAlert,
flattenSubCaseSavedObject,
-} from '../../common';
+} from '../../common/utils';
import { UpdateAlertRequest } from '../../client/alerts/types';
import { CasesClientArgs } from '../types';
diff --git a/x-pack/plugins/cases/server/client/typedoc_interfaces.ts b/x-pack/plugins/cases/server/client/typedoc_interfaces.ts
index feeaa6b6dcb58..b1dd4c47219d8 100644
--- a/x-pack/plugins/cases/server/client/typedoc_interfaces.ts
+++ b/x-pack/plugins/cases/server/client/typedoc_interfaces.ts
@@ -30,7 +30,7 @@ import {
SubCaseResponse,
SubCasesFindResponse,
SubCasesResponse,
-} from '../../common';
+} from '../../common/api';
/**
* These are simply to make typedoc not attempt to expand the type aliases. If it attempts to expand them
diff --git a/x-pack/plugins/cases/server/client/types.ts b/x-pack/plugins/cases/server/client/types.ts
index f6c97df4f8b71..e3d7b8a541b9d 100644
--- a/x-pack/plugins/cases/server/client/types.ts
+++ b/x-pack/plugins/cases/server/client/types.ts
@@ -7,7 +7,7 @@
import type { PublicMethodsOf } from '@kbn/utility-types';
import { SavedObjectsClientContract, Logger } from 'kibana/server';
-import { User } from '../../common';
+import { User } from '../../common/api';
import { Authorization } from '../authorization/authorization';
import {
CaseConfigureService,
diff --git a/x-pack/plugins/cases/server/client/user_actions/get.test.ts b/x-pack/plugins/cases/server/client/user_actions/get.test.ts
index 302e069cde4d1..c735c7d41dfcf 100644
--- a/x-pack/plugins/cases/server/client/user_actions/get.test.ts
+++ b/x-pack/plugins/cases/server/client/user_actions/get.test.ts
@@ -5,8 +5,9 @@
* 2.0.
*/
-import { CaseUserActionResponse, SUB_CASE_SAVED_OBJECT } from '../../../common';
-import { SUB_CASE_REF_NAME } from '../../common';
+import { CaseUserActionResponse } from '../../../common/api';
+import { SUB_CASE_SAVED_OBJECT } from '../../../common/constants';
+import { SUB_CASE_REF_NAME } from '../../common/constants';
import { extractAttributesWithoutSubCases } from './get';
describe('get', () => {
diff --git a/x-pack/plugins/cases/server/client/user_actions/get.ts b/x-pack/plugins/cases/server/client/user_actions/get.ts
index 660cf1b6a336e..0d5ee29529f83 100644
--- a/x-pack/plugins/cases/server/client/user_actions/get.ts
+++ b/x-pack/plugins/cases/server/client/user_actions/get.ts
@@ -9,10 +9,12 @@ import { SavedObjectReference, SavedObjectsFindResponse } from 'kibana/server';
import {
CaseUserActionsResponse,
CaseUserActionsResponseRt,
- SUB_CASE_SAVED_OBJECT,
CaseUserActionResponse,
-} from '../../../common';
-import { createCaseError, checkEnabledCaseConnectorOrThrow, SUB_CASE_REF_NAME } from '../../common';
+} from '../../../common/api';
+import { SUB_CASE_SAVED_OBJECT } from '../../../common/constants';
+import { createCaseError } from '../../common/error';
+import { checkEnabledCaseConnectorOrThrow } from '../../common/utils';
+import { SUB_CASE_REF_NAME } from '../../common/constants';
import { CasesClientArgs } from '..';
import { Operations } from '../../authorization';
import { UserActionGet } from './client';
diff --git a/x-pack/plugins/cases/server/client/utils.test.ts b/x-pack/plugins/cases/server/client/utils.test.ts
index 45ea6bacb0f51..e4e6777dca524 100644
--- a/x-pack/plugins/cases/server/client/utils.test.ts
+++ b/x-pack/plugins/cases/server/client/utils.test.ts
@@ -7,7 +7,7 @@
import { CaseConnector, CaseType, ConnectorTypes } from '../../common/api';
import { newCase } from '../routes/api/__mocks__/request_responses';
-import { transformNewCase } from '../common';
+import { transformNewCase } from '../common/utils';
import { sortToSnake } from './utils';
describe('utils', () => {
diff --git a/x-pack/plugins/cases/server/client/utils.ts b/x-pack/plugins/cases/server/client/utils.ts
index 87bf4d04b3e8f..a29e52b026a27 100644
--- a/x-pack/plugins/cases/server/client/utils.ts
+++ b/x-pack/plugins/cases/server/client/utils.ts
@@ -13,27 +13,26 @@ import { identity } from 'fp-ts/lib/function';
import { pipe } from 'fp-ts/lib/pipeable';
import { nodeBuilder, fromKueryExpression, KueryNode } from '@kbn/es-query';
+import { CASE_SAVED_OBJECT, SUB_CASE_SAVED_OBJECT } from '../../common/constants';
import {
+ OWNER_FIELD,
AlertCommentRequestRt,
ActionsCommentRequestRt,
- CASE_SAVED_OBJECT,
CaseStatuses,
CaseType,
CommentRequest,
ContextTypeUserRt,
excess,
- OWNER_FIELD,
- SUB_CASE_SAVED_OBJECT,
throwErrors,
-} from '../../common';
+} from '../../common/api';
import { combineFilterWithAuthorizationFilter } from '../authorization/utils';
import {
getIDsAndIndicesAsArrays,
isCommentRequestTypeAlertOrGenAlert,
isCommentRequestTypeUser,
isCommentRequestTypeActions,
- SavedObjectFindOptionsKueryNode,
-} from '../common';
+} from '../common/utils';
+import { SavedObjectFindOptionsKueryNode } from '../common/types';
export const decodeCommentRequest = (comment: CommentRequest) => {
if (isCommentRequestTypeUser(comment)) {
diff --git a/x-pack/plugins/cases/server/common/constants.ts b/x-pack/plugins/cases/server/common/constants.ts
index eba0a64a5c0be..556f34c208314 100644
--- a/x-pack/plugins/cases/server/common/constants.ts
+++ b/x-pack/plugins/cases/server/common/constants.ts
@@ -5,7 +5,11 @@
* 2.0.
*/
-import { CASE_COMMENT_SAVED_OBJECT, CASE_SAVED_OBJECT, SUB_CASE_SAVED_OBJECT } from '../../common';
+import {
+ CASE_COMMENT_SAVED_OBJECT,
+ CASE_SAVED_OBJECT,
+ SUB_CASE_SAVED_OBJECT,
+} from '../../common/constants';
/**
* The name of the saved object reference indicating the action connector ID. This is stored in the Saved Object reference
diff --git a/x-pack/plugins/cases/server/common/index.ts b/x-pack/plugins/cases/server/common/index.ts
index 7a1e905c79a98..18bedd3ebeca8 100644
--- a/x-pack/plugins/cases/server/common/index.ts
+++ b/x-pack/plugins/cases/server/common/index.ts
@@ -5,11 +5,10 @@
* 2.0.
*/
-// TODO: https://github.com/elastic/kibana/issues/110896
-/* eslint-disable @kbn/eslint/no_export_all */
+// Careful of exporting anything from this file as any file(s) you export here will cause your functions to be exposed as public.
+// If you're using functions/types/etc... internally or within integration tests it's best to import directly from their paths
+// than expose the functions/types/etc... here. You should _only_ expose functions/types/etc... that need to be shared with other plugins here.
-export * from './models';
-export * from './utils';
-export * from './types';
-export * from './error';
-export * from './constants';
+// When you do have to add things here you might want to consider creating a package such to share with other plugins instead as packages
+// are easier to break down.
+// See: https://docs.elastic.dev/kibana-dev-docs/key-concepts/platform-intro#public-plugin-api
diff --git a/x-pack/plugins/cases/server/common/models/commentable_case.ts b/x-pack/plugins/cases/server/common/models/commentable_case.ts
index 856d6378d5900..1c6d5ad61000a 100644
--- a/x-pack/plugins/cases/server/common/models/commentable_case.ts
+++ b/x-pack/plugins/cases/server/common/models/commentable_case.ts
@@ -17,7 +17,6 @@ import {
import { LensServerPluginSetup } from '../../../../lens/server';
import {
AssociationType,
- CASE_SAVED_OBJECT,
CaseResponse,
CaseResponseRt,
CaseSettings,
@@ -27,18 +26,25 @@ import {
CommentPatchRequest,
CommentRequest,
CommentType,
- MAX_DOCS_PER_PAGE,
- SUB_CASE_SAVED_OBJECT,
SubCaseAttributes,
User,
CommentRequestUserType,
CaseAttributes,
-} from '../../../common';
-import { flattenCommentSavedObjects, flattenSubCaseSavedObject, transformNewComment } from '..';
+} from '../../../common/api';
+import {
+ CASE_SAVED_OBJECT,
+ MAX_DOCS_PER_PAGE,
+ SUB_CASE_SAVED_OBJECT,
+} from '../../../common/constants';
import { AttachmentService, CasesService } from '../../services';
import { createCaseError } from '../error';
-import { countAlertsForID } from '../index';
-import { getOrUpdateLensReferences } from '../utils';
+import {
+ countAlertsForID,
+ flattenCommentSavedObjects,
+ flattenSubCaseSavedObject,
+ transformNewComment,
+ getOrUpdateLensReferences,
+} from '../utils';
interface UpdateCommentResp {
comment: SavedObjectsUpdateResponse;
diff --git a/x-pack/plugins/cases/server/common/types.ts b/x-pack/plugins/cases/server/common/types.ts
index 364be027221d0..7a0d46148cf26 100644
--- a/x-pack/plugins/cases/server/common/types.ts
+++ b/x-pack/plugins/cases/server/common/types.ts
@@ -6,7 +6,7 @@
*/
import type { KueryNode } from '@kbn/es-query';
-import { SavedObjectFindOptions } from '../../common';
+import { SavedObjectFindOptions } from '../../common/api';
/**
* This structure holds the alert ID and index from an alert comment
diff --git a/x-pack/plugins/cases/server/common/utils.test.ts b/x-pack/plugins/cases/server/common/utils.test.ts
index 51d787a0334a2..841831b70eac5 100644
--- a/x-pack/plugins/cases/server/common/utils.test.ts
+++ b/x-pack/plugins/cases/server/common/utils.test.ts
@@ -7,7 +7,7 @@
import { SavedObject, SavedObjectsFindResponse } from 'kibana/server';
import { lensEmbeddableFactory } from '../../../lens/server/embeddable/lens_embeddable_factory';
-import { SECURITY_SOLUTION_OWNER } from '../../common';
+import { SECURITY_SOLUTION_OWNER } from '../../common/constants';
import {
AssociationType,
CaseResponse,
diff --git a/x-pack/plugins/cases/server/common/utils.ts b/x-pack/plugins/cases/server/common/utils.ts
index ae14603d44567..c2ab01bfa5d3d 100644
--- a/x-pack/plugins/cases/server/common/utils.ts
+++ b/x-pack/plugins/cases/server/common/utils.ts
@@ -13,7 +13,7 @@ import {
SavedObjectReference,
} from 'kibana/server';
import { flatMap, uniqWith, isEmpty, xorWith } from 'lodash';
-import { AlertInfo } from '.';
+import { AlertInfo } from './types';
import { LensServerPluginSetup } from '../../../lens/server';
import {
@@ -32,12 +32,12 @@ import {
CommentsResponse,
CommentType,
ConnectorTypes,
- ENABLE_CASE_CONNECTOR,
SubCaseAttributes,
SubCaseResponse,
SubCasesFindResponse,
User,
-} from '../../common';
+} from '../../common/api';
+import { ENABLE_CASE_CONNECTOR } from '../../common/constants';
import { UpdateAlertRequest } from '../client/alerts/types';
import {
parseCommentString,
diff --git a/x-pack/plugins/cases/server/connectors/case/index.test.ts b/x-pack/plugins/cases/server/connectors/case/index.test.ts
index 51c45bd25444e..e5d8dbf3bb38d 100644
--- a/x-pack/plugins/cases/server/connectors/case/index.test.ts
+++ b/x-pack/plugins/cases/server/connectors/case/index.test.ts
@@ -27,7 +27,7 @@ import {
createCasesClientFactory,
createCasesClientMock,
} from '../../client/mocks';
-import { SECURITY_SOLUTION_OWNER } from '../../../common';
+import { SECURITY_SOLUTION_OWNER } from '../../../common/constants';
const services = actionsMock.createServices();
let caseActionType: CaseActionType;
diff --git a/x-pack/plugins/cases/server/connectors/case/index.ts b/x-pack/plugins/cases/server/connectors/case/index.ts
index e566ab7cacc3f..f40a04349068e 100644
--- a/x-pack/plugins/cases/server/connectors/case/index.ts
+++ b/x-pack/plugins/cases/server/connectors/case/index.ts
@@ -13,8 +13,8 @@ import {
CasePostRequest,
CommentRequest,
CommentType,
- ENABLE_CASE_CONNECTOR,
-} from '../../../common';
+} from '../../../common/api';
+import { ENABLE_CASE_CONNECTOR } from '../../../common/constants';
import { CaseExecutorParamsSchema, CaseConfigurationSchema, CommentSchemaType } from './schema';
import {
CaseExecutorResponse,
@@ -25,7 +25,7 @@ import {
import * as i18n from './translations';
import { GetActionTypeParams, isCommentGeneratedAlert, separator } from '..';
-import { createCaseError } from '../../common';
+import { createCaseError } from '../../common/error';
import { CasesClient } from '../../client';
const supportedSubActions: string[] = ['create', 'update', 'addComment'];
diff --git a/x-pack/plugins/cases/server/connectors/case/schema.ts b/x-pack/plugins/cases/server/connectors/case/schema.ts
index b8e46fdf5aa8c..86ee34284a661 100644
--- a/x-pack/plugins/cases/server/connectors/case/schema.ts
+++ b/x-pack/plugins/cases/server/connectors/case/schema.ts
@@ -6,7 +6,7 @@
*/
import { schema } from '@kbn/config-schema';
-import { CommentType, ConnectorTypes } from '../../../common';
+import { CommentType, ConnectorTypes } from '../../../common/api';
import { validateConnector } from './validators';
// Reserved for future implementation
diff --git a/x-pack/plugins/cases/server/connectors/case/types.ts b/x-pack/plugins/cases/server/connectors/case/types.ts
index a71007f0b4946..6a7dfd9c2e687 100644
--- a/x-pack/plugins/cases/server/connectors/case/types.ts
+++ b/x-pack/plugins/cases/server/connectors/case/types.ts
@@ -16,7 +16,7 @@ import {
ConnectorSchema,
CommentSchema,
} from './schema';
-import { CaseResponse, CasesResponse } from '../../../common';
+import { CaseResponse, CasesResponse } from '../../../common/api';
export type CaseConfiguration = TypeOf;
export type Connector = TypeOf;
diff --git a/x-pack/plugins/cases/server/connectors/case/validators.ts b/x-pack/plugins/cases/server/connectors/case/validators.ts
index 6ab4f3a21a24f..163959eec4a6a 100644
--- a/x-pack/plugins/cases/server/connectors/case/validators.ts
+++ b/x-pack/plugins/cases/server/connectors/case/validators.ts
@@ -6,7 +6,7 @@
*/
import { Connector } from './types';
-import { ConnectorTypes } from '../../../common';
+import { ConnectorTypes } from '../../../common/api';
export const validateConnector = (connector: Connector) => {
if (connector.type === ConnectorTypes.none && connector.fields !== null) {
diff --git a/x-pack/plugins/cases/server/connectors/factory.ts b/x-pack/plugins/cases/server/connectors/factory.ts
index d0ae7154fe5d9..40a6702f11b0f 100644
--- a/x-pack/plugins/cases/server/connectors/factory.ts
+++ b/x-pack/plugins/cases/server/connectors/factory.ts
@@ -5,7 +5,7 @@
* 2.0.
*/
-import { ConnectorTypes } from '../../common';
+import { ConnectorTypes } from '../../common/api';
import { ICasesConnector, CasesConnectorsMap } from './types';
import { getCaseConnector as getJiraCaseConnector } from './jira';
import { getCaseConnector as getResilientCaseConnector } from './resilient';
diff --git a/x-pack/plugins/cases/server/connectors/index.ts b/x-pack/plugins/cases/server/connectors/index.ts
index ee7c692c1525b..b5dc1cc4a8ff9 100644
--- a/x-pack/plugins/cases/server/connectors/index.ts
+++ b/x-pack/plugins/cases/server/connectors/index.ts
@@ -12,7 +12,7 @@ import {
ContextTypeAlertSchemaType,
} from './types';
import { getActionType as getCaseConnector } from './case';
-import { CommentRequest, CommentType } from '../../common';
+import { CommentRequest, CommentType } from '../../common/api';
export * from './types';
export { transformConnectorComment } from './case';
diff --git a/x-pack/plugins/cases/server/connectors/jira/format.ts b/x-pack/plugins/cases/server/connectors/jira/format.ts
index b281d94062f4d..e283aff4b4ce9 100644
--- a/x-pack/plugins/cases/server/connectors/jira/format.ts
+++ b/x-pack/plugins/cases/server/connectors/jira/format.ts
@@ -5,7 +5,7 @@
* 2.0.
*/
-import { ConnectorJiraTypeFields } from '../../../common';
+import { ConnectorJiraTypeFields } from '../../../common/api';
import { Format } from './types';
export const format: Format = (theCase, alerts) => {
diff --git a/x-pack/plugins/cases/server/connectors/jira/types.ts b/x-pack/plugins/cases/server/connectors/jira/types.ts
index 1941485ccecee..59d5741d381b9 100644
--- a/x-pack/plugins/cases/server/connectors/jira/types.ts
+++ b/x-pack/plugins/cases/server/connectors/jira/types.ts
@@ -5,7 +5,7 @@
* 2.0.
*/
-import { JiraFieldsType } from '../../../common';
+import { JiraFieldsType } from '../../../common/api';
import { ICasesConnector } from '../types';
interface ExternalServiceFormatterParams extends JiraFieldsType {
diff --git a/x-pack/plugins/cases/server/connectors/resilient/format.test.ts b/x-pack/plugins/cases/server/connectors/resilient/format.test.ts
index 20ba0bc378934..5cfd089b9aa8d 100644
--- a/x-pack/plugins/cases/server/connectors/resilient/format.test.ts
+++ b/x-pack/plugins/cases/server/connectors/resilient/format.test.ts
@@ -5,7 +5,7 @@
* 2.0.
*/
-import { CaseResponse } from '../../../common';
+import { CaseResponse } from '../../../common/api';
import { format } from './format';
describe('IBM Resilient formatter', () => {
diff --git a/x-pack/plugins/cases/server/connectors/resilient/format.ts b/x-pack/plugins/cases/server/connectors/resilient/format.ts
index ba82e2e8d1ea3..64b701731c33f 100644
--- a/x-pack/plugins/cases/server/connectors/resilient/format.ts
+++ b/x-pack/plugins/cases/server/connectors/resilient/format.ts
@@ -5,7 +5,7 @@
* 2.0.
*/
-import { ConnectorResilientTypeFields } from '../../../common';
+import { ConnectorResilientTypeFields } from '../../../common/api';
import { Format } from './types';
export const format: Format = (theCase, alerts) => {
diff --git a/x-pack/plugins/cases/server/connectors/resilient/types.ts b/x-pack/plugins/cases/server/connectors/resilient/types.ts
index 40cde0500280c..f895dccf65214 100644
--- a/x-pack/plugins/cases/server/connectors/resilient/types.ts
+++ b/x-pack/plugins/cases/server/connectors/resilient/types.ts
@@ -5,7 +5,7 @@
* 2.0.
*/
-import { ResilientFieldsType } from '../../../common';
+import { ResilientFieldsType } from '../../../common/api';
import { ICasesConnector } from '../types';
export type ResilientCaseConnector = ICasesConnector;
diff --git a/x-pack/plugins/cases/server/connectors/servicenow/itsm_format.ts b/x-pack/plugins/cases/server/connectors/servicenow/itsm_format.ts
index 1859ea1246f21..81a20d006c22e 100644
--- a/x-pack/plugins/cases/server/connectors/servicenow/itsm_format.ts
+++ b/x-pack/plugins/cases/server/connectors/servicenow/itsm_format.ts
@@ -5,7 +5,7 @@
* 2.0.
*/
-import { ConnectorServiceNowITSMTypeFields } from '../../../common';
+import { ConnectorServiceNowITSMTypeFields } from '../../../common/api';
import { ServiceNowITSMFormat } from './types';
export const format: ServiceNowITSMFormat = (theCase, alerts) => {
diff --git a/x-pack/plugins/cases/server/connectors/servicenow/sir_format.test.ts b/x-pack/plugins/cases/server/connectors/servicenow/sir_format.test.ts
index 706b9f2f23ab5..9b24dfa672bf4 100644
--- a/x-pack/plugins/cases/server/connectors/servicenow/sir_format.test.ts
+++ b/x-pack/plugins/cases/server/connectors/servicenow/sir_format.test.ts
@@ -5,7 +5,7 @@
* 2.0.
*/
-import { CaseResponse } from '../../../common';
+import { CaseResponse } from '../../../common/api';
import { format } from './sir_format';
describe('SIR formatter', () => {
diff --git a/x-pack/plugins/cases/server/connectors/servicenow/sir_format.ts b/x-pack/plugins/cases/server/connectors/servicenow/sir_format.ts
index 02c9fe629f4f8..dae1045502460 100644
--- a/x-pack/plugins/cases/server/connectors/servicenow/sir_format.ts
+++ b/x-pack/plugins/cases/server/connectors/servicenow/sir_format.ts
@@ -5,7 +5,7 @@
* 2.0.
*/
import { get } from 'lodash/fp';
-import { ConnectorServiceNowSIRTypeFields } from '../../../common';
+import { ConnectorServiceNowSIRTypeFields } from '../../../common/api';
import { ServiceNowSIRFormat, SirFieldKey, AlertFieldMappingAndValues } from './types';
export const format: ServiceNowSIRFormat = (theCase, alerts) => {
diff --git a/x-pack/plugins/cases/server/connectors/servicenow/types.ts b/x-pack/plugins/cases/server/connectors/servicenow/types.ts
index b0e71cbe5e743..531786730ff9a 100644
--- a/x-pack/plugins/cases/server/connectors/servicenow/types.ts
+++ b/x-pack/plugins/cases/server/connectors/servicenow/types.ts
@@ -5,7 +5,7 @@
* 2.0.
*/
-import { ServiceNowITSMFieldsType } from '../../../common';
+import { ServiceNowITSMFieldsType } from '../../../common/api';
import { ICasesConnector } from '../types';
interface CorrelationValues {
diff --git a/x-pack/plugins/cases/server/connectors/swimlane/format.test.ts b/x-pack/plugins/cases/server/connectors/swimlane/format.test.ts
index 55cbbdb68691e..e72ca3d145c99 100644
--- a/x-pack/plugins/cases/server/connectors/swimlane/format.test.ts
+++ b/x-pack/plugins/cases/server/connectors/swimlane/format.test.ts
@@ -5,7 +5,7 @@
* 2.0.
*/
-import { CaseResponse } from '../../../common';
+import { CaseResponse } from '../../../common/api';
import { format } from './format';
describe('Swimlane formatter', () => {
diff --git a/x-pack/plugins/cases/server/connectors/swimlane/format.ts b/x-pack/plugins/cases/server/connectors/swimlane/format.ts
index 9531e4099a4f4..48983d745150b 100644
--- a/x-pack/plugins/cases/server/connectors/swimlane/format.ts
+++ b/x-pack/plugins/cases/server/connectors/swimlane/format.ts
@@ -5,7 +5,7 @@
* 2.0.
*/
-import { ConnectorSwimlaneTypeFields } from '../../../common';
+import { ConnectorSwimlaneTypeFields } from '../../../common/api';
import { Format } from './types';
export const format: Format = (theCase) => {
diff --git a/x-pack/plugins/cases/server/connectors/types.ts b/x-pack/plugins/cases/server/connectors/types.ts
index 62b2c8e6f1551..3754dfd62b7c5 100644
--- a/x-pack/plugins/cases/server/connectors/types.ts
+++ b/x-pack/plugins/cases/server/connectors/types.ts
@@ -6,7 +6,7 @@
*/
import { Logger } from 'kibana/server';
-import { CaseResponse, ConnectorMappingsAttributes } from '../../common';
+import { CaseResponse, ConnectorMappingsAttributes } from '../../common/api';
import { CasesClientGetAlertsResponse } from '../client/alerts/types';
import { CasesClientFactory } from '../client/factory';
import { RegisterActionType } from '../types';
diff --git a/x-pack/plugins/cases/server/plugin.ts b/x-pack/plugins/cases/server/plugin.ts
index 9bbc7089c033c..21ee1313ddb11 100644
--- a/x-pack/plugins/cases/server/plugin.ts
+++ b/x-pack/plugins/cases/server/plugin.ts
@@ -13,7 +13,7 @@ import {
PluginSetupContract as ActionsPluginSetup,
PluginStartContract as ActionsPluginStart,
} from '../../actions/server';
-import { APP_ID, ENABLE_CASE_CONNECTOR } from '../common';
+import { APP_ID, ENABLE_CASE_CONNECTOR } from '../common/constants';
import { initCaseApi } from './routes/api';
import {
diff --git a/x-pack/plugins/cases/server/routes/api/__fixtures__/authc_mock.ts b/x-pack/plugins/cases/server/routes/api/__fixtures__/authc_mock.ts
index a9292229d5eea..3c6bec265e3ea 100644
--- a/x-pack/plugins/cases/server/routes/api/__fixtures__/authc_mock.ts
+++ b/x-pack/plugins/cases/server/routes/api/__fixtures__/authc_mock.ts
@@ -7,7 +7,7 @@
import { AuthenticatedUser } from '../../../../../security/server';
import { securityMock } from '../../../../../security/server/mocks';
-import { nullUser } from '../../../common';
+import { nullUser } from '../../../common/utils';
function createAuthenticationMock({
currentUser,
diff --git a/x-pack/plugins/cases/server/routes/api/__fixtures__/mock_saved_objects.ts b/x-pack/plugins/cases/server/routes/api/__fixtures__/mock_saved_objects.ts
index 1551f0fa611b7..6bff6c7d21725 100644
--- a/x-pack/plugins/cases/server/routes/api/__fixtures__/mock_saved_objects.ts
+++ b/x-pack/plugins/cases/server/routes/api/__fixtures__/mock_saved_objects.ts
@@ -14,8 +14,8 @@ import {
CommentAttributes,
CommentType,
ConnectorTypes,
- SECURITY_SOLUTION_OWNER,
-} from '../../../../common';
+} from '../../../../common/api';
+import { SECURITY_SOLUTION_OWNER } from '../../../../common/constants';
export const mockCases: Array> = [
{
diff --git a/x-pack/plugins/cases/server/routes/api/__mocks__/request_responses.ts b/x-pack/plugins/cases/server/routes/api/__mocks__/request_responses.ts
index f3e6bcd7fc9ff..b398f9cfd1ba8 100644
--- a/x-pack/plugins/cases/server/routes/api/__mocks__/request_responses.ts
+++ b/x-pack/plugins/cases/server/routes/api/__mocks__/request_responses.ts
@@ -5,7 +5,8 @@
* 2.0.
*/
-import { SECURITY_SOLUTION_OWNER, CasePostRequest, ConnectorTypes } from '../../../../common';
+import { CasePostRequest, ConnectorTypes } from '../../../../common/api';
+import { SECURITY_SOLUTION_OWNER } from '../../../../common/constants';
export const newCase: CasePostRequest = {
title: 'My new case',
diff --git a/x-pack/plugins/cases/server/routes/api/cases/alerts/get_cases.ts b/x-pack/plugins/cases/server/routes/api/cases/alerts/get_cases.ts
index 3471c1dec6208..8a490e2f68bd0 100644
--- a/x-pack/plugins/cases/server/routes/api/cases/alerts/get_cases.ts
+++ b/x-pack/plugins/cases/server/routes/api/cases/alerts/get_cases.ts
@@ -10,7 +10,8 @@ import Boom from '@hapi/boom';
import { RouteDeps } from '../../types';
import { escapeHatch, wrapError } from '../../utils';
-import { CASE_ALERTS_URL, CasesByAlertIDRequest } from '../../../../../common';
+import { CasesByAlertIDRequest } from '../../../../../common/api';
+import { CASE_ALERTS_URL } from '../../../../../common/constants';
export function initGetCasesByAlertIdApi({ router, logger }: RouteDeps) {
router.get(
diff --git a/x-pack/plugins/cases/server/routes/api/cases/delete_cases.ts b/x-pack/plugins/cases/server/routes/api/cases/delete_cases.ts
index 383f9b82706a4..1784a434292cc 100644
--- a/x-pack/plugins/cases/server/routes/api/cases/delete_cases.ts
+++ b/x-pack/plugins/cases/server/routes/api/cases/delete_cases.ts
@@ -9,7 +9,7 @@ import { schema } from '@kbn/config-schema';
import { RouteDeps } from '../types';
import { wrapError } from '../utils';
-import { CASES_URL } from '../../../../common';
+import { CASES_URL } from '../../../../common/constants';
export function initDeleteCasesApi({ router, logger }: RouteDeps) {
router.delete(
diff --git a/x-pack/plugins/cases/server/routes/api/cases/find_cases.ts b/x-pack/plugins/cases/server/routes/api/cases/find_cases.ts
index 9b3d186ca0adc..8474d781a202a 100644
--- a/x-pack/plugins/cases/server/routes/api/cases/find_cases.ts
+++ b/x-pack/plugins/cases/server/routes/api/cases/find_cases.ts
@@ -5,7 +5,8 @@
* 2.0.
*/
-import { CASES_URL, CasesFindRequest } from '../../../../common';
+import { CasesFindRequest } from '../../../../common/api';
+import { CASES_URL } from '../../../../common/constants';
import { wrapError, escapeHatch } from '../utils';
import { RouteDeps } from '../types';
diff --git a/x-pack/plugins/cases/server/routes/api/cases/get_case.ts b/x-pack/plugins/cases/server/routes/api/cases/get_case.ts
index 4d81b6d5e11b3..b9c50a86fb8fe 100644
--- a/x-pack/plugins/cases/server/routes/api/cases/get_case.ts
+++ b/x-pack/plugins/cases/server/routes/api/cases/get_case.ts
@@ -9,7 +9,7 @@ import { schema } from '@kbn/config-schema';
import { RouteDeps } from '../types';
import { wrapError } from '../utils';
-import { CASE_DETAILS_URL } from '../../../../common';
+import { CASE_DETAILS_URL } from '../../../../common/constants';
export function initGetCaseApi({ router, logger }: RouteDeps) {
router.get(
diff --git a/x-pack/plugins/cases/server/routes/api/cases/patch_cases.ts b/x-pack/plugins/cases/server/routes/api/cases/patch_cases.ts
index e518d3717fcda..5cde28bcb01f9 100644
--- a/x-pack/plugins/cases/server/routes/api/cases/patch_cases.ts
+++ b/x-pack/plugins/cases/server/routes/api/cases/patch_cases.ts
@@ -7,7 +7,8 @@
import { escapeHatch, wrapError } from '../utils';
import { RouteDeps } from '../types';
-import { CASES_URL, CasesPatchRequest } from '../../../../common';
+import { CasesPatchRequest } from '../../../../common/api';
+import { CASES_URL } from '../../../../common/constants';
export function initPatchCasesApi({ router, logger }: RouteDeps) {
router.patch(
diff --git a/x-pack/plugins/cases/server/routes/api/cases/post_case.ts b/x-pack/plugins/cases/server/routes/api/cases/post_case.ts
index 6ee94df007d64..df994f18c5bbd 100644
--- a/x-pack/plugins/cases/server/routes/api/cases/post_case.ts
+++ b/x-pack/plugins/cases/server/routes/api/cases/post_case.ts
@@ -8,7 +8,8 @@
import { wrapError, escapeHatch } from '../utils';
import { RouteDeps } from '../types';
-import { CASES_URL, CasePostRequest } from '../../../../common';
+import { CasePostRequest } from '../../../../common/api';
+import { CASES_URL } from '../../../../common/constants';
export function initPostCaseApi({ router, logger }: RouteDeps) {
router.post(
diff --git a/x-pack/plugins/cases/server/routes/api/cases/push_case.ts b/x-pack/plugins/cases/server/routes/api/cases/push_case.ts
index 4c467c3840c2b..2b3e7954febfe 100644
--- a/x-pack/plugins/cases/server/routes/api/cases/push_case.ts
+++ b/x-pack/plugins/cases/server/routes/api/cases/push_case.ts
@@ -12,7 +12,8 @@ import { identity } from 'fp-ts/lib/function';
import { wrapError, escapeHatch } from '../utils';
-import { throwErrors, CasePushRequestParamsRt, CASE_PUSH_URL } from '../../../../common';
+import { throwErrors, CasePushRequestParamsRt } from '../../../../common/api';
+import { CASE_PUSH_URL } from '../../../../common/constants';
import { RouteDeps } from '../types';
export function initPushCaseApi({ router, logger }: RouteDeps) {
diff --git a/x-pack/plugins/cases/server/routes/api/cases/reporters/get_reporters.ts b/x-pack/plugins/cases/server/routes/api/cases/reporters/get_reporters.ts
index 109cac0d977ca..8e0d0640263ec 100644
--- a/x-pack/plugins/cases/server/routes/api/cases/reporters/get_reporters.ts
+++ b/x-pack/plugins/cases/server/routes/api/cases/reporters/get_reporters.ts
@@ -7,7 +7,8 @@
import { RouteDeps } from '../../types';
import { wrapError, escapeHatch } from '../../utils';
-import { CASE_REPORTERS_URL, AllReportersFindRequest } from '../../../../../common';
+import { AllReportersFindRequest } from '../../../../../common/api';
+import { CASE_REPORTERS_URL } from '../../../../../common/constants';
export function initGetReportersApi({ router, logger }: RouteDeps) {
router.get(
diff --git a/x-pack/plugins/cases/server/routes/api/cases/tags/get_tags.ts b/x-pack/plugins/cases/server/routes/api/cases/tags/get_tags.ts
index 778261c048bf0..2afa96be95bc1 100644
--- a/x-pack/plugins/cases/server/routes/api/cases/tags/get_tags.ts
+++ b/x-pack/plugins/cases/server/routes/api/cases/tags/get_tags.ts
@@ -7,7 +7,8 @@
import { RouteDeps } from '../../types';
import { wrapError, escapeHatch } from '../../utils';
-import { CASE_TAGS_URL, AllTagsFindRequest } from '../../../../../common';
+import { AllTagsFindRequest } from '../../../../../common/api';
+import { CASE_TAGS_URL } from '../../../../../common/constants';
export function initGetTagsApi({ router, logger }: RouteDeps) {
router.get(
diff --git a/x-pack/plugins/cases/server/routes/api/comments/delete_all_comments.ts b/x-pack/plugins/cases/server/routes/api/comments/delete_all_comments.ts
index 9c77b1814376f..a41d4683af2d0 100644
--- a/x-pack/plugins/cases/server/routes/api/comments/delete_all_comments.ts
+++ b/x-pack/plugins/cases/server/routes/api/comments/delete_all_comments.ts
@@ -8,7 +8,7 @@
import { schema } from '@kbn/config-schema';
import { RouteDeps } from '../types';
import { wrapError } from '../utils';
-import { CASE_COMMENTS_URL } from '../../../../common';
+import { CASE_COMMENTS_URL } from '../../../../common/constants';
export function initDeleteAllCommentsApi({ router, logger }: RouteDeps) {
router.delete(
diff --git a/x-pack/plugins/cases/server/routes/api/comments/delete_comment.ts b/x-pack/plugins/cases/server/routes/api/comments/delete_comment.ts
index 6dfb188763aa1..f145fc62efc8a 100644
--- a/x-pack/plugins/cases/server/routes/api/comments/delete_comment.ts
+++ b/x-pack/plugins/cases/server/routes/api/comments/delete_comment.ts
@@ -9,7 +9,7 @@ import { schema } from '@kbn/config-schema';
import { RouteDeps } from '../types';
import { wrapError } from '../utils';
-import { CASE_COMMENT_DETAILS_URL } from '../../../../common';
+import { CASE_COMMENT_DETAILS_URL } from '../../../../common/constants';
export function initDeleteCommentApi({ router, logger }: RouteDeps) {
router.delete(
diff --git a/x-pack/plugins/cases/server/routes/api/comments/find_comments.ts b/x-pack/plugins/cases/server/routes/api/comments/find_comments.ts
index c0e4d8901eec6..d4c65e6306a63 100644
--- a/x-pack/plugins/cases/server/routes/api/comments/find_comments.ts
+++ b/x-pack/plugins/cases/server/routes/api/comments/find_comments.ts
@@ -12,7 +12,8 @@ import { pipe } from 'fp-ts/lib/pipeable';
import { fold } from 'fp-ts/lib/Either';
import { identity } from 'fp-ts/lib/function';
-import { CASE_COMMENTS_URL, FindQueryParamsRt, throwErrors, excess } from '../../../../common';
+import { FindQueryParamsRt, throwErrors, excess } from '../../../../common/api';
+import { CASE_COMMENTS_URL } from '../../../../common/constants';
import { RouteDeps } from '../types';
import { escapeHatch, wrapError } from '../utils';
diff --git a/x-pack/plugins/cases/server/routes/api/comments/get_all_comment.ts b/x-pack/plugins/cases/server/routes/api/comments/get_all_comment.ts
index 41a4b6f796655..b916e22c6b0ed 100644
--- a/x-pack/plugins/cases/server/routes/api/comments/get_all_comment.ts
+++ b/x-pack/plugins/cases/server/routes/api/comments/get_all_comment.ts
@@ -9,7 +9,7 @@ import { schema } from '@kbn/config-schema';
import { RouteDeps } from '../types';
import { wrapError } from '../utils';
-import { CASE_COMMENTS_URL } from '../../../../common';
+import { CASE_COMMENTS_URL } from '../../../../common/constants';
export function initGetAllCommentsApi({ router, logger }: RouteDeps) {
router.get(
diff --git a/x-pack/plugins/cases/server/routes/api/comments/get_comment.ts b/x-pack/plugins/cases/server/routes/api/comments/get_comment.ts
index a3ba0d3f23c37..09805c00cb10a 100644
--- a/x-pack/plugins/cases/server/routes/api/comments/get_comment.ts
+++ b/x-pack/plugins/cases/server/routes/api/comments/get_comment.ts
@@ -9,7 +9,7 @@ import { schema } from '@kbn/config-schema';
import { RouteDeps } from '../types';
import { wrapError } from '../utils';
-import { CASE_COMMENT_DETAILS_URL } from '../../../../common';
+import { CASE_COMMENT_DETAILS_URL } from '../../../../common/constants';
export function initGetCommentApi({ router, logger }: RouteDeps) {
router.get(
diff --git a/x-pack/plugins/cases/server/routes/api/comments/patch_comment.ts b/x-pack/plugins/cases/server/routes/api/comments/patch_comment.ts
index 687b568e67d7f..d6ac39f11b91e 100644
--- a/x-pack/plugins/cases/server/routes/api/comments/patch_comment.ts
+++ b/x-pack/plugins/cases/server/routes/api/comments/patch_comment.ts
@@ -13,7 +13,8 @@ import Boom from '@hapi/boom';
import { RouteDeps } from '../types';
import { escapeHatch, wrapError } from '../utils';
-import { CASE_COMMENTS_URL, CommentPatchRequestRt, throwErrors } from '../../../../common';
+import { CommentPatchRequestRt, throwErrors } from '../../../../common/api';
+import { CASE_COMMENTS_URL } from '../../../../common/constants';
export function initPatchCommentApi({ router, logger }: RouteDeps) {
router.patch(
diff --git a/x-pack/plugins/cases/server/routes/api/comments/post_comment.ts b/x-pack/plugins/cases/server/routes/api/comments/post_comment.ts
index 44871f7f0c81c..1919aef7b72b4 100644
--- a/x-pack/plugins/cases/server/routes/api/comments/post_comment.ts
+++ b/x-pack/plugins/cases/server/routes/api/comments/post_comment.ts
@@ -9,7 +9,8 @@ import Boom from '@hapi/boom';
import { schema } from '@kbn/config-schema';
import { escapeHatch, wrapError } from '../utils';
import { RouteDeps } from '../types';
-import { CASE_COMMENTS_URL, ENABLE_CASE_CONNECTOR, CommentRequest } from '../../../../common';
+import { CASE_COMMENTS_URL, ENABLE_CASE_CONNECTOR } from '../../../../common/constants';
+import { CommentRequest } from '../../../../common/api';
export function initPostCommentApi({ router, logger }: RouteDeps) {
router.post(
diff --git a/x-pack/plugins/cases/server/routes/api/configure/get_configure.ts b/x-pack/plugins/cases/server/routes/api/configure/get_configure.ts
index 59f136b971da4..8222ac8fe5690 100644
--- a/x-pack/plugins/cases/server/routes/api/configure/get_configure.ts
+++ b/x-pack/plugins/cases/server/routes/api/configure/get_configure.ts
@@ -7,7 +7,8 @@
import { RouteDeps } from '../types';
import { escapeHatch, wrapError } from '../utils';
-import { CASE_CONFIGURE_URL, GetConfigureFindRequest } from '../../../../common';
+import { CASE_CONFIGURE_URL } from '../../../../common/constants';
+import { GetConfigureFindRequest } from '../../../../common/api';
export function initGetCaseConfigure({ router, logger }: RouteDeps) {
router.get(
diff --git a/x-pack/plugins/cases/server/routes/api/configure/get_connectors.ts b/x-pack/plugins/cases/server/routes/api/configure/get_connectors.ts
index 220481e8ff07e..46c110bbb8ba5 100644
--- a/x-pack/plugins/cases/server/routes/api/configure/get_connectors.ts
+++ b/x-pack/plugins/cases/server/routes/api/configure/get_connectors.ts
@@ -8,7 +8,7 @@
import { RouteDeps } from '../types';
import { wrapError } from '../utils';
-import { CASE_CONFIGURE_CONNECTORS_URL } from '../../../../common';
+import { CASE_CONFIGURE_CONNECTORS_URL } from '../../../../common/constants';
/*
* Be aware that this api will only return 20 connectors
diff --git a/x-pack/plugins/cases/server/routes/api/configure/patch_configure.ts b/x-pack/plugins/cases/server/routes/api/configure/patch_configure.ts
index a50753413585b..e856a568f387a 100644
--- a/x-pack/plugins/cases/server/routes/api/configure/patch_configure.ts
+++ b/x-pack/plugins/cases/server/routes/api/configure/patch_configure.ts
@@ -11,12 +11,12 @@ import { fold } from 'fp-ts/lib/Either';
import { identity } from 'fp-ts/lib/function';
import {
- CASE_CONFIGURE_DETAILS_URL,
CaseConfigureRequestParamsRt,
throwErrors,
CasesConfigurePatch,
excess,
-} from '../../../../common';
+} from '../../../../common/api';
+import { CASE_CONFIGURE_DETAILS_URL } from '../../../../common/constants';
import { RouteDeps } from '../types';
import { wrapError, escapeHatch } from '../utils';
diff --git a/x-pack/plugins/cases/server/routes/api/configure/post_configure.ts b/x-pack/plugins/cases/server/routes/api/configure/post_configure.ts
index b444ed119318d..ed4c3529f2ca0 100644
--- a/x-pack/plugins/cases/server/routes/api/configure/post_configure.ts
+++ b/x-pack/plugins/cases/server/routes/api/configure/post_configure.ts
@@ -10,7 +10,8 @@ import { pipe } from 'fp-ts/lib/pipeable';
import { fold } from 'fp-ts/lib/Either';
import { identity } from 'fp-ts/lib/function';
-import { CASE_CONFIGURE_URL, CasesConfigureRequestRt, throwErrors } from '../../../../common';
+import { CasesConfigureRequestRt, throwErrors } from '../../../../common/api';
+import { CASE_CONFIGURE_URL } from '../../../../common/constants';
import { RouteDeps } from '../types';
import { wrapError, escapeHatch } from '../utils';
diff --git a/x-pack/plugins/cases/server/routes/api/index.ts b/x-pack/plugins/cases/server/routes/api/index.ts
index 70daef5b528d6..f844505369f93 100644
--- a/x-pack/plugins/cases/server/routes/api/index.ts
+++ b/x-pack/plugins/cases/server/routes/api/index.ts
@@ -37,7 +37,7 @@ import { initGetSubCaseApi } from './sub_case/get_sub_case';
import { initPatchSubCasesApi } from './sub_case/patch_sub_cases';
import { initFindSubCasesApi } from './sub_case/find_sub_cases';
import { initDeleteSubCasesApi } from './sub_case/delete_sub_cases';
-import { ENABLE_CASE_CONNECTOR } from '../../../common';
+import { ENABLE_CASE_CONNECTOR } from '../../../common/constants';
import { initGetCasesByAlertIdApi } from './cases/alerts/get_cases';
import { initGetAllAlertsAttachToCaseApi } from './comments/get_alerts';
import { initGetCaseMetricsApi } from './metrics/get_case_metrics';
diff --git a/x-pack/plugins/cases/server/routes/api/metrics/get_case_metrics.ts b/x-pack/plugins/cases/server/routes/api/metrics/get_case_metrics.ts
index 27b9d139770ce..0cfad10b28316 100644
--- a/x-pack/plugins/cases/server/routes/api/metrics/get_case_metrics.ts
+++ b/x-pack/plugins/cases/server/routes/api/metrics/get_case_metrics.ts
@@ -10,7 +10,7 @@ import { schema } from '@kbn/config-schema';
import { RouteDeps } from '../types';
import { wrapError } from '../utils';
-import { CASE_METRICS_DETAILS_URL } from '../../../../common';
+import { CASE_METRICS_DETAILS_URL } from '../../../../common/constants';
export function initGetCaseMetricsApi({ router, logger }: RouteDeps) {
router.get(
diff --git a/x-pack/plugins/cases/server/routes/api/stats/get_status.ts b/x-pack/plugins/cases/server/routes/api/stats/get_status.ts
index 469e32b466224..4f666c399d8fd 100644
--- a/x-pack/plugins/cases/server/routes/api/stats/get_status.ts
+++ b/x-pack/plugins/cases/server/routes/api/stats/get_status.ts
@@ -8,7 +8,8 @@
import { RouteDeps } from '../types';
import { escapeHatch, wrapError } from '../utils';
-import { CASE_STATUS_URL, CasesStatusRequest } from '../../../../common';
+import { CasesStatusRequest } from '../../../../common/api';
+import { CASE_STATUS_URL } from '../../../../common/constants';
export function initGetCasesStatusApi({ router, logger }: RouteDeps) {
router.get(
diff --git a/x-pack/plugins/cases/server/routes/api/sub_case/delete_sub_cases.ts b/x-pack/plugins/cases/server/routes/api/sub_case/delete_sub_cases.ts
index 0fe436f2269b5..11b68b70390fe 100644
--- a/x-pack/plugins/cases/server/routes/api/sub_case/delete_sub_cases.ts
+++ b/x-pack/plugins/cases/server/routes/api/sub_case/delete_sub_cases.ts
@@ -8,7 +8,7 @@
import { schema } from '@kbn/config-schema';
import { RouteDeps } from '../types';
import { wrapError } from '../utils';
-import { SUB_CASES_PATCH_DEL_URL } from '../../../../common';
+import { SUB_CASES_PATCH_DEL_URL } from '../../../../common/constants';
export function initDeleteSubCasesApi({ router, logger }: RouteDeps) {
router.delete(
diff --git a/x-pack/plugins/cases/server/routes/api/sub_case/find_sub_cases.ts b/x-pack/plugins/cases/server/routes/api/sub_case/find_sub_cases.ts
index 3049f05337b40..8ee5fa21c3a3e 100644
--- a/x-pack/plugins/cases/server/routes/api/sub_case/find_sub_cases.ts
+++ b/x-pack/plugins/cases/server/routes/api/sub_case/find_sub_cases.ts
@@ -12,7 +12,8 @@ import { pipe } from 'fp-ts/lib/pipeable';
import { fold } from 'fp-ts/lib/Either';
import { identity } from 'fp-ts/lib/function';
-import { SubCasesFindRequestRt, SUB_CASES_URL, throwErrors } from '../../../../common';
+import { SUB_CASES_URL } from '../../../../common/constants';
+import { SubCasesFindRequestRt, throwErrors } from '../../../../common/api';
import { RouteDeps } from '../types';
import { escapeHatch, wrapError } from '../utils';
diff --git a/x-pack/plugins/cases/server/routes/api/sub_case/get_sub_case.ts b/x-pack/plugins/cases/server/routes/api/sub_case/get_sub_case.ts
index fea81524b526e..db3e29f5ed96e 100644
--- a/x-pack/plugins/cases/server/routes/api/sub_case/get_sub_case.ts
+++ b/x-pack/plugins/cases/server/routes/api/sub_case/get_sub_case.ts
@@ -9,7 +9,7 @@ import { schema } from '@kbn/config-schema';
import { RouteDeps } from '../types';
import { wrapError } from '../utils';
-import { SUB_CASE_DETAILS_URL } from '../../../../common';
+import { SUB_CASE_DETAILS_URL } from '../../../../common/constants';
export function initGetSubCaseApi({ router, logger }: RouteDeps) {
router.get(
diff --git a/x-pack/plugins/cases/server/routes/api/sub_case/patch_sub_cases.ts b/x-pack/plugins/cases/server/routes/api/sub_case/patch_sub_cases.ts
index d3b24a1e3c06f..1fb260453d188 100644
--- a/x-pack/plugins/cases/server/routes/api/sub_case/patch_sub_cases.ts
+++ b/x-pack/plugins/cases/server/routes/api/sub_case/patch_sub_cases.ts
@@ -5,7 +5,8 @@
* 2.0.
*/
-import { SubCasesPatchRequest, SUB_CASES_PATCH_DEL_URL } from '../../../../common';
+import { SubCasesPatchRequest } from '../../../../common/api';
+import { SUB_CASES_PATCH_DEL_URL } from '../../../../common/constants';
import { RouteDeps } from '../types';
import { escapeHatch, wrapError } from '../utils';
diff --git a/x-pack/plugins/cases/server/routes/api/user_actions/get_all_user_actions.ts b/x-pack/plugins/cases/server/routes/api/user_actions/get_all_user_actions.ts
index 39b277e2239ad..5944ff6176d78 100644
--- a/x-pack/plugins/cases/server/routes/api/user_actions/get_all_user_actions.ts
+++ b/x-pack/plugins/cases/server/routes/api/user_actions/get_all_user_actions.ts
@@ -9,7 +9,7 @@ import { schema } from '@kbn/config-schema';
import { RouteDeps } from '../types';
import { wrapError } from '../utils';
-import { CASE_USER_ACTIONS_URL, SUB_CASE_USER_ACTIONS_URL } from '../../../../common';
+import { CASE_USER_ACTIONS_URL, SUB_CASE_USER_ACTIONS_URL } from '../../../../common/constants';
export function initGetAllCaseUserActionsApi({ router, logger }: RouteDeps) {
router.get(
diff --git a/x-pack/plugins/cases/server/routes/api/utils.test.ts b/x-pack/plugins/cases/server/routes/api/utils.test.ts
index fd7c038f06bc1..c2cff04f56a49 100644
--- a/x-pack/plugins/cases/server/routes/api/utils.test.ts
+++ b/x-pack/plugins/cases/server/routes/api/utils.test.ts
@@ -6,7 +6,7 @@
*/
import { isBoom, boomify } from '@hapi/boom';
-import { HTTPError } from '../../common';
+import { HTTPError } from '../../common/error';
import { wrapError } from './utils';
describe('Utils', () => {
diff --git a/x-pack/plugins/cases/server/routes/api/utils.ts b/x-pack/plugins/cases/server/routes/api/utils.ts
index cb4804aab0054..a09fd4cc9c746 100644
--- a/x-pack/plugins/cases/server/routes/api/utils.ts
+++ b/x-pack/plugins/cases/server/routes/api/utils.ts
@@ -9,7 +9,7 @@ import { Boom, boomify, isBoom } from '@hapi/boom';
import { schema } from '@kbn/config-schema';
import { CustomHttpResponseOptions, ResponseError } from 'kibana/server';
-import { CaseError, isCaseError, HTTPError, isHTTPError } from '../../common';
+import { CaseError, isCaseError, HTTPError, isHTTPError } from '../../common/error';
/**
* Transforms an error into the correct format for a kibana response.
diff --git a/x-pack/plugins/cases/server/saved_object_types/cases.ts b/x-pack/plugins/cases/server/saved_object_types/cases.ts
index 53c52c03afa12..b94b387798262 100644
--- a/x-pack/plugins/cases/server/saved_object_types/cases.ts
+++ b/x-pack/plugins/cases/server/saved_object_types/cases.ts
@@ -12,7 +12,7 @@ import {
SavedObjectsExportTransformContext,
SavedObjectsType,
} from 'src/core/server';
-import { CASE_SAVED_OBJECT } from '../../common';
+import { CASE_SAVED_OBJECT } from '../../common/constants';
import { ESCaseAttributes } from '../services/cases/types';
import { handleExport } from './import_export/export';
import { caseMigrations } from './migrations';
diff --git a/x-pack/plugins/cases/server/saved_object_types/comments.ts b/x-pack/plugins/cases/server/saved_object_types/comments.ts
index c950a432a3440..bc0993a345a5b 100644
--- a/x-pack/plugins/cases/server/saved_object_types/comments.ts
+++ b/x-pack/plugins/cases/server/saved_object_types/comments.ts
@@ -6,7 +6,7 @@
*/
import { SavedObjectsType } from 'src/core/server';
-import { CASE_COMMENT_SAVED_OBJECT } from '../../common';
+import { CASE_COMMENT_SAVED_OBJECT } from '../../common/constants';
import { createCommentsMigrations, CreateCommentsMigrationsDeps } from './migrations';
export const createCaseCommentSavedObjectType = ({
diff --git a/x-pack/plugins/cases/server/saved_object_types/configure.ts b/x-pack/plugins/cases/server/saved_object_types/configure.ts
index de478cae9326e..c9303b848dc23 100644
--- a/x-pack/plugins/cases/server/saved_object_types/configure.ts
+++ b/x-pack/plugins/cases/server/saved_object_types/configure.ts
@@ -6,7 +6,7 @@
*/
import { SavedObjectsType } from 'src/core/server';
-import { CASE_CONFIGURE_SAVED_OBJECT } from '../../common';
+import { CASE_CONFIGURE_SAVED_OBJECT } from '../../common/constants';
import { configureMigrations } from './migrations';
export const caseConfigureSavedObjectType: SavedObjectsType = {
diff --git a/x-pack/plugins/cases/server/saved_object_types/connector_mappings.ts b/x-pack/plugins/cases/server/saved_object_types/connector_mappings.ts
index 479edcba21534..b9bb275f080cf 100644
--- a/x-pack/plugins/cases/server/saved_object_types/connector_mappings.ts
+++ b/x-pack/plugins/cases/server/saved_object_types/connector_mappings.ts
@@ -6,7 +6,7 @@
*/
import { SavedObjectsType } from 'src/core/server';
-import { CASE_CONNECTOR_MAPPINGS_SAVED_OBJECT } from '../../common';
+import { CASE_CONNECTOR_MAPPINGS_SAVED_OBJECT } from '../../common/constants';
import { connectorMappingsMigrations } from './migrations';
export const caseConnectorMappingsSavedObjectType: SavedObjectsType = {
diff --git a/x-pack/plugins/cases/server/saved_object_types/import_export/export.ts b/x-pack/plugins/cases/server/saved_object_types/import_export/export.ts
index d089079314443..2a07d1ee978a0 100644
--- a/x-pack/plugins/cases/server/saved_object_types/import_export/export.ts
+++ b/x-pack/plugins/cases/server/saved_object_types/import_export/export.ts
@@ -12,16 +12,16 @@ import {
SavedObjectsClientContract,
SavedObjectsExportTransformContext,
} from 'kibana/server';
+import { CaseUserActionAttributes, CommentAttributes } from '../../../common/api';
import {
- CaseUserActionAttributes,
CASE_COMMENT_SAVED_OBJECT,
CASE_SAVED_OBJECT,
CASE_USER_ACTION_SAVED_OBJECT,
- CommentAttributes,
MAX_DOCS_PER_PAGE,
SAVED_OBJECT_TYPES,
-} from '../../../common';
-import { createCaseError, defaultSortField } from '../../common';
+} from '../../../common/constants';
+import { defaultSortField } from '../../common/utils';
+import { createCaseError } from '../../common/error';
import { ESCaseAttributes } from '../../services/cases/types';
export async function handleExport({
diff --git a/x-pack/plugins/cases/server/saved_object_types/migrations/cases.test.ts b/x-pack/plugins/cases/server/saved_object_types/migrations/cases.test.ts
index 9020f65ae352c..7bfaec76adf21 100644
--- a/x-pack/plugins/cases/server/saved_object_types/migrations/cases.test.ts
+++ b/x-pack/plugins/cases/server/saved_object_types/migrations/cases.test.ts
@@ -9,11 +9,11 @@ import { SavedObjectSanitizedDoc } from 'kibana/server';
import {
CaseAttributes,
CaseFullExternalService,
- CASE_SAVED_OBJECT,
ConnectorTypes,
noneConnectorId,
-} from '../../../common';
-import { getNoneCaseConnector } from '../../common';
+} from '../../../common/api';
+import { CASE_SAVED_OBJECT } from '../../../common/constants';
+import { getNoneCaseConnector } from '../../common/utils';
import { createExternalService, ESCaseConnectorWithId } from '../../services/test_utils';
import { caseConnectorIdMigration } from './cases';
diff --git a/x-pack/plugins/cases/server/saved_object_types/migrations/cases.ts b/x-pack/plugins/cases/server/saved_object_types/migrations/cases.ts
index 80f02fa3bf6a6..bc85253270cb0 100644
--- a/x-pack/plugins/cases/server/saved_object_types/migrations/cases.ts
+++ b/x-pack/plugins/cases/server/saved_object_types/migrations/cases.ts
@@ -13,12 +13,15 @@ import {
SavedObjectSanitizedDoc,
} from '../../../../../../src/core/server';
import { ESConnectorFields } from '../../services';
-import { ConnectorTypes, CaseType } from '../../../common';
+import { ConnectorTypes, CaseType } from '../../../common/api';
import {
transformConnectorIdToReference,
transformPushConnectorIdToReference,
} from '../../services/user_actions/transform';
-import { CONNECTOR_ID_REFERENCE_NAME, PUSH_CONNECTOR_ID_REFERENCE_NAME } from '../../common';
+import {
+ CONNECTOR_ID_REFERENCE_NAME,
+ PUSH_CONNECTOR_ID_REFERENCE_NAME,
+} from '../../common/constants';
interface UnsanitizedCaseConnector {
connector_id: string;
diff --git a/x-pack/plugins/cases/server/saved_object_types/migrations/comments.ts b/x-pack/plugins/cases/server/saved_object_types/migrations/comments.ts
index d754aec636693..e67e1c8b59887 100644
--- a/x-pack/plugins/cases/server/saved_object_types/migrations/comments.ts
+++ b/x-pack/plugins/cases/server/saved_object_types/migrations/comments.ts
@@ -20,7 +20,7 @@ import {
SavedObjectMigrationFn,
SavedObjectMigrationMap,
} from '../../../../../../src/core/server';
-import { CommentType, AssociationType } from '../../../common';
+import { CommentType, AssociationType } from '../../../common/api';
import {
isLensMarkdownNode,
LensMarkdownNode,
diff --git a/x-pack/plugins/cases/server/saved_object_types/migrations/configuration.test.ts b/x-pack/plugins/cases/server/saved_object_types/migrations/configuration.test.ts
index 9ae0285598dbf..7d9189673079d 100644
--- a/x-pack/plugins/cases/server/saved_object_types/migrations/configuration.test.ts
+++ b/x-pack/plugins/cases/server/saved_object_types/migrations/configuration.test.ts
@@ -7,12 +7,10 @@
import { SavedObjectSanitizedDoc } from 'kibana/server';
import { ACTION_SAVED_OBJECT_TYPE } from '../../../../actions/server';
-import {
- CASE_CONFIGURE_SAVED_OBJECT,
- ConnectorTypes,
- SECURITY_SOLUTION_OWNER,
-} from '../../../common';
-import { getNoneCaseConnector, CONNECTOR_ID_REFERENCE_NAME } from '../../common';
+import { ConnectorTypes } from '../../../common/api';
+import { CASE_CONFIGURE_SAVED_OBJECT, SECURITY_SOLUTION_OWNER } from '../../../common/constants';
+import { CONNECTOR_ID_REFERENCE_NAME } from '../../common/constants';
+import { getNoneCaseConnector } from '../../common/utils';
import { ESCaseConnectorWithId } from '../../services/test_utils';
import { ESCasesConfigureAttributes } from '../../services/configure/types';
import { configureConnectorIdMigration } from './configuration';
diff --git a/x-pack/plugins/cases/server/saved_object_types/migrations/configuration.ts b/x-pack/plugins/cases/server/saved_object_types/migrations/configuration.ts
index f9937253e0d2f..6cd9b5455b978 100644
--- a/x-pack/plugins/cases/server/saved_object_types/migrations/configuration.ts
+++ b/x-pack/plugins/cases/server/saved_object_types/migrations/configuration.ts
@@ -11,10 +11,10 @@ import {
SavedObjectUnsanitizedDoc,
SavedObjectSanitizedDoc,
} from '../../../../../../src/core/server';
-import { ConnectorTypes } from '../../../common';
+import { ConnectorTypes } from '../../../common/api';
import { addOwnerToSO, SanitizedCaseOwner } from '.';
import { transformConnectorIdToReference } from '../../services/user_actions/transform';
-import { CONNECTOR_ID_REFERENCE_NAME } from '../../common';
+import { CONNECTOR_ID_REFERENCE_NAME } from '../../common/constants';
interface UnsanitizedConfigureConnector {
connector_id: string;
diff --git a/x-pack/plugins/cases/server/saved_object_types/migrations/index.ts b/x-pack/plugins/cases/server/saved_object_types/migrations/index.ts
index b0f9c7d2145de..105692b667fb5 100644
--- a/x-pack/plugins/cases/server/saved_object_types/migrations/index.ts
+++ b/x-pack/plugins/cases/server/saved_object_types/migrations/index.ts
@@ -9,7 +9,7 @@ import {
SavedObjectUnsanitizedDoc,
SavedObjectSanitizedDoc,
} from '../../../../../../src/core/server';
-import { SECURITY_SOLUTION_OWNER } from '../../../common';
+import { SECURITY_SOLUTION_OWNER } from '../../../common/constants';
export { caseMigrations } from './cases';
export { configureMigrations } from './configuration';
diff --git a/x-pack/plugins/cases/server/saved_object_types/migrations/user_actions.test.ts b/x-pack/plugins/cases/server/saved_object_types/migrations/user_actions.test.ts
index e71c8db0db694..e9ba80322c222 100644
--- a/x-pack/plugins/cases/server/saved_object_types/migrations/user_actions.test.ts
+++ b/x-pack/plugins/cases/server/saved_object_types/migrations/user_actions.test.ts
@@ -9,7 +9,8 @@
import { SavedObjectMigrationContext, SavedObjectSanitizedDoc } from 'kibana/server';
import { migrationMocks } from 'src/core/server/mocks';
-import { CaseUserActionAttributes, CASE_USER_ACTION_SAVED_OBJECT } from '../../../common';
+import { CaseUserActionAttributes } from '../../../common/api';
+import { CASE_USER_ACTION_SAVED_OBJECT } from '../../../common/constants';
import {
createConnectorObject,
createExternalService,
diff --git a/x-pack/plugins/cases/server/saved_object_types/migrations/user_actions.ts b/x-pack/plugins/cases/server/saved_object_types/migrations/user_actions.ts
index ed6b57ef647f9..a47104dfed5f7 100644
--- a/x-pack/plugins/cases/server/saved_object_types/migrations/user_actions.ts
+++ b/x-pack/plugins/cases/server/saved_object_types/migrations/user_actions.ts
@@ -14,7 +14,8 @@ import {
SavedObjectMigrationContext,
LogMeta,
} from '../../../../../../src/core/server';
-import { ConnectorTypes, isCreateConnector, isPush, isUpdateConnector } from '../../../common';
+import { isPush, isUpdateConnector, isCreateConnector } from '../../../common/utils/user_actions';
+import { ConnectorTypes } from '../../../common/api';
import { extractConnectorIdFromJson } from '../../services/user_actions/transform';
import { UserActionFieldType } from '../../services/user_actions/types';
diff --git a/x-pack/plugins/cases/server/saved_object_types/sub_case.ts b/x-pack/plugins/cases/server/saved_object_types/sub_case.ts
index f7cf2aa65f821..469b27d4b40ba 100644
--- a/x-pack/plugins/cases/server/saved_object_types/sub_case.ts
+++ b/x-pack/plugins/cases/server/saved_object_types/sub_case.ts
@@ -6,7 +6,7 @@
*/
import { SavedObjectsType } from 'src/core/server';
-import { SUB_CASE_SAVED_OBJECT } from '../../common';
+import { SUB_CASE_SAVED_OBJECT } from '../../common/constants';
import { subCasesMigrations } from './migrations';
export const subCaseSavedObjectType: SavedObjectsType = {
diff --git a/x-pack/plugins/cases/server/saved_object_types/user_actions.ts b/x-pack/plugins/cases/server/saved_object_types/user_actions.ts
index 8fea9460e77c5..2af2fd4c7e883 100644
--- a/x-pack/plugins/cases/server/saved_object_types/user_actions.ts
+++ b/x-pack/plugins/cases/server/saved_object_types/user_actions.ts
@@ -6,7 +6,7 @@
*/
import { SavedObjectsType } from 'src/core/server';
-import { CASE_USER_ACTION_SAVED_OBJECT } from '../../common';
+import { CASE_USER_ACTION_SAVED_OBJECT } from '../../common/constants';
import { userActionsMigrations } from './migrations';
export const caseUserActionSavedObjectType: SavedObjectsType = {
diff --git a/x-pack/plugins/cases/server/scripts/sub_cases/index.ts b/x-pack/plugins/cases/server/scripts/sub_cases/index.ts
index 28672160a0737..75a896a4b81fd 100644
--- a/x-pack/plugins/cases/server/scripts/sub_cases/index.ts
+++ b/x-pack/plugins/cases/server/scripts/sub_cases/index.ts
@@ -12,14 +12,8 @@
import yargs from 'yargs';
import { ToolingLog } from '@kbn/dev-utils';
import { KbnClient } from '@kbn/test';
-import {
- CaseResponse,
- CaseType,
- CommentType,
- ConnectorTypes,
- CASES_URL,
- SECURITY_SOLUTION_OWNER,
-} from '../../../common';
+import { CaseResponse, CaseType, CommentType, ConnectorTypes } from '../../../common/api';
+import { CASES_URL, SECURITY_SOLUTION_OWNER } from '../../../common/constants';
import { ActionResult, ActionTypeExecutorResult } from '../../../../actions/common';
import { ContextTypeGeneratedAlertType, createAlertsString } from '../../connectors';
import {
diff --git a/x-pack/plugins/cases/server/services/alerts/index.test.ts b/x-pack/plugins/cases/server/services/alerts/index.test.ts
index 2c98da198fa07..3104b85e0b0b9 100644
--- a/x-pack/plugins/cases/server/services/alerts/index.test.ts
+++ b/x-pack/plugins/cases/server/services/alerts/index.test.ts
@@ -5,7 +5,7 @@
* 2.0.
*/
-import { CaseStatuses } from '../../../common';
+import { CaseStatuses } from '../../../common/api';
import { AlertService } from '.';
import { elasticsearchServiceMock, loggingSystemMock } from 'src/core/server/mocks';
diff --git a/x-pack/plugins/cases/server/services/alerts/index.ts b/x-pack/plugins/cases/server/services/alerts/index.ts
index ca7bfe66804f3..424bbd9814e97 100644
--- a/x-pack/plugins/cases/server/services/alerts/index.ts
+++ b/x-pack/plugins/cases/server/services/alerts/index.ts
@@ -9,8 +9,10 @@ import pMap from 'p-map';
import { isEmpty } from 'lodash';
import { ElasticsearchClient, Logger } from 'kibana/server';
-import { CaseStatuses, MAX_ALERTS_PER_SUB_CASE, MAX_CONCURRENT_SEARCHES } from '../../../common';
-import { AlertInfo, createCaseError } from '../../common';
+import { CaseStatuses } from '../../../common/api';
+import { MAX_ALERTS_PER_SUB_CASE, MAX_CONCURRENT_SEARCHES } from '../../../common/constants';
+import { createCaseError } from '../../common/error';
+import { AlertInfo } from '../../common/types';
import { UpdateAlertRequest } from '../../client/alerts/types';
import {
ALERT_WORKFLOW_STATUS,
diff --git a/x-pack/plugins/cases/server/services/attachments/index.ts b/x-pack/plugins/cases/server/services/attachments/index.ts
index 95a66fd9af192..f4e858eb0ed4f 100644
--- a/x-pack/plugins/cases/server/services/attachments/index.ts
+++ b/x-pack/plugins/cases/server/services/attachments/index.ts
@@ -15,13 +15,15 @@ import {
import type { KueryNode } from '@kbn/es-query';
import {
AttributesTypeAlerts,
- CASE_COMMENT_SAVED_OBJECT,
CommentAttributes as AttachmentAttributes,
CommentPatchAttributes as AttachmentPatchAttributes,
+ CommentType,
+} from '../../../common/api';
+import {
+ CASE_COMMENT_SAVED_OBJECT,
CASE_SAVED_OBJECT,
MAX_DOCS_PER_PAGE,
- CommentType,
-} from '../../../common';
+} from '../../../common/constants';
import { ClientArgs } from '..';
import { buildFilter, combineFilters } from '../../client/utils';
diff --git a/x-pack/plugins/cases/server/services/cases/index.test.ts b/x-pack/plugins/cases/server/services/cases/index.test.ts
index 8c71abe5bff4f..d813a9fc06a66 100644
--- a/x-pack/plugins/cases/server/services/cases/index.test.ts
+++ b/x-pack/plugins/cases/server/services/cases/index.test.ts
@@ -13,12 +13,8 @@
* connector.id.
*/
-import {
- CaseAttributes,
- CaseConnector,
- CaseFullExternalService,
- CASE_SAVED_OBJECT,
-} from '../../../common';
+import { CaseAttributes, CaseConnector, CaseFullExternalService } from '../../../common/api';
+import { CASE_SAVED_OBJECT } from '../../../common/constants';
import { savedObjectsClientMock } from '../../../../../../src/core/server/mocks';
import {
SavedObject,
@@ -30,7 +26,8 @@ import {
} from 'kibana/server';
import { ACTION_SAVED_OBJECT_TYPE } from '../../../../actions/server';
import { loggerMock } from '@kbn/logging/mocks';
-import { getNoneCaseConnector, CONNECTOR_ID_REFERENCE_NAME } from '../../common';
+import { CONNECTOR_ID_REFERENCE_NAME } from '../../common/constants';
+import { getNoneCaseConnector } from '../../common/utils';
import { CasesService } from '.';
import {
createESJiraConnector,
diff --git a/x-pack/plugins/cases/server/services/cases/index.ts b/x-pack/plugins/cases/server/services/cases/index.ts
index 15e60c49768a5..7285761e6558a 100644
--- a/x-pack/plugins/cases/server/services/cases/index.ts
+++ b/x-pack/plugins/cases/server/services/cases/index.ts
@@ -24,9 +24,17 @@ import { nodeBuilder, KueryNode } from '@kbn/es-query';
import { SecurityPluginSetup } from '../../../../security/server';
import {
- AssociationType,
CASE_COMMENT_SAVED_OBJECT,
CASE_SAVED_OBJECT,
+ ENABLE_CASE_CONNECTOR,
+ MAX_CONCURRENT_SEARCHES,
+ MAX_DOCS_PER_PAGE,
+ SUB_CASE_SAVED_OBJECT,
+} from '../../../common/constants';
+import {
+ OWNER_FIELD,
+ GetCaseIdsByAlertIdAggs,
+ AssociationType,
CaseResponse,
CasesFindRequest,
CaseStatuses,
@@ -34,24 +42,18 @@ import {
caseTypeField,
CommentAttributes,
CommentType,
- ENABLE_CASE_CONNECTOR,
- GetCaseIdsByAlertIdAggs,
- MAX_CONCURRENT_SEARCHES,
- MAX_DOCS_PER_PAGE,
- OWNER_FIELD,
- SUB_CASE_SAVED_OBJECT,
SubCaseAttributes,
SubCaseResponse,
User,
CaseAttributes,
-} from '../../../common';
+} from '../../../common/api';
+import { SavedObjectFindOptionsKueryNode } from '../../common/types';
import {
defaultSortField,
flattenCaseSavedObject,
flattenSubCaseSavedObject,
groupTotalAlertsByID,
- SavedObjectFindOptionsKueryNode,
-} from '../../common';
+} from '../../common/utils';
import { defaultPage, defaultPerPage } from '../../routes/api';
import { ClientArgs } from '..';
import { combineFilters } from '../../client/utils';
diff --git a/x-pack/plugins/cases/server/services/cases/transform.test.ts b/x-pack/plugins/cases/server/services/cases/transform.test.ts
index 96312d00b37dd..b28f364dbd03f 100644
--- a/x-pack/plugins/cases/server/services/cases/transform.test.ts
+++ b/x-pack/plugins/cases/server/services/cases/transform.test.ts
@@ -17,12 +17,12 @@ import {
transformUpdateResponseToExternalModel,
} from './transform';
import { ACTION_SAVED_OBJECT_TYPE } from '../../../../actions/server';
-import { ConnectorTypes } from '../../../common';
+import { ConnectorTypes } from '../../../common/api';
import {
- getNoneCaseConnector,
CONNECTOR_ID_REFERENCE_NAME,
PUSH_CONNECTOR_ID_REFERENCE_NAME,
-} from '../../common';
+} from '../../common/constants';
+import { getNoneCaseConnector } from '../../common/utils';
describe('case transforms', () => {
describe('transformUpdateResponseToExternalModel', () => {
diff --git a/x-pack/plugins/cases/server/services/cases/transform.ts b/x-pack/plugins/cases/server/services/cases/transform.ts
index e3609689871d2..260847b326a86 100644
--- a/x-pack/plugins/cases/server/services/cases/transform.ts
+++ b/x-pack/plugins/cases/server/services/cases/transform.ts
@@ -17,8 +17,11 @@ import {
} from 'kibana/server';
import { ACTION_SAVED_OBJECT_TYPE } from '../../../../actions/server';
import { ESCaseAttributes, ExternalServicesWithoutConnectorId } from './types';
-import { CONNECTOR_ID_REFERENCE_NAME, PUSH_CONNECTOR_ID_REFERENCE_NAME } from '../../common';
-import { CaseAttributes, CaseFullExternalService } from '../../../common';
+import {
+ CONNECTOR_ID_REFERENCE_NAME,
+ PUSH_CONNECTOR_ID_REFERENCE_NAME,
+} from '../../common/constants';
+import { CaseAttributes, CaseFullExternalService } from '../../../common/api';
import {
findConnectorIdReference,
transformFieldsToESModel,
diff --git a/x-pack/plugins/cases/server/services/cases/types.ts b/x-pack/plugins/cases/server/services/cases/types.ts
index 55c736b032590..eb47850eeef31 100644
--- a/x-pack/plugins/cases/server/services/cases/types.ts
+++ b/x-pack/plugins/cases/server/services/cases/types.ts
@@ -6,7 +6,7 @@
*/
import * as rt from 'io-ts';
-import { CaseAttributes, CaseExternalServiceBasicRt } from '../../../common';
+import { CaseAttributes, CaseExternalServiceBasicRt } from '../../../common/api';
import { ESCaseConnector } from '..';
/**
diff --git a/x-pack/plugins/cases/server/services/configure/index.test.ts b/x-pack/plugins/cases/server/services/configure/index.test.ts
index 876cb7b21f81a..2b30e4d4de628 100644
--- a/x-pack/plugins/cases/server/services/configure/index.test.ts
+++ b/x-pack/plugins/cases/server/services/configure/index.test.ts
@@ -9,10 +9,9 @@ import {
CaseConnector,
CasesConfigureAttributes,
CasesConfigurePatch,
- CASE_CONFIGURE_SAVED_OBJECT,
ConnectorTypes,
- SECURITY_SOLUTION_OWNER,
-} from '../../../common';
+} from '../../../common/api';
+import { CASE_CONFIGURE_SAVED_OBJECT, SECURITY_SOLUTION_OWNER } from '../../../common/constants';
import { savedObjectsClientMock } from '../../../../../../src/core/server/mocks';
import {
SavedObject,
@@ -26,7 +25,8 @@ import { ACTION_SAVED_OBJECT_TYPE } from '../../../../actions/server';
import { loggerMock } from '@kbn/logging/mocks';
import { CaseConfigureService } from '.';
import { ESCasesConfigureAttributes } from './types';
-import { getNoneCaseConnector, CONNECTOR_ID_REFERENCE_NAME } from '../../common';
+import { CONNECTOR_ID_REFERENCE_NAME } from '../../common/constants';
+import { getNoneCaseConnector } from '../../common/utils';
import { createESJiraConnector, createJiraConnector, ESCaseConnectorWithId } from '../test_utils';
const basicConfigFields = {
diff --git a/x-pack/plugins/cases/server/services/configure/index.ts b/x-pack/plugins/cases/server/services/configure/index.ts
index db6d033f23ca8..0c22d95a5ee33 100644
--- a/x-pack/plugins/cases/server/services/configure/index.ts
+++ b/x-pack/plugins/cases/server/services/configure/index.ts
@@ -13,12 +13,10 @@ import {
SavedObjectsUpdateResponse,
} from 'kibana/server';
-import { SavedObjectFindOptionsKueryNode, CONNECTOR_ID_REFERENCE_NAME } from '../../common';
-import {
- CASE_CONFIGURE_SAVED_OBJECT,
- CasesConfigureAttributes,
- CasesConfigurePatch,
-} from '../../../common';
+import { SavedObjectFindOptionsKueryNode } from '../../common/types';
+import { CONNECTOR_ID_REFERENCE_NAME } from '../../common/constants';
+import { CasesConfigureAttributes, CasesConfigurePatch } from '../../../common/api';
+import { CASE_CONFIGURE_SAVED_OBJECT } from '../../../common/constants';
import { ACTION_SAVED_OBJECT_TYPE } from '../../../../actions/server';
import {
transformFieldsToESModel,
diff --git a/x-pack/plugins/cases/server/services/configure/types.ts b/x-pack/plugins/cases/server/services/configure/types.ts
index f52e05a2ff9b5..3c4405e532e69 100644
--- a/x-pack/plugins/cases/server/services/configure/types.ts
+++ b/x-pack/plugins/cases/server/services/configure/types.ts
@@ -5,7 +5,7 @@
* 2.0.
*/
-import { CasesConfigureAttributes } from '../../../common';
+import { CasesConfigureAttributes } from '../../../common/api';
import { ESCaseConnector } from '..';
/**
diff --git a/x-pack/plugins/cases/server/services/connector_mappings/index.ts b/x-pack/plugins/cases/server/services/connector_mappings/index.ts
index 0798b35a78a4c..46b9f4bac0064 100644
--- a/x-pack/plugins/cases/server/services/connector_mappings/index.ts
+++ b/x-pack/plugins/cases/server/services/connector_mappings/index.ts
@@ -7,8 +7,9 @@
import { Logger, SavedObjectReference, SavedObjectsClientContract } from 'kibana/server';
-import { ConnectorMappings, CASE_CONNECTOR_MAPPINGS_SAVED_OBJECT } from '../../../common';
-import { SavedObjectFindOptionsKueryNode } from '../../common';
+import { CASE_CONNECTOR_MAPPINGS_SAVED_OBJECT } from '../../../common/constants';
+import { ConnectorMappings } from '../../../common/api';
+import { SavedObjectFindOptionsKueryNode } from '../../common/types';
interface ClientArgs {
unsecuredSavedObjectsClient: SavedObjectsClientContract;
diff --git a/x-pack/plugins/cases/server/services/connector_reference_handler.test.ts b/x-pack/plugins/cases/server/services/connector_reference_handler.test.ts
index 4c42332d10627..8b0bc527f9909 100644
--- a/x-pack/plugins/cases/server/services/connector_reference_handler.test.ts
+++ b/x-pack/plugins/cases/server/services/connector_reference_handler.test.ts
@@ -5,7 +5,7 @@
* 2.0.
*/
-import { noneConnectorId } from '../../common';
+import { noneConnectorId } from '../../common/api';
import { ConnectorReferenceHandler } from './connector_reference_handler';
describe('ConnectorReferenceHandler', () => {
diff --git a/x-pack/plugins/cases/server/services/connector_reference_handler.ts b/x-pack/plugins/cases/server/services/connector_reference_handler.ts
index 81e1541366ab5..833cba26f0d1e 100644
--- a/x-pack/plugins/cases/server/services/connector_reference_handler.ts
+++ b/x-pack/plugins/cases/server/services/connector_reference_handler.ts
@@ -6,7 +6,7 @@
*/
import { SavedObjectReference } from 'kibana/server';
-import { noneConnectorId } from '../../common';
+import { noneConnectorId } from '../../common/api';
interface Reference {
soReference?: SavedObjectReference;
diff --git a/x-pack/plugins/cases/server/services/index.ts b/x-pack/plugins/cases/server/services/index.ts
index c14e1fab4a410..9a43aa43c2874 100644
--- a/x-pack/plugins/cases/server/services/index.ts
+++ b/x-pack/plugins/cases/server/services/index.ts
@@ -6,7 +6,7 @@
*/
import { SavedObjectsClientContract } from 'kibana/server';
-import { ConnectorTypes } from '../../common';
+import { ConnectorTypes } from '../../common/api';
export { CasesService } from './cases';
export { CaseConfigureService } from './configure';
diff --git a/x-pack/plugins/cases/server/services/test_utils.ts b/x-pack/plugins/cases/server/services/test_utils.ts
index 07743eda61212..c76ad0d83410b 100644
--- a/x-pack/plugins/cases/server/services/test_utils.ts
+++ b/x-pack/plugins/cases/server/services/test_utils.ts
@@ -7,17 +7,16 @@
import { SavedObject, SavedObjectReference, SavedObjectsFindResult } from 'kibana/server';
import { ESConnectorFields } from '.';
-import { CONNECTOR_ID_REFERENCE_NAME, PUSH_CONNECTOR_ID_REFERENCE_NAME } from '../common';
+import { CONNECTOR_ID_REFERENCE_NAME, PUSH_CONNECTOR_ID_REFERENCE_NAME } from '../common/constants';
import {
CaseConnector,
CaseFullExternalService,
CaseStatuses,
CaseType,
- CASE_SAVED_OBJECT,
ConnectorTypes,
noneConnectorId,
- SECURITY_SOLUTION_OWNER,
-} from '../../common';
+} from '../../common/api';
+import { CASE_SAVED_OBJECT, SECURITY_SOLUTION_OWNER } from '../../common/constants';
import { ESCaseAttributes, ExternalServicesWithoutConnectorId } from './cases/types';
import { ACTION_SAVED_OBJECT_TYPE } from '../../../actions/server';
diff --git a/x-pack/plugins/cases/server/services/transform.test.ts b/x-pack/plugins/cases/server/services/transform.test.ts
index b4346595e4998..f7f49d285b80c 100644
--- a/x-pack/plugins/cases/server/services/transform.test.ts
+++ b/x-pack/plugins/cases/server/services/transform.test.ts
@@ -6,7 +6,7 @@
*/
import { ACTION_SAVED_OBJECT_TYPE } from '../../../actions/server';
-import { ConnectorTypes } from '../../common';
+import { ConnectorTypes } from '../../common/api';
import { createESJiraConnector, createJiraConnector } from './test_utils';
import {
findConnectorIdReference,
diff --git a/x-pack/plugins/cases/server/services/transform.ts b/x-pack/plugins/cases/server/services/transform.ts
index 39351d3a4b50a..8956bfe42954e 100644
--- a/x-pack/plugins/cases/server/services/transform.ts
+++ b/x-pack/plugins/cases/server/services/transform.ts
@@ -6,9 +6,9 @@
*/
import { SavedObjectReference } from 'kibana/server';
-import { CaseConnector, ConnectorTypeFields } from '../../common';
+import { CaseConnector, ConnectorTypeFields } from '../../common/api';
import { ACTION_SAVED_OBJECT_TYPE } from '../../../actions/server';
-import { getNoneCaseConnector } from '../common';
+import { getNoneCaseConnector } from '../common/utils';
import { ESCaseConnector, ESConnectorFields } from '.';
export function findConnectorIdReference(
diff --git a/x-pack/plugins/cases/server/services/user_actions/helpers.test.ts b/x-pack/plugins/cases/server/services/user_actions/helpers.test.ts
index 7bcbaf58d0f6e..e528ca67ce4c2 100644
--- a/x-pack/plugins/cases/server/services/user_actions/helpers.test.ts
+++ b/x-pack/plugins/cases/server/services/user_actions/helpers.test.ts
@@ -5,7 +5,7 @@
* 2.0.
*/
-import { UserActionField } from '../../../common';
+import { UserActionField } from '../../../common/api';
import { createConnectorObject, createExternalService, createJiraConnector } from '../test_utils';
import { buildCaseUserActionItem } from './helpers';
diff --git a/x-pack/plugins/cases/server/services/user_actions/helpers.ts b/x-pack/plugins/cases/server/services/user_actions/helpers.ts
index e91b69f0995bd..d99c1dbbb29e4 100644
--- a/x-pack/plugins/cases/server/services/user_actions/helpers.ts
+++ b/x-pack/plugins/cases/server/services/user_actions/helpers.ts
@@ -10,22 +10,24 @@ import { get, isPlainObject, isString } from 'lodash';
import deepEqual from 'fast-deep-equal';
import {
- CASE_COMMENT_SAVED_OBJECT,
- CASE_SAVED_OBJECT,
CaseUserActionAttributes,
- OWNER_FIELD,
- SUB_CASE_SAVED_OBJECT,
SubCaseAttributes,
User,
UserAction,
UserActionField,
CaseAttributes,
-} from '../../../common';
+ OWNER_FIELD,
+} from '../../../common/api';
+import {
+ CASE_COMMENT_SAVED_OBJECT,
+ CASE_SAVED_OBJECT,
+ SUB_CASE_SAVED_OBJECT,
+} from '../../../common/constants';
import { isTwoArraysDifference } from '../../client/utils';
import { UserActionItem } from '.';
import { extractConnectorId } from './transform';
import { UserActionFieldType } from './types';
-import { CASE_REF_NAME, COMMENT_REF_NAME, SUB_CASE_REF_NAME } from '../../common';
+import { CASE_REF_NAME, COMMENT_REF_NAME, SUB_CASE_REF_NAME } from '../../common/constants';
interface BuildCaseUserActionParams {
action: UserAction;
diff --git a/x-pack/plugins/cases/server/services/user_actions/index.test.ts b/x-pack/plugins/cases/server/services/user_actions/index.test.ts
index c4a350f4ac015..a35fb8f1baba7 100644
--- a/x-pack/plugins/cases/server/services/user_actions/index.test.ts
+++ b/x-pack/plugins/cases/server/services/user_actions/index.test.ts
@@ -7,12 +7,8 @@
import { SavedObject, SavedObjectsFindResult } from 'kibana/server';
import { transformFindResponseToExternalModel, UserActionItem } from '.';
-import {
- CaseUserActionAttributes,
- CASE_USER_ACTION_SAVED_OBJECT,
- UserAction,
- UserActionField,
-} from '../../../common';
+import { CaseUserActionAttributes, UserAction, UserActionField } from '../../../common/api';
+import { CASE_USER_ACTION_SAVED_OBJECT } from '../../../common/constants';
import {
createConnectorObject,
diff --git a/x-pack/plugins/cases/server/services/user_actions/index.ts b/x-pack/plugins/cases/server/services/user_actions/index.ts
index 4f158862e3d63..507c36f866611 100644
--- a/x-pack/plugins/cases/server/services/user_actions/index.ts
+++ b/x-pack/plugins/cases/server/services/user_actions/index.ts
@@ -12,21 +12,18 @@ import {
SavedObjectsFindResult,
} from 'kibana/server';
+import { isCreateConnector, isPush, isUpdateConnector } from '../../../common/utils/user_actions';
+import { CaseUserActionAttributes, CaseUserActionResponse } from '../../../common/api';
import {
CASE_SAVED_OBJECT,
CASE_USER_ACTION_SAVED_OBJECT,
- CaseUserActionAttributes,
MAX_DOCS_PER_PAGE,
SUB_CASE_SAVED_OBJECT,
- CaseUserActionResponse,
CASE_COMMENT_SAVED_OBJECT,
- isCreateConnector,
- isPush,
- isUpdateConnector,
-} from '../../../common';
+} from '../../../common/constants';
import { ClientArgs } from '..';
import { UserActionFieldType } from './types';
-import { CASE_REF_NAME, COMMENT_REF_NAME, SUB_CASE_REF_NAME } from '../../common';
+import { CASE_REF_NAME, COMMENT_REF_NAME, SUB_CASE_REF_NAME } from '../../common/constants';
import { ConnectorIdReferenceName, PushConnectorIdReferenceName } from './transform';
import { findConnectorIdReference } from '../transform';
diff --git a/x-pack/plugins/cases/server/services/user_actions/transform.test.ts b/x-pack/plugins/cases/server/services/user_actions/transform.test.ts
index 2d28770617094..a75d16c4764b6 100644
--- a/x-pack/plugins/cases/server/services/user_actions/transform.test.ts
+++ b/x-pack/plugins/cases/server/services/user_actions/transform.test.ts
@@ -5,14 +5,14 @@
* 2.0.
*/
-import { noneConnectorId } from '../../../common';
+import { noneConnectorId } from '../../../common/api';
import {
CONNECTOR_ID_REFERENCE_NAME,
- getNoneCaseConnector,
PUSH_CONNECTOR_ID_REFERENCE_NAME,
USER_ACTION_OLD_ID_REF_NAME,
USER_ACTION_OLD_PUSH_ID_REF_NAME,
-} from '../../common';
+} from '../../common/constants';
+import { getNoneCaseConnector } from '../../common/utils';
import { createConnectorObject, createExternalService, createJiraConnector } from '../test_utils';
import {
extractConnectorIdHelper,
diff --git a/x-pack/plugins/cases/server/services/user_actions/transform.ts b/x-pack/plugins/cases/server/services/user_actions/transform.ts
index 93595374208a3..a3ec8a2c115b6 100644
--- a/x-pack/plugins/cases/server/services/user_actions/transform.ts
+++ b/x-pack/plugins/cases/server/services/user_actions/transform.ts
@@ -11,23 +11,21 @@ import * as rt from 'io-ts';
import { isString } from 'lodash';
import { SavedObjectReference } from '../../../../../../src/core/server';
+import { isCreateConnector, isPush, isUpdateConnector } from '../../../common/utils/user_actions';
import {
CaseAttributes,
CaseConnector,
CaseConnectorRt,
CaseExternalServiceBasicRt,
- isCreateConnector,
- isPush,
- isUpdateConnector,
noneConnectorId,
-} from '../../../common';
+} from '../../../common/api';
import {
CONNECTOR_ID_REFERENCE_NAME,
- getNoneCaseConnector,
PUSH_CONNECTOR_ID_REFERENCE_NAME,
USER_ACTION_OLD_ID_REF_NAME,
USER_ACTION_OLD_PUSH_ID_REF_NAME,
-} from '../../common';
+} from '../../common/constants';
+import { getNoneCaseConnector } from '../../common/utils';
import { ACTION_SAVED_OBJECT_TYPE } from '../../../../actions/server';
import { UserActionFieldType } from './types';
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 8cd5b86314a2c..376941b018f6a 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
@@ -34,6 +34,7 @@ class DocLinks {
public enterpriseSearchMailService: string;
public enterpriseSearchUsersAccess: string;
public licenseManagement: string;
+ public workplaceSearchApiKeys: string;
public workplaceSearchBox: string;
public workplaceSearchConfluenceCloud: string;
public workplaceSearchConfluenceServer: string;
@@ -86,6 +87,7 @@ class DocLinks {
this.enterpriseSearchMailService = '';
this.enterpriseSearchUsersAccess = '';
this.licenseManagement = '';
+ this.workplaceSearchApiKeys = '';
this.workplaceSearchBox = '';
this.workplaceSearchConfluenceCloud = '';
this.workplaceSearchConfluenceServer = '';
@@ -139,6 +141,7 @@ class DocLinks {
this.enterpriseSearchMailService = docLinks.links.enterpriseSearch.mailService;
this.enterpriseSearchUsersAccess = docLinks.links.enterpriseSearch.usersAccess;
this.licenseManagement = docLinks.links.enterpriseSearch.licenseManagement;
+ this.workplaceSearchApiKeys = docLinks.links.workplaceSearch.apiKeys;
this.workplaceSearchBox = docLinks.links.workplaceSearch.box;
this.workplaceSearchConfluenceCloud = docLinks.links.workplaceSearch.confluenceCloud;
this.workplaceSearchConfluenceServer = docLinks.links.workplaceSearch.confluenceServer;
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/__mocks__/content_sources.mock.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/__mocks__/content_sources.mock.ts
index 85ffde0acfea3..7af40b23d9f64 100644
--- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/__mocks__/content_sources.mock.ts
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/__mocks__/content_sources.mock.ts
@@ -123,6 +123,11 @@ export const fullContentSources = [
urlFieldIsLinkable: true,
createdAt: '2021-01-20',
serviceName: 'myService',
+ secret: {
+ app_id: '99999',
+ fingerprint: '65xM7s0RE6tEWNhnuXpK5EvZ5OAMIcbDHIISm/0T23Y=',
+ base_url: 'http://github.com',
+ },
},
{
...contentSources[1],
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/nav.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/nav.test.tsx
index 65a6a798b032a..01df4bdd02d55 100644
--- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/nav.test.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/nav.test.tsx
@@ -49,6 +49,11 @@ describe('useWorkplaceSearchNav', () => {
name: 'Users and roles',
href: '/users_and_roles',
},
+ {
+ id: 'apiKeys',
+ name: 'API keys',
+ href: '/api_keys',
+ },
{
id: 'security',
name: 'Security',
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/nav.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/nav.tsx
index 7dc005a56bf10..05ec569dcd292 100644
--- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/nav.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/nav.tsx
@@ -10,6 +10,7 @@ import { EuiSideNavItemType } from '@elastic/eui';
import { generateNavLink } from '../../../shared/layout';
import { NAV } from '../../constants';
import {
+ API_KEYS_PATH,
SOURCES_PATH,
SECURITY_PATH,
USERS_AND_ROLES_PATH,
@@ -47,6 +48,11 @@ export const useWorkplaceSearchNav = () => {
name: NAV.ROLE_MAPPINGS,
...generateNavLink({ to: USERS_AND_ROLES_PATH }),
},
+ {
+ id: 'apiKeys',
+ name: NAV.API_KEYS,
+ ...generateNavLink({ to: API_KEYS_PATH }),
+ },
{
id: 'security',
name: NAV.SECURITY,
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/index.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/index.ts
index e6a994d05f3ff..fdccd536c3c6d 100644
--- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/index.ts
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/index.ts
@@ -31,6 +31,8 @@ export const images = {
dropbox,
github,
githubEnterpriseServer: github,
+ githubViaApp: github,
+ githubEnterpriseServerViaApp: github,
gmail,
googleDrive,
jira,
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/constants.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/constants.ts
index 43da4ccef223a..9d3b2cb8aaefd 100644
--- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/constants.ts
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/constants.ts
@@ -42,6 +42,9 @@ export const NAV = {
ROLE_MAPPINGS: i18n.translate('xpack.enterpriseSearch.workplaceSearch.nav.roleMappings', {
defaultMessage: 'Users and roles',
}),
+ API_KEYS: i18n.translate('xpack.enterpriseSearch.workplaceSearch.nav.apiKeys', {
+ defaultMessage: 'API keys',
+ }),
SECURITY: i18n.translate('xpack.enterpriseSearch.workplaceSearch.nav.security', {
defaultMessage: 'Security',
}),
@@ -329,6 +332,20 @@ export const SOURCE_OBJ_TYPES = {
),
};
+export const API_KEYS_TITLE = i18n.translate(
+ 'xpack.enterpriseSearch.workplaceSearch.sources.apiKeysTitle',
+ {
+ defaultMessage: 'API keys',
+ }
+);
+
+export const API_KEY_LABEL = i18n.translate(
+ 'xpack.enterpriseSearch.workplaceSearch.sources.apiKeyLabel',
+ {
+ defaultMessage: 'API key',
+ }
+);
+
export const GITHUB_LINK_TITLE = i18n.translate(
'xpack.enterpriseSearch.workplaceSearch.sources.applicationLinkTitles.github',
{
@@ -336,6 +353,9 @@ export const GITHUB_LINK_TITLE = i18n.translate(
}
);
+export const GITHUB_VIA_APP_SERVICE_TYPE = 'github_via_app';
+export const GITHUB_ENTERPRISE_SERVER_VIA_APP_SERVICE_TYPE = 'github_enterprise_server_via_app';
+
export const CUSTOM_SERVICE_TYPE = 'custom';
export const WORKPLACE_SEARCH_URL_PREFIX = '/app/enterprise_search/workplace_search';
@@ -863,3 +883,14 @@ export const PLATINUM_FEATURE = i18n.translate(
defaultMessage: 'Platinum feature',
}
);
+
+export const COPY_TOOLTIP = i18n.translate('xpack.enterpriseSearch.workplaceSearch.copy.tooltip', {
+ defaultMessage: 'Copy to clipboard',
+});
+
+export const COPIED_TOOLTIP = i18n.translate(
+ 'xpack.enterpriseSearch.workplaceSearch.copied.tooltip',
+ {
+ defaultMessage: 'Copied!',
+ }
+);
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/index.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/index.tsx
index 2b24e09f96315..e7ffabd54a88c 100644
--- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/index.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/index.tsx
@@ -28,11 +28,13 @@ import {
PRIVATE_SOURCES_PATH,
ORG_SETTINGS_PATH,
USERS_AND_ROLES_PATH,
+ API_KEYS_PATH,
SECURITY_PATH,
PERSONAL_SETTINGS_PATH,
PERSONAL_PATH,
} from './routes';
import { AccountSettings } from './views/account_settings';
+import { ApiKeys } from './views/api_keys';
import { SourcesRouter } from './views/content_sources';
import { SourceAdded } from './views/content_sources/components/source_added';
import { ErrorState } from './views/error_state';
@@ -133,6 +135,9 @@ export const WorkplaceSearchConfigured: React.FC = (props) => {
+
+
+
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 5f3c79f9432e7..1b630a47e2f86 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
@@ -9,6 +9,11 @@ import { generatePath } from 'react-router-dom';
import { docLinks } from '../shared/doc_links';
+import {
+ GITHUB_VIA_APP_SERVICE_TYPE,
+ GITHUB_ENTERPRISE_SERVER_VIA_APP_SERVICE_TYPE,
+} from './constants';
+
export const SETUP_GUIDE_PATH = '/setup_guide';
export const NOT_FOUND_PATH = '/404';
@@ -53,6 +58,8 @@ export const SEARCH_AUTHORIZE_PATH = `${PERSONAL_PATH}/authorize_search`;
export const USERS_AND_ROLES_PATH = '/users_and_roles';
+export const API_KEYS_PATH = '/api_keys';
+
export const SECURITY_PATH = '/security';
export const GROUPS_PATH = '/groups';
@@ -70,7 +77,8 @@ export const ADD_CONFLUENCE_SERVER_PATH = `${SOURCES_PATH}/add/confluence_server
export const ADD_DROPBOX_PATH = `${SOURCES_PATH}/add/dropbox`;
export const ADD_GITHUB_ENTERPRISE_PATH = `${SOURCES_PATH}/add/github_enterprise_server`;
export const ADD_GITHUB_PATH = `${SOURCES_PATH}/add/github`;
-export const ADD_GITHUB_APP_PATH = `${SOURCES_PATH}/add/github_app`;
+export const ADD_GITHUB_VIA_APP_PATH = `${SOURCES_PATH}/add/${GITHUB_VIA_APP_SERVICE_TYPE}`;
+export const ADD_GITHUB_ENTERPRISE_SERVER_VIA_APP_PATH = `${SOURCES_PATH}/add/${GITHUB_ENTERPRISE_SERVER_VIA_APP_SERVICE_TYPE}`;
export const ADD_GMAIL_PATH = `${SOURCES_PATH}/add/gmail`;
export const ADD_GOOGLE_DRIVE_PATH = `${SOURCES_PATH}/add/google_drive`;
export const ADD_JIRA_PATH = `${SOURCES_PATH}/add/jira_cloud`;
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/types.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/types.ts
index 0fa8c00409d1a..2e933d7bdf94a 100644
--- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/types.ts
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/types.ts
@@ -81,7 +81,7 @@ export interface SourceDataItem {
features?: Features;
objTypes?: string[];
addPath: string;
- editPath: string;
+ editPath?: string; // undefined for GitHub apps, as they are configured on a source level, and don't use a connector where you can edit the configuration
accountContextOnly: boolean;
}
@@ -181,6 +181,12 @@ export interface IndexingConfig {
schedule: IndexingSchedule;
}
+interface AppSecret {
+ app_id: string;
+ fingerprint: string;
+ base_url?: string;
+}
+
export interface ContentSourceFullData extends ContentSourceDetails {
activities: SourceActivity[];
details: DescriptionList[];
@@ -201,6 +207,7 @@ export interface ContentSourceFullData extends ContentSourceDetails {
urlFieldIsLinkable: boolean;
createdAt: string;
serviceName: string;
+ secret?: AppSecret; // undefined for all content sources except GitHub apps
}
export interface ContentSourceStatus {
@@ -295,3 +302,9 @@ export interface WSRoleMapping extends RoleMapping {
allGroups: boolean;
groups: RoleGroup[];
}
+
+export interface ApiToken {
+ key?: string;
+ id?: string;
+ name: string;
+}
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/utils/handle_private_key_upload.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/utils/handle_private_key_upload.ts
new file mode 100644
index 0000000000000..b1a8877c165e0
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/utils/handle_private_key_upload.ts
@@ -0,0 +1,21 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { readUploadedFileAsText } from './read_uploaded_file_as_text';
+
+export const handlePrivateKeyUpload = async (
+ files: FileList | null,
+ callback: (text: string) => void
+) => {
+ if (!files || files.length < 1) {
+ return null;
+ }
+ const file = files[0];
+ const text = await readUploadedFileAsText(file);
+
+ callback(text);
+};
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/utils/index.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/utils/index.ts
index fb9846dbccde8..92f27500d7262 100644
--- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/utils/index.ts
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/utils/index.ts
@@ -9,3 +9,5 @@ export { toSentenceSerial } from './to_sentence_serial';
export { getAsLocalDateTimeString } from './get_as_local_datetime_string';
export { mimeType } from './mime_types';
export { readUploadedFileAsBase64 } from './read_uploaded_file_as_base64';
+export { readUploadedFileAsText } from './read_uploaded_file_as_text';
+export { handlePrivateKeyUpload } from './handle_private_key_upload';
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/utils/read_uploaded_file_as_text.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/utils/read_uploaded_file_as_text.ts
new file mode 100644
index 0000000000000..c4e8e54057545
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/utils/read_uploaded_file_as_text.ts
@@ -0,0 +1,22 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+export const readUploadedFileAsText = (fileInput: File): Promise => {
+ const reader = new FileReader();
+
+ return new Promise((resolve, reject) => {
+ reader.onload = () => {
+ resolve(reader.result as string);
+ };
+ try {
+ reader.readAsText(fileInput);
+ } catch {
+ reader.abort();
+ reject(new Error());
+ }
+ });
+};
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/api_keys/api_keys.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/api_keys/api_keys.test.tsx
new file mode 100644
index 0000000000000..caea725ca67a9
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/api_keys/api_keys.test.tsx
@@ -0,0 +1,102 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { setMockValues, setMockActions } from '../../../__mocks__/kea_logic';
+import { unmountHandler } from '../../../__mocks__/shallow_useeffect.mock';
+
+import React from 'react';
+
+import { shallow } from 'enzyme';
+
+import { EuiEmptyPrompt, EuiCopy } from '@elastic/eui';
+
+import { DEFAULT_META } from '../../../shared/constants';
+import { externalUrl } from '../../../shared/enterprise_search_url';
+
+import { ApiKeys } from './api_keys';
+import { ApiKeyFlyout } from './components/api_key_flyout';
+import { ApiKeysList } from './components/api_keys_list';
+
+describe('ApiKeys', () => {
+ const fetchApiKeys = jest.fn();
+ const resetApiKeys = jest.fn();
+ const showApiKeysForm = jest.fn();
+ const apiToken = {
+ id: '1',
+ name: 'test',
+ key: 'foo',
+ };
+
+ const values = {
+ apiKeyFormVisible: false,
+ meta: DEFAULT_META,
+ dataLoading: false,
+ apiTokens: [apiToken],
+ };
+
+ beforeEach(() => {
+ jest.clearAllMocks();
+ setMockValues(values);
+ setMockActions({
+ fetchApiKeys,
+ resetApiKeys,
+ showApiKeysForm,
+ });
+ });
+
+ it('renders', () => {
+ const wrapper = shallow();
+
+ expect(wrapper.find(ApiKeysList)).toHaveLength(1);
+ });
+
+ it('renders EuiEmptyPrompt when no api keys present', () => {
+ setMockValues({ ...values, apiTokens: [] });
+ const wrapper = shallow();
+
+ expect(wrapper.find(ApiKeysList)).toHaveLength(0);
+ expect(wrapper.find(EuiEmptyPrompt)).toHaveLength(1);
+ });
+
+ it('fetches data on mount', () => {
+ shallow();
+
+ expect(fetchApiKeys).toHaveBeenCalledTimes(1);
+ });
+
+ it('calls resetApiKeys on unmount', () => {
+ shallow();
+ unmountHandler();
+
+ expect(resetApiKeys).toHaveBeenCalledTimes(1);
+ });
+
+ it('renders the API endpoint and a button to copy it', () => {
+ externalUrl.enterpriseSearchUrl = 'http://localhost:3002';
+ const copyMock = jest.fn();
+ const wrapper = shallow();
+ // We wrap children in a div so that `shallow` can render it.
+ const copyEl = shallow({wrapper.find(EuiCopy).props().children(copyMock)}
);
+
+ expect(copyEl.find('EuiButtonIcon').props().onClick).toEqual(copyMock);
+ expect(copyEl.text().replace('', '')).toEqual('http://localhost:3002');
+ });
+
+ it('will render ApiKeyFlyout if apiKeyFormVisible is true', () => {
+ setMockValues({ ...values, apiKeyFormVisible: true });
+ const wrapper = shallow();
+
+ expect(wrapper.find(ApiKeyFlyout)).toHaveLength(1);
+ });
+
+ it('will NOT render ApiKeyFlyout if apiKeyFormVisible is false', () => {
+ setMockValues({ ...values, apiKeyFormVisible: false });
+ const wrapper = shallow();
+
+ expect(wrapper.find(ApiKeyFlyout)).toHaveLength(0);
+ });
+});
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/api_keys/api_keys.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/api_keys/api_keys.tsx
new file mode 100644
index 0000000000000..dd20020c619c8
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/api_keys/api_keys.tsx
@@ -0,0 +1,109 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import React, { useEffect } from 'react';
+
+import { useActions, useValues } from 'kea';
+
+import {
+ EuiButton,
+ EuiTitle,
+ EuiPanel,
+ EuiCopy,
+ EuiButtonIcon,
+ EuiSpacer,
+ EuiEmptyPrompt,
+} from '@elastic/eui';
+
+import { docLinks } from '../../../shared/doc_links';
+import { externalUrl } from '../../../shared/enterprise_search_url/external_url';
+
+import { WorkplaceSearchPageTemplate } from '../../components/layout';
+import { NAV, API_KEYS_TITLE } from '../../constants';
+
+import { ApiKeysLogic } from './api_keys_logic';
+import { ApiKeyFlyout } from './components/api_key_flyout';
+import { ApiKeysList } from './components/api_keys_list';
+import {
+ API_KEYS_EMPTY_TITLE,
+ API_KEYS_EMPTY_BODY,
+ API_KEYS_EMPTY_BUTTON_LABEL,
+ CREATE_KEY_BUTTON_LABEL,
+ ENDPOINT_TITLE,
+ COPIED_TOOLTIP,
+ COPY_API_ENDPOINT_BUTTON_LABEL,
+} from './constants';
+
+export const ApiKeys: React.FC = () => {
+ const { fetchApiKeys, resetApiKeys, showApiKeyForm } = useActions(ApiKeysLogic);
+
+ const { meta, dataLoading, apiKeyFormVisible, apiTokens } = useValues(ApiKeysLogic);
+
+ useEffect(() => {
+ fetchApiKeys();
+ return resetApiKeys;
+ }, [meta.page.current]);
+
+ const hasApiKeys = apiTokens.length > 0;
+
+ const addKeyButton = (
+
+ {CREATE_KEY_BUTTON_LABEL}
+
+ );
+
+ const emptyPrompt = (
+ {API_KEYS_EMPTY_TITLE}}
+ body={API_KEYS_EMPTY_BODY}
+ actions={
+
+ {API_KEYS_EMPTY_BUTTON_LABEL}
+
+ }
+ />
+ );
+
+ return (
+
+ {apiKeyFormVisible && }
+
+
+ {ENDPOINT_TITLE}
+
+
+ {(copy) => (
+ <>
+
+ {externalUrl.enterpriseSearchUrl}
+ >
+ )}
+
+
+
+ {hasApiKeys ? : emptyPrompt}
+
+ );
+};
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/api_keys/api_keys_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/api_keys/api_keys_logic.test.ts
new file mode 100644
index 0000000000000..a02b1578bd38a
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/api_keys/api_keys_logic.test.ts
@@ -0,0 +1,491 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import {
+ LogicMounter,
+ mockFlashMessageHelpers,
+ mockHttpValues,
+} from '../../../__mocks__/kea_logic';
+
+import { nextTick } from '@kbn/test/jest';
+
+import { DEFAULT_META } from '../../../shared/constants';
+import { itShowsServerErrorAsFlashMessage } from '../../../test_helpers';
+
+import { ApiKeysLogic } from './api_keys_logic';
+
+describe('ApiKeysLogic', () => {
+ const { mount } = new LogicMounter(ApiKeysLogic);
+ const { http } = mockHttpValues;
+ const { clearFlashMessages, flashSuccessToast } = mockFlashMessageHelpers;
+
+ const DEFAULT_VALUES = {
+ dataLoading: true,
+ apiTokens: [],
+ meta: DEFAULT_META,
+ nameInputBlurred: false,
+ activeApiToken: {
+ name: '',
+ },
+ activeApiTokenRawName: '',
+ apiKeyFormVisible: false,
+ apiTokenNameToDelete: '',
+ deleteModalVisible: false,
+ formErrors: [],
+ };
+
+ const newToken = {
+ id: '1',
+ name: 'myToken',
+ };
+
+ beforeEach(() => {
+ jest.clearAllMocks();
+ });
+
+ it('has expected default values', () => {
+ mount();
+ expect(ApiKeysLogic.values).toEqual(DEFAULT_VALUES);
+ });
+
+ describe('actions', () => {
+ describe('onApiTokenCreateSuccess', () => {
+ const values = {
+ ...DEFAULT_VALUES,
+ apiTokens: expect.any(Array),
+ activeApiToken: expect.any(Object),
+ activeApiTokenRawName: expect.any(String),
+ apiKeyFormVisible: expect.any(Boolean),
+ formErrors: expect.any(Array),
+ };
+
+ describe('apiTokens', () => {
+ const existingToken = {
+ name: 'some_token',
+ };
+
+ it('should add the provided token to the apiTokens list', () => {
+ mount({
+ apiTokens: [existingToken],
+ });
+
+ ApiKeysLogic.actions.onApiTokenCreateSuccess(newToken);
+ expect(ApiKeysLogic.values).toEqual({
+ ...values,
+ apiTokens: [existingToken, newToken],
+ });
+ });
+ });
+
+ describe('activeApiToken', () => {
+ it('should reset to the default value, which effectively clears out the current form', () => {
+ mount({
+ activeApiToken: newToken,
+ });
+
+ ApiKeysLogic.actions.onApiTokenCreateSuccess(newToken);
+ expect(ApiKeysLogic.values).toEqual({
+ ...values,
+ activeApiToken: DEFAULT_VALUES.activeApiToken,
+ });
+ });
+ });
+
+ describe('activeApiTokenRawName', () => {
+ it('should reset to the default value, which effectively clears out the current form', () => {
+ mount({
+ activeApiTokenRawName: 'foo',
+ });
+
+ ApiKeysLogic.actions.onApiTokenCreateSuccess(newToken);
+ expect(ApiKeysLogic.values).toEqual({
+ ...values,
+ activeApiTokenRawName: DEFAULT_VALUES.activeApiTokenRawName,
+ });
+ });
+ });
+
+ describe('apiKeyFormVisible', () => {
+ it('should reset to the default value, which closes the api key form', () => {
+ mount({
+ apiKeyFormVisible: true,
+ });
+
+ ApiKeysLogic.actions.onApiTokenCreateSuccess(newToken);
+ expect(ApiKeysLogic.values).toEqual({
+ ...values,
+ apiKeyFormVisible: false,
+ });
+ });
+ });
+
+ describe('deleteModalVisible', () => {
+ const tokenName = 'my-token';
+
+ it('should set deleteModalVisible to true and set apiTokenNameToDelete', () => {
+ ApiKeysLogic.actions.stageTokenNameForDeletion(tokenName);
+
+ expect(ApiKeysLogic.values).toEqual({
+ ...values,
+ deleteModalVisible: true,
+ apiTokenNameToDelete: tokenName,
+ });
+ });
+
+ it('should set deleteModalVisible to false and reset apiTokenNameToDelete', () => {
+ mount({
+ deleteModalVisible: true,
+ apiTokenNameToDelete: tokenName,
+ });
+ ApiKeysLogic.actions.hideDeleteModal();
+
+ expect(ApiKeysLogic.values).toEqual(values);
+ });
+ });
+
+ describe('formErrors', () => {
+ it('should reset `formErrors`', () => {
+ mount({
+ formErrors: ['I am an error'],
+ });
+
+ ApiKeysLogic.actions.onApiTokenCreateSuccess(newToken);
+ expect(ApiKeysLogic.values).toEqual({
+ ...values,
+ formErrors: [],
+ });
+ });
+ });
+ });
+
+ describe('onApiTokenError', () => {
+ const values = {
+ ...DEFAULT_VALUES,
+ formErrors: expect.any(Array),
+ };
+
+ describe('formErrors', () => {
+ it('should set `formErrors`', () => {
+ mount({
+ formErrors: ['I am an error'],
+ });
+
+ ApiKeysLogic.actions.onApiTokenError(['I am the NEW error']);
+ expect(ApiKeysLogic.values).toEqual({
+ ...values,
+ formErrors: ['I am the NEW error'],
+ });
+ });
+ });
+ });
+
+ describe('setApiKeysData', () => {
+ const meta = {
+ page: {
+ current: 1,
+ size: 1,
+ total_pages: 1,
+ total_results: 1,
+ },
+ };
+
+ const values = {
+ ...DEFAULT_VALUES,
+ dataLoading: false,
+ apiTokens: expect.any(Array),
+ meta: expect.any(Object),
+ };
+
+ describe('apiTokens', () => {
+ it('should be set', () => {
+ mount();
+
+ ApiKeysLogic.actions.setApiKeysData(meta, [newToken, newToken]);
+ expect(ApiKeysLogic.values).toEqual({
+ ...values,
+ apiTokens: [newToken, newToken],
+ });
+ });
+ });
+
+ describe('meta', () => {
+ it('should be set', () => {
+ mount();
+
+ ApiKeysLogic.actions.setApiKeysData(meta, [newToken, newToken]);
+ expect(ApiKeysLogic.values).toEqual({
+ ...values,
+ meta,
+ });
+ });
+ });
+ });
+
+ describe('setNameInputBlurred', () => {
+ const values = {
+ ...DEFAULT_VALUES,
+ nameInputBlurred: expect.any(Boolean),
+ };
+
+ describe('nameInputBlurred', () => {
+ it('should set this value', () => {
+ mount({
+ nameInputBlurred: false,
+ });
+
+ ApiKeysLogic.actions.setNameInputBlurred(true);
+ expect(ApiKeysLogic.values).toEqual({
+ ...values,
+ nameInputBlurred: true,
+ });
+ });
+ });
+ });
+
+ describe('setApiKeyName', () => {
+ const values = {
+ ...DEFAULT_VALUES,
+ activeApiToken: expect.any(Object),
+ activeApiTokenRawName: expect.any(String),
+ };
+
+ describe('activeApiToken', () => {
+ it('update the name property on the activeApiToken, formatted correctly', () => {
+ mount({
+ activeApiToken: {
+ ...newToken,
+ name: 'bar',
+ },
+ });
+
+ ApiKeysLogic.actions.setApiKeyName('New Name');
+ expect(ApiKeysLogic.values).toEqual({
+ ...values,
+ activeApiToken: { ...newToken, name: 'new-name' },
+ });
+ });
+ });
+
+ describe('activeApiTokenRawName', () => {
+ it('updates the raw name, with no formatting applied', () => {
+ mount();
+
+ ApiKeysLogic.actions.setApiKeyName('New Name');
+ expect(ApiKeysLogic.values).toEqual({
+ ...values,
+ activeApiTokenRawName: 'New Name',
+ });
+ });
+ });
+ });
+
+ describe('showApiKeyForm', () => {
+ const values = {
+ ...DEFAULT_VALUES,
+ activeApiToken: expect.any(Object),
+ activeApiTokenRawName: expect.any(String),
+ formErrors: expect.any(Array),
+ apiKeyFormVisible: expect.any(Boolean),
+ };
+
+ describe('apiKeyFormVisible', () => {
+ it('should toggle `apiKeyFormVisible`', () => {
+ mount({
+ apiKeyFormVisible: false,
+ });
+
+ ApiKeysLogic.actions.showApiKeyForm();
+ expect(ApiKeysLogic.values).toEqual({
+ ...values,
+ apiKeyFormVisible: true,
+ });
+ });
+ });
+
+ describe('formErrors', () => {
+ it('should reset `formErrors`', () => {
+ mount({
+ formErrors: ['I am an error'],
+ });
+
+ ApiKeysLogic.actions.showApiKeyForm();
+ expect(ApiKeysLogic.values).toEqual({
+ ...values,
+ formErrors: [],
+ });
+ });
+ });
+
+ describe('listener side-effects', () => {
+ it('should clear flashMessages whenever the api key form flyout is opened', () => {
+ ApiKeysLogic.actions.showApiKeyForm();
+ expect(clearFlashMessages).toHaveBeenCalled();
+ });
+ });
+ });
+
+ describe('hideApiKeyForm', () => {
+ const values = {
+ ...DEFAULT_VALUES,
+ apiKeyFormVisible: expect.any(Boolean),
+ activeApiTokenRawName: expect.any(String),
+ };
+
+ describe('activeApiTokenRawName', () => {
+ it('resets this value', () => {
+ mount({
+ activeApiTokenRawName: 'foo',
+ });
+
+ ApiKeysLogic.actions.hideApiKeyForm();
+ expect(ApiKeysLogic.values).toEqual({
+ ...values,
+ activeApiTokenRawName: '',
+ });
+ });
+ });
+
+ describe('apiKeyFormVisible', () => {
+ it('resets this value', () => {
+ mount({
+ apiKeyFormVisible: true,
+ });
+
+ ApiKeysLogic.actions.hideApiKeyForm();
+ expect(ApiKeysLogic.values).toEqual({
+ ...values,
+ apiKeyFormVisible: false,
+ });
+ });
+ });
+ });
+
+ describe('resetApiKeys', () => {
+ const values = {
+ ...DEFAULT_VALUES,
+ formErrors: expect.any(Array),
+ };
+
+ describe('formErrors', () => {
+ it('should reset', () => {
+ mount({
+ formErrors: ['I am an error'],
+ });
+
+ ApiKeysLogic.actions.resetApiKeys();
+ expect(ApiKeysLogic.values).toEqual({
+ ...values,
+ formErrors: [],
+ });
+ });
+ });
+ });
+
+ describe('onPaginate', () => {
+ it('should set meta.page.current', () => {
+ mount({ meta: DEFAULT_META });
+
+ ApiKeysLogic.actions.onPaginate(5);
+ expect(ApiKeysLogic.values).toEqual({
+ ...DEFAULT_VALUES,
+ meta: {
+ page: {
+ ...DEFAULT_META.page,
+ current: 5,
+ },
+ },
+ });
+ });
+ });
+ });
+
+ describe('listeners', () => {
+ describe('fetchApiKeys', () => {
+ const meta = {
+ page: {
+ current: 1,
+ size: 1,
+ total_pages: 1,
+ total_results: 1,
+ },
+ };
+ const results: object[] = [];
+
+ it('will call an API endpoint and set the results with the `setApiKeysData` action', async () => {
+ mount();
+ jest.spyOn(ApiKeysLogic.actions, 'setApiKeysData').mockImplementationOnce(() => {});
+ http.get.mockReturnValue(Promise.resolve({ meta, results }));
+
+ ApiKeysLogic.actions.fetchApiKeys();
+ expect(http.get).toHaveBeenCalledWith('/internal/workplace_search/api_keys', {
+ query: {
+ 'page[current]': 1,
+ 'page[size]': 10,
+ },
+ });
+ await nextTick();
+ expect(ApiKeysLogic.actions.setApiKeysData).toHaveBeenCalledWith(meta, results);
+ });
+
+ itShowsServerErrorAsFlashMessage(http.get, () => {
+ mount();
+ ApiKeysLogic.actions.fetchApiKeys();
+ });
+ });
+
+ describe('deleteApiKey', () => {
+ const tokenName = 'abc123';
+
+ it('will call an API endpoint and re-fetch the api keys list', async () => {
+ mount();
+ jest.spyOn(ApiKeysLogic.actions, 'fetchApiKeys').mockImplementationOnce(() => {});
+ http.delete.mockReturnValue(Promise.resolve());
+
+ ApiKeysLogic.actions.stageTokenNameForDeletion(tokenName);
+ ApiKeysLogic.actions.deleteApiKey();
+ expect(http.delete).toHaveBeenCalledWith(
+ `/internal/workplace_search/api_keys/${tokenName}`
+ );
+ await nextTick();
+
+ expect(ApiKeysLogic.actions.fetchApiKeys).toHaveBeenCalled();
+ expect(flashSuccessToast).toHaveBeenCalled();
+ });
+
+ itShowsServerErrorAsFlashMessage(http.delete, () => {
+ mount();
+ ApiKeysLogic.actions.deleteApiKey();
+ });
+ });
+
+ describe('onApiFormSubmit', () => {
+ it('calls a POST API endpoint that creates a new token if the active token does not exist yet', async () => {
+ const createdToken = {
+ name: 'new-key',
+ };
+ mount({
+ activeApiToken: createdToken,
+ });
+ jest.spyOn(ApiKeysLogic.actions, 'onApiTokenCreateSuccess');
+ http.post.mockReturnValue(Promise.resolve(createdToken));
+
+ ApiKeysLogic.actions.onApiFormSubmit();
+ expect(http.post).toHaveBeenCalledWith('/internal/workplace_search/api_keys', {
+ body: JSON.stringify(createdToken),
+ });
+ await nextTick();
+ expect(ApiKeysLogic.actions.onApiTokenCreateSuccess).toHaveBeenCalledWith(createdToken);
+ expect(flashSuccessToast).toHaveBeenCalled();
+ });
+
+ itShowsServerErrorAsFlashMessage(http.post, () => {
+ mount();
+ ApiKeysLogic.actions.onApiFormSubmit();
+ });
+ });
+ });
+});
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/api_keys/api_keys_logic.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/api_keys/api_keys_logic.ts
new file mode 100644
index 0000000000000..ca3662a8784c8
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/api_keys/api_keys_logic.ts
@@ -0,0 +1,213 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { kea, MakeLogicType } from 'kea';
+
+import { Meta } from '../../../../../common/types';
+import { DEFAULT_META } from '../../../shared/constants';
+import {
+ clearFlashMessages,
+ flashSuccessToast,
+ flashAPIErrors,
+} from '../../../shared/flash_messages';
+import { HttpLogic } from '../../../shared/http';
+import { updateMetaPageIndex } from '../../../shared/table_pagination';
+
+import { ApiToken } from '../../types';
+
+import { CREATE_MESSAGE, DELETE_MESSAGE } from './constants';
+
+const formatApiName = (rawName: string): string =>
+ rawName
+ .trim()
+ .replace(/[^a-zA-Z0-9]+/g, '-') // Replace all special/non-alphanumerical characters with dashes
+ .replace(/^[-]+|[-]+$/g, '') // Strip all leading and trailing dashes
+ .toLowerCase();
+
+export const defaultApiToken: ApiToken = {
+ name: '',
+};
+
+interface ApiKeysLogicActions {
+ onApiTokenCreateSuccess(apiToken: ApiToken): ApiToken;
+ onApiTokenError(formErrors: string[]): string[];
+ setApiKeysData(meta: Meta, apiTokens: ApiToken[]): { meta: Meta; apiTokens: ApiToken[] };
+ setNameInputBlurred(isBlurred: boolean): boolean;
+ setApiKeyName(name: string): string;
+ showApiKeyForm(): void;
+ hideApiKeyForm(): { value: boolean };
+ resetApiKeys(): { value: boolean };
+ fetchApiKeys(): void;
+ onPaginate(newPageIndex: number): { newPageIndex: number };
+ deleteApiKey(): void;
+ onApiFormSubmit(): void;
+ stageTokenNameForDeletion(tokenName: string): string;
+ hideDeleteModal(): void;
+}
+
+interface ApiKeysLogicValues {
+ activeApiToken: ApiToken;
+ activeApiTokenRawName: string;
+ apiTokens: ApiToken[];
+ dataLoading: boolean;
+ formErrors: string[];
+ meta: Meta;
+ nameInputBlurred: boolean;
+ apiKeyFormVisible: boolean;
+ deleteModalVisible: boolean;
+ apiTokenNameToDelete: string;
+}
+
+export const ApiKeysLogic = kea>({
+ path: ['enterprise_search', 'workplace_search', 'api_keys_logic'],
+ actions: () => ({
+ onApiTokenCreateSuccess: (apiToken) => apiToken,
+ onApiTokenError: (formErrors) => formErrors,
+ setApiKeysData: (meta, apiTokens) => ({ meta, apiTokens }),
+ setNameInputBlurred: (nameInputBlurred) => nameInputBlurred,
+ setApiKeyName: (name) => name,
+ showApiKeyForm: true,
+ hideApiKeyForm: false,
+ resetApiKeys: false,
+ fetchApiKeys: true,
+ onPaginate: (newPageIndex) => ({ newPageIndex }),
+ deleteApiKey: true,
+ stageTokenNameForDeletion: (tokenName) => tokenName,
+ hideDeleteModal: true,
+ onApiFormSubmit: () => null,
+ }),
+ reducers: () => ({
+ dataLoading: [
+ true,
+ {
+ setApiKeysData: () => false,
+ },
+ ],
+ apiTokens: [
+ [],
+ {
+ setApiKeysData: (_, { apiTokens }) => apiTokens,
+ onApiTokenCreateSuccess: (apiTokens, apiToken) => [...apiTokens, apiToken],
+ },
+ ],
+ meta: [
+ DEFAULT_META,
+ {
+ setApiKeysData: (_, { meta }) => meta,
+ onPaginate: (state, { newPageIndex }) => updateMetaPageIndex(state, newPageIndex),
+ },
+ ],
+ nameInputBlurred: [
+ false,
+ {
+ setNameInputBlurred: (_, nameInputBlurred) => nameInputBlurred,
+ },
+ ],
+ activeApiToken: [
+ defaultApiToken,
+ {
+ onApiTokenCreateSuccess: () => defaultApiToken,
+ hideApiKeyForm: () => defaultApiToken,
+ setApiKeyName: (activeApiToken, name) => ({ ...activeApiToken, name: formatApiName(name) }),
+ },
+ ],
+ activeApiTokenRawName: [
+ '',
+ {
+ setApiKeyName: (_, activeApiTokenRawName) => activeApiTokenRawName,
+ hideApiKeyForm: () => '',
+ onApiTokenCreateSuccess: () => '',
+ },
+ ],
+ apiKeyFormVisible: [
+ false,
+ {
+ showApiKeyForm: () => true,
+ hideApiKeyForm: () => false,
+ onApiTokenCreateSuccess: () => false,
+ },
+ ],
+ deleteModalVisible: [
+ false,
+ {
+ stageTokenNameForDeletion: () => true,
+ hideDeleteModal: () => false,
+ },
+ ],
+ apiTokenNameToDelete: [
+ '',
+ {
+ stageTokenNameForDeletion: (_, tokenName) => tokenName,
+ hideDeleteModal: () => '',
+ },
+ ],
+ formErrors: [
+ [],
+ {
+ onApiTokenError: (_, formErrors) => formErrors,
+ onApiTokenCreateSuccess: () => [],
+ showApiKeyForm: () => [],
+ resetApiKeys: () => [],
+ },
+ ],
+ }),
+ listeners: ({ actions, values }) => ({
+ showApiKeyForm: () => {
+ clearFlashMessages();
+ },
+ fetchApiKeys: async () => {
+ try {
+ const { http } = HttpLogic.values;
+ const { meta } = values;
+ const query = {
+ 'page[current]': meta.page.current,
+ 'page[size]': meta.page.size,
+ };
+ const response = await http.get<{ meta: Meta; results: ApiToken[] }>(
+ '/internal/workplace_search/api_keys',
+ { query }
+ );
+ actions.setApiKeysData(response.meta, response.results);
+ } catch (e) {
+ flashAPIErrors(e);
+ }
+ },
+ deleteApiKey: async () => {
+ const { apiTokenNameToDelete } = values;
+
+ try {
+ const { http } = HttpLogic.values;
+ await http.delete(`/internal/workplace_search/api_keys/${apiTokenNameToDelete}`);
+
+ actions.fetchApiKeys();
+ flashSuccessToast(DELETE_MESSAGE(apiTokenNameToDelete));
+ } catch (e) {
+ flashAPIErrors(e);
+ } finally {
+ actions.hideDeleteModal();
+ }
+ },
+ onApiFormSubmit: async () => {
+ const { name } = values.activeApiToken;
+
+ const data: ApiToken = {
+ name,
+ };
+
+ try {
+ const { http } = HttpLogic.values;
+ const body = JSON.stringify(data);
+
+ const response = await http.post('/internal/workplace_search/api_keys', { body });
+ actions.onApiTokenCreateSuccess(response);
+ flashSuccessToast(CREATE_MESSAGE(name));
+ } catch (e) {
+ flashAPIErrors(e);
+ }
+ },
+ }),
+});
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/api_keys/components/api_key.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/api_keys/components/api_key.test.tsx
new file mode 100644
index 0000000000000..d99ab3f260c77
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/api_keys/components/api_key.test.tsx
@@ -0,0 +1,59 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license 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 { EuiButtonIcon } from '@elastic/eui';
+
+import { ApiKey } from './api_key';
+
+describe('ApiKey', () => {
+ beforeEach(() => {
+ jest.clearAllMocks();
+ });
+
+ const props = {
+ copy: jest.fn(),
+ toggleIsHidden: jest.fn(),
+ isHidden: true,
+ text: 'some-api-key',
+ };
+
+ it('renders', () => {
+ const wrapper = shallow();
+ expect(wrapper.find(EuiButtonIcon).length).toEqual(2);
+ });
+
+ it('will call copy when the first button is clicked', () => {
+ const wrapper = shallow();
+ wrapper.find(EuiButtonIcon).first().simulate('click');
+ expect(props.copy).toHaveBeenCalled();
+ });
+
+ it('will call hide when the second button is clicked', () => {
+ const wrapper = shallow();
+ wrapper.find(EuiButtonIcon).last().simulate('click');
+ expect(props.toggleIsHidden).toHaveBeenCalled();
+ });
+
+ it('will render the "eye" icon when isHidden is true', () => {
+ const wrapper = shallow();
+ expect(wrapper.find(EuiButtonIcon).last().prop('iconType')).toBe('eye');
+ });
+
+ it('will render the "eyeClosed" icon when isHidden is false', () => {
+ const wrapper = shallow();
+ expect(wrapper.find(EuiButtonIcon).last().prop('iconType')).toBe('eyeClosed');
+ });
+
+ it('will render the provided text', () => {
+ const wrapper = shallow();
+ expect(wrapper.text()).toContain('some-api-key');
+ });
+});
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/api_keys/components/api_key.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/api_keys/components/api_key.tsx
new file mode 100644
index 0000000000000..0ea24d9b684ea
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/api_keys/components/api_key.tsx
@@ -0,0 +1,42 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license 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 { EuiButtonIcon } from '@elastic/eui';
+
+import { SHOW_API_KEY_LABEL, HIDE_API_KEY_LABEL, COPY_API_KEY_BUTTON_LABEL } from '../constants';
+
+interface Props {
+ copy: () => void;
+ toggleIsHidden: () => void;
+ isHidden: boolean;
+ text: React.ReactNode;
+}
+
+export const ApiKey: React.FC = ({ copy, toggleIsHidden, isHidden, text }) => {
+ const hideIcon = isHidden ? 'eye' : 'eyeClosed';
+ const hideIconLabel = isHidden ? SHOW_API_KEY_LABEL : HIDE_API_KEY_LABEL;
+
+ return (
+ <>
+
+
+ {text}
+ >
+ );
+};
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/api_keys/components/api_key_flyout.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/api_keys/components/api_key_flyout.test.tsx
new file mode 100644
index 0000000000000..e31ae94e968ce
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/api_keys/components/api_key_flyout.test.tsx
@@ -0,0 +1,94 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { setMockValues, setMockActions } from '../../../../__mocks__/kea_logic';
+
+import React from 'react';
+
+import { shallow } from 'enzyme';
+
+import { EuiFlyout, EuiForm, EuiFieldText, EuiFormRow } from '@elastic/eui';
+
+import { ApiKeyFlyout } from './api_key_flyout';
+
+describe('ApiKeyFlyout', () => {
+ const setNameInputBlurred = jest.fn();
+ const setApiKeyName = jest.fn();
+ const onApiFormSubmit = jest.fn();
+ const hideApiKeyForm = jest.fn();
+
+ const apiKey = {
+ id: '123',
+ name: 'test',
+ };
+
+ const values = {
+ activeApiToken: apiKey,
+ };
+
+ beforeEach(() => {
+ setMockValues(values);
+ setMockActions({
+ setNameInputBlurred,
+ setApiKeyName,
+ onApiFormSubmit,
+ hideApiKeyForm,
+ });
+ });
+
+ it('renders', () => {
+ const wrapper = shallow();
+ const flyout = wrapper.find(EuiFlyout);
+
+ expect(flyout).toHaveLength(1);
+ expect(flyout.prop('onClose')).toEqual(hideApiKeyForm);
+ });
+
+ it('calls onApiTokenChange on form submit', () => {
+ const wrapper = shallow();
+ const preventDefault = jest.fn();
+ wrapper.find(EuiForm).simulate('submit', { preventDefault });
+
+ expect(preventDefault).toHaveBeenCalled();
+ expect(onApiFormSubmit).toHaveBeenCalled();
+ });
+
+ it('shows help text if the raw name does not match the expected name', () => {
+ setMockValues({
+ ...values,
+ activeApiToken: { name: 'my-api-key' },
+ activeApiTokenRawName: 'my api key!!',
+ });
+ const wrapper = shallow();
+
+ expect(wrapper.find(EuiFormRow).prop('helpText')).toEqual('Your key will be named: my-api-key');
+ });
+
+ it('controls the input value', () => {
+ setMockValues({
+ ...values,
+ activeApiTokenRawName: 'test',
+ });
+ const wrapper = shallow();
+
+ expect(wrapper.find(EuiFieldText).prop('value')).toEqual('test');
+ });
+
+ it('calls setApiKeyName when the input value is changed', () => {
+ const wrapper = shallow();
+ wrapper.find(EuiFieldText).simulate('change', { target: { value: 'changed' } });
+
+ expect(setApiKeyName).toHaveBeenCalledWith('changed');
+ });
+
+ it('calls setNameInputBlurred when the user stops focusing the input', () => {
+ const wrapper = shallow();
+ wrapper.find(EuiFieldText).simulate('blur');
+
+ expect(setNameInputBlurred).toHaveBeenCalledWith(true);
+ });
+});
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/api_keys/components/api_key_flyout.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/api_keys/components/api_key_flyout.tsx
new file mode 100644
index 0000000000000..150778ad7fdbc
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/api_keys/components/api_key_flyout.tsx
@@ -0,0 +1,103 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license 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 { useValues, useActions } from 'kea';
+
+import {
+ EuiPortal,
+ EuiFormRow,
+ EuiFieldText,
+ EuiFlyout,
+ EuiFlyoutHeader,
+ EuiFlyoutBody,
+ EuiFlyoutFooter,
+ EuiFlexGroup,
+ EuiFlexItem,
+ EuiButtonEmpty,
+ EuiButton,
+ EuiForm,
+ EuiTitle,
+} from '@elastic/eui';
+
+import { CLOSE_BUTTON_LABEL, SAVE_BUTTON_LABEL } from '../../../../shared/constants';
+import { FlashMessages } from '../../../../shared/flash_messages';
+
+import { ApiKeysLogic } from '../api_keys_logic';
+import {
+ API_KEY_FLYOUT_TITLE,
+ API_KEY_FORM_LABEL,
+ API_KEY_FORM_HELP_TEXT,
+ API_KEY_NAME_PLACEHOLDER,
+} from '../constants';
+
+export const ApiKeyFlyout: React.FC = () => {
+ const { setNameInputBlurred, setApiKeyName, onApiFormSubmit, hideApiKeyForm } =
+ useActions(ApiKeysLogic);
+ const {
+ activeApiToken: { name },
+ activeApiTokenRawName: rawName,
+ } = useValues(ApiKeysLogic);
+
+ return (
+
+
+
+
+ {API_KEY_FLYOUT_TITLE}
+
+
+
+
+ {
+ e.preventDefault();
+ onApiFormSubmit();
+ }}
+ component="form"
+ >
+
+ setApiKeyName(e.target.value)}
+ onBlur={() => setNameInputBlurred(true)}
+ autoComplete="off"
+ maxLength={64}
+ required
+ fullWidth
+ autoFocus
+ />
+
+
+
+
+
+
+
+ {CLOSE_BUTTON_LABEL}
+
+
+
+
+ {SAVE_BUTTON_LABEL}
+
+
+
+
+
+
+ );
+};
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/api_keys/components/api_keys_list.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/api_keys/components/api_keys_list.test.tsx
new file mode 100644
index 0000000000000..3dd300d0eb5c5
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/api_keys/components/api_keys_list.test.tsx
@@ -0,0 +1,193 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { setMockValues, setMockActions } from '../../../../__mocks__/kea_logic';
+
+import React from 'react';
+
+import { shallow } from 'enzyme';
+
+import { EuiBasicTable, EuiCopy, EuiConfirmModal } from '@elastic/eui';
+
+import { HiddenText } from '../../../../shared/hidden_text';
+
+import { ApiKey } from './api_key';
+import { ApiKeysList } from './api_keys_list';
+
+describe('ApiKeysList', () => {
+ const stageTokenNameForDeletion = jest.fn();
+ const hideDeleteModal = jest.fn();
+ const deleteApiKey = jest.fn();
+ const onPaginate = jest.fn();
+ const apiToken = {
+ id: '1',
+ name: 'test',
+ key: 'foo',
+ };
+ const apiTokens = [apiToken];
+ const meta = {
+ page: {
+ current: 1,
+ size: 10,
+ total_pages: 1,
+ total_results: 5,
+ },
+ };
+
+ const values = { apiTokens, meta, dataLoading: false };
+
+ beforeEach(() => {
+ setMockValues(values);
+ setMockActions({ deleteApiKey, onPaginate, stageTokenNameForDeletion, hideDeleteModal });
+ });
+
+ it('renders', () => {
+ const wrapper = shallow();
+
+ expect(wrapper.find(EuiBasicTable)).toHaveLength(1);
+ });
+
+ describe('loading state', () => {
+ it('renders as loading when dataLoading is true', () => {
+ setMockValues({
+ ...values,
+ dataLoading: true,
+ });
+ const wrapper = shallow();
+
+ expect(wrapper.find(EuiBasicTable).prop('loading')).toBe(true);
+ });
+ });
+
+ describe('pagination', () => {
+ it('derives pagination from meta object', () => {
+ setMockValues({
+ ...values,
+ meta: {
+ page: {
+ current: 6,
+ size: 55,
+ total_pages: 1,
+ total_results: 1004,
+ },
+ },
+ });
+ const wrapper = shallow();
+ const { pagination } = wrapper.find(EuiBasicTable).props();
+
+ expect(pagination).toEqual({
+ pageIndex: 5,
+ pageSize: 55,
+ totalItemCount: 1004,
+ hidePerPageOptions: true,
+ });
+ });
+ });
+
+ it('handles confirmModal submission', () => {
+ setMockValues({
+ ...values,
+ deleteModalVisible: true,
+ });
+ const wrapper = shallow();
+ const modal = wrapper.find(EuiConfirmModal);
+ modal.prop('onConfirm')!({} as any);
+
+ expect(deleteApiKey).toHaveBeenCalled();
+ });
+
+ describe('columns', () => {
+ let columns: any[];
+
+ beforeAll(() => {
+ setMockValues(values);
+ const wrapper = shallow();
+ columns = wrapper.find(EuiBasicTable).props().columns;
+ });
+
+ describe('column 1 (name)', () => {
+ const token = {
+ ...apiToken,
+ name: 'some-name',
+ };
+
+ it('renders correctly', () => {
+ const column = columns[0];
+ const wrapper = shallow({column.render(token)}
);
+
+ expect(wrapper.text()).toEqual('some-name');
+ });
+ });
+
+ describe('column 2 (key)', () => {
+ const token = {
+ ...apiToken,
+ key: 'abc-123',
+ };
+
+ it('renders nothing if no key is present', () => {
+ const tokenWithNoKey = {
+ key: undefined,
+ };
+ const column = columns[1];
+ const wrapper = shallow({column.render(tokenWithNoKey)}
);
+
+ expect(wrapper.text()).toBe('');
+ });
+
+ it('renders an EuiCopy component with the key', () => {
+ const column = columns[1];
+ const wrapper = shallow({column.render(token)}
);
+
+ expect(wrapper.find(EuiCopy).props().textToCopy).toEqual('abc-123');
+ });
+
+ it('renders a HiddenText component with the key', () => {
+ const column = columns[1];
+ const wrapper = shallow({column.render(token)}
)
+ .find(EuiCopy)
+ .dive();
+
+ expect(wrapper.find(HiddenText).props().text).toEqual('abc-123');
+ });
+
+ it('renders a Key component', () => {
+ const column = columns[1];
+ const wrapper = shallow({column.render(token)}
)
+ .find(EuiCopy)
+ .dive()
+ .find(HiddenText)
+ .dive();
+
+ expect(wrapper.find(ApiKey).props()).toEqual({
+ copy: expect.any(Function),
+ toggleIsHidden: expect.any(Function),
+ isHidden: expect.any(Boolean),
+ text: (
+
+ •••••••
+
+ ),
+ });
+ });
+ });
+
+ describe('column 3 (delete action)', () => {
+ const token = {
+ ...apiToken,
+ name: 'some-name',
+ };
+
+ it('calls stageTokenNameForDeletion when clicked', () => {
+ const action = columns[2].actions[0];
+ action.onClick(token);
+
+ expect(stageTokenNameForDeletion).toHaveBeenCalledWith('some-name');
+ });
+ });
+ });
+});
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/api_keys/components/api_keys_list.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/api_keys/components/api_keys_list.tsx
new file mode 100644
index 0000000000000..5a79e965454b2
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/api_keys/components/api_keys_list.tsx
@@ -0,0 +1,112 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license 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 { useActions, useValues } from 'kea';
+
+import { EuiBasicTable, EuiBasicTableColumn, EuiCopy, EuiConfirmModal } from '@elastic/eui';
+
+import { DELETE_BUTTON_LABEL, CANCEL_BUTTON_LABEL } from '../../../../shared/constants';
+import { HiddenText } from '../../../../shared/hidden_text';
+import { convertMetaToPagination, handlePageChange } from '../../../../shared/table_pagination';
+import { ApiToken } from '../../../types';
+
+import { ApiKeysLogic } from '../api_keys_logic';
+import {
+ DELETE_API_KEY_BUTTON_DESCRIPTION,
+ COPIED_TOOLTIP,
+ NAME_TITLE,
+ KEY_TITLE,
+ API_KEYS_CONFIRM_DELETE_TITLE,
+ API_KEYS_CONFIRM_DELETE_LABEL,
+} from '../constants';
+
+import { ApiKey } from './api_key';
+
+export const ApiKeysList: React.FC = () => {
+ const { deleteApiKey, onPaginate, stageTokenNameForDeletion, hideDeleteModal } =
+ useActions(ApiKeysLogic);
+ const { apiTokens, meta, dataLoading, deleteModalVisible } = useValues(ApiKeysLogic);
+
+ const deleteModal = (
+
+ {API_KEYS_CONFIRM_DELETE_LABEL}
+
+ );
+
+ const columns: Array> = [
+ {
+ name: NAME_TITLE,
+ render: (token: ApiToken) => token.name,
+ },
+ {
+ name: KEY_TITLE,
+ className: 'eui-textBreakAll',
+ render: (token: ApiToken) => {
+ const { key } = token;
+ if (!key) return null;
+
+ return (
+
+ {(copy) => (
+
+ {({ hiddenText, isHidden, toggle }) => (
+
+ )}
+
+ )}
+
+ );
+ },
+ mobileOptions: {
+ width: '100%',
+ },
+ },
+ {
+ actions: [
+ {
+ name: DELETE_BUTTON_LABEL,
+ description: DELETE_API_KEY_BUTTON_DESCRIPTION,
+ type: 'icon',
+ icon: 'trash',
+ color: 'danger',
+ onClick: (token: ApiToken) => stageTokenNameForDeletion(token.name),
+ },
+ ],
+ },
+ ];
+
+ return (
+ <>
+ {deleteModalVisible && deleteModal}
+
+ >
+ );
+};
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/api_keys/constants.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/api_keys/constants.ts
new file mode 100644
index 0000000000000..6c45dc38339c4
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/api_keys/constants.ts
@@ -0,0 +1,149 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { i18n } from '@kbn/i18n';
+
+export const CREATE_KEY_BUTTON_LABEL = i18n.translate(
+ 'xpack.enterpriseSearch.workplaceSearch.apiKeys.createKey.buttonLabel',
+ {
+ defaultMessage: 'Create key',
+ }
+);
+
+export const ENDPOINT_TITLE = i18n.translate(
+ 'xpack.enterpriseSearch.workplaceSearch.apiKeys.endpointTitle',
+ {
+ defaultMessage: 'Endpoint',
+ }
+);
+
+export const NAME_TITLE = i18n.translate(
+ 'xpack.enterpriseSearch.workplaceSearch.apiKeys.nameTitle',
+ {
+ defaultMessage: 'Name',
+ }
+);
+
+export const KEY_TITLE = i18n.translate('xpack.enterpriseSearch.workplaceSearch.apiKeys.keyTitle', {
+ defaultMessage: 'Key',
+});
+
+export const COPIED_TOOLTIP = i18n.translate(
+ 'xpack.enterpriseSearch.workplaceSearch.apiKeys.copied.tooltip',
+ {
+ defaultMessage: 'Copied',
+ }
+);
+
+export const COPY_API_ENDPOINT_BUTTON_LABEL = i18n.translate(
+ 'xpack.enterpriseSearch.workplaceSearch.apiKeys.copyApiEndpoint.buttonLabel',
+ {
+ defaultMessage: 'Copy API Endpoint to clipboard.',
+ }
+);
+
+export const COPY_API_KEY_BUTTON_LABEL = i18n.translate(
+ 'xpack.enterpriseSearch.workplaceSearch.apiKeys.copyApiKey.buttonLabel',
+ {
+ defaultMessage: 'Copy API Key to clipboard.',
+ }
+);
+
+export const DELETE_API_KEY_BUTTON_DESCRIPTION = i18n.translate(
+ 'xpack.enterpriseSearch.workplaceSearch.apiKeys.deleteApiKey.buttonDescription',
+ {
+ defaultMessage: 'Delete API key',
+ }
+);
+
+export const CREATE_MESSAGE = (name: string) =>
+ i18n.translate('xpack.enterpriseSearch.workplaceSearch.apiKeys.createdMessage', {
+ defaultMessage: "API key '{name}' was created",
+ values: { name },
+ });
+
+export const DELETE_MESSAGE = (name: string) =>
+ i18n.translate('xpack.enterpriseSearch.workplaceSearch.apiKeys.deletedMessage', {
+ defaultMessage: "API key '{name}' was deleted",
+ values: { name },
+ });
+
+export const API_KEY_FLYOUT_TITLE = i18n.translate(
+ 'xpack.enterpriseSearch.workplaceSearch.apiKeys.flyoutTitle',
+ {
+ defaultMessage: 'Create a new key',
+ }
+);
+
+export const API_KEY_FORM_LABEL = i18n.translate(
+ 'xpack.enterpriseSearch.workplaceSearch.apiKeys.formLabel',
+ {
+ defaultMessage: 'Key name',
+ }
+);
+
+export const API_KEY_FORM_HELP_TEXT = (name: string) =>
+ i18n.translate('xpack.enterpriseSearch.workplaceSearch.apiKeys.formHelpText', {
+ defaultMessage: 'Your key will be named: {name}',
+ values: { name },
+ });
+
+export const API_KEY_NAME_PLACEHOLDER = i18n.translate(
+ 'xpack.enterpriseSearch.workplaceSearch.apiKeys.namePlaceholder',
+ {
+ defaultMessage: 'i.e., my-api-key',
+ }
+);
+
+export const SHOW_API_KEY_LABEL = i18n.translate(
+ 'xpack.enterpriseSearch.workplaceSearch.apiKeys.showApiKeyLabel',
+ {
+ defaultMessage: 'Show API Key',
+ }
+);
+
+export const HIDE_API_KEY_LABEL = i18n.translate(
+ 'xpack.enterpriseSearch.workplaceSearch.apiKeys.hideApiKeyLabel',
+ {
+ defaultMessage: 'Hide API Key',
+ }
+);
+
+export const API_KEYS_EMPTY_TITLE = i18n.translate(
+ 'xpack.enterpriseSearch.workplaceSearch.apiKeys.emptyTitle',
+ {
+ defaultMessage: 'Create your first API key',
+ }
+);
+
+export const API_KEYS_EMPTY_BODY = i18n.translate(
+ 'xpack.enterpriseSearch.workplaceSearch.apiKeys.emptyBody',
+ {
+ defaultMessage: 'Allow applications to access Elastic Workplace Search on your behalf.',
+ }
+);
+
+export const API_KEYS_EMPTY_BUTTON_LABEL = i18n.translate(
+ 'xpack.enterpriseSearch.workplaceSearch.apiKeys.emptyButtonLabel',
+ {
+ defaultMessage: 'Learn about API keys',
+ }
+);
+
+export const API_KEYS_CONFIRM_DELETE_TITLE = i18n.translate(
+ 'xpack.enterpriseSearch.workplaceSearch.apiKeys.confirmDeleteTitle',
+ {
+ defaultMessage: 'Delete API key',
+ }
+);
+
+export const API_KEYS_CONFIRM_DELETE_LABEL = i18n.translate(
+ 'xpack.enterpriseSearch.workplaceSearch.apiKeys.confirmDeleteLabel',
+ {
+ defaultMessage: 'Are you sure you want to delete this API key? This action cannot be undone.',
+ }
+);
diff --git a/x-pack/plugins/security_solution/server/endpoint/schemas/artifacts/response/index.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/api_keys/index.ts
similarity index 85%
rename from x-pack/plugins/security_solution/server/endpoint/schemas/artifacts/response/index.ts
rename to x-pack/plugins/enterprise_search/public/applications/workplace_search/views/api_keys/index.ts
index 47615a02668c6..4affd04611624 100644
--- a/x-pack/plugins/security_solution/server/endpoint/schemas/artifacts/response/index.ts
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/api_keys/index.ts
@@ -5,4 +5,4 @@
* 2.0.
*/
-export * from './download_artifact_schema';
+export { ApiKeys } from './api_keys';
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source_logic.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source_logic.ts
index 05a5fd5a73fe8..6dbac2dcd1452 100644
--- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source_logic.ts
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source_logic.ts
@@ -571,7 +571,7 @@ export const AddSourceLogic = kea 0 ? githubOrganizations : undefined,
- indexPermissions: indexPermissionsValue || undefined,
+ index_permissions: indexPermissionsValue || undefined,
} as {
[key: string]: string | string[] | undefined;
};
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/constants.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/constants.ts
index 079cb5e1a5a3d..cbc18f6d7a19e 100644
--- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/constants.ts
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/constants.ts
@@ -282,7 +282,7 @@ export const SAVE_CUSTOM_BODY1 = i18n.translate(
export const SAVE_CUSTOM_BODY2 = i18n.translate(
'xpack.enterpriseSearch.workplaceSearch.contentSource.saveCustom.body2',
{
- defaultMessage: 'Be sure to copy your API keys below.',
+ defaultMessage: 'Be sure to copy your Source Identifier below.',
}
);
@@ -293,20 +293,6 @@ export const SAVE_CUSTOM_RETURN_BUTTON = i18n.translate(
}
);
-export const SAVE_CUSTOM_API_KEYS_TITLE = i18n.translate(
- 'xpack.enterpriseSearch.workplaceSearch.contentSource.saveCustom.apiKeys.title',
- {
- defaultMessage: 'API Keys',
- }
-);
-
-export const SAVE_CUSTOM_API_KEYS_BODY = i18n.translate(
- 'xpack.enterpriseSearch.workplaceSearch.contentSource.saveCustom.apiKeys.body',
- {
- defaultMessage: "You'll need these keys to sync documents for this custom source.",
- }
-);
-
export const SAVE_CUSTOM_VISUAL_WALKTHROUGH_TITLE = i18n.translate(
'xpack.enterpriseSearch.workplaceSearch.contentSource.saveCustom.visualWalkthrough.title',
{
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/github_app.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/github_app.tsx
deleted file mode 100644
index 7f518d272d842..0000000000000
--- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/github_app.tsx
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import React from 'react';
-
-import { useValues } from 'kea';
-
-import { EuiFlexGroup, EuiFlexItem, EuiHorizontalRule, EuiPanel, EuiSpacer } from '@elastic/eui';
-
-import { AppLogic } from '../../../../app_logic';
-import {
- WorkplaceSearchPageTemplate,
- PersonalDashboardLayout,
-} from '../../../../components/layout';
-import { NAV, SOURCE_NAMES } from '../../../../constants';
-
-import { staticSourceData } from '../../source_data';
-
-import { AddSourceHeader } from './add_source_header';
-import { SourceFeatures } from './source_features';
-
-export const GitHubApp: React.FC = () => {
- const { isOrganization } = useValues(AppLogic);
-
- const name = SOURCE_NAMES.GITHUB;
- const data = staticSourceData.find((source) => (source.name = name));
- const Layout = isOrganization ? WorkplaceSearchPageTemplate : PersonalDashboardLayout;
-
- return (
-
-
-
- );
-};
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/github_via_app.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/github_via_app.tsx
new file mode 100644
index 0000000000000..a08f49b8bbe78
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/github_via_app.tsx
@@ -0,0 +1,128 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import React, { useEffect } from 'react';
+import type { FormEvent } from 'react';
+
+import { useValues, useActions } from 'kea';
+
+import {
+ EuiHorizontalRule,
+ EuiPanel,
+ EuiSpacer,
+ EuiFieldText,
+ EuiFormRow,
+ EuiFilePicker,
+ EuiButton,
+} from '@elastic/eui';
+
+import { LicensingLogic } from '../../../../../shared/licensing';
+import { AppLogic } from '../../../../app_logic';
+
+import {
+ WorkplaceSearchPageTemplate,
+ PersonalDashboardLayout,
+} from '../../../../components/layout';
+import { NAV, SOURCE_NAMES } from '../../../../constants';
+import { handlePrivateKeyUpload } from '../../../../utils';
+
+import { staticSourceData } from '../../source_data';
+
+import { AddSourceHeader } from './add_source_header';
+import { DocumentPermissionsCallout } from './document_permissions_callout';
+import { DocumentPermissionsField } from './document_permissions_field';
+import { GithubViaAppLogic } from './github_via_app_logic';
+import { SourceFeatures } from './source_features';
+
+interface GithubViaAppProps {
+ isGithubEnterpriseServer: boolean;
+}
+
+export const GitHubViaApp: React.FC = ({ isGithubEnterpriseServer }) => {
+ const { isOrganization } = useValues(AppLogic);
+ const { githubAppId, githubEnterpriseServerUrl, isSubmitButtonLoading, indexPermissionsValue } =
+ useValues(GithubViaAppLogic);
+ const {
+ setGithubAppId,
+ setGithubEnterpriseServerUrl,
+ setStagedPrivateKey,
+ createContentSource,
+ setSourceIndexPermissionsValue,
+ } = useActions(GithubViaAppLogic);
+
+ const { hasPlatinumLicense } = useValues(LicensingLogic);
+ const name = isGithubEnterpriseServer ? SOURCE_NAMES.GITHUB_ENTERPRISE : SOURCE_NAMES.GITHUB;
+ const data = staticSourceData.find((source) => source.name === name);
+ const Layout = isOrganization ? WorkplaceSearchPageTemplate : PersonalDashboardLayout;
+
+ const handleSubmit = (e: FormEvent) => {
+ e.preventDefault();
+ createContentSource(isGithubEnterpriseServer);
+ };
+
+ // Default indexPermissions to true, if needed
+ useEffect(() => {
+ setSourceIndexPermissionsValue(isOrganization && hasPlatinumLicense);
+ }, []);
+
+ return (
+
+
+
+ );
+};
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/github_via_app_logic.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/github_via_app_logic.ts
new file mode 100644
index 0000000000000..e779d53b6a1eb
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/github_via_app_logic.ts
@@ -0,0 +1,116 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { kea, MakeLogicType } from 'kea';
+
+import { flashAPIErrors, flashSuccessToast } from '../../../../../shared/flash_messages';
+import { HttpLogic } from '../../../../../shared/http';
+import { KibanaLogic } from '../../../../../shared/kibana';
+import { AppLogic } from '../../../../app_logic';
+import {
+ GITHUB_VIA_APP_SERVICE_TYPE,
+ GITHUB_ENTERPRISE_SERVER_VIA_APP_SERVICE_TYPE,
+} from '../../../../constants';
+import { SOURCES_PATH, getSourcesPath } from '../../../../routes';
+import { ContentSourceFullData } from '../../../../types';
+
+interface GithubViaAppValues {
+ githubAppId: string;
+ githubEnterpriseServerUrl: string;
+ stagedPrivateKey: string | null;
+ isSubmitButtonLoading: boolean;
+ indexPermissionsValue: boolean;
+}
+
+interface GithubViaAppActions {
+ setGithubAppId(githubAppId: string): string;
+ setGithubEnterpriseServerUrl(githubEnterpriseServerUrl: string): string;
+ setStagedPrivateKey(stagedPrivateKey: string | null): string | null;
+ setButtonNotLoading(): void;
+ createContentSource(isGithubEnterpriseServer: boolean): boolean;
+ setSourceIndexPermissionsValue(indexPermissionsValue: boolean): boolean;
+}
+
+export const GithubViaAppLogic = kea>({
+ path: ['enterprise_search', 'workplace_search', 'github_via_app_logic'],
+ actions: {
+ setGithubAppId: (githubAppId: string) => githubAppId,
+ setGithubEnterpriseServerUrl: (githubEnterpriseServerUrl: string) => githubEnterpriseServerUrl,
+ createContentSource: (isGithubEnterpriseServer: boolean) => isGithubEnterpriseServer,
+ setStagedPrivateKey: (stagedPrivateKey: string) => stagedPrivateKey,
+ setButtonNotLoading: false,
+ setSourceIndexPermissionsValue: (indexPermissionsValue: boolean) => indexPermissionsValue,
+ },
+ reducers: {
+ githubAppId: [
+ '',
+ {
+ setGithubAppId: (_, githubAppId) => githubAppId,
+ },
+ ],
+ githubEnterpriseServerUrl: [
+ '',
+ {
+ setGithubEnterpriseServerUrl: (_, githubEnterpriseServerUrl) => githubEnterpriseServerUrl,
+ },
+ ],
+ stagedPrivateKey: [
+ null,
+ {
+ setStagedPrivateKey: (_, stagedPrivateKey) => stagedPrivateKey,
+ },
+ ],
+ isSubmitButtonLoading: [
+ false,
+ {
+ createContentSource: () => true,
+ setButtonNotLoading: () => false,
+ },
+ ],
+ indexPermissionsValue: [
+ true,
+ {
+ setSourceIndexPermissionsValue: (_, indexPermissionsValue) => indexPermissionsValue,
+ resetSourceState: () => false,
+ },
+ ],
+ },
+ listeners: ({ actions, values }) => ({
+ createContentSource: async (isGithubEnterpriseServer) => {
+ const { isOrganization } = AppLogic.values;
+ const route = isOrganization
+ ? '/internal/workplace_search/org/create_source'
+ : '/internal/workplace_search/account/create_source';
+
+ const { githubAppId, githubEnterpriseServerUrl, stagedPrivateKey, indexPermissionsValue } =
+ values;
+
+ const params = {
+ service_type: isGithubEnterpriseServer
+ ? GITHUB_ENTERPRISE_SERVER_VIA_APP_SERVICE_TYPE
+ : GITHUB_VIA_APP_SERVICE_TYPE,
+ app_id: githubAppId,
+ base_url: githubEnterpriseServerUrl,
+ private_key: stagedPrivateKey,
+ index_permissions: indexPermissionsValue,
+ };
+
+ try {
+ const response = await HttpLogic.values.http.post(route, {
+ body: JSON.stringify({ ...params }),
+ });
+
+ KibanaLogic.values.navigateToUrl(`${getSourcesPath(SOURCES_PATH, isOrganization)}`);
+ flashSuccessToast(`${response.serviceName} connected`);
+ } catch (e) {
+ flashAPIErrors(e);
+ } finally {
+ actions.setButtonNotLoading();
+ }
+ },
+ }),
+});
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/index.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/index.ts
index 033cf9f356342..8daa71672d203 100644
--- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/index.ts
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/index.ts
@@ -7,4 +7,4 @@
export { AddSource } from './add_source';
export { AddSourceList } from './add_source_list';
-export { GitHubApp } from './github_app';
+export { GitHubViaApp } from './github_via_app';
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/save_custom.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/save_custom.test.tsx
index a8a5810e7c0a2..4715c50e4233c 100644
--- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/save_custom.test.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/save_custom.test.tsx
@@ -36,7 +36,7 @@ describe('SaveCustom', () => {
const wrapper = shallow();
expect(wrapper.find(EuiPanel)).toHaveLength(1);
- expect(wrapper.find(EuiTitle)).toHaveLength(5);
+ expect(wrapper.find(EuiTitle)).toHaveLength(4);
expect(wrapper.find(EuiLinkTo)).toHaveLength(1);
});
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/save_custom.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/save_custom.tsx
index 8108f8211f93d..bbf1b66277c70 100644
--- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/save_custom.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/save_custom.tsx
@@ -26,7 +26,6 @@ import { FormattedMessage } from '@kbn/i18n-react';
import { LicensingLogic } from '../../../../../shared/licensing';
import { EuiLinkTo } from '../../../../../shared/react_router_helpers';
-import { CredentialItem } from '../../../../components/shared/credential_item';
import { LicenseBadge } from '../../../../components/shared/license_badge';
import {
SOURCES_PATH,
@@ -37,14 +36,14 @@ import {
getSourcesPath,
} from '../../../../routes';
import { CustomSource } from '../../../../types';
-import { ACCESS_TOKEN_LABEL, ID_LABEL, LEARN_CUSTOM_FEATURES_BUTTON } from '../../constants';
+import { LEARN_CUSTOM_FEATURES_BUTTON } from '../../constants';
+
+import { SourceIdentifier } from '../source_identifier';
import {
SAVE_CUSTOM_BODY1,
SAVE_CUSTOM_BODY2,
SAVE_CUSTOM_RETURN_BUTTON,
- SAVE_CUSTOM_API_KEYS_TITLE,
- SAVE_CUSTOM_API_KEYS_BODY,
SAVE_CUSTOM_VISUAL_WALKTHROUGH_TITLE,
SAVE_CUSTOM_VISUAL_WALKTHROUGH_LINK,
SAVE_CUSTOM_STYLING_RESULTS_TITLE,
@@ -62,7 +61,7 @@ interface SaveCustomProps {
export const SaveCustom: React.FC = ({
documentationUrl,
- newCustomSource: { id, accessToken, name },
+ newCustomSource: { id, name },
isOrganization,
header,
}) => {
@@ -106,24 +105,8 @@ export const SaveCustom: React.FC = ({
-
-
-
- {SAVE_CUSTOM_API_KEYS_TITLE}
-
-
- {SAVE_CUSTOM_API_KEYS_BODY}
-
-
-
-
-
-
-
+
+
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 fd29b5f590967..29abbf94db397 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
@@ -37,7 +37,6 @@ import { EuiListGroupItemTo, EuiLinkTo } from '../../../../shared/react_router_h
import { AppLogic } from '../../../app_logic';
import aclImage from '../../../assets/supports_acl.svg';
import { ComponentLoader } from '../../../components/shared/component_loader';
-import { CredentialItem } from '../../../components/shared/credential_item';
import { LicenseBadge } from '../../../components/shared/license_badge';
import { StatusItem } from '../../../components/shared/status_item';
import { ViewContentHeader } from '../../../components/shared/view_content_header';
@@ -78,8 +77,6 @@ import {
STATUS_TEXT,
ADDITIONAL_CONFIG_HEADING,
EXTERNAL_IDENTITIES_LINK,
- ACCESS_TOKEN_LABEL,
- ID_LABEL,
LEARN_CUSTOM_FEATURES_BUTTON,
DOC_PERMISSIONS_DESCRIPTION,
CUSTOM_CALLOUT_TITLE,
@@ -92,6 +89,7 @@ import {
} from '../constants';
import { SourceLogic } from '../source_logic';
+import { SourceIdentifier } from './source_identifier';
import { SourceLayout } from './source_layout';
export const Overview: React.FC = () => {
@@ -106,7 +104,6 @@ export const Overview: React.FC = () => {
groups,
details,
custom,
- accessToken,
licenseSupportsPermissions,
serviceTypeSupportsPermissions,
indexPermissions,
@@ -432,9 +429,7 @@ export const Overview: React.FC = () => {
-
-
-
+
);
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_identifier.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_identifier.test.tsx
new file mode 100644
index 0000000000000..2a9af72f596ed
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_identifier.test.tsx
@@ -0,0 +1,32 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license 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 { EuiCopy, EuiButtonIcon, EuiFieldText } from '@elastic/eui';
+
+import { SourceIdentifier } from './source_identifier';
+
+describe('SourceIdentifier', () => {
+ const id = 'foo123';
+
+ it('renders the Source Identifier', () => {
+ const wrapper = shallow();
+
+ expect(wrapper.find(EuiFieldText).prop('value')).toEqual(id);
+ });
+
+ it('renders the copy button', () => {
+ const copyMock = jest.fn();
+ const wrapper = shallow();
+
+ const copyEl = shallow({wrapper.find(EuiCopy).props().children(copyMock)}
);
+ expect(copyEl.find(EuiButtonIcon).props().onClick).toEqual(copyMock);
+ });
+});
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_identifier.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_identifier.tsx
new file mode 100644
index 0000000000000..2c7784a554a25
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_identifier.tsx
@@ -0,0 +1,73 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license 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 {
+ EuiFlexGroup,
+ EuiFlexItem,
+ EuiText,
+ EuiCopy,
+ EuiButtonIcon,
+ EuiFieldText,
+ EuiSpacer,
+} from '@elastic/eui';
+import { FormattedMessage } from '@kbn/i18n-react';
+
+import { EuiLinkTo } from '../../../../shared/react_router_helpers';
+
+import { API_KEY_LABEL, COPY_TOOLTIP, COPIED_TOOLTIP } from '../../../constants';
+import { API_KEYS_PATH } from '../../../routes';
+
+import { ID_LABEL } from '../constants';
+
+interface Props {
+ id: string;
+}
+
+export const SourceIdentifier: React.FC = ({ id }) => (
+ <>
+
+
+
+ {ID_LABEL}
+
+
+
+
+ {(copy) => (
+
+ )}
+
+
+
+
+
+
+
+
+
+
+ {API_KEY_LABEL}
+
+ ),
+ }}
+ />
+
+
+ >
+);
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_settings.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_settings.tsx
index f0ccfb201e3b3..e5924b672c771 100644
--- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_settings.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_settings.tsx
@@ -17,6 +17,9 @@ import {
EuiFlexGroup,
EuiFlexItem,
EuiFormRow,
+ EuiForm,
+ EuiSpacer,
+ EuiFilePicker,
} from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n-react';
@@ -26,7 +29,11 @@ import { AppLogic } from '../../../app_logic';
import { ContentSection } from '../../../components/shared/content_section';
import { SourceConfigFields } from '../../../components/shared/source_config_fields';
import { ViewContentHeader } from '../../../components/shared/view_content_header';
-import { NAV } from '../../../constants';
+import {
+ NAV,
+ GITHUB_VIA_APP_SERVICE_TYPE,
+ GITHUB_ENTERPRISE_SERVER_VIA_APP_SERVICE_TYPE,
+} from '../../../constants';
import {
CANCEL_BUTTON,
@@ -36,6 +43,7 @@ import {
REMOVE_BUTTON,
} from '../../../constants';
import { SourceDataItem } from '../../../types';
+import { handlePrivateKeyUpload } from '../../../utils';
import { AddSourceLogic } from '../components/add_source/add_source_logic';
import {
SOURCE_SETTINGS_HEADING,
@@ -58,12 +66,19 @@ import { SourceLayout } from './source_layout';
export const SourceSettings: React.FC = () => {
const { http } = useValues(HttpLogic);
- const { updateContentSource, removeContentSource } = useActions(SourceLogic);
+ const {
+ updateContentSource,
+ removeContentSource,
+ setStagedPrivateKey,
+ updateContentSourceConfiguration,
+ } = useActions(SourceLogic);
const { getSourceConfigData } = useActions(AddSourceLogic);
const {
- contentSource: { name, id, serviceType, isOauth1 },
+ contentSource: { name, id, serviceType, isOauth1, secret },
buttonLoading,
+ stagedPrivateKey,
+ isConfigurationUpdateButtonLoading,
} = useValues(SourceLogic);
const {
@@ -76,16 +91,22 @@ export const SourceSettings: React.FC = () => {
getSourceConfigData(serviceType);
}, []);
- const { editPath } = staticSourceData.find(
- (source) => source.serviceType === serviceType
- ) as SourceDataItem;
+ const isGithubApp =
+ serviceType === GITHUB_VIA_APP_SERVICE_TYPE ||
+ serviceType === GITHUB_ENTERPRISE_SERVER_VIA_APP_SERVICE_TYPE;
+
+ const editPath = isGithubApp
+ ? undefined // undefined for GitHub apps, as they are configured source-wide, and don't use a connector where you can edit the configuration
+ : (staticSourceData.find((source) => source.serviceType === serviceType) as SourceDataItem)
+ .editPath;
const [inputValue, setValue] = useState(name);
const [confirmModalVisible, setModalVisibility] = useState(false);
const showConfirm = () => setModalVisibility(true);
const hideConfirm = () => setModalVisibility(false);
- const showConfig = isOrganization && !isEmpty(configuredFields);
+ const showOauthConfig = !isGithubApp && isOrganization && !isEmpty(configuredFields);
+ const showGithubAppConfig = isGithubApp;
const { clientId, clientSecret, publicKey, consumerKey, baseUrl } = configuredFields || {};
@@ -102,6 +123,11 @@ export const SourceSettings: React.FC = () => {
updateContentSource(id, { name: inputValue });
};
+ const submitConfigurationChange = (e: FormEvent) => {
+ e.preventDefault();
+ updateContentSourceConfiguration(id, { private_key: stagedPrivateKey });
+ };
+
const handleSourceRemoval = () => {
/**
* The modal was just hanging while the UI waited for the server to respond.
@@ -164,7 +190,7 @@ export const SourceSettings: React.FC = () => {
- {showConfig && (
+ {showOauthConfig && (
{
baseUrl={baseUrl}
/>
-
+
{SOURCE_CONFIG_LINK}
)}
+ {showGithubAppConfig && (
+
+
+
+ {secret!.app_id}
+
+ {secret!.base_url && (
+
+ {secret!.base_url}
+
+ )}
+
+ <>
+ SHA256:{secret!.fingerprint}
+
+ handlePrivateKeyUpload(files, setStagedPrivateKey)}
+ initialPromptText="Upload a new .pem file to rotate the private key"
+ accept=".pem"
+ />
+ >
+
+
+ {isConfigurationUpdateButtonLoading ? 'Loading…' : 'Save'}
+
+
+
+ )}
{
buttonLoading: false,
contentMeta: DEFAULT_META,
contentFilterValue: '',
+ isConfigurationUpdateButtonLoading: false,
+ stagedPrivateKey: null,
};
const searchServerResponse = {
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_logic.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_logic.ts
index e97d48889d809..b76627f57b3a3 100644
--- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_logic.ts
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_logic.ts
@@ -39,10 +39,19 @@ export interface SourceActions {
sourceId: string;
source: ContentSourceFullData;
};
+ updateContentSourceConfiguration(
+ sourceId: string,
+ source: SourceUpdatePayload
+ ): {
+ sourceId: string;
+ source: ContentSourceFullData;
+ };
resetSourceState(): void;
removeContentSource(sourceId: string): { sourceId: string };
initializeSource(sourceId: string): { sourceId: string };
setButtonNotLoading(): void;
+ setStagedPrivateKey(stagedPrivateKey: string | null): string | null;
+ setConfigurationUpdateButtonNotLoading(): void;
}
interface SourceValues {
@@ -53,6 +62,8 @@ interface SourceValues {
contentItems: SourceContentItem[];
contentMeta: Meta;
contentFilterValue: string;
+ stagedPrivateKey: string | null;
+ isConfigurationUpdateButtonLoading: boolean;
}
interface SearchResultsResponse {
@@ -62,6 +73,7 @@ interface SearchResultsResponse {
interface SourceUpdatePayload {
name?: string;
+ private_key?: string | null;
indexing?: {
enabled?: boolean;
features?: {
@@ -85,11 +97,17 @@ export const SourceLogic = kea>({
initializeSourceSynchronization: (sourceId: string) => ({ sourceId }),
searchContentSourceDocuments: (sourceId: string) => ({ sourceId }),
updateContentSource: (sourceId: string, source: SourceUpdatePayload) => ({ sourceId, source }),
+ updateContentSourceConfiguration: (sourceId: string, source: SourceUpdatePayload) => ({
+ sourceId,
+ source,
+ }),
removeContentSource: (sourceId: string) => ({
sourceId,
}),
resetSourceState: () => true,
setButtonNotLoading: () => false,
+ setStagedPrivateKey: (stagedPrivateKey: string) => stagedPrivateKey,
+ setConfigurationUpdateButtonNotLoading: () => false,
},
reducers: {
contentSource: [
@@ -150,6 +168,20 @@ export const SourceLogic = kea>({
resetSourceState: () => '',
},
],
+ stagedPrivateKey: [
+ null,
+ {
+ setStagedPrivateKey: (_, stagedPrivateKey) => stagedPrivateKey,
+ setContentSource: () => null,
+ },
+ ],
+ isConfigurationUpdateButtonLoading: [
+ false,
+ {
+ updateContentSourceConfiguration: () => true,
+ setConfigurationUpdateButtonNotLoading: () => false,
+ },
+ ],
},
listeners: ({ actions, values }) => ({
initializeSource: async ({ sourceId }) => {
@@ -233,6 +265,26 @@ export const SourceLogic = kea>({
flashAPIErrors(e);
}
},
+ updateContentSourceConfiguration: async ({ sourceId, source }) => {
+ const { isOrganization } = AppLogic.values;
+ const route = isOrganization
+ ? `/internal/workplace_search/org/sources/${sourceId}/settings`
+ : `/internal/workplace_search/account/sources/${sourceId}/settings`;
+
+ try {
+ const response = await HttpLogic.values.http.patch(route, {
+ body: JSON.stringify({ content_source: source }),
+ });
+
+ actions.setContentSource(response);
+
+ flashSuccessToast('Content source configuration was updated.');
+ } catch (e) {
+ flashAPIErrors(e);
+ } finally {
+ actions.setConfigurationUpdateButtonNotLoading();
+ }
+ },
removeContentSource: async ({ sourceId }) => {
clearFlashMessages();
const { isOrganization } = AppLogic.values;
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/sources_router.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/sources_router.test.tsx
index bcf2b2792c5d5..cf5dc48682ae8 100644
--- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/sources_router.test.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/sources_router.test.tsx
@@ -34,7 +34,7 @@ describe('SourcesRouter', () => {
});
it('renders sources routes', () => {
- const TOTAL_ROUTES = 62;
+ const TOTAL_ROUTES = 63;
const wrapper = shallow();
expect(wrapper.find(Switch)).toHaveLength(1);
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/sources_router.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/sources_router.tsx
index 5142f5d6597ae..23109506b364e 100644
--- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/sources_router.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/sources_router.tsx
@@ -14,7 +14,8 @@ import { useActions, useValues } from 'kea';
import { LicensingLogic } from '../../../shared/licensing';
import { AppLogic } from '../../app_logic';
import {
- ADD_GITHUB_APP_PATH,
+ ADD_GITHUB_VIA_APP_PATH,
+ ADD_GITHUB_ENTERPRISE_SERVER_VIA_APP_PATH,
ADD_SOURCE_PATH,
SOURCE_DETAILS_PATH,
PRIVATE_SOURCES_PATH,
@@ -22,7 +23,7 @@ import {
getSourcesPath,
} from '../../routes';
-import { AddSource, AddSourceList, GitHubApp } from './components/add_source';
+import { AddSource, AddSourceList, GitHubViaApp } from './components/add_source';
import { OrganizationSources } from './organization_sources';
import { PrivateSources } from './private_sources';
import { staticSourceData } from './source_data';
@@ -67,8 +68,11 @@ export const SourcesRouter: React.FC = () => {
-
-
+
+
+
+
+
{staticSourceData.map(({ addPath, accountContextOnly }, i) => (
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/settings/components/connectors.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/settings/components/connectors.tsx
index f86b390f99ceb..85f91f769cc77 100644
--- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/settings/components/connectors.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/settings/components/connectors.tsx
@@ -60,7 +60,7 @@ export const Connectors: React.FC = () => {
const updateButtons = (
-
+
{UPDATE_BUTTON}
diff --git a/x-pack/plugins/enterprise_search/server/routes/workplace_search/api_keys.test.ts b/x-pack/plugins/enterprise_search/server/routes/workplace_search/api_keys.test.ts
new file mode 100644
index 0000000000000..4855716cfc2fd
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/server/routes/workplace_search/api_keys.test.ts
@@ -0,0 +1,92 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { MockRouter, mockRequestHandler, mockDependencies } from '../../__mocks__';
+
+import { registerApiKeysRoute } from './api_keys';
+
+describe('api keys routes', () => {
+ describe('GET /internal/workplace_search/api_keys', () => {
+ let mockRouter: MockRouter;
+
+ beforeEach(() => {
+ jest.clearAllMocks();
+ mockRouter = new MockRouter({
+ method: 'get',
+ path: '/internal/workplace_search/api_keys',
+ });
+
+ registerApiKeysRoute({
+ ...mockDependencies,
+ router: mockRouter.router,
+ });
+ });
+
+ it('creates a request handler', () => {
+ expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({
+ path: '/ws/org/api_tokens',
+ });
+ });
+ });
+
+ describe('POST /internal/workplace_search/api_keys', () => {
+ let mockRouter: MockRouter;
+
+ beforeEach(() => {
+ jest.clearAllMocks();
+ mockRouter = new MockRouter({
+ method: 'post',
+ path: '/internal/workplace_search/api_keys',
+ });
+
+ registerApiKeysRoute({
+ ...mockDependencies,
+ router: mockRouter.router,
+ });
+ });
+
+ it('creates a request handler', () => {
+ expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({
+ path: '/ws/org/api_tokens',
+ });
+ });
+
+ describe('validates', () => {
+ it('correctly', () => {
+ const request = {
+ body: {
+ name: 'my-api-key',
+ },
+ };
+ mockRouter.shouldValidate(request);
+ });
+ });
+ });
+
+ describe('DELETE /internal/workplace_search/api_keys/{tokenName}', () => {
+ let mockRouter: MockRouter;
+
+ beforeEach(() => {
+ jest.clearAllMocks();
+ mockRouter = new MockRouter({
+ method: 'delete',
+ path: '/internal/workplace_search/api_keys/{tokenName}',
+ });
+
+ registerApiKeysRoute({
+ ...mockDependencies,
+ router: mockRouter.router,
+ });
+ });
+
+ it('creates a request handler', () => {
+ expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({
+ path: '/ws/org/api_tokens/:tokenName',
+ });
+ });
+ });
+});
diff --git a/x-pack/plugins/enterprise_search/server/routes/workplace_search/api_keys.ts b/x-pack/plugins/enterprise_search/server/routes/workplace_search/api_keys.ts
new file mode 100644
index 0000000000000..ff63c7b146750
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/server/routes/workplace_search/api_keys.ts
@@ -0,0 +1,57 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license 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 { RouteDependencies } from '../../plugin';
+
+export function registerApiKeysRoute({
+ router,
+ enterpriseSearchRequestHandler,
+}: RouteDependencies) {
+ router.get(
+ {
+ path: '/internal/workplace_search/api_keys',
+ validate: false,
+ },
+ enterpriseSearchRequestHandler.createRequest({
+ path: '/ws/org/api_tokens',
+ })
+ );
+
+ router.post(
+ {
+ path: '/internal/workplace_search/api_keys',
+ validate: {
+ body: schema.object({
+ name: schema.string(),
+ }),
+ },
+ },
+ enterpriseSearchRequestHandler.createRequest({
+ path: '/ws/org/api_tokens',
+ })
+ );
+
+ router.delete(
+ {
+ path: '/internal/workplace_search/api_keys/{tokenName}',
+ validate: {
+ params: schema.object({
+ tokenName: schema.string(),
+ }),
+ },
+ },
+ enterpriseSearchRequestHandler.createRequest({
+ path: '/ws/org/api_tokens/:tokenName',
+ })
+ );
+}
+
+export const registerApiKeysRoutes = (dependencies: RouteDependencies) => {
+ registerApiKeysRoute(dependencies);
+};
diff --git a/x-pack/plugins/enterprise_search/server/routes/workplace_search/index.ts b/x-pack/plugins/enterprise_search/server/routes/workplace_search/index.ts
index aa3b60a5ba047..24eff218c3345 100644
--- a/x-pack/plugins/enterprise_search/server/routes/workplace_search/index.ts
+++ b/x-pack/plugins/enterprise_search/server/routes/workplace_search/index.ts
@@ -7,6 +7,7 @@
import { RouteDependencies } from '../../plugin';
+import { registerApiKeysRoutes } from './api_keys';
import { registerGroupsRoutes } from './groups';
import { registerOAuthRoutes } from './oauth';
import { registerOverviewRoute } from './overview';
@@ -16,6 +17,7 @@ import { registerSettingsRoutes } from './settings';
import { registerSourcesRoutes } from './sources';
export const registerWorkplaceSearchRoutes = (dependencies: RouteDependencies) => {
+ registerApiKeysRoutes(dependencies);
registerOverviewRoute(dependencies);
registerOAuthRoutes(dependencies);
registerGroupsRoutes(dependencies);
diff --git a/x-pack/plugins/enterprise_search/server/routes/workplace_search/sources.test.ts b/x-pack/plugins/enterprise_search/server/routes/workplace_search/sources.test.ts
index 961635c3f9001..3702298e8bcae 100644
--- a/x-pack/plugins/enterprise_search/server/routes/workplace_search/sources.test.ts
+++ b/x-pack/plugins/enterprise_search/server/routes/workplace_search/sources.test.ts
@@ -180,7 +180,7 @@ describe('sources routes', () => {
login: 'user',
password: 'changeme',
organizations: ['swiftype'],
- indexPermissions: true,
+ index_permissions: true,
},
};
mockRouter.shouldValidate(request);
@@ -688,7 +688,7 @@ describe('sources routes', () => {
login: 'user',
password: 'changeme',
organizations: ['swiftype'],
- indexPermissions: true,
+ index_permissions: true,
},
};
mockRouter.shouldValidate(request);
diff --git a/x-pack/plugins/enterprise_search/server/routes/workplace_search/sources.ts b/x-pack/plugins/enterprise_search/server/routes/workplace_search/sources.ts
index 011fe341d6edf..12f4844461409 100644
--- a/x-pack/plugins/enterprise_search/server/routes/workplace_search/sources.ts
+++ b/x-pack/plugins/enterprise_search/server/routes/workplace_search/sources.ts
@@ -60,6 +60,7 @@ const displaySettingsSchema = schema.object({
const sourceSettingsSchema = schema.object({
content_source: schema.object({
name: schema.maybe(schema.string()),
+ private_key: schema.maybe(schema.nullable(schema.string())),
indexing: schema.maybe(
schema.object({
enabled: schema.maybe(schema.boolean()),
@@ -178,7 +179,7 @@ export function registerAccountCreateSourceRoute({
login: schema.maybe(schema.string()),
password: schema.maybe(schema.string()),
organizations: schema.maybe(schema.arrayOf(schema.string())),
- indexPermissions: schema.maybe(schema.boolean()),
+ index_permissions: schema.maybe(schema.boolean()),
}),
},
},
@@ -522,7 +523,10 @@ export function registerOrgCreateSourceRoute({
login: schema.maybe(schema.string()),
password: schema.maybe(schema.string()),
organizations: schema.maybe(schema.arrayOf(schema.string())),
- indexPermissions: schema.maybe(schema.boolean()),
+ index_permissions: schema.maybe(schema.boolean()),
+ app_id: schema.maybe(schema.string()),
+ base_url: schema.maybe(schema.string()),
+ private_key: schema.nullable(schema.maybe(schema.string())),
}),
},
},
diff --git a/x-pack/plugins/fleet/common/openapi/bundled.json b/x-pack/plugins/fleet/common/openapi/bundled.json
index 1e17c693e01b9..f30369b5792b8 100644
--- a/x-pack/plugins/fleet/common/openapi/bundled.json
+++ b/x-pack/plugins/fleet/common/openapi/bundled.json
@@ -2673,8 +2673,7 @@
},
"required": [
"name",
- "version",
- "title"
+ "version"
]
},
"namespace": {
@@ -2713,8 +2712,7 @@
},
"required": [
"type",
- "enabled",
- "streams"
+ "enabled"
]
}
},
@@ -2729,9 +2727,7 @@
}
},
"required": [
- "output_id",
"inputs",
- "policy_id",
"name"
]
},
@@ -2858,19 +2854,86 @@
},
"update_package_policy": {
"title": "Update package policy",
- "allOf": [
- {
+ "type": "object",
+ "description": "",
+ "properties": {
+ "version": {
+ "type": "string"
+ },
+ "enabled": {
+ "type": "boolean"
+ },
+ "package": {
"type": "object",
"properties": {
+ "name": {
+ "type": "string"
+ },
"version": {
"type": "string"
+ },
+ "title": {
+ "type": "string"
}
+ },
+ "required": [
+ "name",
+ "title",
+ "version"
+ ]
+ },
+ "namespace": {
+ "type": "string"
+ },
+ "output_id": {
+ "type": "string"
+ },
+ "inputs": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string"
+ },
+ "enabled": {
+ "type": "boolean"
+ },
+ "processors": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "streams": {
+ "type": "array",
+ "items": {}
+ },
+ "config": {
+ "type": "object"
+ },
+ "vars": {
+ "type": "object"
+ }
+ },
+ "required": [
+ "type",
+ "enabled",
+ "streams"
+ ]
}
},
- {
- "$ref": "#/components/schemas/new_package_policy"
+ "policy_id": {
+ "type": "string"
+ },
+ "name": {
+ "type": "string"
+ },
+ "description": {
+ "type": "string"
}
- ]
+ },
+ "required": null
},
"output": {
"title": "Output",
diff --git a/x-pack/plugins/fleet/common/openapi/bundled.yaml b/x-pack/plugins/fleet/common/openapi/bundled.yaml
index 1d7f1cb9ccf1f..44242423aa420 100644
--- a/x-pack/plugins/fleet/common/openapi/bundled.yaml
+++ b/x-pack/plugins/fleet/common/openapi/bundled.yaml
@@ -1683,7 +1683,6 @@ components:
required:
- name
- version
- - title
namespace:
type: string
output_id:
@@ -1711,7 +1710,6 @@ components:
required:
- type
- enabled
- - streams
policy_id:
type: string
name:
@@ -1719,9 +1717,7 @@ components:
description:
type: string
required:
- - output_id
- inputs
- - policy_id
- name
package_policy:
title: Package policy
@@ -1801,12 +1797,61 @@ components:
items: {}
update_package_policy:
title: Update package policy
- allOf:
- - type: object
+ type: object
+ description: ''
+ properties:
+ version:
+ type: string
+ enabled:
+ type: boolean
+ package:
+ type: object
properties:
+ name:
+ type: string
version:
type: string
- - $ref: '#/components/schemas/new_package_policy'
+ title:
+ type: string
+ required:
+ - name
+ - title
+ - version
+ namespace:
+ type: string
+ output_id:
+ type: string
+ inputs:
+ type: array
+ items:
+ type: object
+ properties:
+ type:
+ type: string
+ enabled:
+ type: boolean
+ processors:
+ type: array
+ items:
+ type: string
+ streams:
+ type: array
+ items: {}
+ config:
+ type: object
+ vars:
+ type: object
+ required:
+ - type
+ - enabled
+ - streams
+ policy_id:
+ type: string
+ name:
+ type: string
+ description:
+ type: string
+ required: null
output:
title: Output
type: object
diff --git a/x-pack/plugins/fleet/common/openapi/components/schemas/new_package_policy.yaml b/x-pack/plugins/fleet/common/openapi/components/schemas/new_package_policy.yaml
index e5e4451881b57..ad400a9eb8e0c 100644
--- a/x-pack/plugins/fleet/common/openapi/components/schemas/new_package_policy.yaml
+++ b/x-pack/plugins/fleet/common/openapi/components/schemas/new_package_policy.yaml
@@ -16,7 +16,6 @@ properties:
required:
- name
- version
- - title
namespace:
type: string
output_id:
@@ -44,7 +43,6 @@ properties:
required:
- type
- enabled
- - streams
policy_id:
type: string
name:
@@ -52,7 +50,5 @@ properties:
description:
type: string
required:
- - output_id
- inputs
- - policy_id
- name
diff --git a/x-pack/plugins/fleet/common/openapi/components/schemas/update_package_policy.yaml b/x-pack/plugins/fleet/common/openapi/components/schemas/update_package_policy.yaml
index 8f7f856a6649f..1d7fb2e7213de 100644
--- a/x-pack/plugins/fleet/common/openapi/components/schemas/update_package_policy.yaml
+++ b/x-pack/plugins/fleet/common/openapi/components/schemas/update_package_policy.yaml
@@ -1,7 +1,56 @@
title: Update package policy
-allOf:
- - type: object
+type: object
+description: ''
+properties:
+ version:
+ type: string
+ enabled:
+ type: boolean
+ package:
+ type: object
properties:
+ name:
+ type: string
version:
type: string
- - $ref: ./new_package_policy.yaml
+ title:
+ type: string
+ required:
+ - name
+ - title
+ - version
+ namespace:
+ type: string
+ output_id:
+ type: string
+ inputs:
+ type: array
+ items:
+ type: object
+ properties:
+ type:
+ type: string
+ enabled:
+ type: boolean
+ processors:
+ type: array
+ items:
+ type: string
+ streams:
+ type: array
+ items: {}
+ config:
+ type: object
+ vars:
+ type: object
+ required:
+ - type
+ - enabled
+ - streams
+ policy_id:
+ type: string
+ name:
+ type: string
+ description:
+ type: string
+required:
\ No newline at end of file
diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/assets/assets.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/assets/assets.tsx
index 16a16205261c9..dc931f835b043 100644
--- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/assets/assets.tsx
+++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/assets/assets.tsx
@@ -11,6 +11,8 @@ import { FormattedMessage } from '@kbn/i18n-react';
import { EuiFlexGroup, EuiFlexItem, EuiTitle, EuiSpacer } from '@elastic/eui';
import { groupBy } from 'lodash';
+import type { ResolvedSimpleSavedObject } from 'src/core/public';
+
import { Loading, Error, ExtensionWrapper } from '../../../../../components';
import type { PackageInfo } from '../../../../../types';
@@ -27,6 +29,7 @@ import type { AssetSavedObject } from './types';
import { allowedAssetTypes } from './constants';
import { AssetsAccordion } from './assets_accordion';
+const allowedAssetTypesLookup = new Set(allowedAssetTypes);
interface AssetsPanelProps {
packageInfo: PackageInfo;
}
@@ -74,19 +77,32 @@ export const AssetsPage = ({ packageInfo }: AssetsPanelProps) => {
const objectsByType = await Promise.all(
Object.entries(groupBy(objectsToGet, 'type')).map(([type, objects]) =>
savedObjectsClient
- .bulkGet(objects)
+ .bulkResolve(objects)
// Ignore privilege errors
.catch((e: any) => {
if (e?.body?.statusCode === 403) {
- return { savedObjects: [] };
+ return { resolved_objects: [] };
} else {
throw e;
}
})
- .then(({ savedObjects }) => savedObjects as AssetSavedObject[])
+ .then(
+ ({
+ resolved_objects: resolvedObjects,
+ }: {
+ resolved_objects: ResolvedSimpleSavedObject[];
+ }) => {
+ return resolvedObjects
+ .map(({ saved_object: savedObject }) => savedObject)
+ .filter(
+ (savedObject) =>
+ savedObject?.error?.statusCode !== 404 &&
+ allowedAssetTypesLookup.has(savedObject.type)
+ ) as AssetSavedObject[];
+ }
+ )
)
);
-
setAssetsSavedObjects(objectsByType.flat());
} catch (e) {
setFetchError(e);
@@ -107,7 +123,6 @@ export const AssetsPage = ({ packageInfo }: AssetsPanelProps) => {
}
let content: JSX.Element | Array;
-
if (isLoading) {
content = ;
} else if (fetchError) {
@@ -122,7 +137,7 @@ export const AssetsPage = ({ packageInfo }: AssetsPanelProps) => {
error={fetchError}
/>
);
- } else if (assetSavedObjects === undefined) {
+ } else if (assetSavedObjects === undefined || assetSavedObjects.length === 0) {
if (customAssetsExtension) {
// If a UI extension for custom asset entries is defined, render the custom component here depisite
// there being no saved objects found
diff --git a/x-pack/plugins/fleet/server/mocks/index.ts b/x-pack/plugins/fleet/server/mocks/index.ts
index 90c9181b5007a..acdc0ba5e3fdd 100644
--- a/x-pack/plugins/fleet/server/mocks/index.ts
+++ b/x-pack/plugins/fleet/server/mocks/index.ts
@@ -99,6 +99,7 @@ export const createPackagePolicyServiceMock = (): jest.Mocked {
try {
+ // Fleet remains `available` during setup as to excessively delay Kibana's boot process.
+ // This should be reevaluated as Fleet's setup process is optimized and stabilized.
this.fleetStatus$.next({
- level: ServiceStatusLevels.degraded,
+ level: ServiceStatusLevels.available,
summary: 'Fleet is setting up',
});
diff --git a/x-pack/plugins/fleet/server/routes/package_policy/handlers.test.ts b/x-pack/plugins/fleet/server/routes/package_policy/handlers.test.ts
index 5441af0af686a..2408f8226f5d6 100644
--- a/x-pack/plugins/fleet/server/routes/package_policy/handlers.test.ts
+++ b/x-pack/plugins/fleet/server/routes/package_policy/handlers.test.ts
@@ -17,7 +17,12 @@ import type {
PostPackagePolicyCreateCallback,
PutPackagePolicyUpdateCallback,
} from '../..';
-import type { CreatePackagePolicyRequestSchema } from '../../types/rest_spec';
+import type {
+ CreatePackagePolicyRequestSchema,
+ UpdatePackagePolicyRequestSchema,
+} from '../../types/rest_spec';
+
+import type { PackagePolicy } from '../../types';
import { registerRoutes } from './index';
@@ -72,6 +77,9 @@ jest.mock(
),
upgrade: jest.fn(),
getUpgradeDryRunDiff: jest.fn(),
+ enrichPolicyWithDefaultsFromPackage: jest
+ .fn()
+ .mockImplementation((soClient, newPolicy) => newPolicy),
},
};
}
@@ -91,7 +99,7 @@ describe('When calling package policy', () => {
let context: ReturnType;
let response: ReturnType;
- beforeAll(() => {
+ beforeEach(() => {
routerMock = httpServiceMock.createRouter();
registerRoutes(routerMock);
});
@@ -132,7 +140,7 @@ describe('When calling package policy', () => {
};
// Set the routeConfig and routeHandler to the Create API
- beforeAll(() => {
+ beforeEach(() => {
[routeConfig, routeHandler] = routerMock.post.mock.calls.find(([{ path }]) =>
path.startsWith(PACKAGE_POLICY_API_ROUTES.CREATE_PATTERN)
)!;
@@ -259,4 +267,148 @@ describe('When calling package policy', () => {
});
});
});
+
+ describe('update api handler', () => {
+ const getUpdateKibanaRequest = (
+ newData?: typeof UpdatePackagePolicyRequestSchema.body
+ ): KibanaRequest<
+ typeof UpdatePackagePolicyRequestSchema.params,
+ undefined,
+ typeof UpdatePackagePolicyRequestSchema.body
+ > => {
+ return httpServerMock.createKibanaRequest<
+ typeof UpdatePackagePolicyRequestSchema.params,
+ undefined,
+ typeof UpdatePackagePolicyRequestSchema.body
+ >({
+ path: routeConfig.path,
+ method: 'put',
+ params: { packagePolicyId: '1' },
+ body: newData || {},
+ });
+ };
+
+ const existingPolicy = {
+ name: 'endpoint-1',
+ description: 'desc',
+ policy_id: '2',
+ enabled: true,
+ output_id: '3',
+ inputs: [
+ {
+ type: 'logfile',
+ enabled: true,
+ streams: [
+ {
+ enabled: true,
+ data_stream: {
+ type: 'logs',
+ dataset: 'apache.access',
+ },
+ id: '1',
+ },
+ ],
+ },
+ ],
+ namespace: 'default',
+ package: { name: 'endpoint', title: 'Elastic Endpoint', version: '0.5.0' },
+ vars: {
+ paths: {
+ value: ['/var/log/apache2/access.log*'],
+ type: 'text',
+ },
+ },
+ };
+
+ beforeEach(() => {
+ [routeConfig, routeHandler] = routerMock.put.mock.calls.find(([{ path }]) =>
+ path.startsWith(PACKAGE_POLICY_API_ROUTES.UPDATE_PATTERN)
+ )!;
+ });
+
+ beforeEach(() => {
+ packagePolicyServiceMock.update.mockImplementation((soClient, esClient, policyId, newData) =>
+ Promise.resolve(newData as PackagePolicy)
+ );
+ packagePolicyServiceMock.get.mockResolvedValue({
+ id: '1',
+ revision: 1,
+ created_at: '',
+ created_by: '',
+ updated_at: '',
+ updated_by: '',
+ ...existingPolicy,
+ inputs: [
+ {
+ ...existingPolicy.inputs[0],
+ compiled_input: '',
+ streams: [
+ {
+ ...existingPolicy.inputs[0].streams[0],
+ compiled_stream: {},
+ },
+ ],
+ },
+ ],
+ });
+ });
+
+ it('should use existing package policy props if not provided by request', async () => {
+ const request = getUpdateKibanaRequest();
+ await routeHandler(context, request, response);
+ expect(response.ok).toHaveBeenCalledWith({
+ body: { item: existingPolicy },
+ });
+ });
+
+ it('should use request package policy props if provided by request', async () => {
+ const newData = {
+ name: 'endpoint-2',
+ description: '',
+ policy_id: '3',
+ enabled: false,
+ output_id: '',
+ inputs: [
+ {
+ type: 'metrics',
+ enabled: true,
+ streams: [
+ {
+ enabled: true,
+ data_stream: {
+ type: 'metrics',
+ dataset: 'apache.access',
+ },
+ id: '1',
+ },
+ ],
+ },
+ ],
+ namespace: 'namespace',
+ package: { name: 'endpoint', title: 'Elastic Endpoint', version: '0.6.0' },
+ vars: {
+ paths: {
+ value: ['/my/access.log*'],
+ type: 'text',
+ },
+ },
+ };
+ const request = getUpdateKibanaRequest(newData as any);
+ await routeHandler(context, request, response);
+ expect(response.ok).toHaveBeenCalledWith({
+ body: { item: newData },
+ });
+ });
+
+ it('should override props provided by request only', async () => {
+ const newData = {
+ namespace: 'namespace',
+ };
+ const request = getUpdateKibanaRequest(newData as any);
+ await routeHandler(context, request, response);
+ expect(response.ok).toHaveBeenCalledWith({
+ body: { item: { ...existingPolicy, namespace: 'namespace' } },
+ });
+ });
+ });
});
diff --git a/x-pack/plugins/fleet/server/routes/package_policy/handlers.ts b/x-pack/plugins/fleet/server/routes/package_policy/handlers.ts
index 402a60e512145..33553a8699180 100644
--- a/x-pack/plugins/fleet/server/routes/package_policy/handlers.ts
+++ b/x-pack/plugins/fleet/server/routes/package_policy/handlers.ts
@@ -23,6 +23,7 @@ import type {
import type {
CreatePackagePolicyResponse,
DeletePackagePoliciesResponse,
+ NewPackagePolicy,
UpgradePackagePolicyDryRunResponse,
UpgradePackagePolicyResponse,
} from '../../../common';
@@ -89,9 +90,14 @@ export const createPackagePolicyHandler: RequestHandler<
const user = appContextService.getSecurity()?.authc.getCurrentUser(request) || undefined;
const { force, ...newPolicy } = request.body;
try {
+ const newPackagePolicy = await packagePolicyService.enrichPolicyWithDefaultsFromPackage(
+ soClient,
+ newPolicy as NewPackagePolicy
+ );
+
const newData = await packagePolicyService.runExternalCallbacks(
'packagePolicyCreate',
- newPolicy,
+ newPackagePolicy,
context,
request
);
@@ -130,9 +136,33 @@ export const updatePackagePolicyHandler: RequestHandler<
throw Boom.notFound('Package policy not found');
}
- let newData = { ...request.body };
- const pkg = newData.package || packagePolicy.package;
- const inputs = newData.inputs || packagePolicy.inputs;
+ const body = { ...request.body };
+ // removed fields not recognized by schema
+ const packagePolicyInputs = packagePolicy.inputs.map((input) => {
+ const newInput = {
+ ...input,
+ streams: input.streams.map((stream) => {
+ const newStream = { ...stream };
+ delete newStream.compiled_stream;
+ return newStream;
+ }),
+ };
+ delete newInput.compiled_input;
+ return newInput;
+ });
+ // listing down accepted properties, because loaded packagePolicy contains some that are not accepted in update
+ let newData = {
+ ...body,
+ name: body.name ?? packagePolicy.name,
+ description: body.description ?? packagePolicy.description,
+ namespace: body.namespace ?? packagePolicy.namespace,
+ policy_id: body.policy_id ?? packagePolicy.policy_id,
+ enabled: body.enabled ?? packagePolicy.enabled,
+ output_id: body.output_id ?? packagePolicy.output_id,
+ package: body.package ?? packagePolicy.package,
+ inputs: body.inputs ?? packagePolicyInputs,
+ vars: body.vars ?? packagePolicy.vars,
+ } as NewPackagePolicy;
try {
newData = await packagePolicyService.runExternalCallbacks(
@@ -146,7 +176,7 @@ export const updatePackagePolicyHandler: RequestHandler<
soClient,
esClient,
request.params.packagePolicyId,
- { ...newData, package: pkg, inputs },
+ newData,
{ user },
packagePolicy.package?.version
);
diff --git a/x-pack/plugins/fleet/server/routes/security.ts b/x-pack/plugins/fleet/server/routes/security.ts
index 9853877dc2d61..e0a2a557391df 100644
--- a/x-pack/plugins/fleet/server/routes/security.ts
+++ b/x-pack/plugins/fleet/server/routes/security.ts
@@ -151,8 +151,8 @@ export async function getAuthzFromRequest(req: KibanaRequest): Promise;
+const formatImportErrorsForLog = (errors: SavedObjectsImportFailure[]) =>
+ JSON.stringify(
+ errors.map(({ type, id, error }) => ({ type, id, error })) // discard other fields
+ );
+const validKibanaAssetTypes = new Set(Object.values(KibanaAssetType));
type SavedObjectToBe = Required> & {
type: KibanaSavedObjectType;
};
@@ -42,23 +54,8 @@ const KibanaSavedObjectTypeMapping: Record Promise>>
-> = {
- [KibanaAssetType.dashboard]: installKibanaSavedObjects,
- [KibanaAssetType.indexPattern]: installKibanaIndexPatterns,
- [KibanaAssetType.map]: installKibanaSavedObjects,
- [KibanaAssetType.search]: installKibanaSavedObjects,
- [KibanaAssetType.visualization]: installKibanaSavedObjects,
- [KibanaAssetType.lens]: installKibanaSavedObjects,
- [KibanaAssetType.mlModule]: installKibanaSavedObjects,
- [KibanaAssetType.securityRule]: installKibanaSavedObjects,
- [KibanaAssetType.tag]: installKibanaSavedObjects,
+const AssetFilters: Record ArchiveAsset[]> = {
+ [KibanaAssetType.indexPattern]: removeReservedIndexPatterns,
};
export async function getKibanaAsset(key: string): Promise {
@@ -79,29 +76,46 @@ export function createSavedObjectKibanaAsset(asset: ArchiveAsset): SavedObjectTo
};
}
-// TODO: make it an exhaustive list
-// e.g. switch statement with cases for each enum key returning `never` for default case
export async function installKibanaAssets(options: {
- savedObjectsClient: SavedObjectsClientContract;
+ savedObjectsImporter: SavedObjectsImporterContract;
+ logger: Logger;
pkgName: string;
kibanaAssets: Record;
-}): Promise {
- const { savedObjectsClient, kibanaAssets } = options;
+}): Promise {
+ const { kibanaAssets, savedObjectsImporter, logger } = options;
+ const assetsToInstall = Object.entries(kibanaAssets).flatMap(([assetType, assets]) => {
+ if (!validKibanaAssetTypes.has(assetType as KibanaAssetType)) {
+ return [];
+ }
- // install the assets
- const kibanaAssetTypes = Object.values(KibanaAssetType);
- const installedAssets = await Promise.all(
- kibanaAssetTypes.map((assetType) => {
- if (kibanaAssets[assetType]) {
- return AssetInstallers[assetType]({
- savedObjectsClient,
- kibanaAssets: kibanaAssets[assetType],
- });
- }
+ if (!assets.length) {
return [];
- })
- );
- return installedAssets.flat();
+ }
+
+ const assetFilter = AssetFilters[assetType];
+ if (assetFilter) {
+ return assetFilter(assets);
+ }
+
+ return assets;
+ });
+
+ if (!assetsToInstall.length) {
+ return [];
+ }
+
+ // As we use `import` to create our saved objects, we have to install
+ // their references (the index patterns) at the same time
+ // to prevent a reference error
+ const indexPatternSavedObjects = getIndexPatternSavedObjects() as ArchiveAsset[];
+
+ const installedAssets = await installKibanaSavedObjects({
+ logger,
+ savedObjectsImporter,
+ kibanaAssets: [...indexPatternSavedObjects, ...assetsToInstall],
+ });
+
+ return installedAssets;
}
export const deleteKibanaInstalledRefs = async (
savedObjectsClient: SavedObjectsClientContract,
@@ -153,39 +167,95 @@ export async function getKibanaAssets(
}
async function installKibanaSavedObjects({
- savedObjectsClient,
+ savedObjectsImporter,
kibanaAssets,
+ logger,
}: {
- savedObjectsClient: SavedObjectsClientContract;
kibanaAssets: ArchiveAsset[];
+ savedObjectsImporter: SavedObjectsImporterContract;
+ logger: Logger;
}) {
const toBeSavedObjects = await Promise.all(
kibanaAssets.map((asset) => createSavedObjectKibanaAsset(asset))
);
+ let allSuccessResults = [];
+
if (toBeSavedObjects.length === 0) {
return [];
} else {
- const createResults = await savedObjectsClient.bulkCreate(toBeSavedObjects, {
- overwrite: true,
- });
- return createResults.saved_objects;
+ const { successResults: importSuccessResults = [], errors: importErrors = [] } =
+ await savedObjectsImporter.import({
+ overwrite: true,
+ readStream: createListStream(toBeSavedObjects),
+ createNewCopies: false,
+ });
+
+ allSuccessResults = importSuccessResults;
+ const [referenceErrors, otherErrors] = partition(
+ importErrors,
+ (e) => e?.error?.type === 'missing_references'
+ );
+
+ if (otherErrors?.length) {
+ throw new Error(
+ `Encountered ${
+ otherErrors.length
+ } errors creating saved objects: ${formatImportErrorsForLog(otherErrors)}`
+ );
+ }
+ /*
+ A reference error here means that a saved object reference in the references
+ array cannot be found. This is an error in the package its-self but not a fatal
+ one. For example a dashboard may still refer to the legacy `metricbeat-*` index
+ pattern. We ignore reference errors here so that legacy version of a package
+ can still be installed, but if a warning is logged it should be reported to
+ the integrations team. */
+ if (referenceErrors.length) {
+ logger.debug(
+ `Resolving ${
+ referenceErrors.length
+ } reference errors creating saved objects: ${formatImportErrorsForLog(referenceErrors)}`
+ );
+
+ const idsToResolve = new Set(referenceErrors.map(({ id }) => id));
+
+ const resolveSavedObjects = toBeSavedObjects.filter(({ id }) => idsToResolve.has(id));
+ const retries = referenceErrors.map(({ id, type }) => ({
+ id,
+ type,
+ ignoreMissingReferences: true,
+ replaceReferences: [],
+ overwrite: true,
+ }));
+
+ const { successResults: resolveSuccessResults = [], errors: resolveErrors = [] } =
+ await savedObjectsImporter.resolveImportErrors({
+ readStream: createListStream(resolveSavedObjects),
+ createNewCopies: false,
+ retries,
+ });
+
+ if (resolveErrors?.length) {
+ throw new Error(
+ `Encountered ${
+ resolveErrors.length
+ } errors resolving reference errors: ${formatImportErrorsForLog(resolveErrors)}`
+ );
+ }
+
+ allSuccessResults = [...allSuccessResults, ...resolveSuccessResults];
+ }
+
+ return allSuccessResults;
}
}
-async function installKibanaIndexPatterns({
- savedObjectsClient,
- kibanaAssets,
-}: {
- savedObjectsClient: SavedObjectsClientContract;
- kibanaAssets: ArchiveAsset[];
-}) {
- // Filter out any reserved index patterns
+// Filter out any reserved index patterns
+function removeReservedIndexPatterns(kibanaAssets: ArchiveAsset[]) {
const reservedPatterns = indexPatternTypes.map((pattern) => `${pattern}-*`);
- const nonReservedPatterns = kibanaAssets.filter((asset) => !reservedPatterns.includes(asset.id));
-
- return installKibanaSavedObjects({ savedObjectsClient, kibanaAssets: nonReservedPatterns });
+ return kibanaAssets.filter((asset) => !reservedPatterns.includes(asset.id));
}
export function toAssetReference({ id, type }: SavedObject) {
diff --git a/x-pack/plugins/fleet/server/services/epm/kibana/index_pattern/__snapshots__/install.test.ts.snap b/x-pack/plugins/fleet/server/services/epm/kibana/index_pattern/__snapshots__/install.test.ts.snap
deleted file mode 100644
index da870290329a8..0000000000000
--- a/x-pack/plugins/fleet/server/services/epm/kibana/index_pattern/__snapshots__/install.test.ts.snap
+++ /dev/null
@@ -1,935 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`creating index patterns from yaml fields createFieldFormatMap creates correct map based on inputs all variations and all the params get passed through: createFieldFormatMap 1`] = `
-{
- "fieldPattern": {
- "params": {
- "pattern": "patternVal"
- }
- },
- "fieldFormat": {
- "id": "formatVal"
- },
- "fieldFormatWithParam": {
- "id": "formatVal",
- "params": {
- "outputPrecision": 2
- }
- },
- "fieldFormatAndPattern": {
- "id": "formatVal",
- "params": {
- "pattern": "patternVal"
- }
- },
- "fieldFormatAndAllParams": {
- "id": "formatVal",
- "params": {
- "pattern": "pattenVal",
- "inputFormat": "inputFormatVal",
- "outputFormat": "outputFormalVal",
- "outputPrecision": 3,
- "labelTemplate": "labelTemplateVal",
- "urlTemplate": "urlTemplateVal"
- }
- }
-}
-`;
-
-exports[`creating index patterns from yaml fields createIndexPattern function creates Kibana index pattern: createIndexPattern 1`] = `
-{
- "title": "logs-*",
- "timeFieldName": "@timestamp",
- "fields": "[{\\"name\\":\\"coredns.id\\",\\"type\\":\\"string\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"searchable\\":true,\\"aggregatable\\":true,\\"readFromDocValues\\":true},{\\"name\\":\\"coredns.allParams\\",\\"type\\":\\"number\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"searchable\\":true,\\"aggregatable\\":true,\\"readFromDocValues\\":true},{\\"name\\":\\"coredns.query.length\\",\\"type\\":\\"number\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"searchable\\":true,\\"aggregatable\\":true,\\"readFromDocValues\\":true},{\\"name\\":\\"coredns.query.size\\",\\"type\\":\\"number\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"searchable\\":true,\\"aggregatable\\":true,\\"readFromDocValues\\":true},{\\"name\\":\\"coredns.query.class\\",\\"type\\":\\"string\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"searchable\\":true,\\"aggregatable\\":true,\\"readFromDocValues\\":true},{\\"name\\":\\"coredns.query.name\\",\\"type\\":\\"string\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"searchable\\":true,\\"aggregatable\\":true,\\"readFromDocValues\\":true},{\\"name\\":\\"coredns.query.type\\",\\"type\\":\\"string\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"searchable\\":true,\\"aggregatable\\":true,\\"readFromDocValues\\":true},{\\"name\\":\\"coredns.response.code\\",\\"type\\":\\"string\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"searchable\\":true,\\"aggregatable\\":true,\\"readFromDocValues\\":true},{\\"name\\":\\"coredns.response.flags\\",\\"type\\":\\"string\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"searchable\\":true,\\"aggregatable\\":true,\\"readFromDocValues\\":true},{\\"name\\":\\"coredns.response.size\\",\\"type\\":\\"number\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"searchable\\":true,\\"aggregatable\\":true,\\"readFromDocValues\\":true},{\\"name\\":\\"coredns.dnssec_ok\\",\\"type\\":\\"boolean\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"searchable\\":true,\\"aggregatable\\":true,\\"readFromDocValues\\":true},{\\"name\\":\\"@timestamp\\",\\"type\\":\\"date\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"searchable\\":true,\\"aggregatable\\":true,\\"readFromDocValues\\":true},{\\"name\\":\\"labels\\",\\"type\\":\\"string\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"searchable\\":true,\\"aggregatable\\":true,\\"readFromDocValues\\":true},{\\"name\\":\\"message\\",\\"type\\":\\"string\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"searchable\\":true,\\"aggregatable\\":false,\\"readFromDocValues\\":true},{\\"name\\":\\"tags\\",\\"type\\":\\"string\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"searchable\\":true,\\"aggregatable\\":true,\\"readFromDocValues\\":true},{\\"name\\":\\"agent.ephemeral_id\\",\\"type\\":\\"string\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"searchable\\":true,\\"aggregatable\\":true,\\"readFromDocValues\\":true},{\\"name\\":\\"agent.id\\",\\"type\\":\\"string\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"searchable\\":true,\\"aggregatable\\":true,\\"readFromDocValues\\":true},{\\"name\\":\\"agent.name\\",\\"type\\":\\"string\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"searchable\\":true,\\"aggregatable\\":true,\\"readFromDocValues\\":true},{\\"name\\":\\"agent.type\\",\\"type\\":\\"string\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"searchable\\":true,\\"aggregatable\\":true,\\"readFromDocValues\\":true},{\\"name\\":\\"agent.version\\",\\"type\\":\\"string\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"searchable\\":true,\\"aggregatable\\":true,\\"readFromDocValues\\":true},{\\"name\\":\\"as.number\\",\\"type\\":\\"number\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"searchable\\":true,\\"aggregatable\\":true,\\"readFromDocValues\\":true},{\\"name\\":\\"as.organization.name\\",\\"type\\":\\"string\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"searchable\\":true,\\"aggregatable\\":true,\\"readFromDocValues\\":true},{\\"name\\":\\"nginx.access.remote_ip_list\\",\\"type\\":\\"string\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"searchable\\":true,\\"aggregatable\\":true,\\"readFromDocValues\\":true},{\\"name\\":\\"nginx.access.body_sent.bytes\\",\\"type\\":\\"string\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"searchable\\":true,\\"aggregatable\\":true,\\"readFromDocValues\\":true},{\\"name\\":\\"nginx.access.user_name\\",\\"type\\":\\"string\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"searchable\\":true,\\"aggregatable\\":true,\\"readFromDocValues\\":true},{\\"name\\":\\"nginx.access.method\\",\\"type\\":\\"string\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"searchable\\":true,\\"aggregatable\\":true,\\"readFromDocValues\\":true},{\\"name\\":\\"nginx.access.url\\",\\"type\\":\\"string\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"searchable\\":true,\\"aggregatable\\":true,\\"readFromDocValues\\":true},{\\"name\\":\\"nginx.access.http_version\\",\\"type\\":\\"string\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"searchable\\":true,\\"aggregatable\\":true,\\"readFromDocValues\\":true},{\\"name\\":\\"nginx.access.response_code\\",\\"type\\":\\"string\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"searchable\\":true,\\"aggregatable\\":true,\\"readFromDocValues\\":true},{\\"name\\":\\"nginx.access.referrer\\",\\"type\\":\\"string\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"searchable\\":true,\\"aggregatable\\":true,\\"readFromDocValues\\":true},{\\"name\\":\\"nginx.access.agent\\",\\"type\\":\\"string\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"searchable\\":true,\\"aggregatable\\":true,\\"readFromDocValues\\":true},{\\"name\\":\\"nginx.access.user_agent.device\\",\\"type\\":\\"string\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"searchable\\":true,\\"aggregatable\\":true,\\"readFromDocValues\\":true},{\\"name\\":\\"nginx.access.user_agent.name\\",\\"type\\":\\"string\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"searchable\\":true,\\"aggregatable\\":true,\\"readFromDocValues\\":true},{\\"name\\":\\"nginx.access.user_agent.os\\",\\"type\\":\\"string\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"searchable\\":true,\\"aggregatable\\":true,\\"readFromDocValues\\":true},{\\"name\\":\\"nginx.access.user_agent.os_name\\",\\"type\\":\\"string\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"searchable\\":true,\\"aggregatable\\":true,\\"readFromDocValues\\":true},{\\"name\\":\\"nginx.access.user_agent.original\\",\\"type\\":\\"string\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"searchable\\":true,\\"aggregatable\\":true,\\"readFromDocValues\\":true},{\\"name\\":\\"nginx.access.geoip.continent_name\\",\\"type\\":\\"string\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"searchable\\":true,\\"aggregatable\\":false,\\"readFromDocValues\\":true},{\\"name\\":\\"nginx.access.geoip.country_iso_code\\",\\"type\\":\\"string\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"searchable\\":true,\\"aggregatable\\":true,\\"readFromDocValues\\":true},{\\"name\\":\\"nginx.access.geoip.location\\",\\"type\\":\\"string\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"searchable\\":true,\\"aggregatable\\":true,\\"readFromDocValues\\":true},{\\"name\\":\\"nginx.access.geoip.region_name\\",\\"type\\":\\"string\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"searchable\\":true,\\"aggregatable\\":true,\\"readFromDocValues\\":true},{\\"name\\":\\"nginx.access.geoip.city_name\\",\\"type\\":\\"string\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"searchable\\":true,\\"aggregatable\\":true,\\"readFromDocValues\\":true},{\\"name\\":\\"nginx.access.geoip.region_iso_code\\",\\"type\\":\\"string\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"searchable\\":true,\\"aggregatable\\":true,\\"readFromDocValues\\":true},{\\"name\\":\\"source.geo.continent_name\\",\\"type\\":\\"string\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"searchable\\":true,\\"aggregatable\\":false,\\"readFromDocValues\\":true},{\\"name\\":\\"country\\",\\"type\\":\\"string\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"searchable\\":true,\\"aggregatable\\":true,\\"readFromDocValues\\":true},{\\"name\\":\\"country.keyword\\",\\"type\\":\\"string\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"searchable\\":true,\\"aggregatable\\":true,\\"readFromDocValues\\":true},{\\"name\\":\\"country.text\\",\\"type\\":\\"string\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"searchable\\":true,\\"aggregatable\\":false,\\"readFromDocValues\\":true}]",
- "fieldFormatMap": "{\\"coredns.allParams\\":{\\"id\\":\\"bytes\\",\\"params\\":{\\"pattern\\":\\"patternValQueryWeight\\",\\"inputFormat\\":\\"inputFormatVal,\\",\\"outputFormat\\":\\"outputFormalVal,\\",\\"outputPrecision\\":\\"3,\\",\\"labelTemplate\\":\\"labelTemplateVal,\\",\\"urlTemplate\\":\\"urlTemplateVal,\\"}},\\"coredns.query.length\\":{\\"params\\":{\\"pattern\\":\\"patternValQueryLength\\"}},\\"coredns.query.size\\":{\\"id\\":\\"bytes\\",\\"params\\":{\\"pattern\\":\\"patternValQuerySize\\"}},\\"coredns.response.size\\":{\\"id\\":\\"bytes\\"}}",
- "allowNoIndex": true
-}
-`;
-
-exports[`creating index patterns from yaml fields createIndexPatternFields function creates Kibana index pattern fields and fieldFormatMap: createIndexPatternFields 1`] = `
-{
- "indexPatternFields": [
- {
- "name": "coredns.id",
- "type": "string",
- "count": 0,
- "scripted": false,
- "indexed": true,
- "searchable": true,
- "aggregatable": true,
- "readFromDocValues": true
- },
- {
- "name": "coredns.allParams",
- "type": "number",
- "count": 0,
- "scripted": false,
- "indexed": true,
- "searchable": true,
- "aggregatable": true,
- "readFromDocValues": true
- },
- {
- "name": "coredns.query.length",
- "type": "number",
- "count": 0,
- "scripted": false,
- "indexed": true,
- "searchable": true,
- "aggregatable": true,
- "readFromDocValues": true
- },
- {
- "name": "coredns.query.size",
- "type": "number",
- "count": 0,
- "scripted": false,
- "indexed": true,
- "searchable": true,
- "aggregatable": true,
- "readFromDocValues": true
- },
- {
- "name": "coredns.query.class",
- "type": "string",
- "count": 0,
- "scripted": false,
- "indexed": true,
- "searchable": true,
- "aggregatable": true,
- "readFromDocValues": true
- },
- {
- "name": "coredns.query.name",
- "type": "string",
- "count": 0,
- "scripted": false,
- "indexed": true,
- "searchable": true,
- "aggregatable": true,
- "readFromDocValues": true
- },
- {
- "name": "coredns.query.type",
- "type": "string",
- "count": 0,
- "scripted": false,
- "indexed": true,
- "searchable": true,
- "aggregatable": true,
- "readFromDocValues": true
- },
- {
- "name": "coredns.response.code",
- "type": "string",
- "count": 0,
- "scripted": false,
- "indexed": true,
- "searchable": true,
- "aggregatable": true,
- "readFromDocValues": true
- },
- {
- "name": "coredns.response.flags",
- "type": "string",
- "count": 0,
- "scripted": false,
- "indexed": true,
- "searchable": true,
- "aggregatable": true,
- "readFromDocValues": true
- },
- {
- "name": "coredns.response.size",
- "type": "number",
- "count": 0,
- "scripted": false,
- "indexed": true,
- "searchable": true,
- "aggregatable": true,
- "readFromDocValues": true
- },
- {
- "name": "coredns.dnssec_ok",
- "type": "boolean",
- "count": 0,
- "scripted": false,
- "indexed": true,
- "searchable": true,
- "aggregatable": true,
- "readFromDocValues": true
- },
- {
- "name": "@timestamp",
- "type": "date",
- "count": 0,
- "scripted": false,
- "indexed": true,
- "searchable": true,
- "aggregatable": true,
- "readFromDocValues": true
- },
- {
- "name": "labels",
- "type": "string",
- "count": 0,
- "scripted": false,
- "indexed": true,
- "searchable": true,
- "aggregatable": true,
- "readFromDocValues": true
- },
- {
- "name": "message",
- "type": "string",
- "count": 0,
- "scripted": false,
- "indexed": true,
- "searchable": true,
- "aggregatable": false,
- "readFromDocValues": true
- },
- {
- "name": "tags",
- "type": "string",
- "count": 0,
- "scripted": false,
- "indexed": true,
- "searchable": true,
- "aggregatable": true,
- "readFromDocValues": true
- },
- {
- "name": "agent.ephemeral_id",
- "type": "string",
- "count": 0,
- "scripted": false,
- "indexed": true,
- "searchable": true,
- "aggregatable": true,
- "readFromDocValues": true
- },
- {
- "name": "agent.id",
- "type": "string",
- "count": 0,
- "scripted": false,
- "indexed": true,
- "searchable": true,
- "aggregatable": true,
- "readFromDocValues": true
- },
- {
- "name": "agent.name",
- "type": "string",
- "count": 0,
- "scripted": false,
- "indexed": true,
- "searchable": true,
- "aggregatable": true,
- "readFromDocValues": true
- },
- {
- "name": "agent.type",
- "type": "string",
- "count": 0,
- "scripted": false,
- "indexed": true,
- "searchable": true,
- "aggregatable": true,
- "readFromDocValues": true
- },
- {
- "name": "agent.version",
- "type": "string",
- "count": 0,
- "scripted": false,
- "indexed": true,
- "searchable": true,
- "aggregatable": true,
- "readFromDocValues": true
- },
- {
- "name": "as.number",
- "type": "number",
- "count": 0,
- "scripted": false,
- "indexed": true,
- "searchable": true,
- "aggregatable": true,
- "readFromDocValues": true
- },
- {
- "name": "as.organization.name",
- "type": "string",
- "count": 0,
- "scripted": false,
- "indexed": true,
- "searchable": true,
- "aggregatable": true,
- "readFromDocValues": true
- },
- {
- "name": "nginx.access.remote_ip_list",
- "type": "string",
- "count": 0,
- "scripted": false,
- "indexed": true,
- "searchable": true,
- "aggregatable": true,
- "readFromDocValues": true
- },
- {
- "name": "nginx.access.body_sent.bytes",
- "type": "string",
- "count": 0,
- "scripted": false,
- "indexed": true,
- "searchable": true,
- "aggregatable": true,
- "readFromDocValues": true
- },
- {
- "name": "nginx.access.user_name",
- "type": "string",
- "count": 0,
- "scripted": false,
- "indexed": true,
- "searchable": true,
- "aggregatable": true,
- "readFromDocValues": true
- },
- {
- "name": "nginx.access.method",
- "type": "string",
- "count": 0,
- "scripted": false,
- "indexed": true,
- "searchable": true,
- "aggregatable": true,
- "readFromDocValues": true
- },
- {
- "name": "nginx.access.url",
- "type": "string",
- "count": 0,
- "scripted": false,
- "indexed": true,
- "searchable": true,
- "aggregatable": true,
- "readFromDocValues": true
- },
- {
- "name": "nginx.access.http_version",
- "type": "string",
- "count": 0,
- "scripted": false,
- "indexed": true,
- "searchable": true,
- "aggregatable": true,
- "readFromDocValues": true
- },
- {
- "name": "nginx.access.response_code",
- "type": "string",
- "count": 0,
- "scripted": false,
- "indexed": true,
- "searchable": true,
- "aggregatable": true,
- "readFromDocValues": true
- },
- {
- "name": "nginx.access.referrer",
- "type": "string",
- "count": 0,
- "scripted": false,
- "indexed": true,
- "searchable": true,
- "aggregatable": true,
- "readFromDocValues": true
- },
- {
- "name": "nginx.access.agent",
- "type": "string",
- "count": 0,
- "scripted": false,
- "indexed": true,
- "searchable": true,
- "aggregatable": true,
- "readFromDocValues": true
- },
- {
- "name": "nginx.access.user_agent.device",
- "type": "string",
- "count": 0,
- "scripted": false,
- "indexed": true,
- "searchable": true,
- "aggregatable": true,
- "readFromDocValues": true
- },
- {
- "name": "nginx.access.user_agent.name",
- "type": "string",
- "count": 0,
- "scripted": false,
- "indexed": true,
- "searchable": true,
- "aggregatable": true,
- "readFromDocValues": true
- },
- {
- "name": "nginx.access.user_agent.os",
- "type": "string",
- "count": 0,
- "scripted": false,
- "indexed": true,
- "searchable": true,
- "aggregatable": true,
- "readFromDocValues": true
- },
- {
- "name": "nginx.access.user_agent.os_name",
- "type": "string",
- "count": 0,
- "scripted": false,
- "indexed": true,
- "searchable": true,
- "aggregatable": true,
- "readFromDocValues": true
- },
- {
- "name": "nginx.access.user_agent.original",
- "type": "string",
- "count": 0,
- "scripted": false,
- "indexed": true,
- "searchable": true,
- "aggregatable": true,
- "readFromDocValues": true
- },
- {
- "name": "nginx.access.geoip.continent_name",
- "type": "string",
- "count": 0,
- "scripted": false,
- "indexed": true,
- "searchable": true,
- "aggregatable": false,
- "readFromDocValues": true
- },
- {
- "name": "nginx.access.geoip.country_iso_code",
- "type": "string",
- "count": 0,
- "scripted": false,
- "indexed": true,
- "searchable": true,
- "aggregatable": true,
- "readFromDocValues": true
- },
- {
- "name": "nginx.access.geoip.location",
- "type": "string",
- "count": 0,
- "scripted": false,
- "indexed": true,
- "searchable": true,
- "aggregatable": true,
- "readFromDocValues": true
- },
- {
- "name": "nginx.access.geoip.region_name",
- "type": "string",
- "count": 0,
- "scripted": false,
- "indexed": true,
- "searchable": true,
- "aggregatable": true,
- "readFromDocValues": true
- },
- {
- "name": "nginx.access.geoip.city_name",
- "type": "string",
- "count": 0,
- "scripted": false,
- "indexed": true,
- "searchable": true,
- "aggregatable": true,
- "readFromDocValues": true
- },
- {
- "name": "nginx.access.geoip.region_iso_code",
- "type": "string",
- "count": 0,
- "scripted": false,
- "indexed": true,
- "searchable": true,
- "aggregatable": true,
- "readFromDocValues": true
- },
- {
- "name": "source.geo.continent_name",
- "type": "string",
- "count": 0,
- "scripted": false,
- "indexed": true,
- "searchable": true,
- "aggregatable": false,
- "readFromDocValues": true
- },
- {
- "name": "country",
- "type": "string",
- "count": 0,
- "scripted": false,
- "indexed": true,
- "searchable": true,
- "aggregatable": true,
- "readFromDocValues": true
- },
- {
- "name": "country.keyword",
- "type": "string",
- "count": 0,
- "scripted": false,
- "indexed": true,
- "searchable": true,
- "aggregatable": true,
- "readFromDocValues": true
- },
- {
- "name": "country.text",
- "type": "string",
- "count": 0,
- "scripted": false,
- "indexed": true,
- "searchable": true,
- "aggregatable": false,
- "readFromDocValues": true
- }
- ],
- "fieldFormatMap": {
- "coredns.allParams": {
- "id": "bytes",
- "params": {
- "pattern": "patternValQueryWeight",
- "inputFormat": "inputFormatVal,",
- "outputFormat": "outputFormalVal,",
- "outputPrecision": "3,",
- "labelTemplate": "labelTemplateVal,",
- "urlTemplate": "urlTemplateVal,"
- }
- },
- "coredns.query.length": {
- "params": {
- "pattern": "patternValQueryLength"
- }
- },
- "coredns.query.size": {
- "id": "bytes",
- "params": {
- "pattern": "patternValQuerySize"
- }
- },
- "coredns.response.size": {
- "id": "bytes"
- }
- }
-}
-`;
-
-exports[`creating index patterns from yaml fields flattenFields function flattens recursively and handles copying alias fields flattenFields matches snapshot: flattenFields 1`] = `
-[
- {
- "name": "coredns.id",
- "type": "keyword",
- "description": "id of the DNS transaction\\n"
- },
- {
- "name": "coredns.allParams",
- "type": "integer",
- "format": "bytes",
- "pattern": "patternValQueryWeight",
- "input_format": "inputFormatVal,",
- "output_format": "outputFormalVal,",
- "output_precision": "3,",
- "label_template": "labelTemplateVal,",
- "url_template": "urlTemplateVal,",
- "openLinkInCurrentTab": "true,",
- "description": "weight of the DNS query\\n"
- },
- {
- "name": "coredns.query.length",
- "type": "integer",
- "pattern": "patternValQueryLength",
- "description": "length of the DNS query\\n"
- },
- {
- "name": "coredns.query.size",
- "type": "integer",
- "format": "bytes",
- "pattern": "patternValQuerySize",
- "description": "size of the DNS query\\n"
- },
- {
- "name": "coredns.query.class",
- "type": "keyword",
- "description": "DNS query class\\n"
- },
- {
- "name": "coredns.query.name",
- "type": "keyword",
- "description": "DNS query name\\n"
- },
- {
- "name": "coredns.query.type",
- "type": "keyword",
- "description": "DNS query type\\n"
- },
- {
- "name": "coredns.response.code",
- "type": "keyword",
- "description": "DNS response code\\n"
- },
- {
- "name": "coredns.response.flags",
- "type": "keyword",
- "description": "DNS response flags\\n"
- },
- {
- "name": "coredns.response.size",
- "type": "integer",
- "format": "bytes",
- "description": "size of the DNS response\\n"
- },
- {
- "name": "coredns.dnssec_ok",
- "type": "boolean",
- "description": "dnssec flag\\n"
- },
- {
- "name": "@timestamp",
- "level": "core",
- "required": true,
- "type": "date",
- "description": "Date/time when the event originated. This is the date/time extracted from the event, typically representing when the event was generated by the source. If the event source has no original timestamp, this value is typically populated by the first time the event was received by the pipeline. Required field for all events.",
- "example": "2016-05-23T08:05:34.853Z"
- },
- {
- "name": "labels",
- "level": "core",
- "type": "object",
- "object_type": "keyword",
- "description": "Custom key/value pairs. Can be used to add meta information to events. Should not contain nested objects. All values are stored as keyword. Example: \`docker\` and \`k8s\` labels.",
- "example": {
- "application": "foo-bar",
- "env": "production"
- }
- },
- {
- "name": "message",
- "level": "core",
- "type": "text",
- "description": "For log events the message field contains the log message, optimized for viewing in a log viewer. For structured logs without an original message field, other fields can be concatenated to form a human-readable summary of the event. If multiple messages exist, they can be combined into one message.",
- "example": "Hello World"
- },
- {
- "name": "tags",
- "level": "core",
- "type": "keyword",
- "ignore_above": 1024,
- "description": "List of keywords used to tag each event.",
- "example": "[\\"production\\", \\"env2\\"]"
- },
- {
- "name": "agent.ephemeral_id",
- "level": "extended",
- "type": "keyword",
- "ignore_above": 1024,
- "description": "Ephemeral identifier of this agent (if one exists). This id normally changes across restarts, but \`agent.id\` does not.",
- "example": "8a4f500f"
- },
- {
- "name": "agent.id",
- "level": "core",
- "type": "keyword",
- "ignore_above": 1024,
- "description": "Unique identifier of this agent (if one exists). Example: For Beats this would be beat.id.",
- "example": "8a4f500d"
- },
- {
- "name": "agent.name",
- "level": "core",
- "type": "keyword",
- "ignore_above": 1024,
- "description": "Custom name of the agent. This is a name that can be given to an agent. This can be helpful if for example two Filebeat instances are running on the same host but a human readable separation is needed on which Filebeat instance data is coming from. If no name is given, the name is often left empty.",
- "example": "foo"
- },
- {
- "name": "agent.type",
- "level": "core",
- "type": "keyword",
- "ignore_above": 1024,
- "description": "Type of the agent. The agent type stays always the same and should be given by the agent used. In case of Filebeat the agent would always be Filebeat also if two Filebeat instances are run on the same machine.",
- "example": "filebeat"
- },
- {
- "name": "agent.version",
- "level": "core",
- "type": "keyword",
- "ignore_above": 1024,
- "description": "Version of the agent.",
- "example": "6.0.0-rc2"
- },
- {
- "name": "as.number",
- "level": "extended",
- "type": "long",
- "description": "Unique number allocated to the autonomous system. The autonomous system number (ASN) uniquely identifies each network on the Internet.",
- "example": 15169
- },
- {
- "name": "as.organization.name",
- "level": "extended",
- "type": "keyword",
- "ignore_above": 1024,
- "description": "Organization name.",
- "example": "Google LLC"
- },
- {
- "name": "@timestamp",
- "level": "core",
- "required": true,
- "type": "date",
- "description": "Date/time when the event originated. This is the date/time extracted from the event, typically representing when the event was generated by the source. If the event source has no original timestamp, this value is typically populated by the first time the event was received by the pipeline. Required field for all events.",
- "example": "2016-05-23T08:05:34.853Z"
- },
- {
- "name": "labels",
- "level": "core",
- "type": "object",
- "object_type": "keyword",
- "description": "Custom key/value pairs. Can be used to add meta information to events. Should not contain nested objects. All values are stored as keyword. Example: \`docker\` and \`k8s\` labels.",
- "example": {
- "application": "foo-bar",
- "env": "production"
- }
- },
- {
- "name": "message",
- "level": "core",
- "type": "text",
- "description": "For log events the message field contains the log message, optimized for viewing in a log viewer. For structured logs without an original message field, other fields can be concatenated to form a human-readable summary of the event. If multiple messages exist, they can be combined into one message.",
- "example": "Hello World"
- },
- {
- "name": "tags",
- "level": "core",
- "type": "keyword",
- "ignore_above": 1024,
- "description": "List of keywords used to tag each event.",
- "example": "[\\"production\\", \\"env2\\"]"
- },
- {
- "name": "agent.ephemeral_id",
- "level": "extended",
- "type": "keyword",
- "ignore_above": 1024,
- "description": "Ephemeral identifier of this agent (if one exists). This id normally changes across restarts, but \`agent.id\` does not.",
- "example": "8a4f500f"
- },
- {
- "name": "agent.id",
- "level": "core",
- "type": "keyword",
- "ignore_above": 1024,
- "description": "Unique identifier of this agent (if one exists). Example: For Beats this would be beat.id.",
- "example": "8a4f500d"
- },
- {
- "name": "agent.name",
- "level": "core",
- "type": "keyword",
- "ignore_above": 1024,
- "description": "Custom name of the agent. This is a name that can be given to an agent. This can be helpful if for example two Filebeat instances are running on the same host but a human readable separation is needed on which Filebeat instance data is coming from. If no name is given, the name is often left empty.",
- "example": "foo"
- },
- {
- "name": "agent.type",
- "level": "core",
- "type": "keyword",
- "ignore_above": 1024,
- "description": "Type of the agent. The agent type stays always the same and should be given by the agent used. In case of Filebeat the agent would always be Filebeat also if two Filebeat instances are run on the same machine.",
- "example": "filebeat"
- },
- {
- "name": "agent.version",
- "level": "core",
- "type": "keyword",
- "ignore_above": 1024,
- "description": "Version of the agent.",
- "example": "6.0.0-rc2"
- },
- {
- "name": "as.number",
- "level": "extended",
- "type": "long",
- "description": "Unique number allocated to the autonomous system. The autonomous system number (ASN) uniquely identifies each network on the Internet.",
- "example": 15169
- },
- {
- "name": "as.organization.name",
- "level": "extended",
- "type": "keyword",
- "ignore_above": 1024,
- "description": "Organization name.",
- "example": "Google LLC"
- },
- {
- "name": "nginx.access.remote_ip_list",
- "type": "array",
- "description": "An array of remote IP addresses. It is a list because it is common to include, besides the client IP address, IP addresses from headers like \`X-Forwarded-For\`. Real source IP is restored to \`source.ip\`.\\n"
- },
- {
- "name": "nginx.access.body_sent.bytes",
- "type": "alias",
- "path": "http.response.body.bytes",
- "migration": true
- },
- {
- "name": "nginx.access.user_name",
- "type": "alias",
- "path": "user.name",
- "migration": true
- },
- {
- "name": "nginx.access.method",
- "type": "alias",
- "path": "http.request.method",
- "migration": true
- },
- {
- "name": "nginx.access.url",
- "type": "alias",
- "path": "url.original",
- "migration": true
- },
- {
- "name": "nginx.access.http_version",
- "type": "alias",
- "path": "http.version",
- "migration": true
- },
- {
- "name": "nginx.access.response_code",
- "type": "alias",
- "path": "http.response.status_code",
- "migration": true
- },
- {
- "name": "nginx.access.referrer",
- "type": "alias",
- "path": "http.request.referrer",
- "migration": true
- },
- {
- "name": "nginx.access.agent",
- "type": "alias",
- "path": "user_agent.original",
- "migration": true
- },
- {
- "name": "nginx.access.user_agent.device",
- "type": "alias",
- "path": "user_agent.device.name",
- "migration": true
- },
- {
- "name": "nginx.access.user_agent.name",
- "type": "alias",
- "path": "user_agent.name",
- "migration": true
- },
- {
- "name": "nginx.access.user_agent.os",
- "type": "alias",
- "path": "user_agent.os.full_name",
- "migration": true
- },
- {
- "name": "nginx.access.user_agent.os_name",
- "type": "alias",
- "path": "user_agent.os.name",
- "migration": true
- },
- {
- "name": "nginx.access.user_agent.original",
- "type": "alias",
- "path": "user_agent.original",
- "migration": true
- },
- {
- "name": "nginx.access.geoip.continent_name",
- "type": "text",
- "path": "source.geo.continent_name"
- },
- {
- "name": "nginx.access.geoip.country_iso_code",
- "type": "alias",
- "path": "source.geo.country_iso_code",
- "migration": true
- },
- {
- "name": "nginx.access.geoip.location",
- "type": "alias",
- "path": "source.geo.location",
- "migration": true
- },
- {
- "name": "nginx.access.geoip.region_name",
- "type": "alias",
- "path": "source.geo.region_name",
- "migration": true
- },
- {
- "name": "nginx.access.geoip.city_name",
- "type": "alias",
- "path": "source.geo.city_name",
- "migration": true
- },
- {
- "name": "nginx.access.geoip.region_iso_code",
- "type": "alias",
- "path": "source.geo.region_iso_code",
- "migration": true
- },
- {
- "name": "source.geo.continent_name",
- "type": "text"
- },
- {
- "name": "country",
- "type": "",
- "multi_fields": [
- {
- "name": "keyword",
- "type": "keyword"
- },
- {
- "name": "text",
- "type": "text"
- }
- ]
- },
- {
- "name": "country.keyword",
- "type": "keyword"
- },
- {
- "name": "country.text",
- "type": "text"
- }
-]
-`;
diff --git a/x-pack/plugins/fleet/server/services/epm/kibana/index_pattern/install.test.ts b/x-pack/plugins/fleet/server/services/epm/kibana/index_pattern/install.test.ts
deleted file mode 100644
index dfdaa66a7b43e..0000000000000
--- a/x-pack/plugins/fleet/server/services/epm/kibana/index_pattern/install.test.ts
+++ /dev/null
@@ -1,309 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import path from 'path';
-import { readFileSync } from 'fs';
-
-import glob from 'glob';
-import { safeLoad } from 'js-yaml';
-
-import type { FieldSpec } from 'src/plugins/data/common';
-
-import type { Fields, Field } from '../../fields/field';
-
-import {
- flattenFields,
- dedupeFields,
- transformField,
- findFieldByPath,
- createFieldFormatMap,
- createIndexPatternFields,
- createIndexPattern,
-} from './install';
-import { dupeFields } from './tests/test_data';
-
-// Add our own serialiser to just do JSON.stringify
-expect.addSnapshotSerializer({
- print(val) {
- return JSON.stringify(val, null, 2);
- },
-
- test(val) {
- return val;
- },
-});
-const files = glob.sync(path.join(__dirname, '/tests/*.yml'));
-let fields: Fields = [];
-for (const file of files) {
- const fieldsYML = readFileSync(file, 'utf-8');
- fields = fields.concat(safeLoad(fieldsYML));
-}
-
-describe('creating index patterns from yaml fields', () => {
- interface Test {
- fields: Field[];
- expect: string | number | boolean | undefined;
- }
-
- const name = 'testField';
-
- test('createIndexPatternFields function creates Kibana index pattern fields and fieldFormatMap', () => {
- const indexPatternFields = createIndexPatternFields(fields);
- expect(indexPatternFields).toMatchSnapshot('createIndexPatternFields');
- });
-
- test('createIndexPattern function creates Kibana index pattern', () => {
- const indexPattern = createIndexPattern('logs', fields);
- expect(indexPattern).toMatchSnapshot('createIndexPattern');
- });
-
- describe('flattenFields function flattens recursively and handles copying alias fields', () => {
- test('a field of type group with no nested fields is skipped', () => {
- const flattened = flattenFields([{ name: 'nginx', type: 'group' }]);
- expect(flattened.length).toBe(0);
- });
- test('flattenFields matches snapshot', () => {
- const flattened = flattenFields(fields);
- expect(flattened).toMatchSnapshot('flattenFields');
- });
- });
-
- describe('dedupFields', () => {
- const deduped = dedupeFields(dupeFields);
- const checkIfDup = (field: Field) => {
- return deduped.filter((item) => item.name === field.name);
- };
- test('there there is one field object with name of "1"', () => {
- expect(checkIfDup({ name: '1' }).length).toBe(1);
- });
- test('there there is one field object with name of "1.1"', () => {
- expect(checkIfDup({ name: '1.1' }).length).toBe(1);
- });
- test('there there is one field object with name of "2"', () => {
- expect(checkIfDup({ name: '2' }).length).toBe(1);
- });
- test('there there is one field object with name of "4"', () => {
- expect(checkIfDup({ name: '4' }).length).toBe(1);
- });
- // existing field takes precendence
- test('the new merged field has correct attributes', () => {
- const mergedField = deduped.find((field) => field.name === '1');
- expect(mergedField?.searchable).toBe(true);
- expect(mergedField?.aggregatable).toBe(true);
- expect(mergedField?.count).toBe(0);
- });
- });
-
- describe('getFieldByPath searches recursively for field in fields given dot separated path', () => {
- const searchFields: Fields = [
- {
- name: '1',
- fields: [
- {
- name: '1-1',
- },
- {
- name: '1-2',
- },
- ],
- },
- {
- name: '2',
- fields: [
- {
- name: '2-1',
- },
- {
- name: '2-2',
- fields: [
- {
- name: '2-2-1',
- },
- {
- name: '2-2-2',
- },
- ],
- },
- ],
- },
- ];
- test('returns undefined when the field does not exist', () => {
- expect(findFieldByPath(searchFields, '0')).toBe(undefined);
- });
- test('returns undefined if the field is not a leaf node', () => {
- expect(findFieldByPath(searchFields, '1')?.name).toBe(undefined);
- });
- test('returns undefined searching for a nested field that does not exist', () => {
- expect(findFieldByPath(searchFields, '1.1-3')?.name).toBe(undefined);
- });
- test('returns nested field that is a leaf node', () => {
- expect(findFieldByPath(searchFields, '2.2-2.2-2-1')?.name).toBe('2-2-1');
- });
- });
-
- test('transformField maps field types to kibana index pattern data types', () => {
- const tests: Test[] = [
- { fields: [{ name: 'testField' }], expect: 'string' },
- { fields: [{ name: 'testField', type: 'half_float' }], expect: 'number' },
- { fields: [{ name: 'testField', type: 'scaled_float' }], expect: 'number' },
- { fields: [{ name: 'testField', type: 'float' }], expect: 'number' },
- { fields: [{ name: 'testField', type: 'integer' }], expect: 'number' },
- { fields: [{ name: 'testField', type: 'long' }], expect: 'number' },
- { fields: [{ name: 'testField', type: 'short' }], expect: 'number' },
- { fields: [{ name: 'testField', type: 'byte' }], expect: 'number' },
- { fields: [{ name: 'testField', type: 'keyword' }], expect: 'string' },
- { fields: [{ name: 'testField', type: 'invalidType' }], expect: 'string' },
- { fields: [{ name: 'testField', type: 'text' }], expect: 'string' },
- { fields: [{ name: 'testField', type: 'date' }], expect: 'date' },
- { fields: [{ name: 'testField', type: 'geo_point' }], expect: 'geo_point' },
- { fields: [{ name: 'testField', type: 'constant_keyword' }], expect: 'string' },
- ];
-
- tests.forEach((test) => {
- const res = test.fields.map(transformField);
- expect(res[0].type).toBe(test.expect);
- });
- });
-
- test('transformField changes values based on other values', () => {
- interface TestWithAttr extends Test {
- attr: keyof FieldSpec;
- }
-
- const tests: TestWithAttr[] = [
- // count
- { fields: [{ name }], expect: 0, attr: 'count' },
- { fields: [{ name, count: 4 }], expect: 4, attr: 'count' },
-
- // searchable
- { fields: [{ name }], expect: true, attr: 'searchable' },
- { fields: [{ name, searchable: true }], expect: true, attr: 'searchable' },
- { fields: [{ name, searchable: false }], expect: false, attr: 'searchable' },
- { fields: [{ name, type: 'binary' }], expect: false, attr: 'searchable' },
- { fields: [{ name, searchable: true, type: 'binary' }], expect: false, attr: 'searchable' },
- {
- fields: [{ name, searchable: true, type: 'object', enabled: false }],
- expect: false,
- attr: 'searchable',
- },
-
- // aggregatable
- { fields: [{ name }], expect: true, attr: 'aggregatable' },
- { fields: [{ name, aggregatable: true }], expect: true, attr: 'aggregatable' },
- { fields: [{ name, aggregatable: false }], expect: false, attr: 'aggregatable' },
- { fields: [{ name, type: 'binary' }], expect: false, attr: 'aggregatable' },
- {
- fields: [{ name, aggregatable: true, type: 'binary' }],
- expect: false,
- attr: 'aggregatable',
- },
- { fields: [{ name, type: 'keyword' }], expect: true, attr: 'aggregatable' },
- { fields: [{ name, type: 'constant_keyword' }], expect: true, attr: 'aggregatable' },
- { fields: [{ name, type: 'text', aggregatable: true }], expect: false, attr: 'aggregatable' },
- { fields: [{ name, type: 'text' }], expect: false, attr: 'aggregatable' },
- {
- fields: [{ name, aggregatable: true, type: 'object', enabled: false }],
- expect: false,
- attr: 'aggregatable',
- },
-
- // indexed
- { fields: [{ name, type: 'binary' }], expect: false, attr: 'indexed' },
- {
- fields: [{ name, index: true, type: 'binary' }],
- expect: false,
- attr: 'indexed',
- },
- {
- fields: [{ name, index: true, type: 'object', enabled: false }],
- expect: false,
- attr: 'indexed',
- },
-
- // script, scripted
- { fields: [{ name }], expect: false, attr: 'scripted' },
- { fields: [{ name }], expect: undefined, attr: 'script' },
- { fields: [{ name, script: 'doc[]' }], expect: true, attr: 'scripted' },
- { fields: [{ name, script: 'doc[]' }], expect: 'doc[]', attr: 'script' },
-
- // lang
- { fields: [{ name }], expect: undefined, attr: 'lang' },
- { fields: [{ name, script: 'doc[]' }], expect: 'painless', attr: 'lang' },
- ];
- tests.forEach((test) => {
- const res = test.fields.map(transformField);
- expect(res[0][test.attr]).toBe(test.expect);
- });
- });
-
- describe('createFieldFormatMap creates correct map based on inputs', () => {
- test('field with no format or pattern have empty fieldFormatMap', () => {
- const fieldsToFormat = [{ name: 'fieldName', input_format: 'inputFormatVal' }];
- const fieldFormatMap = createFieldFormatMap(fieldsToFormat);
- expect(fieldFormatMap).toEqual({});
- });
- test('field with pattern and no format creates fieldFormatMap with no id', () => {
- const fieldsToFormat = [
- { name: 'fieldName', pattern: 'patternVal', input_format: 'inputFormatVal' },
- ];
- const fieldFormatMap = createFieldFormatMap(fieldsToFormat);
- const expectedFieldFormatMap = {
- fieldName: {
- params: {
- pattern: 'patternVal',
- inputFormat: 'inputFormatVal',
- },
- },
- };
- expect(fieldFormatMap).toEqual(expectedFieldFormatMap);
- });
-
- test('field with format and params creates fieldFormatMap with id', () => {
- const fieldsToFormat = [
- {
- name: 'fieldName',
- format: 'formatVal',
- pattern: 'patternVal',
- input_format: 'inputFormatVal',
- },
- ];
- const fieldFormatMap = createFieldFormatMap(fieldsToFormat);
- const expectedFieldFormatMap = {
- fieldName: {
- id: 'formatVal',
- params: {
- pattern: 'patternVal',
- inputFormat: 'inputFormatVal',
- },
- },
- };
- expect(fieldFormatMap).toEqual(expectedFieldFormatMap);
- });
-
- test('all variations and all the params get passed through', () => {
- const fieldsToFormat = [
- { name: 'fieldPattern', pattern: 'patternVal' },
- { name: 'fieldFormat', format: 'formatVal' },
- { name: 'fieldFormatWithParam', format: 'formatVal', output_precision: 2 },
- { name: 'fieldFormatAndPattern', format: 'formatVal', pattern: 'patternVal' },
- {
- name: 'fieldFormatAndAllParams',
- format: 'formatVal',
- pattern: 'pattenVal',
- input_format: 'inputFormatVal',
- output_format: 'outputFormalVal',
- output_precision: 3,
- label_template: 'labelTemplateVal',
- url_template: 'urlTemplateVal',
- openLinkInCurrentTab: true,
- },
- ];
- const fieldFormatMap = createFieldFormatMap(fieldsToFormat);
- expect(fieldFormatMap).toMatchSnapshot('createFieldFormatMap');
- });
- });
-});
diff --git a/x-pack/plugins/fleet/server/services/epm/kibana/index_pattern/install.ts b/x-pack/plugins/fleet/server/services/epm/kibana/index_pattern/install.ts
index 61d6f6ed8818a..c42029f2c453d 100644
--- a/x-pack/plugins/fleet/server/services/epm/kibana/index_pattern/install.ts
+++ b/x-pack/plugins/fleet/server/services/epm/kibana/index_pattern/install.ts
@@ -5,380 +5,58 @@
* 2.0.
*/
-import type { SavedObjectsClientContract, ElasticsearchClient } from 'src/core/server';
-import type { FieldSpec } from 'src/plugins/data/common';
+import type { SavedObjectsClientContract } from 'src/core/server';
-import { loadFieldsFromYaml } from '../../fields/field';
-import type { Fields, Field } from '../../fields/field';
import { dataTypes, installationStatuses } from '../../../../../common/constants';
-import type {
- ArchivePackage,
- Installation,
- InstallSource,
- ValueOf,
-} from '../../../../../common/types';
import { appContextService } from '../../../../services';
-import type { RegistryPackage, DataType } from '../../../../types';
-import { getInstallation, getPackageFromSource, getPackageSavedObjects } from '../../packages/get';
-
-interface FieldFormatMap {
- [key: string]: FieldFormatMapItem;
-}
-interface FieldFormatMapItem {
- id?: string;
- params?: FieldFormatParams;
-}
-interface FieldFormatParams {
- pattern?: string;
- inputFormat?: string;
- outputFormat?: string;
- outputPrecision?: number;
- labelTemplate?: string;
- urlTemplate?: string;
- openLinkInCurrentTab?: boolean;
-}
-/* this should match https://github.com/elastic/beats/blob/d9a4c9c240a9820fab15002592e5bb6db318543b/libbeat/kibana/fields_transformer.go */
-interface TypeMap {
- [key: string]: string;
-}
-const typeMap: TypeMap = {
- binary: 'binary',
- half_float: 'number',
- scaled_float: 'number',
- float: 'number',
- integer: 'number',
- long: 'number',
- short: 'number',
- byte: 'number',
- text: 'string',
- keyword: 'string',
- '': 'string',
- geo_point: 'geo_point',
- date: 'date',
- ip: 'ip',
- boolean: 'boolean',
- constant_keyword: 'string',
-};
-
+import { getPackageSavedObjects } from '../../packages/get';
const INDEX_PATTERN_SAVED_OBJECT_TYPE = 'index-pattern';
export const indexPatternTypes = Object.values(dataTypes);
-export async function installIndexPatterns({
- savedObjectsClient,
- pkgName,
- pkgVersion,
- installSource,
-}: {
- savedObjectsClient: SavedObjectsClientContract;
- esClient: ElasticsearchClient;
- pkgName?: string;
- pkgVersion?: string;
- installSource?: InstallSource;
-}) {
- const logger = appContextService.getLogger();
- logger.debug(
- `kicking off installation of index patterns for ${
- pkgName && pkgVersion ? `${pkgName}-${pkgVersion}` : 'no specific package'
- }`
- );
+export function getIndexPatternSavedObjects() {
+ return indexPatternTypes.map((indexPatternType) => ({
+ id: `${indexPatternType}-*`,
+ type: INDEX_PATTERN_SAVED_OBJECT_TYPE,
+ attributes: {
+ title: `${indexPatternType}-*`,
+ timeFieldName: '@timestamp',
+ allowNoIndex: true,
+ },
+ }));
+}
+export async function removeUnusedIndexPatterns(savedObjectsClient: SavedObjectsClientContract) {
+ const logger = appContextService.getLogger();
// get all user installed packages
const installedPackagesRes = await getPackageSavedObjects(savedObjectsClient);
const installedPackagesSavedObjects = installedPackagesRes.saved_objects.filter(
(so) => so.attributes.install_status === installationStatuses.Installed
);
- const packagesToFetch = installedPackagesSavedObjects.reduce<
- Array<{ name: string; version: string; installedPkg: Installation | undefined }>
- >((acc, pkg) => {
- acc.push({
- name: pkg.attributes.name,
- version: pkg.attributes.version,
- installedPkg: pkg.attributes,
- });
- return acc;
- }, []);
-
- if (pkgName && pkgVersion && installSource) {
- const packageToInstall = packagesToFetch.find((pkg) => pkg.name === pkgName);
- if (packageToInstall) {
- // set the version to the one we want to install
- // if we're reinstalling the number will be the same
- // if this is an upgrade then we'll be modifying the version number to the upgrade version
- packageToInstall.version = pkgVersion;
- } else {
- // if we're installing for the first time, add to the list
- packagesToFetch.push({
- name: pkgName,
- version: pkgVersion,
- installedPkg: await getInstallation({ savedObjectsClient, pkgName }),
- });
- }
+ if (installedPackagesSavedObjects.length > 0) {
+ return [];
}
- // get each package's registry info
- const packagesToFetchPromise = packagesToFetch.map((pkg) =>
- getPackageFromSource({
- pkgName: pkg.name,
- pkgVersion: pkg.version,
- installedPkg: pkg.installedPkg,
- savedObjectsClient,
- })
+ const patternsToDelete = indexPatternTypes.map((indexPatternType) => `${indexPatternType}-*`);
+
+ const { resolved_objects: resolvedObjects } = await savedObjectsClient.bulkResolve(
+ patternsToDelete.map((pattern) => ({ id: pattern, type: INDEX_PATTERN_SAVED_OBJECT_TYPE }))
);
- const packages = await Promise.all(packagesToFetchPromise);
- // for each index pattern type, create an index pattern
+ // eslint-disable-next-line @typescript-eslint/naming-convention
+ const idsToDelete = resolvedObjects.map(({ saved_object }) => saved_object.id);
+
return Promise.all(
- indexPatternTypes.map(async (indexPatternType) => {
- // if this is an update because a package is being uninstalled (no pkgkey argument passed) and no other packages are installed, remove the index pattern
- if (!pkgName && installedPackagesSavedObjects.length === 0) {
- try {
- logger.debug(`deleting index pattern ${indexPatternType}-*`);
- await savedObjectsClient.delete(INDEX_PATTERN_SAVED_OBJECT_TYPE, `${indexPatternType}-*`);
- } catch (err) {
- // index pattern was probably deleted by the user already
- }
- return;
+ idsToDelete.map(async (id) => {
+ try {
+ logger.debug(`deleting index pattern ${id}`);
+ await savedObjectsClient.delete(INDEX_PATTERN_SAVED_OBJECT_TYPE, id);
+ } catch (err) {
+ // index pattern was probably deleted by the user already
+ logger.debug(`Non fatal error encountered deleting index pattern ${id} : ${err}`);
}
- const packagesWithInfo = packages.map((pkg) => pkg.packageInfo);
- // get all data stream fields from all installed packages
- const fields = await getAllDataStreamFieldsByType(packagesWithInfo, indexPatternType);
- const kibanaIndexPattern = createIndexPattern(indexPatternType, fields);
-
- // create or overwrite the index pattern
- await savedObjectsClient.create(INDEX_PATTERN_SAVED_OBJECT_TYPE, kibanaIndexPattern, {
- id: `${indexPatternType}-*`,
- overwrite: true,
- });
- logger.debug(`created index pattern ${kibanaIndexPattern.title}`);
+ return;
})
);
}
-
-// loops through all given packages and returns an array
-// of all fields from all data streams matching data stream type
-export const getAllDataStreamFieldsByType = async (
- packages: Array,
- dataStreamType: ValueOf
-): Promise => {
- const dataStreamsPromises = packages.reduce>>((acc, pkg) => {
- if (pkg.data_streams) {
- // filter out data streams by data stream type
- const matchingDataStreams = pkg.data_streams.filter(
- (dataStream) => dataStream.type === dataStreamType
- );
- matchingDataStreams.forEach((dataStream) => {
- acc.push(loadFieldsFromYaml(pkg, dataStream.path));
- });
- }
- return acc;
- }, []);
-
- // get all the data stream fields for each installed package into one array
- const allDataStreamFields: Fields[] = await Promise.all(dataStreamsPromises);
- return allDataStreamFields.flat();
-};
-
-// creates or updates index pattern
-export const createIndexPattern = (indexPatternType: string, fields: Fields) => {
- const { indexPatternFields, fieldFormatMap } = createIndexPatternFields(fields);
-
- return {
- title: `${indexPatternType}-*`,
- timeFieldName: '@timestamp',
- fields: JSON.stringify(indexPatternFields),
- fieldFormatMap: JSON.stringify(fieldFormatMap),
- allowNoIndex: true,
- };
-};
-
-// takes fields from yaml files and transforms into Kibana Index Pattern fields
-// and also returns the fieldFormatMap
-export const createIndexPatternFields = (
- fields: Fields
-): { indexPatternFields: FieldSpec[]; fieldFormatMap: FieldFormatMap } => {
- const flattenedFields = flattenFields(fields);
- const fieldFormatMap = createFieldFormatMap(flattenedFields);
- const transformedFields = flattenedFields.map(transformField);
- const dedupedFields = dedupeFields(transformedFields);
- return { indexPatternFields: dedupedFields, fieldFormatMap };
-};
-
-// merges fields that are duplicates with the existing taking precedence
-export const dedupeFields = (fields: FieldSpec[]) => {
- const uniqueObj = fields.reduce<{ [name: string]: FieldSpec }>((acc, field) => {
- // if field doesn't exist yet
- if (!acc[field.name]) {
- acc[field.name] = field;
- // if field exists already
- } else {
- const existingField = acc[field.name];
- // if the existing field and this field have the same type, merge
- if (existingField.type === field.type) {
- const mergedField = { ...field, ...existingField };
- acc[field.name] = mergedField;
- } else {
- // log when there is a dup with different types
- }
- }
- return acc;
- }, {});
-
- return Object.values(uniqueObj);
-};
-
-/**
- * search through fields with field's path property
- * returns undefined if field not found or field is not a leaf node
- * @param allFields fields to search
- * @param path dot separated path from field.path
- */
-export const findFieldByPath = (allFields: Fields, path: string): Field | undefined => {
- const pathParts = path.split('.');
- return getField(allFields, pathParts);
-};
-
-const getField = (fields: Fields, pathNames: string[]): Field | undefined => {
- if (!pathNames.length) return undefined;
- // get the first rest of path names
- const [name, ...restPathNames] = pathNames;
- for (const field of fields) {
- if (field.name === name) {
- // check field's fields, passing in the remaining path names
- if (field.fields && field.fields.length > 0) {
- return getField(field.fields, restPathNames);
- }
- // no nested fields to search, but still more names - not found
- if (restPathNames.length) {
- return undefined;
- }
- return field;
- }
- }
- return undefined;
-};
-
-export const transformField = (field: Field, i: number, fields: Fields): FieldSpec => {
- const newField: FieldSpec = {
- name: field.name,
- type: field.type && typeMap[field.type] ? typeMap[field.type] : 'string',
- count: field.count ?? 0,
- scripted: false,
- indexed: field.index ?? true,
- searchable: field.searchable ?? true,
- aggregatable: field.aggregatable ?? true,
- readFromDocValues: field.doc_values ?? true,
- };
-
- if (newField.type === 'binary') {
- newField.aggregatable = false;
- newField.readFromDocValues = field.doc_values ?? false;
- newField.indexed = false;
- newField.searchable = false;
- }
-
- if (field.type === 'object' && field.hasOwnProperty('enabled')) {
- const enabled = field.enabled ?? true;
- if (!enabled) {
- newField.aggregatable = false;
- newField.readFromDocValues = false;
- newField.indexed = false;
- newField.searchable = false;
- }
- }
-
- if (field.type === 'text') {
- newField.aggregatable = false;
- }
-
- if (field.hasOwnProperty('script')) {
- newField.scripted = true;
- newField.script = field.script;
- newField.lang = 'painless';
- newField.readFromDocValues = false;
- }
-
- return newField;
-};
-
-/**
- * flattenFields
- *
- * flattens fields and renames them with a path of the parent names
- */
-
-export const flattenFields = (allFields: Fields): Fields => {
- const flatten = (fields: Fields): Fields =>
- fields.reduce((acc, field) => {
- // if this is a group fields with no fields, skip the field
- if (field.type === 'group' && !field.fields?.length) {
- return acc;
- }
- // recurse through nested fields
- if (field.type === 'group' && field.fields?.length) {
- // skip if field.enabled is not explicitly set to false
- if (!field.hasOwnProperty('enabled') || field.enabled === true) {
- acc = renameAndFlatten(field, field.fields, [...acc]);
- }
- } else {
- // handle alias type fields
- if (field.type === 'alias' && field.path) {
- const foundField = findFieldByPath(allFields, field.path);
- // if aliased leaf field is found copy its props over except path and name
- if (foundField) {
- const { path, name } = field;
- field = { ...foundField, path, name };
- }
- }
- // add field before going through multi_fields because we still want to add the parent field
- acc.push(field);
-
- // for each field in multi_field add new field
- if (field.multi_fields?.length) {
- acc = renameAndFlatten(field, field.multi_fields, [...acc]);
- }
- }
- return acc;
- }, []);
-
- // helper function to call flatten() and rename the fields
- const renameAndFlatten = (field: Field, fields: Fields, acc: Fields): Fields => {
- const flattenedFields = flatten(fields);
- flattenedFields.forEach((nestedField) => {
- acc.push({
- ...nestedField,
- name: `${field.name}.${nestedField.name}`,
- });
- });
- return acc;
- };
-
- return flatten(allFields);
-};
-
-export const createFieldFormatMap = (fields: Fields): FieldFormatMap =>
- fields.reduce((acc, field) => {
- if (field.format || field.pattern) {
- const fieldFormatMapItem: FieldFormatMapItem = {};
- if (field.format) {
- fieldFormatMapItem.id = field.format;
- }
- const params = getFieldFormatParams(field);
- if (Object.keys(params).length) fieldFormatMapItem.params = params;
- acc[field.name] = fieldFormatMapItem;
- }
- return acc;
- }, {});
-
-const getFieldFormatParams = (field: Field): FieldFormatParams => {
- const params: FieldFormatParams = {};
- if (field.pattern) params.pattern = field.pattern;
- if (field.input_format) params.inputFormat = field.input_format;
- if (field.output_format) params.outputFormat = field.output_format;
- if (field.output_precision) params.outputPrecision = field.output_precision;
- if (field.label_template) params.labelTemplate = field.label_template;
- if (field.url_template) params.urlTemplate = field.url_template;
- if (field.open_link_in_current_tab) params.openLinkInCurrentTab = field.open_link_in_current_tab;
- return params;
-};
diff --git a/x-pack/plugins/fleet/server/services/epm/kibana/index_pattern/tests/coredns.logs.yml b/x-pack/plugins/fleet/server/services/epm/kibana/index_pattern/tests/coredns.logs.yml
deleted file mode 100644
index d66a4cf62bc41..0000000000000
--- a/x-pack/plugins/fleet/server/services/epm/kibana/index_pattern/tests/coredns.logs.yml
+++ /dev/null
@@ -1,71 +0,0 @@
-- name: coredns
- type: group
- description: >
- coredns fields after normalization
- fields:
- - name: id
- type: keyword
- description: >
- id of the DNS transaction
-
- - name: allParams
- type: integer
- format: bytes
- pattern: patternValQueryWeight
- input_format: inputFormatVal,
- output_format: outputFormalVal,
- output_precision: 3,
- label_template: labelTemplateVal,
- url_template: urlTemplateVal,
- openLinkInCurrentTab: true,
- description: >
- weight of the DNS query
-
- - name: query.length
- type: integer
- pattern: patternValQueryLength
- description: >
- length of the DNS query
-
- - name: query.size
- type: integer
- format: bytes
- pattern: patternValQuerySize
- description: >
- size of the DNS query
-
- - name: query.class
- type: keyword
- description: >
- DNS query class
-
- - name: query.name
- type: keyword
- description: >
- DNS query name
-
- - name: query.type
- type: keyword
- description: >
- DNS query type
-
- - name: response.code
- type: keyword
- description: >
- DNS response code
-
- - name: response.flags
- type: keyword
- description: >
- DNS response flags
-
- - name: response.size
- type: integer
- format: bytes
- description: >
- size of the DNS response
-
- - name: dnssec_ok
- type: boolean
- description: >
- dnssec flag
diff --git a/x-pack/plugins/fleet/server/services/epm/kibana/index_pattern/tests/nginx.access.ecs.yml b/x-pack/plugins/fleet/server/services/epm/kibana/index_pattern/tests/nginx.access.ecs.yml
deleted file mode 100644
index 51090a0fe7cf0..0000000000000
--- a/x-pack/plugins/fleet/server/services/epm/kibana/index_pattern/tests/nginx.access.ecs.yml
+++ /dev/null
@@ -1,112 +0,0 @@
-- name: '@timestamp'
- level: core
- required: true
- type: date
- description: 'Date/time when the event originated.
- This is the date/time extracted from the event, typically representing when
- the event was generated by the source.
- If the event source has no original timestamp, this value is typically populated
- by the first time the event was received by the pipeline.
- Required field for all events.'
- example: '2016-05-23T08:05:34.853Z'
-- name: labels
- level: core
- type: object
- object_type: keyword
- description: 'Custom key/value pairs.
- Can be used to add meta information to events. Should not contain nested objects.
- All values are stored as keyword.
- Example: `docker` and `k8s` labels.'
- example:
- application: foo-bar
- env: production
-- name: message
- level: core
- type: text
- description: 'For log events the message field contains the log message, optimized
- for viewing in a log viewer.
- For structured logs without an original message field, other fields can be concatenated
- to form a human-readable summary of the event.
- If multiple messages exist, they can be combined into one message.'
- example: Hello World
-- name: tags
- level: core
- type: keyword
- ignore_above: 1024
- description: List of keywords used to tag each event.
- example: '["production", "env2"]'
-- name: agent
- title: Agent
- group: 2
- description: 'The agent fields contain the data about the software entity, if
- any, that collects, detects, or observes events on a host, or takes measurements
- on a host.
- Examples include Beats. Agents may also run on observers. ECS agent.* fields
- shall be populated with details of the agent running on the host or observer
- where the event happened or the measurement was taken.'
- footnote: 'Examples: In the case of Beats for logs, the agent.name is filebeat.
- For APM, it is the agent running in the app/service. The agent information does
- not change if data is sent through queuing systems like Kafka, Redis, or processing
- systems such as Logstash or APM Server.'
- type: group
- fields:
- - name: ephemeral_id
- level: extended
- type: keyword
- ignore_above: 1024
- description: 'Ephemeral identifier of this agent (if one exists).
- This id normally changes across restarts, but `agent.id` does not.'
- example: 8a4f500f
- - name: id
- level: core
- type: keyword
- ignore_above: 1024
- description: 'Unique identifier of this agent (if one exists).
- Example: For Beats this would be beat.id.'
- example: 8a4f500d
- - name: name
- level: core
- type: keyword
- ignore_above: 1024
- description: 'Custom name of the agent.
- This is a name that can be given to an agent. This can be helpful if for example
- two Filebeat instances are running on the same host but a human readable separation
- is needed on which Filebeat instance data is coming from.
- If no name is given, the name is often left empty.'
- example: foo
- - name: type
- level: core
- type: keyword
- ignore_above: 1024
- description: 'Type of the agent.
- The agent type stays always the same and should be given by the agent used.
- In case of Filebeat the agent would always be Filebeat also if two Filebeat
- instances are run on the same machine.'
- example: filebeat
- - name: version
- level: core
- type: keyword
- ignore_above: 1024
- description: Version of the agent.
- example: 6.0.0-rc2
-- name: as
- title: Autonomous System
- group: 2
- description: An autonomous system (AS) is a collection of connected Internet Protocol
- (IP) routing prefixes under the control of one or more network operators on
- behalf of a single administrative entity or domain that presents a common, clearly
- defined routing policy to the internet.
- type: group
- fields:
- - name: number
- level: extended
- type: long
- description: Unique number allocated to the autonomous system. The autonomous
- system number (ASN) uniquely identifies each network on the Internet.
- example: 15169
- - name: organization.name
- level: extended
- type: keyword
- ignore_above: 1024
- description: Organization name.
- example: Google LLC
\ No newline at end of file
diff --git a/x-pack/plugins/fleet/server/services/epm/kibana/index_pattern/tests/nginx.error.ecs.yml b/x-pack/plugins/fleet/server/services/epm/kibana/index_pattern/tests/nginx.error.ecs.yml
deleted file mode 100644
index 51090a0fe7cf0..0000000000000
--- a/x-pack/plugins/fleet/server/services/epm/kibana/index_pattern/tests/nginx.error.ecs.yml
+++ /dev/null
@@ -1,112 +0,0 @@
-- name: '@timestamp'
- level: core
- required: true
- type: date
- description: 'Date/time when the event originated.
- This is the date/time extracted from the event, typically representing when
- the event was generated by the source.
- If the event source has no original timestamp, this value is typically populated
- by the first time the event was received by the pipeline.
- Required field for all events.'
- example: '2016-05-23T08:05:34.853Z'
-- name: labels
- level: core
- type: object
- object_type: keyword
- description: 'Custom key/value pairs.
- Can be used to add meta information to events. Should not contain nested objects.
- All values are stored as keyword.
- Example: `docker` and `k8s` labels.'
- example:
- application: foo-bar
- env: production
-- name: message
- level: core
- type: text
- description: 'For log events the message field contains the log message, optimized
- for viewing in a log viewer.
- For structured logs without an original message field, other fields can be concatenated
- to form a human-readable summary of the event.
- If multiple messages exist, they can be combined into one message.'
- example: Hello World
-- name: tags
- level: core
- type: keyword
- ignore_above: 1024
- description: List of keywords used to tag each event.
- example: '["production", "env2"]'
-- name: agent
- title: Agent
- group: 2
- description: 'The agent fields contain the data about the software entity, if
- any, that collects, detects, or observes events on a host, or takes measurements
- on a host.
- Examples include Beats. Agents may also run on observers. ECS agent.* fields
- shall be populated with details of the agent running on the host or observer
- where the event happened or the measurement was taken.'
- footnote: 'Examples: In the case of Beats for logs, the agent.name is filebeat.
- For APM, it is the agent running in the app/service. The agent information does
- not change if data is sent through queuing systems like Kafka, Redis, or processing
- systems such as Logstash or APM Server.'
- type: group
- fields:
- - name: ephemeral_id
- level: extended
- type: keyword
- ignore_above: 1024
- description: 'Ephemeral identifier of this agent (if one exists).
- This id normally changes across restarts, but `agent.id` does not.'
- example: 8a4f500f
- - name: id
- level: core
- type: keyword
- ignore_above: 1024
- description: 'Unique identifier of this agent (if one exists).
- Example: For Beats this would be beat.id.'
- example: 8a4f500d
- - name: name
- level: core
- type: keyword
- ignore_above: 1024
- description: 'Custom name of the agent.
- This is a name that can be given to an agent. This can be helpful if for example
- two Filebeat instances are running on the same host but a human readable separation
- is needed on which Filebeat instance data is coming from.
- If no name is given, the name is often left empty.'
- example: foo
- - name: type
- level: core
- type: keyword
- ignore_above: 1024
- description: 'Type of the agent.
- The agent type stays always the same and should be given by the agent used.
- In case of Filebeat the agent would always be Filebeat also if two Filebeat
- instances are run on the same machine.'
- example: filebeat
- - name: version
- level: core
- type: keyword
- ignore_above: 1024
- description: Version of the agent.
- example: 6.0.0-rc2
-- name: as
- title: Autonomous System
- group: 2
- description: An autonomous system (AS) is a collection of connected Internet Protocol
- (IP) routing prefixes under the control of one or more network operators on
- behalf of a single administrative entity or domain that presents a common, clearly
- defined routing policy to the internet.
- type: group
- fields:
- - name: number
- level: extended
- type: long
- description: Unique number allocated to the autonomous system. The autonomous
- system number (ASN) uniquely identifies each network on the Internet.
- example: 15169
- - name: organization.name
- level: extended
- type: keyword
- ignore_above: 1024
- description: Organization name.
- example: Google LLC
\ No newline at end of file
diff --git a/x-pack/plugins/fleet/server/services/epm/kibana/index_pattern/tests/nginx.fields.yml b/x-pack/plugins/fleet/server/services/epm/kibana/index_pattern/tests/nginx.fields.yml
deleted file mode 100644
index 7c2e721d564e7..0000000000000
--- a/x-pack/plugins/fleet/server/services/epm/kibana/index_pattern/tests/nginx.fields.yml
+++ /dev/null
@@ -1,120 +0,0 @@
-- name: nginx.access
- type: group
- description: >
- Contains fields for the Nginx access logs.
- fields:
- - name: group_disabled
- type: group
- enabled: false
- fields:
- - name: message
- type: text
- - name: remote_ip_list
- type: array
- description: >
- An array of remote IP addresses. It is a list because it is common to include, besides the client
- IP address, IP addresses from headers like `X-Forwarded-For`.
- Real source IP is restored to `source.ip`.
-
- - name: body_sent.bytes
- type: alias
- path: http.response.body.bytes
- migration: true
- - name: user_name
- type: alias
- path: user.name
- migration: true
- - name: method
- type: alias
- path: http.request.method
- migration: true
- - name: url
- type: alias
- path: url.original
- migration: true
- - name: http_version
- type: alias
- path: http.version
- migration: true
- - name: response_code
- type: alias
- path: http.response.status_code
- migration: true
- - name: referrer
- type: alias
- path: http.request.referrer
- migration: true
- - name: agent
- type: alias
- path: user_agent.original
- migration: true
-
- - name: user_agent
- type: group
- fields:
- - name: device
- type: alias
- path: user_agent.device.name
- migration: true
- - name: name
- type: alias
- path: user_agent.name
- migration: true
- - name: os
- type: alias
- path: user_agent.os.full_name
- migration: true
- - name: os_name
- type: alias
- path: user_agent.os.name
- migration: true
- - name: original
- type: alias
- path: user_agent.original
- migration: true
-
- - name: geoip
- type: group
- fields:
- - name: continent_name
- type: alias
- path: source.geo.continent_name
- migration: true
- - name: country_iso_code
- type: alias
- path: source.geo.country_iso_code
- migration: true
- - name: location
- type: alias
- path: source.geo.location
- migration: true
- - name: region_name
- type: alias
- path: source.geo.region_name
- migration: true
- - name: city_name
- type: alias
- path: source.geo.city_name
- migration: true
- - name: region_iso_code
- type: alias
- path: source.geo.region_iso_code
- migration: true
-
-- name: source
- type: group
- fields:
- - name: geo
- type: group
- fields:
- - name: continent_name
- type: text
-- name: country
- type: ""
- multi_fields:
- - name: keyword
- type: keyword
- - name: text
- type: text
-- name: nginx
- type: group
\ No newline at end of file
diff --git a/x-pack/plugins/fleet/server/services/epm/kibana/index_pattern/tests/test_data.ts b/x-pack/plugins/fleet/server/services/epm/kibana/index_pattern/tests/test_data.ts
deleted file mode 100644
index d9bcf36651081..0000000000000
--- a/x-pack/plugins/fleet/server/services/epm/kibana/index_pattern/tests/test_data.ts
+++ /dev/null
@@ -1,91 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license 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 { FieldSpec } from 'src/plugins/data/common';
-
-export const dupeFields: FieldSpec[] = [
- {
- name: '1',
- type: 'integer',
- searchable: true,
- aggregatable: true,
- count: 0,
- indexed: true,
- readFromDocValues: true,
- scripted: false,
- },
- {
- name: '2',
- type: 'integer',
- searchable: true,
- aggregatable: true,
- count: 0,
- indexed: true,
- readFromDocValues: true,
- scripted: false,
- },
- {
- name: '3',
- type: 'integer',
- searchable: true,
- aggregatable: true,
- count: 0,
- indexed: true,
- readFromDocValues: true,
- scripted: false,
- },
- {
- name: '1',
- type: 'integer',
- searchable: false,
- aggregatable: false,
- count: 2,
- indexed: true,
- readFromDocValues: true,
- scripted: false,
- },
- {
- name: '1.1',
- type: 'integer',
- searchable: false,
- aggregatable: false,
- count: 0,
- indexed: true,
- readFromDocValues: true,
- scripted: false,
- },
- {
- name: '4',
- type: 'integer',
- searchable: false,
- aggregatable: false,
- count: 0,
- indexed: true,
- readFromDocValues: true,
- scripted: false,
- },
- {
- name: '2',
- type: 'integer',
- searchable: false,
- aggregatable: false,
- count: 0,
- indexed: true,
- readFromDocValues: true,
- scripted: false,
- },
- {
- name: '1',
- type: 'integer',
- searchable: false,
- aggregatable: false,
- count: 1,
- indexed: true,
- readFromDocValues: true,
- scripted: false,
- },
-];
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 5ee0f57b6e03a..dbec18851cfc9 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
@@ -20,7 +20,6 @@ jest.mock('./get');
import { updateCurrentWriteIndices } from '../elasticsearch/template/template';
import { installKibanaAssets } from '../kibana/assets/install';
-import { installIndexPatterns } from '../kibana/index_pattern/install';
import { _installPackage } from './_install_package';
@@ -30,9 +29,6 @@ const mockedUpdateCurrentWriteIndices = updateCurrentWriteIndices as jest.Mocked
const mockedGetKibanaAssets = installKibanaAssets as jest.MockedFunction<
typeof installKibanaAssets
>;
-const mockedInstallIndexPatterns = installIndexPatterns as jest.MockedFunction<
- typeof installIndexPatterns
->;
function sleep(millis: number) {
return new Promise((resolve) => setTimeout(resolve, millis));
@@ -50,14 +46,11 @@ describe('_installPackage', () => {
afterEach(async () => {
appContextService.stop();
});
- it('handles errors from installIndexPatterns or installKibanaAssets', async () => {
- // force errors from either/both these functions
+ it('handles errors from installKibanaAssets', async () => {
+ // force errors from this function
mockedGetKibanaAssets.mockImplementation(async () => {
throw new Error('mocked async error A: should be caught');
});
- mockedInstallIndexPatterns.mockImplementation(async () => {
- throw new Error('mocked async error B: should be caught');
- });
// pick any function between when those are called and when await Promise.all is defined later
// and force it to take long enough for the errors to occur
@@ -66,6 +59,8 @@ describe('_installPackage', () => {
const installationPromise = _installPackage({
savedObjectsClient: soClient,
+ // @ts-ignore
+ savedObjectsImporter: jest.fn(),
esClient,
logger: loggerMock.create(),
paths: [],
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 e2027a99463fc..ac0c7e1729913 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
@@ -10,6 +10,7 @@ import type {
Logger,
SavedObject,
SavedObjectsClientContract,
+ SavedObjectsImporter,
} from 'src/core/server';
import {
@@ -36,7 +37,6 @@ import { installMlModel } from '../elasticsearch/ml_model/';
import { installIlmForDataStream } from '../elasticsearch/datastream_ilm/install';
import { saveArchiveEntries } from '../archive/storage';
import { ConcurrentInstallOperationError } from '../../../errors';
-
import { packagePolicyService } from '../..';
import { createInstallation, saveKibanaAssetsRefs, updateVersion } from './install';
@@ -48,6 +48,7 @@ import { deleteKibanaSavedObjectsAssets } from './remove';
export async function _installPackage({
savedObjectsClient,
+ savedObjectsImporter,
esClient,
logger,
installedPkg,
@@ -57,6 +58,7 @@ export async function _installPackage({
installSource,
}: {
savedObjectsClient: SavedObjectsClientContract;
+ savedObjectsImporter: Pick;
esClient: ElasticsearchClient;
logger: Logger;
installedPkg?: SavedObject;
@@ -100,21 +102,6 @@ export async function _installPackage({
});
}
- // kick off `installKibanaAssets` as early as possible because they're the longest running operations
- // we don't `await` here because we don't want to delay starting the many other `install*` functions
- // however, without an `await` or a `.catch` we haven't defined how to handle a promise rejection
- // we define it many lines and potentially seconds of wall clock time later in
- // `await installKibanaAssetsPromise`
- // if we encounter an error before we there, we'll have an "unhandled rejection" which causes its own problems
- // the program will log something like this _and exit/crash_
- // Unhandled Promise rejection detected:
- // RegistryResponseError or some other error
- // Terminating process...
- // server crashed with status code 1
- //
- // add a `.catch` to prevent the "unhandled rejection" case
- // in that `.catch`, set something that indicates a failure
- // check for that failure later and act accordingly (throw, ignore, return)
const kibanaAssets = await getKibanaAssets(paths);
if (installedPkg)
await deleteKibanaSavedObjectsAssets(
@@ -127,12 +114,13 @@ export async function _installPackage({
pkgName,
kibanaAssets
);
- let installKibanaAssetsError;
- const installKibanaAssetsPromise = installKibanaAssets({
- savedObjectsClient,
+
+ await installKibanaAssets({
+ logger,
+ savedObjectsImporter,
pkgName,
kibanaAssets,
- }).catch((reason) => (installKibanaAssetsError = reason));
+ });
// the rest of the installation must happen in sequential order
// currently only the base package has an ILM policy
@@ -211,10 +199,6 @@ export async function _installPackage({
}
const installedTemplateRefs = getAllTemplateRefs(installedTemplates);
- // make sure the assets are installed (or didn't error)
- if (installKibanaAssetsError) throw installKibanaAssetsError;
- await installKibanaAssetsPromise;
-
const packageAssetResults = await saveArchiveEntries({
savedObjectsClient,
paths,
diff --git a/x-pack/plugins/fleet/server/services/epm/packages/bulk_install_packages.ts b/x-pack/plugins/fleet/server/services/epm/packages/bulk_install_packages.ts
index c77e2a0a22a0a..8a7fb9ae005d0 100644
--- a/x-pack/plugins/fleet/server/services/epm/packages/bulk_install_packages.ts
+++ b/x-pack/plugins/fleet/server/services/epm/packages/bulk_install_packages.ts
@@ -9,7 +9,6 @@ import type { ElasticsearchClient, SavedObjectsClientContract } from 'src/core/s
import { appContextService } from '../../app_context';
import * as Registry from '../registry';
-import { installIndexPatterns } from '../kibana/index_pattern/install';
import type { InstallResult } from '../../../types';
@@ -71,7 +70,6 @@ export async function bulkInstallPackages({
esClient,
pkgkey: Registry.pkgToPkgKey(pkgKeyProps),
installSource,
- skipPostInstall: true,
force,
});
if (installResult.error) {
@@ -92,19 +90,6 @@ export async function bulkInstallPackages({
})
);
- // only install index patterns if we completed install for any package-version for the
- // first time, aka fresh installs or upgrades
- if (
- bulkInstallResults.find(
- (result) =>
- result.status === 'fulfilled' &&
- !result.value.result?.error &&
- result.value.result?.status === 'installed'
- )
- ) {
- await installIndexPatterns({ savedObjectsClient, esClient, installSource });
- }
-
return bulkInstallResults.map((result, index) => {
const packageName = getNameFromPackagesToInstall(packagesToInstall, index);
if (result.status === 'fulfilled') {
diff --git a/x-pack/plugins/fleet/server/services/epm/packages/index.ts b/x-pack/plugins/fleet/server/services/epm/packages/index.ts
index a6970a8d19db4..feee4277ab0e1 100644
--- a/x-pack/plugins/fleet/server/services/epm/packages/index.ts
+++ b/x-pack/plugins/fleet/server/services/epm/packages/index.ts
@@ -7,7 +7,11 @@
import type { SavedObject } from 'src/core/server';
-import { unremovablePackages, installationStatuses } from '../../../../common';
+import {
+ unremovablePackages,
+ installationStatuses,
+ KibanaSavedObjectType,
+} from '../../../../common';
import { KibanaAssetType } from '../../../types';
import type { AssetType, Installable, Installation } from '../../../types';
@@ -40,7 +44,7 @@ export class PackageNotInstalledError extends Error {
// only Kibana Assets use Saved Objects at this point
export const savedObjectTypes: AssetType[] = Object.values(KibanaAssetType);
-
+export const kibanaSavedObjectTypes: KibanaSavedObjectType[] = Object.values(KibanaSavedObjectType);
export function createInstallableFrom(
from: T,
savedObject?: SavedObject
diff --git a/x-pack/plugins/fleet/server/services/epm/packages/install.test.ts b/x-pack/plugins/fleet/server/services/epm/packages/install.test.ts
index a9bb235c22cb8..261a0d9a6d688 100644
--- a/x-pack/plugins/fleet/server/services/epm/packages/install.test.ts
+++ b/x-pack/plugins/fleet/server/services/epm/packages/install.test.ts
@@ -26,6 +26,9 @@ jest.mock('../../app_context', () => {
return { error: jest.fn(), debug: jest.fn(), warn: jest.fn() };
}),
getTelemetryEventsSender: jest.fn(),
+ getSavedObjects: jest.fn(() => ({
+ createImporter: jest.fn(),
+ })),
},
};
});
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 330fd84e789b8..a580248b43731 100644
--- a/x-pack/plugins/fleet/server/services/epm/packages/install.ts
+++ b/x-pack/plugins/fleet/server/services/epm/packages/install.ts
@@ -39,7 +39,6 @@ import * as Registry from '../registry';
import { setPackageInfo, parseAndVerifyArchiveEntries, unpackBufferToCache } from '../archive';
import { toAssetReference } from '../kibana/assets/install';
import type { ArchiveAsset } from '../kibana/assets/install';
-import { installIndexPatterns } from '../kibana/index_pattern/install';
import type { PackageUpdateEvent } from '../../upgrade_sender';
import { sendTelemetryEvents, UpdateEventType } from '../../upgrade_sender';
@@ -303,10 +302,15 @@ async function installPackageFromRegistry({
return { error: err, installType };
}
+ const savedObjectsImporter = appContextService
+ .getSavedObjects()
+ .createImporter(savedObjectsClient);
+
// try installing the package, if there was an error, call error handler and rethrow
// @ts-expect-error status is string instead of InstallResult.status 'installed' | 'already_installed'
return _installPackage({
savedObjectsClient,
+ savedObjectsImporter,
esClient,
logger,
installedPkg,
@@ -407,9 +411,15 @@ async function installPackageByUpload({
version: packageInfo.version,
packageInfo,
});
+
+ const savedObjectsImporter = appContextService
+ .getSavedObjects()
+ .createImporter(savedObjectsClient);
+
// @ts-expect-error status is string instead of InstallResult.status 'installed' | 'already_installed'
return _installPackage({
savedObjectsClient,
+ savedObjectsImporter,
esClient,
logger,
installedPkg,
@@ -441,41 +451,25 @@ async function installPackageByUpload({
}
}
-export type InstallPackageParams = {
- skipPostInstall?: boolean;
-} & (
+export type InstallPackageParams =
| ({ installSource: Extract } & InstallRegistryPackageParams)
- | ({ installSource: Extract } & InstallUploadedArchiveParams)
-);
+ | ({ installSource: Extract } & InstallUploadedArchiveParams);
export async function installPackage(args: InstallPackageParams) {
if (!('installSource' in args)) {
throw new Error('installSource is required');
}
const logger = appContextService.getLogger();
- const { savedObjectsClient, esClient, skipPostInstall = false, installSource } = args;
+ const { savedObjectsClient, esClient } = args;
if (args.installSource === 'registry') {
const { pkgkey, force } = args;
- const { pkgName, pkgVersion } = Registry.splitPkgKey(pkgkey);
logger.debug(`kicking off install of ${pkgkey} from registry`);
const response = installPackageFromRegistry({
savedObjectsClient,
pkgkey,
esClient,
force,
- }).then(async (installResult) => {
- if (skipPostInstall || installResult.error) {
- return installResult;
- }
- logger.debug(`install of ${pkgkey} finished, running post-install`);
- return installIndexPatterns({
- savedObjectsClient,
- esClient,
- pkgName,
- pkgVersion,
- installSource,
- }).then(() => installResult);
});
return response;
} else if (args.installSource === 'upload') {
@@ -486,16 +480,6 @@ export async function installPackage(args: InstallPackageParams) {
esClient,
archiveBuffer,
contentType,
- }).then(async (installResult) => {
- if (skipPostInstall || installResult.error) {
- return installResult;
- }
- logger.debug(`install of uploaded package finished, running post-install`);
- return installIndexPatterns({
- savedObjectsClient,
- esClient,
- installSource,
- }).then(() => installResult);
});
return response;
}
diff --git a/x-pack/plugins/fleet/server/services/epm/packages/remove.ts b/x-pack/plugins/fleet/server/services/epm/packages/remove.ts
index cd85eecbf1e78..957dac8c1aacb 100644
--- a/x-pack/plugins/fleet/server/services/epm/packages/remove.ts
+++ b/x-pack/plugins/fleet/server/services/epm/packages/remove.ts
@@ -18,7 +18,7 @@ import type {
Installation,
} from '../../../types';
import { deletePipeline } from '../elasticsearch/ingest_pipeline/';
-import { installIndexPatterns } from '../kibana/index_pattern/install';
+import { removeUnusedIndexPatterns } from '../kibana/index_pattern/install';
import { deleteTransforms } from '../elasticsearch/transform/remove';
import { deleteMlModel } from '../elasticsearch/ml_model';
import { packagePolicyService, appContextService } from '../..';
@@ -27,7 +27,7 @@ import { deletePackageCache } from '../archive';
import { deleteIlms } from '../elasticsearch/datastream_ilm/remove';
import { removeArchiveEntries } from '../archive/storage';
-import { getInstallation, savedObjectTypes } from './index';
+import { getInstallation, kibanaSavedObjectTypes } from './index';
export async function removeInstallation(options: {
savedObjectsClient: SavedObjectsClientContract;
@@ -62,10 +62,10 @@ export async function removeInstallation(options: {
// could also update with [] or some other state
await savedObjectsClient.delete(PACKAGES_SAVED_OBJECT_TYPE, pkgName);
- // recreate or delete index patterns when a package is uninstalled
+ // delete the index patterns if no packages are installed
// this must be done after deleting the saved object for the current package otherwise it will retrieve the package
- // from the registry again and reinstall the index patterns
- await installIndexPatterns({ savedObjectsClient, esClient });
+ // from the registry again and keep the index patterns
+ await removeUnusedIndexPatterns(savedObjectsClient);
// remove the package archive and its contents from the cache so that a reinstall fetches
// a fresh copy from the registry
@@ -80,14 +80,26 @@ export async function removeInstallation(options: {
return installedAssets;
}
-// TODO: this is very much like deleteKibanaSavedObjectsAssets below
-function deleteKibanaAssets(
+async function deleteKibanaAssets(
installedObjects: KibanaAssetReference[],
savedObjectsClient: SavedObjectsClientContract
) {
- return installedObjects.map(async ({ id, type }) => {
+ const { resolved_objects: resolvedObjects } = await savedObjectsClient.bulkResolve(
+ installedObjects
+ );
+
+ const foundObjects = resolvedObjects.filter(
+ ({ saved_object: savedObject }) => savedObject?.error?.statusCode !== 404
+ );
+
+ // in the case of a partial install, it is expected that some assets will be not found
+ // we filter these out before calling delete
+ const assetsToDelete = foundObjects.map(({ saved_object: { id, type } }) => ({ id, type }));
+ const promises = assetsToDelete.map(async ({ id, type }) => {
return savedObjectsClient.delete(type, id);
});
+
+ return Promise.all(promises);
}
function deleteESAssets(
@@ -145,7 +157,7 @@ async function deleteAssets(
// then the other asset types
await Promise.all([
...deleteESAssets(otherAssets, esClient),
- ...deleteKibanaAssets(installedKibana, savedObjectsClient),
+ deleteKibanaAssets(installedKibana, savedObjectsClient),
]);
} catch (err) {
// in the rollback case, partial installs are likely, so missing assets are not an error
@@ -177,23 +189,19 @@ async function deleteComponentTemplate(esClient: ElasticsearchClient, name: stri
}
}
-// TODO: this is very much like deleteKibanaAssets above
export async function deleteKibanaSavedObjectsAssets(
savedObjectsClient: SavedObjectsClientContract,
- installedRefs: AssetReference[]
+ installedRefs: KibanaAssetReference[]
) {
if (!installedRefs.length) return;
const logger = appContextService.getLogger();
- const deletePromises = installedRefs.map(({ id, type }) => {
- const assetType = type as AssetType;
+ const assetsToDelete = installedRefs
+ .filter(({ type }) => kibanaSavedObjectTypes.includes(type))
+ .map(({ id, type }) => ({ id, type }));
- if (savedObjectTypes.includes(assetType)) {
- return savedObjectsClient.delete(assetType, id);
- }
- });
try {
- await Promise.all(deletePromises);
+ await deleteKibanaAssets(assetsToDelete, savedObjectsClient);
} catch (err) {
// in the rollback case, partial installs are likely, so missing assets are not an error
if (!savedObjectsClient.errors.isNotFoundError(err)) {
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 78a4d3d1a778d..2e69cd03242f9 100644
--- a/x-pack/plugins/fleet/server/services/package_policy.test.ts
+++ b/x-pack/plugins/fleet/server/services/package_policy.test.ts
@@ -33,8 +33,10 @@ import type {
InputsOverride,
NewPackagePolicy,
NewPackagePolicyInput,
+ PackagePolicyPackage,
RegistryPackage,
} from '../../common';
+import { packageToPackagePolicy } from '../../common';
import { IngestManagerError } from '../errors';
@@ -107,6 +109,11 @@ jest.mock('./epm/packages', () => {
};
});
+jest.mock('../../common', () => ({
+ ...jest.requireActual('../../common'),
+ packageToPackagePolicy: jest.fn(),
+}));
+
jest.mock('./epm/registry');
jest.mock('./agent_policy', () => {
@@ -125,6 +132,7 @@ jest.mock('./agent_policy', () => {
return agentPolicy;
},
bumpRevision: () => {},
+ getDefaultAgentPolicyId: () => Promise.resolve('1'),
},
};
});
@@ -2815,6 +2823,216 @@ describe('Package policy service', () => {
});
});
});
+
+ describe('enrich package policy on create', () => {
+ beforeEach(() => {
+ (packageToPackagePolicy as jest.Mock).mockReturnValue({
+ package: { name: 'apache', title: 'Apache', version: '1.0.0' },
+ inputs: [
+ {
+ type: 'logfile',
+ policy_template: 'log',
+ enabled: true,
+ streams: [
+ {
+ enabled: true,
+ data_stream: {
+ type: 'logs',
+ dataset: 'apache.access',
+ },
+ },
+ ],
+ },
+ ],
+ vars: {
+ paths: {
+ value: ['/var/log/apache2/access.log*'],
+ type: 'text',
+ },
+ },
+ });
+ });
+
+ it('should enrich from epm with defaults', async () => {
+ const newPolicy = {
+ name: 'apache-1',
+ inputs: [{ type: 'logfile', enabled: false }],
+ package: { name: 'apache', version: '0.3.3' },
+ } as NewPackagePolicy;
+ const result = await packagePolicyService.enrichPolicyWithDefaultsFromPackage(
+ savedObjectsClientMock.create(),
+ newPolicy
+ );
+ expect(result).toEqual({
+ name: 'apache-1',
+ namespace: 'default',
+ description: '',
+ package: { name: 'apache', title: 'Apache', version: '1.0.0' },
+ enabled: true,
+ policy_id: '1',
+ output_id: '',
+ inputs: [
+ {
+ enabled: false,
+ type: 'logfile',
+ policy_template: 'log',
+ streams: [
+ {
+ enabled: false,
+ data_stream: {
+ type: 'logs',
+ dataset: 'apache.access',
+ },
+ },
+ ],
+ },
+ ],
+ vars: {
+ paths: {
+ value: ['/var/log/apache2/access.log*'],
+ type: 'text',
+ },
+ },
+ });
+ });
+
+ it('should enrich from epm with defaults using policy template', async () => {
+ (packageToPackagePolicy as jest.Mock).mockReturnValueOnce({
+ package: { name: 'aws', title: 'AWS', version: '1.0.0' },
+ inputs: [
+ {
+ type: 'aws/metrics',
+ policy_template: 'cloudtrail',
+ enabled: true,
+ streams: [
+ {
+ enabled: true,
+ data_stream: {
+ type: 'metrics',
+ dataset: 'cloudtrail',
+ },
+ },
+ ],
+ },
+ {
+ type: 'aws/metrics',
+ policy_template: 'cloudwatch',
+ enabled: true,
+ streams: [
+ {
+ enabled: true,
+ data_stream: {
+ type: 'metrics',
+ dataset: 'cloudwatch',
+ },
+ },
+ ],
+ },
+ ],
+ });
+ const newPolicy = {
+ name: 'aws-1',
+ inputs: [{ type: 'aws/metrics', policy_template: 'cloudwatch', enabled: true }],
+ package: { name: 'aws', version: '1.0.0' },
+ } as NewPackagePolicy;
+ const result = await packagePolicyService.enrichPolicyWithDefaultsFromPackage(
+ savedObjectsClientMock.create(),
+ newPolicy
+ );
+ expect(result).toEqual({
+ name: 'aws-1',
+ namespace: 'default',
+ description: '',
+ package: { name: 'aws', title: 'AWS', version: '1.0.0' },
+ enabled: true,
+ policy_id: '1',
+ output_id: '',
+ inputs: [
+ {
+ type: 'aws/metrics',
+ policy_template: 'cloudwatch',
+ enabled: true,
+ streams: [
+ {
+ enabled: true,
+ data_stream: {
+ type: 'metrics',
+ dataset: 'cloudwatch',
+ },
+ },
+ ],
+ },
+ ],
+ });
+ });
+
+ it('should override defaults with new values', async () => {
+ const newPolicy = {
+ name: 'apache-2',
+ namespace: 'namespace',
+ description: 'desc',
+ enabled: false,
+ policy_id: '2',
+ output_id: '3',
+ inputs: [
+ {
+ type: 'logfile',
+ enabled: true,
+ streams: [
+ {
+ enabled: true,
+ data_stream: {
+ type: 'logs',
+ dataset: 'apache.error',
+ },
+ },
+ ],
+ },
+ ],
+ vars: {
+ paths: {
+ value: ['/my/access.log*'],
+ type: 'text',
+ },
+ },
+ package: { name: 'apache', version: '1.0.0' } as PackagePolicyPackage,
+ } as NewPackagePolicy;
+ const result = await packagePolicyService.enrichPolicyWithDefaultsFromPackage(
+ savedObjectsClientMock.create(),
+ newPolicy
+ );
+ expect(result).toEqual({
+ name: 'apache-2',
+ namespace: 'namespace',
+ description: 'desc',
+ package: { name: 'apache', title: 'Apache', version: '1.0.0' },
+ enabled: false,
+ policy_id: '2',
+ output_id: '3',
+ inputs: [
+ {
+ enabled: true,
+ type: 'logfile',
+ streams: [
+ {
+ enabled: true,
+ data_stream: {
+ type: 'logs',
+ dataset: 'apache.error',
+ },
+ },
+ ],
+ },
+ ],
+ vars: {
+ paths: {
+ value: ['/my/access.log*'],
+ type: 'text',
+ },
+ },
+ });
+ });
+ });
});
describe('_applyIndexPrivileges()', () => {
diff --git a/x-pack/plugins/fleet/server/services/package_policy.ts b/x-pack/plugins/fleet/server/services/package_policy.ts
index cef27d79a184a..6ebfb84ebb523 100644
--- a/x-pack/plugins/fleet/server/services/package_policy.ts
+++ b/x-pack/plugins/fleet/server/services/package_policy.ts
@@ -710,6 +710,67 @@ class PackagePolicyService {
}
}
+ public async enrichPolicyWithDefaultsFromPackage(
+ soClient: SavedObjectsClientContract,
+ newPolicy: NewPackagePolicy
+ ): Promise {
+ let newPackagePolicy: NewPackagePolicy = newPolicy;
+ if (newPolicy.package) {
+ const newPP = await this.buildPackagePolicyFromPackageWithVersion(
+ soClient,
+ newPolicy.package.name,
+ newPolicy.package.version
+ );
+ if (newPP) {
+ const inputs = newPolicy.inputs.map((input) => {
+ const defaultInput = newPP.inputs.find(
+ (i) =>
+ i.type === input.type &&
+ (!input.policy_template || input.policy_template === i.policy_template)
+ );
+ return {
+ ...defaultInput,
+ enabled: input.enabled,
+ type: input.type,
+ // to propagate "enabled: false" to streams
+ streams: defaultInput?.streams?.map((stream) => ({
+ ...stream,
+ enabled: input.enabled,
+ })),
+ } as NewPackagePolicyInput;
+ });
+ newPackagePolicy = {
+ ...newPP,
+ name: newPolicy.name,
+ namespace: newPolicy.namespace ?? 'default',
+ description: newPolicy.description ?? '',
+ enabled: newPolicy.enabled ?? true,
+ policy_id:
+ newPolicy.policy_id ?? (await agentPolicyService.getDefaultAgentPolicyId(soClient)),
+ output_id: newPolicy.output_id ?? '',
+ inputs: newPolicy.inputs[0]?.streams ? newPolicy.inputs : inputs,
+ vars: newPolicy.vars || newPP.vars,
+ };
+ }
+ }
+ return newPackagePolicy;
+ }
+
+ public async buildPackagePolicyFromPackageWithVersion(
+ soClient: SavedObjectsClientContract,
+ pkgName: string,
+ pkgVersion: string
+ ): Promise {
+ const packageInfo = await getPackageInfo({
+ savedObjectsClient: soClient,
+ pkgName,
+ pkgVersion,
+ });
+ if (packageInfo) {
+ return packageToPackagePolicy(packageInfo, '', '');
+ }
+ }
+
public async buildPackagePolicyFromPackage(
soClient: SavedObjectsClientContract,
pkgName: string
diff --git a/x-pack/plugins/fleet/server/types/models/package_policy.ts b/x-pack/plugins/fleet/server/types/models/package_policy.ts
index 30321bdca3309..904e4e18a8541 100644
--- a/x-pack/plugins/fleet/server/types/models/package_policy.ts
+++ b/x-pack/plugins/fleet/server/types/models/package_policy.ts
@@ -28,6 +28,53 @@ const ConfigRecordSchema = schema.recordOf(
})
);
+const PackagePolicyStreamsSchema = {
+ id: schema.maybe(schema.string()), // BWC < 7.11
+ enabled: schema.boolean(),
+ keep_enabled: schema.maybe(schema.boolean()),
+ data_stream: schema.object({
+ dataset: schema.string(),
+ type: schema.string(),
+ elasticsearch: schema.maybe(
+ schema.object({
+ privileges: schema.maybe(
+ schema.object({
+ indices: schema.maybe(schema.arrayOf(schema.string())),
+ })
+ ),
+ })
+ ),
+ }),
+ vars: schema.maybe(ConfigRecordSchema),
+ config: schema.maybe(
+ schema.recordOf(
+ schema.string(),
+ schema.object({
+ type: schema.maybe(schema.string()),
+ value: schema.maybe(schema.any()),
+ })
+ )
+ ),
+};
+
+const PackagePolicyInputsSchema = {
+ type: schema.string(),
+ policy_template: schema.maybe(schema.string()),
+ enabled: schema.boolean(),
+ keep_enabled: schema.maybe(schema.boolean()),
+ vars: schema.maybe(ConfigRecordSchema),
+ config: schema.maybe(
+ schema.recordOf(
+ schema.string(),
+ schema.object({
+ type: schema.maybe(schema.string()),
+ value: schema.maybe(schema.any()),
+ })
+ )
+ ),
+ streams: schema.arrayOf(schema.object(PackagePolicyStreamsSchema)),
+};
+
const PackagePolicyBaseSchema = {
name: schema.string(),
description: schema.maybe(schema.string()),
@@ -42,63 +89,57 @@ const PackagePolicyBaseSchema = {
})
),
output_id: schema.string(),
+ inputs: schema.arrayOf(schema.object(PackagePolicyInputsSchema)),
+ vars: schema.maybe(ConfigRecordSchema),
+};
+
+export const NewPackagePolicySchema = schema.object({
+ ...PackagePolicyBaseSchema,
+ id: schema.maybe(schema.string()),
+ force: schema.maybe(schema.boolean()),
+});
+
+const CreatePackagePolicyProps = {
+ ...PackagePolicyBaseSchema,
+ namespace: schema.maybe(NamespaceSchema),
+ policy_id: schema.maybe(schema.string()),
+ enabled: schema.maybe(schema.boolean()),
+ package: schema.maybe(
+ schema.object({
+ name: schema.string(),
+ title: schema.maybe(schema.string()),
+ version: schema.string(),
+ })
+ ),
+ output_id: schema.maybe(schema.string()),
inputs: schema.arrayOf(
schema.object({
- type: schema.string(),
- policy_template: schema.maybe(schema.string()),
- enabled: schema.boolean(),
- keep_enabled: schema.maybe(schema.boolean()),
- vars: schema.maybe(ConfigRecordSchema),
- config: schema.maybe(
- schema.recordOf(
- schema.string(),
- schema.object({
- type: schema.maybe(schema.string()),
- value: schema.maybe(schema.any()),
- })
- )
- ),
- streams: schema.arrayOf(
- schema.object({
- id: schema.maybe(schema.string()), // BWC < 7.11
- enabled: schema.boolean(),
- keep_enabled: schema.maybe(schema.boolean()),
- data_stream: schema.object({
- dataset: schema.string(),
- type: schema.string(),
- elasticsearch: schema.maybe(
- schema.object({
- privileges: schema.maybe(
- schema.object({
- indices: schema.maybe(schema.arrayOf(schema.string())),
- })
- ),
- })
- ),
- }),
- vars: schema.maybe(ConfigRecordSchema),
- config: schema.maybe(
- schema.recordOf(
- schema.string(),
- schema.object({
- type: schema.maybe(schema.string()),
- value: schema.maybe(schema.any()),
- })
- )
- ),
- })
- ),
+ ...PackagePolicyInputsSchema,
+ streams: schema.maybe(schema.arrayOf(schema.object(PackagePolicyStreamsSchema))),
})
),
- vars: schema.maybe(ConfigRecordSchema),
};
-export const NewPackagePolicySchema = schema.object({
- ...PackagePolicyBaseSchema,
+export const CreatePackagePolicyRequestBodySchema = schema.object({
+ ...CreatePackagePolicyProps,
id: schema.maybe(schema.string()),
force: schema.maybe(schema.boolean()),
});
+export const UpdatePackagePolicyRequestBodySchema = schema.object({
+ ...CreatePackagePolicyProps,
+ name: schema.maybe(schema.string()),
+ inputs: schema.maybe(
+ schema.arrayOf(
+ schema.object({
+ ...PackagePolicyInputsSchema,
+ streams: schema.maybe(schema.arrayOf(schema.object(PackagePolicyStreamsSchema))),
+ })
+ )
+ ),
+ version: schema.maybe(schema.string()),
+});
+
export const UpdatePackagePolicySchema = schema.object({
...PackagePolicyBaseSchema,
version: schema.maybe(schema.string()),
diff --git a/x-pack/plugins/fleet/server/types/rest_spec/package_policy.ts b/x-pack/plugins/fleet/server/types/rest_spec/package_policy.ts
index 34649602d2a02..010cd10492bf0 100644
--- a/x-pack/plugins/fleet/server/types/rest_spec/package_policy.ts
+++ b/x-pack/plugins/fleet/server/types/rest_spec/package_policy.ts
@@ -7,7 +7,10 @@
import { schema } from '@kbn/config-schema';
-import { NewPackagePolicySchema, UpdatePackagePolicySchema } from '../models';
+import {
+ CreatePackagePolicyRequestBodySchema,
+ UpdatePackagePolicyRequestBodySchema,
+} from '../models';
import { ListWithKuerySchema } from './index';
@@ -22,12 +25,12 @@ export const GetOnePackagePolicyRequestSchema = {
};
export const CreatePackagePolicyRequestSchema = {
- body: NewPackagePolicySchema,
+ body: CreatePackagePolicyRequestBodySchema,
};
export const UpdatePackagePolicyRequestSchema = {
...GetOnePackagePolicyRequestSchema,
- body: UpdatePackagePolicySchema,
+ body: UpdatePackagePolicyRequestBodySchema,
};
export const DeletePackagePoliciesRequestSchema = {
diff --git a/x-pack/plugins/infra/public/alerting/inventory/index.ts b/x-pack/plugins/infra/public/alerting/inventory/index.ts
index 5e3c8a219ae47..4a724694bbad8 100644
--- a/x-pack/plugins/infra/public/alerting/inventory/index.ts
+++ b/x-pack/plugins/infra/public/alerting/inventory/index.ts
@@ -15,15 +15,15 @@ import {
import { ObservabilityRuleTypeModel } from '../../../../observability/public';
-import { AlertTypeParams } from '../../../../alerting/common';
+import { AlertTypeParams as RuleTypeParams } from '../../../../alerting/common';
import { validateMetricThreshold } from './components/validation';
import { formatReason } from './rule_data_formatters';
-interface InventoryMetricAlertTypeParams extends AlertTypeParams {
+interface InventoryMetricRuleTypeParams extends RuleTypeParams {
criteria: InventoryMetricConditions[];
}
-export function createInventoryMetricAlertType(): ObservabilityRuleTypeModel {
+export function createInventoryMetricRuleType(): ObservabilityRuleTypeModel {
return {
id: METRIC_INVENTORY_THRESHOLD_ALERT_TYPE_ID,
description: i18n.translate('xpack.infra.metrics.inventory.alertFlyout.alertDescription', {
diff --git a/x-pack/plugins/infra/public/alerting/log_threshold/index.ts b/x-pack/plugins/infra/public/alerting/log_threshold/index.ts
index 0f2746b446927..b6eff8ef3826e 100644
--- a/x-pack/plugins/infra/public/alerting/log_threshold/index.ts
+++ b/x-pack/plugins/infra/public/alerting/log_threshold/index.ts
@@ -5,5 +5,5 @@
* 2.0.
*/
-export * from './log_threshold_alert_type';
+export * from './log_threshold_rule_type';
export { AlertDropdown } from './components/alert_dropdown';
diff --git a/x-pack/plugins/infra/public/alerting/log_threshold/log_threshold_alert_type.ts b/x-pack/plugins/infra/public/alerting/log_threshold/log_threshold_rule_type.ts
similarity index 92%
rename from x-pack/plugins/infra/public/alerting/log_threshold/log_threshold_alert_type.ts
rename to x-pack/plugins/infra/public/alerting/log_threshold/log_threshold_rule_type.ts
index c6b2385f93c65..b0a8737a994a1 100644
--- a/x-pack/plugins/infra/public/alerting/log_threshold/log_threshold_alert_type.ts
+++ b/x-pack/plugins/infra/public/alerting/log_threshold/log_threshold_rule_type.ts
@@ -11,11 +11,11 @@ import { ObservabilityRuleTypeModel } from '../../../../observability/public';
import {
LOG_DOCUMENT_COUNT_ALERT_TYPE_ID,
PartialAlertParams,
-} from '../../../common/alerting/logs/log_threshold/types';
+} from '../../../common/alerting/logs/log_threshold';
import { formatRuleData } from './rule_data_formatters';
import { validateExpression } from './validation';
-export function createLogThresholdAlertType(): ObservabilityRuleTypeModel {
+export function createLogThresholdRuleType(): ObservabilityRuleTypeModel {
return {
id: LOG_DOCUMENT_COUNT_ALERT_TYPE_ID,
description: i18n.translate('xpack.infra.logs.alertFlyout.alertDescription', {
diff --git a/x-pack/plugins/infra/public/alerting/metric_anomaly/index.ts b/x-pack/plugins/infra/public/alerting/metric_anomaly/index.ts
index f8dbd46a0776c..1fab0ea89fe5a 100644
--- a/x-pack/plugins/infra/public/alerting/metric_anomaly/index.ts
+++ b/x-pack/plugins/infra/public/alerting/metric_anomaly/index.ts
@@ -8,16 +8,15 @@
import { i18n } from '@kbn/i18n';
import React from 'react';
import { METRIC_ANOMALY_ALERT_TYPE_ID } from '../../../common/alerting/metrics';
-// eslint-disable-next-line @kbn/eslint/no-restricted-paths
-import { AlertTypeModel } from '../../../../triggers_actions_ui/public/types';
-import { AlertTypeParams } from '../../../../alerting/common';
+import { AlertTypeModel } from '../../../../triggers_actions_ui/public';
+import { AlertTypeParams as RuleTypeParams } from '../../../../alerting/common';
import { validateMetricAnomaly } from './components/validation';
-interface MetricAnomalyAlertTypeParams extends AlertTypeParams {
+interface MetricAnomalyRuleTypeParams extends RuleTypeParams {
hasInfraMLCapabilities: boolean;
}
-export function createMetricAnomalyAlertType(): AlertTypeModel {
+export function createMetricAnomalyRuleType(): AlertTypeModel {
return {
id: METRIC_ANOMALY_ALERT_TYPE_ID,
description: i18n.translate('xpack.infra.metrics.anomaly.alertFlyout.alertDescription', {
diff --git a/x-pack/plugins/infra/public/alerting/metric_threshold/index.ts b/x-pack/plugins/infra/public/alerting/metric_threshold/index.ts
index 679019eb2e520..d45d090e0ec92 100644
--- a/x-pack/plugins/infra/public/alerting/metric_threshold/index.ts
+++ b/x-pack/plugins/infra/public/alerting/metric_threshold/index.ts
@@ -10,18 +10,18 @@ import React from 'react';
import { ObservabilityRuleTypeModel } from '../../../../observability/public';
import { validateMetricThreshold } from './components/validation';
import { formatReason } from './rule_data_formatters';
-import { AlertTypeParams } from '../../../../alerting/common';
+import { AlertTypeParams as RuleTypeParams } from '../../../../alerting/common';
import {
MetricExpressionParams,
METRIC_THRESHOLD_ALERT_TYPE_ID,
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
} from '../../../server/lib/alerting/metric_threshold/types';
-interface MetricThresholdAlertTypeParams extends AlertTypeParams {
+interface MetricThresholdRuleTypeParams extends RuleTypeParams {
criteria: MetricExpressionParams[];
}
-export function createMetricThresholdAlertType(): ObservabilityRuleTypeModel {
+export function createMetricThresholdRuleType(): ObservabilityRuleTypeModel {
return {
id: METRIC_THRESHOLD_ALERT_TYPE_ID,
description: i18n.translate('xpack.infra.metrics.alertFlyout.alertDescription', {
diff --git a/x-pack/plugins/infra/public/apps/common_providers.tsx b/x-pack/plugins/infra/public/apps/common_providers.tsx
index 64867c5743d0d..39808ab7d3ac4 100644
--- a/x-pack/plugins/infra/public/apps/common_providers.tsx
+++ b/x-pack/plugins/infra/public/apps/common_providers.tsx
@@ -10,6 +10,7 @@ import React, { useMemo } from 'react';
import { EuiThemeProvider } from '../../../../../src/plugins/kibana_react/common';
import {
KibanaContextProvider,
+ KibanaThemeProvider,
useUiSetting$,
} from '../../../../../src/plugins/kibana_react/public';
import { Storage } from '../../../../../src/plugins/kibana_utils/public';
@@ -44,7 +45,8 @@ export const CommonInfraProviders: React.FC<{
export const CoreProviders: React.FC<{
core: CoreStart;
plugins: InfraClientStartDeps;
-}> = ({ children, core, plugins }) => {
+ theme$: AppMountParameters['theme$'];
+}> = ({ children, core, plugins, theme$ }) => {
const { Provider: KibanaContextProviderForPlugin } = useMemo(
() => createKibanaContextForPlugin(core, plugins),
[core, plugins]
@@ -52,7 +54,9 @@ export const CoreProviders: React.FC<{
return (
- {children}
+
+ {children}
+
);
};
diff --git a/x-pack/plugins/infra/public/apps/logs_app.tsx b/x-pack/plugins/infra/public/apps/logs_app.tsx
index b512b5ce4a176..6ebaf3e805d91 100644
--- a/x-pack/plugins/infra/public/apps/logs_app.tsx
+++ b/x-pack/plugins/infra/public/apps/logs_app.tsx
@@ -23,7 +23,7 @@ import { prepareMountElement } from './common_styles';
export const renderApp = (
core: CoreStart,
plugins: InfraClientStartDeps,
- { element, history, setHeaderActionMenu }: AppMountParameters
+ { element, history, setHeaderActionMenu, theme$ }: AppMountParameters
) => {
const storage = new Storage(window.localStorage);
@@ -36,6 +36,7 @@ export const renderApp = (
history={history}
plugins={plugins}
setHeaderActionMenu={setHeaderActionMenu}
+ theme$={theme$}
/>,
element
);
@@ -51,11 +52,12 @@ const LogsApp: React.FC<{
plugins: InfraClientStartDeps;
setHeaderActionMenu: AppMountParameters['setHeaderActionMenu'];
storage: Storage;
-}> = ({ core, history, plugins, setHeaderActionMenu, storage }) => {
+ theme$: AppMountParameters['theme$'];
+}> = ({ core, history, plugins, setHeaderActionMenu, storage, theme$ }) => {
const uiCapabilities = core.application.capabilities;
return (
-
+
{
const storage = new Storage(window.localStorage);
@@ -37,6 +37,7 @@ export const renderApp = (
plugins={plugins}
setHeaderActionMenu={setHeaderActionMenu}
storage={storage}
+ theme$={theme$}
/>,
element
);
@@ -52,11 +53,12 @@ const MetricsApp: React.FC<{
plugins: InfraClientStartDeps;
setHeaderActionMenu: AppMountParameters['setHeaderActionMenu'];
storage: Storage;
-}> = ({ core, history, plugins, setHeaderActionMenu, storage }) => {
+ theme$: AppMountParameters['theme$'];
+}> = ({ core, history, plugins, setHeaderActionMenu, storage, theme$ }) => {
const uiCapabilities = core.application.capabilities;
return (
-
+
(
-
- {wrappedStory()}
-
- ),
+ (wrappedStory) => {wrappedStory()}
,
+ decorateWithGlobalStorybookThemeProviders,
],
parameters: {
layout: 'padded',
diff --git a/x-pack/plugins/infra/public/components/data_search_progress.stories.tsx b/x-pack/plugins/infra/public/components/data_search_progress.stories.tsx
index 7a4966ebeb6f2..0efbfb25c9749 100644
--- a/x-pack/plugins/infra/public/components/data_search_progress.stories.tsx
+++ b/x-pack/plugins/infra/public/components/data_search_progress.stories.tsx
@@ -8,17 +8,14 @@
import { PropsOf } from '@elastic/eui';
import { Meta, Story } from '@storybook/react/types-6-0';
import React from 'react';
-import { EuiThemeProvider } from '../../../../../src/plugins/kibana_react/common';
+import { decorateWithGlobalStorybookThemeProviders } from '../test_utils/use_global_storybook_theme';
import { DataSearchProgress } from './data_search_progress';
export default {
title: 'infra/dataSearch/DataSearchProgress',
decorators: [
- (wrappedStory) => (
-
- {wrappedStory()}
-
- ),
+ (wrappedStory) => {wrappedStory()}
,
+ decorateWithGlobalStorybookThemeProviders,
],
parameters: {
layout: 'padded',
diff --git a/x-pack/plugins/infra/public/components/loading/__examples__/index.stories.tsx b/x-pack/plugins/infra/public/components/loading/__examples__/index.stories.tsx
index b43991f4b9df4..161708b2bd358 100644
--- a/x-pack/plugins/infra/public/components/loading/__examples__/index.stories.tsx
+++ b/x-pack/plugins/infra/public/components/loading/__examples__/index.stories.tsx
@@ -5,10 +5,17 @@
* 2.0.
*/
-import { storiesOf } from '@storybook/react';
+import { Meta, Story } from '@storybook/react/types-6-0';
import React from 'react';
import { InfraLoadingPanel } from '..';
+import { decorateWithGlobalStorybookThemeProviders } from '../../../test_utils/use_global_storybook_theme';
-storiesOf('infra/InfraLoadingPanel', module).add('example', () => (
-
-));
+export default {
+ title: 'infra/InfraLoadingPanel',
+ decorators: [
+ (wrappedStory) => {wrappedStory()}
,
+ decorateWithGlobalStorybookThemeProviders,
+ ],
+} as Meta;
+
+export const LoadingPanel: Story = () => ;
diff --git a/x-pack/plugins/infra/public/components/log_stream/log_stream.stories.mdx b/x-pack/plugins/infra/public/components/log_stream/log_stream.stories.mdx
index 82b02059ecc1f..f11430586764d 100644
--- a/x-pack/plugins/infra/public/components/log_stream/log_stream.stories.mdx
+++ b/x-pack/plugins/infra/public/components/log_stream/log_stream.stories.mdx
@@ -4,12 +4,12 @@ import { delay } from 'rxjs/operators';
import { I18nProvider } from '@kbn/i18n-react';
import { KBN_FIELD_TYPES } from '../../../../../../src/plugins/data/public';
-import { EuiThemeProvider } from '../../../../../../src/plugins/kibana_react/common';
import { KibanaContextProvider } from '../../../../../../src/plugins/kibana_react/public';
import { LOG_ENTRIES_SEARCH_STRATEGY } from '../../../common/search_strategies/log_entries/log_entries';
import { createIndexPatternMock, createIndexPatternsMock } from '../../hooks/use_kibana_index_patterns.mock';
import { DEFAULT_SOURCE_CONFIGURATION } from '../../test_utils/source_configuration';
import { generateFakeEntries, ENTRIES_EMPTY } from '../../test_utils/entries';
+import { decorateWithGlobalStorybookThemeProviders } from '../../test_utils/use_global_storybook_theme';
import { LogStream } from './';
@@ -145,13 +145,12 @@ export const Template = (args) => ;
decorators={[
(story) => (
-
-
- {story()}
-
-
+
+ {story()}
+
),
+ decorateWithGlobalStorybookThemeProviders,
]}
/>
@@ -172,7 +171,8 @@ To use the component your plugin needs to follow certain criteria:
- Ensure `"infra"` and `"data"` are specified as a `requiredPlugins` in your plugin's `kibana.json`.
- Ensure the `` component is mounted inside the hierachy of a [`kibana-react` provider](https://github.com/elastic/kibana/blob/b2d0aa7b7fae1c89c8f9e8854ae73e71be64e765/src/plugins/kibana_react/README.md#L45). At a minimum, the kibana-react provider must pass `http` (from core start services) and `data` (from core plugin start dependencies).
-- Ensure the `` component is mounted inside the hierachy of a [`EuiThemeProvider`](https://github.com/elastic/kibana/blob/main/src/plugins/kibana_react/common/eui_styled_components.tsx).
+- Ensure the `` component is mounted inside the hierachy of a [`KibanaThemeProvider`](https://github.com/elastic/kibana/blob/31d2db035c905fb5819fa6dc2354f3be795a34cf/src/plugins/kibana_react/public/theme/kibana_theme_provider.tsx#L27).
+- Ensure the `` component is mounted inside the hierachy of a [`EuiThemeProvider`](https://github.com/elastic/kibana/blob/main/src/plugins/kibana_react/common/eui_styled_components.tsx). This is not the same as the provider exported by EUI. It bridges the gap between EUI and styled components and predates the css-in-js support in EUI.
## Usage
diff --git a/x-pack/plugins/infra/public/components/log_stream/log_stream_embeddable.tsx b/x-pack/plugins/infra/public/components/log_stream/log_stream_embeddable.tsx
index e3fc4ca1de565..19f7d7f1b4a7a 100644
--- a/x-pack/plugins/infra/public/components/log_stream/log_stream_embeddable.tsx
+++ b/x-pack/plugins/infra/public/components/log_stream/log_stream_embeddable.tsx
@@ -77,7 +77,7 @@ export class LogStreamEmbeddable extends Embeddable {
}
ReactDOM.render(
-
+
{renderStory()})
+ .addDecorator(decorateWithGlobalStorybookThemeProviders)
.add('Partitioned warnings', () => {
return (
(
-
- {renderStory()}
-
- ))
+ .addDecorator((renderStory) => {renderStory()}
)
+ .addDecorator(decorateWithGlobalStorybookThemeProviders)
.add('Reconfiguration with partitioned warnings', () => {
return (
{
return (
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
);
},
+ decorateWithGlobalStorybookThemeProviders,
],
argTypes: {
logIndices: {
diff --git a/x-pack/plugins/infra/public/plugin.ts b/x-pack/plugins/infra/public/plugin.ts
index 5565c90970ecd..bc3aff9f01637 100644
--- a/x-pack/plugins/infra/public/plugin.ts
+++ b/x-pack/plugins/infra/public/plugin.ts
@@ -31,19 +31,17 @@ export class Plugin implements InfraClientPluginClass {
registerFeatures(pluginsSetup.home);
}
- const { createInventoryMetricAlertType } = await import('./alerting/inventory');
- const { createLogThresholdAlertType } = await import('./alerting/log_threshold');
- const { createMetricThresholdAlertType } = await import('./alerting/metric_threshold');
+ const { createInventoryMetricRuleType } = await import('./alerting/inventory');
+ const { createLogThresholdRuleType } = await import('./alerting/log_threshold');
+ const { createMetricThresholdRuleType } = await import('./alerting/metric_threshold');
pluginsSetup.observability.observabilityRuleTypeRegistry.register(
- createInventoryMetricAlertType()
+ createInventoryMetricRuleType()
);
+ pluginsSetup.observability.observabilityRuleTypeRegistry.register(createLogThresholdRuleType());
pluginsSetup.observability.observabilityRuleTypeRegistry.register(
- createLogThresholdAlertType()
- );
- pluginsSetup.observability.observabilityRuleTypeRegistry.register(
- createMetricThresholdAlertType()
+ createMetricThresholdRuleType()
);
pluginsSetup.observability.dashboard.register({
appName: 'infra_logs',
diff --git a/x-pack/plugins/infra/public/test_utils/use_global_storybook_theme.tsx b/x-pack/plugins/infra/public/test_utils/use_global_storybook_theme.tsx
new file mode 100644
index 0000000000000..9330f83cd968a
--- /dev/null
+++ b/x-pack/plugins/infra/public/test_utils/use_global_storybook_theme.tsx
@@ -0,0 +1,59 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license 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 { StoryContext } from '@storybook/addons';
+import React, { useEffect, useMemo, useState } from 'react';
+import { BehaviorSubject } from 'rxjs';
+import type { CoreTheme } from '../../../../../src/core/public';
+import { EuiThemeProvider } from '../../../../../src/plugins/kibana_react/common';
+import { KibanaThemeProvider } from '../../../../../src/plugins/kibana_react/public';
+
+export const useGlobalStorybookTheme = ({ globals: { euiTheme } }: StoryContext) => {
+ const theme = useMemo(() => euiThemeFromId(euiTheme), [euiTheme]);
+ const [theme$] = useState(() => new BehaviorSubject(theme));
+
+ useEffect(() => {
+ theme$.next(theme);
+ }, [theme$, theme]);
+
+ return {
+ theme,
+ theme$,
+ };
+};
+
+export const GlobalStorybookThemeProviders: React.FC<{ storyContext: StoryContext }> = ({
+ children,
+ storyContext,
+}) => {
+ const { theme, theme$ } = useGlobalStorybookTheme(storyContext);
+ return (
+
+ {children}
+
+ );
+};
+
+export const decorateWithGlobalStorybookThemeProviders = <
+ StoryFnReactReturnType extends React.ReactNode
+>(
+ wrappedStory: () => StoryFnReactReturnType,
+ storyContext: StoryContext
+) => (
+
+ {wrappedStory()}
+
+);
+
+const euiThemeFromId = (themeId: string): CoreTheme => {
+ switch (themeId) {
+ case 'v8.dark':
+ return { darkMode: true };
+ default:
+ return { darkMode: false };
+ }
+};
diff --git a/x-pack/plugins/infra/server/lib/alerting/index.ts b/x-pack/plugins/infra/server/lib/alerting/index.ts
index fb3cd7702c3df..570b44fd44db9 100644
--- a/x-pack/plugins/infra/server/lib/alerting/index.ts
+++ b/x-pack/plugins/infra/server/lib/alerting/index.ts
@@ -5,4 +5,4 @@
* 2.0.
*/
-export { registerAlertTypes } from './register_alert_types';
+export { registerRuleTypes } from './register_rule_types';
diff --git a/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/evaluate_condition.ts b/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/evaluate_condition.ts
index 922b10e8bd2b0..364c6b5a0d23a 100644
--- a/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/evaluate_condition.ts
+++ b/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/evaluate_condition.ts
@@ -8,18 +8,18 @@
import { mapValues, last, first } from 'lodash';
import moment from 'moment';
import { ElasticsearchClient } from 'kibana/server';
-import { SnapshotCustomMetricInput } from '../../../../common/http_api/snapshot_api';
import {
isTooManyBucketsPreviewException,
TOO_MANY_BUCKETS_PREVIEW_EXCEPTION,
} from '../../../../common/alerting/metrics';
-import {
- InfraDatabaseSearchResponse,
- CallWithRequestParams,
-} from '../../adapters/framework/adapter_types';
+import { InfraDatabaseSearchResponse, CallWithRequestParams } from '../../adapters/framework';
import { Comparator, InventoryMetricConditions } from './types';
import { InventoryItemType, SnapshotMetricType } from '../../../../common/inventory_models/types';
-import { InfraTimerangeInput, SnapshotRequest } from '../../../../common/http_api/snapshot_api';
+import {
+ InfraTimerangeInput,
+ SnapshotRequest,
+ SnapshotCustomMetricInput,
+} from '../../../../common/http_api';
import { InfraSource } from '../../sources';
import { UNGROUPED_FACTORY_KEY } from '../common/utils';
import { getNodes } from '../../../routes/snapshot/lib/get_nodes';
diff --git a/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/inventory_metric_threshold_executor.ts b/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/inventory_metric_threshold_executor.ts
index 654d69eb7fabb..dda43178713c2 100644
--- a/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/inventory_metric_threshold_executor.ts
+++ b/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/inventory_metric_threshold_executor.ts
@@ -15,11 +15,14 @@ import { AlertStates } from './types';
import {
ActionGroupIdsOf,
ActionGroup,
- AlertInstanceContext,
- AlertInstanceState,
+ AlertInstanceContext as AlertContext,
+ AlertInstanceState as AlertState,
RecoveredActionGroup,
} from '../../../../../alerting/common';
-import { AlertInstance, AlertTypeState } from '../../../../../alerting/server';
+import {
+ AlertInstance as Alert,
+ AlertTypeState as RuleTypeState,
+} from '../../../../../alerting/server';
import { SnapshotMetricType } from '../../../../common/inventory_models/types';
import { InfraBackendLibs } from '../../infra_types';
import { METRIC_FORMATTERS } from '../../../../common/formatters/snapshot_metric_formats';
@@ -39,34 +42,34 @@ type InventoryMetricThresholdAllowedActionGroups = ActionGroupIdsOf<
typeof FIRED_ACTIONS | typeof WARNING_ACTIONS
>;
-export type InventoryMetricThresholdAlertTypeState = AlertTypeState; // no specific state used
-export type InventoryMetricThresholdAlertInstanceState = AlertInstanceState; // no specific state used
-export type InventoryMetricThresholdAlertInstanceContext = AlertInstanceContext; // no specific instance context used
+export type InventoryMetricThresholdRuleTypeState = RuleTypeState; // no specific state used
+export type InventoryMetricThresholdAlertState = AlertState; // no specific state used
+export type InventoryMetricThresholdAlertContext = AlertContext; // no specific instance context used
-type InventoryMetricThresholdAlertInstance = AlertInstance<
- InventoryMetricThresholdAlertInstanceState,
- InventoryMetricThresholdAlertInstanceContext,
+type InventoryMetricThresholdAlert = Alert<
+ InventoryMetricThresholdAlertState,
+ InventoryMetricThresholdAlertContext,
InventoryMetricThresholdAllowedActionGroups
>;
-type InventoryMetricThresholdAlertInstanceFactory = (
+type InventoryMetricThresholdAlertFactory = (
id: string,
reason: string,
threshold?: number | undefined,
value?: number | undefined
-) => InventoryMetricThresholdAlertInstance;
+) => InventoryMetricThresholdAlert;
export const createInventoryMetricThresholdExecutor = (libs: InfraBackendLibs) =>
libs.metricsRules.createLifecycleRuleExecutor<
InventoryMetricThresholdParams & Record,
- InventoryMetricThresholdAlertTypeState,
- InventoryMetricThresholdAlertInstanceState,
- InventoryMetricThresholdAlertInstanceContext,
+ InventoryMetricThresholdRuleTypeState,
+ InventoryMetricThresholdAlertState,
+ InventoryMetricThresholdAlertContext,
InventoryMetricThresholdAllowedActionGroups
>(async ({ services, params }) => {
const { criteria, filterQuery, sourceId, nodeType, alertOnNoData } = params;
if (criteria.length === 0) throw new Error('Cannot execute an alert with 0 conditions');
const { alertWithLifecycle, savedObjectsClient } = services;
- const alertInstanceFactory: InventoryMetricThresholdAlertInstanceFactory = (id, reason) =>
+ const alertFactory: InventoryMetricThresholdAlertFactory = (id, reason) =>
alertWithLifecycle({
id,
fields: {
@@ -82,8 +85,8 @@ export const createInventoryMetricThresholdExecutor = (libs: InfraBackendLibs) =
} catch (e) {
const actionGroupId = FIRED_ACTIONS.id; // Change this to an Error action group when able
const reason = buildInvalidQueryAlertReason(params.filterQueryText);
- const alertInstance = alertInstanceFactory('*', reason);
- alertInstance.scheduleActions(actionGroupId, {
+ const alert = alertFactory('*', reason);
+ alert.scheduleActions(actionGroupId, {
group: '*',
alertState: stateToAlertMessage[AlertStates.ERROR],
reason,
@@ -191,8 +194,8 @@ export const createInventoryMetricThresholdExecutor = (libs: InfraBackendLibs) =
? WARNING_ACTIONS.id
: FIRED_ACTIONS.id;
- const alertInstance = alertInstanceFactory(`${group}`, reason);
- alertInstance.scheduleActions(
+ const alert = alertFactory(`${group}`, reason);
+ alert.scheduleActions(
/**
* TODO: We're lying to the compiler here as explicitly calling `scheduleActions` on
* the RecoveredActionGroup isn't allowed
diff --git a/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/register_inventory_metric_threshold_alert_type.ts b/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/register_inventory_metric_threshold_rule_type.ts
similarity index 98%
rename from x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/register_inventory_metric_threshold_alert_type.ts
rename to x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/register_inventory_metric_threshold_rule_type.ts
index 77c85967e64f6..9776d1ab66915 100644
--- a/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/register_inventory_metric_threshold_alert_type.ts
+++ b/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/register_inventory_metric_threshold_rule_type.ts
@@ -58,7 +58,7 @@ const condition = schema.object({
),
});
-export async function registerMetricInventoryThresholdAlertType(
+export async function registerMetricInventoryThresholdRuleType(
alertingPlugin: PluginSetupContract,
libs: InfraBackendLibs
) {
diff --git a/x-pack/plugins/infra/server/lib/alerting/log_threshold/log_threshold_executor.test.ts b/x-pack/plugins/infra/server/lib/alerting/log_threshold/log_threshold_executor.test.ts
index e5d8bab948581..b5cf05512b353 100644
--- a/x-pack/plugins/infra/server/lib/alerting/log_threshold/log_threshold_executor.test.ts
+++ b/x-pack/plugins/infra/server/lib/alerting/log_threshold/log_threshold_executor.test.ts
@@ -22,7 +22,7 @@ import {
Criterion,
UngroupedSearchQueryResponse,
GroupedSearchQueryResponse,
-} from '../../../../common/alerting/logs/log_threshold/types';
+} from '../../../../common/alerting/logs/log_threshold';
import { alertsMock } from '../../../../../alerting/server/mocks';
import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
@@ -407,7 +407,7 @@ describe('Log threshold executor', () => {
describe('Results processors', () => {
describe('Can process ungrouped results', () => {
test('It handles the ALERT state correctly', () => {
- const alertInstanceUpdaterMock = jest.fn();
+ const alertUpdaterMock = jest.fn();
const alertParams = {
...baseAlertParams,
criteria: [positiveCriteria[0]],
@@ -423,12 +423,12 @@ describe('Log threshold executor', () => {
results,
alertParams,
alertsMock.createAlertInstanceFactory,
- alertInstanceUpdaterMock
+ alertUpdaterMock
);
// First call, second argument
- expect(alertInstanceUpdaterMock.mock.calls[0][1]).toBe(AlertStates.ALERT);
+ expect(alertUpdaterMock.mock.calls[0][1]).toBe(AlertStates.ALERT);
// First call, third argument
- expect(alertInstanceUpdaterMock.mock.calls[0][2]).toEqual([
+ expect(alertUpdaterMock.mock.calls[0][2]).toEqual([
{
actionGroup: 'logs.threshold.fired',
context: {
@@ -444,7 +444,7 @@ describe('Log threshold executor', () => {
describe('Can process grouped results', () => {
test('It handles the ALERT state correctly', () => {
- const alertInstanceUpdaterMock = jest.fn();
+ const alertUpdaterMock = jest.fn();
const alertParams = {
...baseAlertParams,
criteria: [positiveCriteria[0]],
@@ -487,13 +487,13 @@ describe('Log threshold executor', () => {
results,
alertParams,
alertsMock.createAlertInstanceFactory,
- alertInstanceUpdaterMock
+ alertUpdaterMock
);
- expect(alertInstanceUpdaterMock.mock.calls.length).toBe(2);
+ expect(alertUpdaterMock.mock.calls.length).toBe(2);
// First call, second argument
- expect(alertInstanceUpdaterMock.mock.calls[0][1]).toBe(AlertStates.ALERT);
+ expect(alertUpdaterMock.mock.calls[0][1]).toBe(AlertStates.ALERT);
// First call, third argument
- expect(alertInstanceUpdaterMock.mock.calls[0][2]).toEqual([
+ expect(alertUpdaterMock.mock.calls[0][2]).toEqual([
{
actionGroup: 'logs.threshold.fired',
context: {
@@ -506,9 +506,9 @@ describe('Log threshold executor', () => {
]);
// Second call, second argument
- expect(alertInstanceUpdaterMock.mock.calls[1][1]).toBe(AlertStates.ALERT);
+ expect(alertUpdaterMock.mock.calls[1][1]).toBe(AlertStates.ALERT);
// Second call, third argument
- expect(alertInstanceUpdaterMock.mock.calls[1][2]).toEqual([
+ expect(alertUpdaterMock.mock.calls[1][2]).toEqual([
{
actionGroup: 'logs.threshold.fired',
context: {
diff --git a/x-pack/plugins/infra/server/lib/alerting/log_threshold/log_threshold_executor.ts b/x-pack/plugins/infra/server/lib/alerting/log_threshold/log_threshold_executor.ts
index 6d2b074c45bb0..a41c70f5c2869 100644
--- a/x-pack/plugins/infra/server/lib/alerting/log_threshold/log_threshold_executor.ts
+++ b/x-pack/plugins/infra/server/lib/alerting/log_threshold/log_threshold_executor.ts
@@ -16,10 +16,10 @@ import { ElasticsearchClient } from 'kibana/server';
import {
ActionGroup,
ActionGroupIdsOf,
- AlertInstance,
- AlertInstanceContext,
- AlertInstanceState,
- AlertTypeState,
+ AlertInstance as Alert,
+ AlertInstanceContext as AlertContext,
+ AlertInstanceState as AlertState,
+ AlertTypeState as RuleTypeState,
} from '../../../../../alerting/server';
import {
AlertParams,
@@ -40,7 +40,7 @@ import {
RatioAlertParams,
UngroupedSearchQueryResponse,
UngroupedSearchQueryResponseRT,
-} from '../../../../common/alerting/logs/log_threshold/types';
+} from '../../../../common/alerting/logs/log_threshold';
import { resolveLogSourceConfiguration } from '../../../../common/log_sources';
import { decodeOrThrow } from '../../../../common/runtime_types';
import { getIntervalInSeconds } from '../../../utils/get_interval_in_seconds';
@@ -54,22 +54,22 @@ import {
} from './reason_formatters';
export type LogThresholdActionGroups = ActionGroupIdsOf;
-export type LogThresholdAlertTypeParams = AlertParams;
-export type LogThresholdAlertTypeState = AlertTypeState; // no specific state used
-export type LogThresholdAlertInstanceState = AlertInstanceState; // no specific state used
-export type LogThresholdAlertInstanceContext = AlertInstanceContext; // no specific instance context used
-
-type LogThresholdAlertInstance = AlertInstance<
- LogThresholdAlertInstanceState,
- LogThresholdAlertInstanceContext,
+export type LogThresholdRuleTypeParams = AlertParams;
+export type LogThresholdRuleTypeState = RuleTypeState; // no specific state used
+export type LogThresholdAlertState = AlertState; // no specific state used
+export type LogThresholdAlertContext = AlertContext; // no specific instance context used
+
+type LogThresholdAlert = Alert<
+ LogThresholdAlertState,
+ LogThresholdAlertContext,
LogThresholdActionGroups
>;
-type LogThresholdAlertInstanceFactory = (
+type LogThresholdAlertFactory = (
id: string,
reason: string,
value: number,
threshold: number
-) => LogThresholdAlertInstance;
+) => LogThresholdAlert;
const COMPOSITE_GROUP_SIZE = 2000;
@@ -88,15 +88,15 @@ const checkValueAgainstComparatorMap: {
export const createLogThresholdExecutor = (libs: InfraBackendLibs) =>
libs.logsRules.createLifecycleRuleExecutor<
- LogThresholdAlertTypeParams,
- LogThresholdAlertTypeState,
- LogThresholdAlertInstanceState,
- LogThresholdAlertInstanceContext,
+ LogThresholdRuleTypeParams,
+ LogThresholdRuleTypeState,
+ LogThresholdAlertState,
+ LogThresholdAlertContext,
LogThresholdActionGroups
>(async ({ services, params }) => {
const { alertWithLifecycle, savedObjectsClient, scopedClusterClient } = services;
const { sources } = libs;
- const alertInstanceFactory: LogThresholdAlertInstanceFactory = (id, reason, value, threshold) =>
+ const alertFactory: LogThresholdAlertFactory = (id, reason, value, threshold) =>
alertWithLifecycle({
id,
fields: {
@@ -125,7 +125,7 @@ export const createLogThresholdExecutor = (libs: InfraBackendLibs) =>
indices,
runtimeMappings,
scopedClusterClient.asCurrentUser,
- alertInstanceFactory
+ alertFactory
);
} else {
await executeRatioAlert(
@@ -134,7 +134,7 @@ export const createLogThresholdExecutor = (libs: InfraBackendLibs) =>
indices,
runtimeMappings,
scopedClusterClient.asCurrentUser,
- alertInstanceFactory
+ alertFactory
);
}
} catch (e) {
@@ -148,7 +148,7 @@ async function executeAlert(
indexPattern: string,
runtimeMappings: estypes.MappingRuntimeFields,
esClient: ElasticsearchClient,
- alertInstanceFactory: LogThresholdAlertInstanceFactory
+ alertFactory: LogThresholdAlertFactory
) {
const query = getESQuery(alertParams, timestampField, indexPattern, runtimeMappings);
@@ -160,15 +160,15 @@ async function executeAlert(
processGroupByResults(
await getGroupedResults(query, esClient),
alertParams,
- alertInstanceFactory,
- updateAlertInstance
+ alertFactory,
+ updateAlert
);
} else {
processUngroupedResults(
await getUngroupedResults(query, esClient),
alertParams,
- alertInstanceFactory,
- updateAlertInstance
+ alertFactory,
+ updateAlert
);
}
}
@@ -179,7 +179,7 @@ async function executeRatioAlert(
indexPattern: string,
runtimeMappings: estypes.MappingRuntimeFields,
esClient: ElasticsearchClient,
- alertInstanceFactory: LogThresholdAlertInstanceFactory
+ alertFactory: LogThresholdAlertFactory
) {
// Ratio alert params are separated out into two standard sets of alert params
const numeratorParams: AlertParams = {
@@ -211,8 +211,8 @@ async function executeRatioAlert(
numeratorGroupedResults,
denominatorGroupedResults,
alertParams,
- alertInstanceFactory,
- updateAlertInstance
+ alertFactory,
+ updateAlert
);
} else {
const numeratorUngroupedResults = await getUngroupedResults(numeratorQuery, esClient);
@@ -221,8 +221,8 @@ async function executeRatioAlert(
numeratorUngroupedResults,
denominatorUngroupedResults,
alertParams,
- alertInstanceFactory,
- updateAlertInstance
+ alertFactory,
+ updateAlert
);
}
}
@@ -241,20 +241,20 @@ const getESQuery = (
export const processUngroupedResults = (
results: UngroupedSearchQueryResponse,
params: CountAlertParams,
- alertInstanceFactory: LogThresholdAlertInstanceFactory,
- alertInstaceUpdater: AlertInstanceUpdater
+ alertFactory: LogThresholdAlertFactory,
+ alertUpdater: AlertUpdater
) => {
const { count, criteria } = params;
const documentCount = results.hits.total.value;
if (checkValueAgainstComparatorMap[count.comparator](documentCount, count.value)) {
- const alertInstance = alertInstanceFactory(
+ const alert = alertFactory(
UNGROUPED_FACTORY_KEY,
getReasonMessageForUngroupedCountAlert(documentCount, count.value, count.comparator),
documentCount,
count.value
);
- alertInstaceUpdater(alertInstance, AlertStates.ALERT, [
+ alertUpdater(alert, AlertStates.ALERT, [
{
actionGroup: FIRED_ACTIONS.id,
context: {
@@ -272,8 +272,8 @@ export const processUngroupedRatioResults = (
numeratorResults: UngroupedSearchQueryResponse,
denominatorResults: UngroupedSearchQueryResponse,
params: RatioAlertParams,
- alertInstanceFactory: LogThresholdAlertInstanceFactory,
- alertInstaceUpdater: AlertInstanceUpdater
+ alertFactory: LogThresholdAlertFactory,
+ alertUpdater: AlertUpdater
) => {
const { count, criteria } = params;
@@ -282,13 +282,13 @@ export const processUngroupedRatioResults = (
const ratio = getRatio(numeratorCount, denominatorCount);
if (ratio !== undefined && checkValueAgainstComparatorMap[count.comparator](ratio, count.value)) {
- const alertInstance = alertInstanceFactory(
+ const alert = alertFactory(
UNGROUPED_FACTORY_KEY,
getReasonMessageForUngroupedRatioAlert(ratio, count.value, count.comparator),
ratio,
count.value
);
- alertInstaceUpdater(alertInstance, AlertStates.ALERT, [
+ alertUpdater(alert, AlertStates.ALERT, [
{
actionGroup: FIRED_ACTIONS.id,
context: {
@@ -345,8 +345,8 @@ const getReducedGroupByResults = (
export const processGroupByResults = (
results: GroupedSearchQueryResponse['aggregations']['groups']['buckets'],
params: CountAlertParams,
- alertInstanceFactory: LogThresholdAlertInstanceFactory,
- alertInstaceUpdater: AlertInstanceUpdater
+ alertFactory: LogThresholdAlertFactory,
+ alertUpdater: AlertUpdater
) => {
const { count, criteria } = params;
@@ -356,7 +356,7 @@ export const processGroupByResults = (
const documentCount = group.documentCount;
if (checkValueAgainstComparatorMap[count.comparator](documentCount, count.value)) {
- const alertInstance = alertInstanceFactory(
+ const alert = alertFactory(
group.name,
getReasonMessageForGroupedCountAlert(
documentCount,
@@ -367,7 +367,7 @@ export const processGroupByResults = (
documentCount,
count.value
);
- alertInstaceUpdater(alertInstance, AlertStates.ALERT, [
+ alertUpdater(alert, AlertStates.ALERT, [
{
actionGroup: FIRED_ACTIONS.id,
context: {
@@ -386,8 +386,8 @@ export const processGroupByRatioResults = (
numeratorResults: GroupedSearchQueryResponse['aggregations']['groups']['buckets'],
denominatorResults: GroupedSearchQueryResponse['aggregations']['groups']['buckets'],
params: RatioAlertParams,
- alertInstanceFactory: LogThresholdAlertInstanceFactory,
- alertInstaceUpdater: AlertInstanceUpdater
+ alertFactory: LogThresholdAlertFactory,
+ alertUpdater: AlertUpdater
) => {
const { count, criteria } = params;
@@ -407,7 +407,7 @@ export const processGroupByRatioResults = (
ratio !== undefined &&
checkValueAgainstComparatorMap[count.comparator](ratio, count.value)
) {
- const alertInstance = alertInstanceFactory(
+ const alert = alertFactory(
numeratorGroup.name,
getReasonMessageForGroupedRatioAlert(
ratio,
@@ -418,7 +418,7 @@ export const processGroupByRatioResults = (
ratio,
count.value
);
- alertInstaceUpdater(alertInstance, AlertStates.ALERT, [
+ alertUpdater(alert, AlertStates.ALERT, [
{
actionGroup: FIRED_ACTIONS.id,
context: {
@@ -434,24 +434,24 @@ export const processGroupByRatioResults = (
});
};
-type AlertInstanceUpdater = (
- alertInstance: AlertInstance,
+type AlertUpdater = (
+ alert: Alert,
state: AlertStates,
- actions?: Array<{ actionGroup: LogThresholdActionGroups; context: AlertInstanceContext }>
+ actions?: Array<{ actionGroup: LogThresholdActionGroups; context: AlertContext }>
) => void;
-export const updateAlertInstance: AlertInstanceUpdater = (alertInstance, state, actions) => {
+export const updateAlert: AlertUpdater = (alert, state, actions) => {
if (actions && actions.length > 0) {
const sharedContext = {
timestamp: new Date().toISOString(),
};
actions.forEach((actionSet) => {
const { actionGroup, context } = actionSet;
- alertInstance.scheduleActions(actionGroup, { ...sharedContext, ...context });
+ alert.scheduleActions(actionGroup, { ...sharedContext, ...context });
});
}
- alertInstance.replaceState({
+ alert.replaceState({
alertState: state,
});
};
diff --git a/x-pack/plugins/infra/server/lib/alerting/log_threshold/register_log_threshold_alert_type.ts b/x-pack/plugins/infra/server/lib/alerting/log_threshold/register_log_threshold_rule_type.ts
similarity index 97%
rename from x-pack/plugins/infra/server/lib/alerting/log_threshold/register_log_threshold_alert_type.ts
rename to x-pack/plugins/infra/server/lib/alerting/log_threshold/register_log_threshold_rule_type.ts
index 3d0bac3dd2bf5..05dc2682fc3b7 100644
--- a/x-pack/plugins/infra/server/lib/alerting/log_threshold/register_log_threshold_alert_type.ts
+++ b/x-pack/plugins/infra/server/lib/alerting/log_threshold/register_log_threshold_rule_type.ts
@@ -11,7 +11,7 @@ import { createLogThresholdExecutor, FIRED_ACTIONS } from './log_threshold_execu
import {
LOG_DOCUMENT_COUNT_ALERT_TYPE_ID,
alertParamsRT,
-} from '../../../../common/alerting/logs/log_threshold/types';
+} from '../../../../common/alerting/logs/log_threshold';
import { InfraBackendLibs } from '../../infra_types';
import { decodeOrThrow } from '../../../../common/runtime_types';
@@ -71,7 +71,7 @@ const denominatorConditionsActionVariableDescription = i18n.translate(
}
);
-export async function registerLogThresholdAlertType(
+export async function registerLogThresholdRuleType(
alertingPlugin: PluginSetupContract,
libs: InfraBackendLibs
) {
diff --git a/x-pack/plugins/infra/server/lib/alerting/metric_anomaly/metric_anomaly_executor.ts b/x-pack/plugins/infra/server/lib/alerting/metric_anomaly/metric_anomaly_executor.ts
index f7dbe95b4161c..a0eac87ed161e 100644
--- a/x-pack/plugins/infra/server/lib/alerting/metric_anomaly/metric_anomaly_executor.ts
+++ b/x-pack/plugins/infra/server/lib/alerting/metric_anomaly/metric_anomaly_executor.ts
@@ -8,20 +8,20 @@
import { i18n } from '@kbn/i18n';
import { first } from 'lodash';
import moment from 'moment';
+import { KibanaRequest } from 'kibana/server';
import { stateToAlertMessage } from '../common/messages';
import { MetricAnomalyParams } from '../../../../common/alerting/metrics';
import { MappedAnomalyHit } from '../../infra_ml';
import { AlertStates } from '../common/types';
import {
ActionGroup,
- AlertInstanceContext,
- AlertInstanceState,
+ AlertInstanceContext as AlertContext,
+ AlertInstanceState as AlertState,
} from '../../../../../alerting/common';
-import { AlertExecutorOptions } from '../../../../../alerting/server';
+import { AlertExecutorOptions as RuleExecutorOptions } from '../../../../../alerting/server';
import { getIntervalInSeconds } from '../../../utils/get_interval_in_seconds';
-import { MetricAnomalyAllowedActionGroups } from './register_metric_anomaly_alert_type';
+import { MetricAnomalyAllowedActionGroups } from './register_metric_anomaly_rule_type';
import { MlPluginSetup } from '../../../../../ml/server';
-import { KibanaRequest } from '../../../../../../../src/core/server';
import { InfraBackendLibs } from '../../infra_types';
import { evaluateCondition } from './evaluate_condition';
@@ -31,14 +31,14 @@ export const createMetricAnomalyExecutor =
services,
params,
startedAt,
- }: AlertExecutorOptions<
+ }: RuleExecutorOptions<
/**
* TODO: Remove this use of `any` by utilizing a proper type
*/
Record,
Record,
- AlertInstanceState,
- AlertInstanceContext,
+ AlertState,
+ AlertContext,
MetricAnomalyAllowedActionGroups
>) => {
if (!ml) {
@@ -84,9 +84,9 @@ export const createMetricAnomalyExecutor =
typical,
influencers,
} = first(data as MappedAnomalyHit[])!;
- const alertInstance = services.alertInstanceFactory(`${nodeType}-${metric}`);
+ const alert = services.alertInstanceFactory(`${nodeType}-${metric}`);
- alertInstance.scheduleActions(FIRED_ACTIONS_ID, {
+ alert.scheduleActions(FIRED_ACTIONS_ID, {
alertState: stateToAlertMessage[AlertStates.ALERT],
timestamp: moment(anomalyStartTime).toISOString(),
anomalyScore,
diff --git a/x-pack/plugins/infra/server/lib/alerting/metric_anomaly/register_metric_anomaly_alert_type.ts b/x-pack/plugins/infra/server/lib/alerting/metric_anomaly/register_metric_anomaly_rule_type.ts
similarity index 95%
rename from x-pack/plugins/infra/server/lib/alerting/metric_anomaly/register_metric_anomaly_alert_type.ts
rename to x-pack/plugins/infra/server/lib/alerting/metric_anomaly/register_metric_anomaly_rule_type.ts
index 2e3660c901b4a..dd90297355742 100644
--- a/x-pack/plugins/infra/server/lib/alerting/metric_anomaly/register_metric_anomaly_alert_type.ts
+++ b/x-pack/plugins/infra/server/lib/alerting/metric_anomaly/register_metric_anomaly_rule_type.ts
@@ -9,9 +9,9 @@ import { schema } from '@kbn/config-schema';
import { i18n } from '@kbn/i18n';
import { MlPluginSetup } from '../../../../../ml/server';
import {
- AlertType,
- AlertInstanceState,
- AlertInstanceContext,
+ AlertType as RuleType,
+ AlertInstanceState as AlertState,
+ AlertInstanceContext as AlertContext,
} from '../../../../../alerting/server';
import {
createMetricAnomalyExecutor,
@@ -26,18 +26,18 @@ import { RecoveredActionGroupId } from '../../../../../alerting/common';
export type MetricAnomalyAllowedActionGroups = typeof FIRED_ACTIONS_ID;
-export const registerMetricAnomalyAlertType = (
+export const registerMetricAnomalyRuleType = (
libs: InfraBackendLibs,
ml?: MlPluginSetup
-): AlertType<
+): RuleType<
/**
* TODO: Remove this use of `any` by utilizing a proper type
*/
Record,
never, // Only use if defining useSavedObjectReferences hook
Record,
- AlertInstanceState,
- AlertInstanceContext,
+ AlertState,
+ AlertContext,
MetricAnomalyAllowedActionGroups,
RecoveredActionGroupId
> => ({
diff --git a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/lib/evaluate_alert.ts b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/lib/evaluate_rule.ts
similarity index 98%
rename from x-pack/plugins/infra/server/lib/alerting/metric_threshold/lib/evaluate_alert.ts
rename to x-pack/plugins/infra/server/lib/alerting/metric_threshold/lib/evaluate_rule.ts
index e8910572d4a09..47727314cc64f 100644
--- a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/lib/evaluate_alert.ts
+++ b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/lib/evaluate_rule.ts
@@ -53,7 +53,7 @@ interface CompositeAggregationsResponse {
};
}
-export interface EvaluatedAlertParams {
+export interface EvaluatedRuleParams {
criteria: MetricExpressionParams[];
groupBy: string | undefined | string[];
filterQuery?: string;
@@ -61,7 +61,7 @@ export interface EvaluatedAlertParams {
shouldDropPartialBuckets?: boolean;
}
-export const evaluateAlert = (
+export const evaluateRule = (
esClient: ElasticsearchClient,
params: Params,
config: InfraSource['configuration'],
diff --git a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.test.ts b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.test.ts
index 792e3a60747d0..5a75b18e47590 100644
--- a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.test.ts
+++ b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.test.ts
@@ -19,7 +19,10 @@ import { createLifecycleRuleExecutorMock } from '../../../../../rule_registry/se
import { InfraSources } from '../../sources';
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
import { elasticsearchClientMock } from 'src/core/server/elasticsearch/client/mocks';
-import { AlertInstanceContext, AlertInstanceState } from '../../../../../alerting/server';
+import {
+ AlertInstanceContext as AlertContext,
+ AlertInstanceState as AlertState,
+} from '../../../../../alerting/server';
import {
Aggregators,
Comparator,
@@ -763,8 +766,7 @@ const mockLibs: any = {
const executor = createMetricThresholdExecutor(mockLibs);
const alertsServices = alertsMock.createAlertServices();
-const services: AlertServicesMock &
- LifecycleAlertServices = {
+const services: AlertServicesMock & LifecycleAlertServices = {
...alertsServices,
...ruleRegistryMocks.createLifecycleAlertServices(alertsServices),
};
diff --git a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.ts b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.ts
index c4e485af5bdb1..810055fc1771a 100644
--- a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.ts
+++ b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.ts
@@ -12,10 +12,13 @@ import { ALERT_REASON } from '@kbn/rule-data-utils';
import {
ActionGroupIdsOf,
RecoveredActionGroup,
- AlertInstanceState,
- AlertInstanceContext,
+ AlertInstanceState as AlertState,
+ AlertInstanceContext as AlertContext,
} from '../../../../../alerting/common';
-import { AlertTypeState, AlertInstance } from '../../../../../alerting/server';
+import {
+ AlertTypeState as RuleTypeState,
+ AlertInstance as Alert,
+} from '../../../../../alerting/server';
import { InfraBackendLibs } from '../../infra_types';
import {
buildErrorAlertReason,
@@ -28,47 +31,47 @@ import {
import { UNGROUPED_FACTORY_KEY } from '../common/utils';
import { createFormatter } from '../../../../common/formatters';
import { AlertStates, Comparator } from './types';
-import { evaluateAlert, EvaluatedAlertParams } from './lib/evaluate_alert';
+import { evaluateRule, EvaluatedRuleParams } from './lib/evaluate_rule';
-export type MetricThresholdAlertTypeParams = Record;
-export type MetricThresholdAlertTypeState = AlertTypeState & {
+export type MetricThresholdRuleParams = Record;
+export type MetricThresholdRuleTypeState = RuleTypeState & {
groups: string[];
groupBy?: string | string[];
filterQuery?: string;
};
-export type MetricThresholdAlertInstanceState = AlertInstanceState; // no specific instace state used
-export type MetricThresholdAlertInstanceContext = AlertInstanceContext; // no specific instace state used
+export type MetricThresholdAlertState = AlertState; // no specific instace state used
+export type MetricThresholdAlertContext = AlertContext; // no specific instace state used
type MetricThresholdAllowedActionGroups = ActionGroupIdsOf<
typeof FIRED_ACTIONS | typeof WARNING_ACTIONS
>;
-type MetricThresholdAlertInstance = AlertInstance<
- MetricThresholdAlertInstanceState,
- MetricThresholdAlertInstanceContext,
+type MetricThresholdAlert = Alert<
+ MetricThresholdAlertState,
+ MetricThresholdAlertContext,
MetricThresholdAllowedActionGroups
>;
-type MetricThresholdAlertInstanceFactory = (
+type MetricThresholdAlertFactory = (
id: string,
reason: string,
threshold?: number | undefined,
value?: number | undefined
-) => MetricThresholdAlertInstance;
+) => MetricThresholdAlert;
export const createMetricThresholdExecutor = (libs: InfraBackendLibs) =>
libs.metricsRules.createLifecycleRuleExecutor<
- MetricThresholdAlertTypeParams,
- MetricThresholdAlertTypeState,
- MetricThresholdAlertInstanceState,
- MetricThresholdAlertInstanceContext,
+ MetricThresholdRuleParams,
+ MetricThresholdRuleTypeState,
+ MetricThresholdAlertState,
+ MetricThresholdAlertContext,
MetricThresholdAllowedActionGroups
>(async function (options) {
const { services, params, state } = options;
const { criteria } = params;
if (criteria.length === 0) throw new Error('Cannot execute an alert with 0 conditions');
const { alertWithLifecycle, savedObjectsClient } = services;
- const alertInstanceFactory: MetricThresholdAlertInstanceFactory = (id, reason) =>
+ const alertFactory: MetricThresholdAlertFactory = (id, reason) =>
alertWithLifecycle({
id,
fields: {
@@ -94,8 +97,8 @@ export const createMetricThresholdExecutor = (libs: InfraBackendLibs) =>
const timestamp = moment().toISOString();
const actionGroupId = FIRED_ACTIONS.id; // Change this to an Error action group when able
const reason = buildInvalidQueryAlertReason(params.filterQueryText);
- const alertInstance = alertInstanceFactory(UNGROUPED_FACTORY_KEY, reason);
- alertInstance.scheduleActions(actionGroupId, {
+ const alert = alertFactory(UNGROUPED_FACTORY_KEY, reason);
+ alert.scheduleActions(actionGroupId, {
group: UNGROUPED_FACTORY_KEY,
alertState: stateToAlertMessage[AlertStates.ERROR],
reason,
@@ -128,9 +131,9 @@ export const createMetricThresholdExecutor = (libs: InfraBackendLibs) =>
state.groups?.filter((g) => g !== UNGROUPED_FACTORY_KEY) ?? []
: [];
- const alertResults = await evaluateAlert(
+ const alertResults = await evaluateRule(
services.scopedClusterClient.asCurrentUser,
- params as EvaluatedAlertParams,
+ params as EvaluatedRuleParams,
config,
prevGroups
);
@@ -227,8 +230,8 @@ export const createMetricThresholdExecutor = (libs: InfraBackendLibs) =>
: nextState === AlertStates.WARNING
? WARNING_ACTIONS.id
: FIRED_ACTIONS.id;
- const alertInstance = alertInstanceFactory(`${group}`, reason);
- alertInstance.scheduleActions(actionGroupId, {
+ const alert = alertFactory(`${group}`, reason);
+ alert.scheduleActions(actionGroupId, {
group,
alertState: stateToAlertMessage[nextState],
reason,
diff --git a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/register_metric_threshold_alert_type.ts b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/register_metric_threshold_rule_type.ts
similarity index 97%
rename from x-pack/plugins/infra/server/lib/alerting/metric_threshold/register_metric_threshold_alert_type.ts
rename to x-pack/plugins/infra/server/lib/alerting/metric_threshold/register_metric_threshold_rule_type.ts
index 251531b4515a9..0a67dbdc3190f 100644
--- a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/register_metric_threshold_alert_type.ts
+++ b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/register_metric_threshold_rule_type.ts
@@ -9,7 +9,7 @@ import { schema } from '@kbn/config-schema';
import { i18n } from '@kbn/i18n';
import { ActionGroupIdsOf } from '../../../../../alerting/common';
import { AlertType, PluginSetupContract } from '../../../../../alerting/server';
-import { METRIC_EXPLORER_AGGREGATIONS } from '../../../../common/http_api/metrics_explorer';
+import { METRIC_EXPLORER_AGGREGATIONS } from '../../../../common/http_api';
import {
createMetricThresholdExecutor,
FIRED_ACTIONS,
@@ -35,7 +35,7 @@ export type MetricThresholdAlertType = Omit & {
ActionGroupIdsOf: MetricThresholdAllowedActionGroups;
};
-export async function registerMetricThresholdAlertType(
+export async function registerMetricThresholdRuleType(
alertingPlugin: PluginSetupContract,
libs: InfraBackendLibs
) {
diff --git a/x-pack/plugins/infra/server/lib/alerting/register_alert_types.ts b/x-pack/plugins/infra/server/lib/alerting/register_alert_types.ts
deleted file mode 100644
index d0af9ac4ce669..0000000000000
--- a/x-pack/plugins/infra/server/lib/alerting/register_alert_types.ts
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { PluginSetupContract } from '../../../../alerting/server';
-import { registerMetricThresholdAlertType } from './metric_threshold/register_metric_threshold_alert_type';
-import { registerMetricInventoryThresholdAlertType } from './inventory_metric_threshold/register_inventory_metric_threshold_alert_type';
-import { registerMetricAnomalyAlertType } from './metric_anomaly/register_metric_anomaly_alert_type';
-
-import { registerLogThresholdAlertType } from './log_threshold/register_log_threshold_alert_type';
-import { InfraBackendLibs } from '../infra_types';
-import { MlPluginSetup } from '../../../../ml/server';
-
-const registerAlertTypes = (
- alertingPlugin: PluginSetupContract,
- libs: InfraBackendLibs,
- ml?: MlPluginSetup
-) => {
- if (alertingPlugin) {
- alertingPlugin.registerType(registerMetricAnomalyAlertType(libs, ml));
-
- const registerFns = [
- registerLogThresholdAlertType,
- registerMetricInventoryThresholdAlertType,
- registerMetricThresholdAlertType,
- ];
- registerFns.forEach((fn) => {
- fn(alertingPlugin, libs);
- });
- }
-};
-
-export { registerAlertTypes };
diff --git a/x-pack/plugins/infra/server/lib/alerting/register_rule_types.ts b/x-pack/plugins/infra/server/lib/alerting/register_rule_types.ts
new file mode 100644
index 0000000000000..a60de8b9c0ff9
--- /dev/null
+++ b/x-pack/plugins/infra/server/lib/alerting/register_rule_types.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 { PluginSetupContract } from '../../../../alerting/server';
+import { registerMetricThresholdRuleType } from './metric_threshold/register_metric_threshold_rule_type';
+import { registerMetricInventoryThresholdRuleType } from './inventory_metric_threshold/register_inventory_metric_threshold_rule_type';
+import { registerMetricAnomalyRuleType } from './metric_anomaly/register_metric_anomaly_rule_type';
+import { registerLogThresholdRuleType } from './log_threshold/register_log_threshold_rule_type';
+import { InfraBackendLibs } from '../infra_types';
+import { MlPluginSetup } from '../../../../ml/server';
+
+const registerRuleTypes = (
+ alertingPlugin: PluginSetupContract,
+ libs: InfraBackendLibs,
+ ml?: MlPluginSetup
+) => {
+ if (alertingPlugin) {
+ alertingPlugin.registerType(registerMetricAnomalyRuleType(libs, ml));
+
+ const registerFns = [
+ registerLogThresholdRuleType,
+ registerMetricInventoryThresholdRuleType,
+ registerMetricThresholdRuleType,
+ ];
+ registerFns.forEach((fn) => {
+ fn(alertingPlugin, libs);
+ });
+ }
+};
+
+export { registerRuleTypes };
diff --git a/x-pack/plugins/infra/server/plugin.ts b/x-pack/plugins/infra/server/plugin.ts
index 4e655f200d94f..79998ae6d5690 100644
--- a/x-pack/plugins/infra/server/plugin.ts
+++ b/x-pack/plugins/infra/server/plugin.ts
@@ -27,7 +27,7 @@ import { KibanaFramework } from './lib/adapters/framework/kibana_framework_adapt
import { InfraKibanaLogEntriesAdapter } from './lib/adapters/log_entries/kibana_log_entries_adapter';
import { KibanaMetricsAdapter } from './lib/adapters/metrics/kibana_metrics_adapter';
import { InfraElasticsearchSourceStatusAdapter } from './lib/adapters/source_status';
-import { registerAlertTypes } from './lib/alerting';
+import { registerRuleTypes } from './lib/alerting';
import { InfraFieldsDomain } from './lib/domains/fields_domain';
import { InfraLogEntriesDomain } from './lib/domains/log_entries_domain';
import { InfraMetricsDomain } from './lib/domains/metrics_domain';
@@ -161,7 +161,7 @@ export class InfraServerPlugin implements Plugin {
]);
initInfraServer(this.libs);
- registerAlertTypes(plugins.alerting, this.libs, plugins.ml);
+ registerRuleTypes(plugins.alerting, this.libs, plugins.ml);
core.http.registerRouteHandlerContext(
'infra',
diff --git a/x-pack/plugins/lens/common/expressions/pie_chart/pie_chart.ts b/x-pack/plugins/lens/common/expressions/pie_chart/pie_chart.ts
index 053b46e480c7b..4f2736d739b11 100644
--- a/x-pack/plugins/lens/common/expressions/pie_chart/pie_chart.ts
+++ b/x-pack/plugins/lens/common/expressions/pie_chart/pie_chart.ts
@@ -83,6 +83,10 @@ export const pie: ExpressionFunctionDefinition<
types: ['boolean'],
help: '',
},
+ showValuesInLegend: {
+ types: ['boolean'],
+ help: '',
+ },
legendPosition: {
types: ['string'],
options: [Position.Top, Position.Right, Position.Bottom, Position.Left],
diff --git a/x-pack/plugins/lens/common/expressions/pie_chart/types.ts b/x-pack/plugins/lens/common/expressions/pie_chart/types.ts
index 8c9ec4e5a54e7..a7aa92369dce2 100644
--- a/x-pack/plugins/lens/common/expressions/pie_chart/types.ts
+++ b/x-pack/plugins/lens/common/expressions/pie_chart/types.ts
@@ -17,6 +17,7 @@ export interface SharedPieLayerState {
categoryDisplay: 'default' | 'inside' | 'hide';
legendDisplay: 'default' | 'show' | 'hide';
legendPosition?: 'left' | 'right' | 'top' | 'bottom';
+ showValuesInLegend?: boolean;
nestedLegend?: boolean;
percentDecimals?: number;
legendMaxLines?: number;
diff --git a/x-pack/plugins/lens/public/pie_visualization/render_function.tsx b/x-pack/plugins/lens/public/pie_visualization/render_function.tsx
index 3b9fdaf094822..559a3cfc48164 100644
--- a/x-pack/plugins/lens/public/pie_visualization/render_function.tsx
+++ b/x-pack/plugins/lens/public/pie_visualization/render_function.tsx
@@ -86,6 +86,7 @@ export function PieComponent(
truncateLegend,
hideLabels,
palette,
+ showValuesInLegend,
} = props.args;
const chartTheme = chartsThemeService.useChartsTheme();
const chartBaseTheme = chartsThemeService.useChartsBaseTheme();
@@ -315,7 +316,7 @@ export function PieComponent(
(legend.getShowLegendDefault?.(bucketColumns) ?? false)))
}
flatLegend={legend.flat}
- showLegendExtra={legend.showValues}
+ showLegendExtra={showValuesInLegend}
legendPosition={legendPosition || Position.Right}
legendMaxDepth={nestedLegend ? undefined : 1 /* Color is based only on first layer */}
onElementClick={props.interactive ?? true ? onElementClickHandler : undefined}
diff --git a/x-pack/plugins/lens/public/pie_visualization/render_helpers.test.ts b/x-pack/plugins/lens/public/pie_visualization/render_helpers.test.ts
index d86500ff8a4fa..bcd9d79babbab 100644
--- a/x-pack/plugins/lens/public/pie_visualization/render_helpers.test.ts
+++ b/x-pack/plugins/lens/public/pie_visualization/render_helpers.test.ts
@@ -14,8 +14,10 @@ import {
byDataColorPaletteMap,
extractUniqTermsMap,
checkTableForContainsSmallValues,
+ shouldShowValuesInLegend,
} from './render_helpers';
import { chartPluginMock } from '../../../../../src/plugins/charts/public/mocks';
+import type { PieLayerState } from '../../common/expressions';
describe('render helpers', () => {
describe('#getSliceValue', () => {
@@ -374,4 +376,28 @@ describe('render helpers', () => {
expect(checkTableForContainsSmallValues(datatable, columnId, 1)).toBeFalsy();
});
});
+
+ describe('#shouldShowValuesInLegend', () => {
+ it('should firstly read the state value', () => {
+ expect(
+ shouldShowValuesInLegend({ showValuesInLegend: true } as PieLayerState, 'waffle')
+ ).toBeTruthy();
+
+ expect(
+ shouldShowValuesInLegend({ showValuesInLegend: false } as PieLayerState, 'waffle')
+ ).toBeFalsy();
+ });
+
+ it('should read value from meta in case of value in state is undefined', () => {
+ expect(
+ shouldShowValuesInLegend({ showValuesInLegend: undefined } as PieLayerState, 'waffle')
+ ).toBeTruthy();
+
+ expect(shouldShowValuesInLegend({} as PieLayerState, 'waffle')).toBeTruthy();
+
+ expect(
+ shouldShowValuesInLegend({ showValuesInLegend: undefined } as PieLayerState, 'pie')
+ ).toBeFalsy();
+ });
+ });
});
diff --git a/x-pack/plugins/lens/public/pie_visualization/render_helpers.ts b/x-pack/plugins/lens/public/pie_visualization/render_helpers.ts
index fa20eb6f20fa8..a9685e13e1774 100644
--- a/x-pack/plugins/lens/public/pie_visualization/render_helpers.ts
+++ b/x-pack/plugins/lens/public/pie_visualization/render_helpers.ts
@@ -8,8 +8,9 @@
import type { Datum, LayerValue } from '@elastic/charts';
import type { Datatable, DatatableColumn } from 'src/plugins/expressions/public';
import type { LensFilterEvent } from '../types';
-import type { PieChartTypes } from '../../common/expressions/pie_chart/types';
+import type { PieChartTypes, PieLayerState } from '../../common/expressions/pie_chart/types';
import type { PaletteDefinition, PaletteOutput } from '../../../../../src/plugins/charts/public';
+import { PartitionChartsMeta } from './partition_charts_meta';
export function getSliceValue(d: Datum, metricColumn: DatatableColumn) {
const value = d[metricColumn.id];
@@ -44,6 +45,14 @@ export const isPartitionShape = (shape: PieChartTypes | string) =>
export const isTreemapOrMosaicShape = (shape: PieChartTypes | string) =>
['treemap', 'mosaic'].includes(shape);
+export const shouldShowValuesInLegend = (layer: PieLayerState, shape: PieChartTypes) => {
+ if ('showValues' in PartitionChartsMeta[shape]?.legend) {
+ return layer.showValuesInLegend ?? PartitionChartsMeta[shape]?.legend?.showValues ?? true;
+ }
+
+ return false;
+};
+
export const extractUniqTermsMap = (dataTable: Datatable, columnId: string) =>
[...new Set(dataTable.rows.map((item) => item[columnId]))].reduce(
(acc, item, index) => ({
diff --git a/x-pack/plugins/lens/public/pie_visualization/suggestions.test.ts b/x-pack/plugins/lens/public/pie_visualization/suggestions.test.ts
index 92dde282da502..a2e3f6d3ca865 100644
--- a/x-pack/plugins/lens/public/pie_visualization/suggestions.test.ts
+++ b/x-pack/plugins/lens/public/pie_visualization/suggestions.test.ts
@@ -784,7 +784,7 @@ describe('suggestions', () => {
).toHaveLength(0);
});
- it('mosaic type should be added only in case of 2 groups', () => {
+ it('mosaic type should be hidden from the suggestion list', () => {
expect(
suggestions({
table: {
@@ -827,97 +827,6 @@ describe('suggestions', () => {
},
keptLayerIds: ['first'],
}).filter(({ hide, state }) => !hide && state.shape === 'mosaic')
- ).toMatchInlineSnapshot(`
- Array [
- Object {
- "hide": false,
- "previewIcon": "bullseye",
- "score": 0.6,
- "state": Object {
- "layers": Array [
- Object {
- "categoryDisplay": "default",
- "groups": Array [
- "a",
- "b",
- ],
- "layerId": "first",
- "layerType": "data",
- "legendDisplay": "show",
- "legendMaxLines": 1,
- "metric": "c",
- "nestedLegend": true,
- "numberDisplay": "hidden",
- "percentDecimals": 0,
- "truncateLegend": true,
- },
- ],
- "palette": undefined,
- "shape": "mosaic",
- },
- "title": "As Mosaic",
- },
- ]
- `);
- });
-
- it('mosaic type should be added only in case of 2 groups (negative test)', () => {
- const meta: Parameters[0] = {
- table: {
- layerId: 'first',
- isMultiRow: true,
- columns: [
- {
- columnId: 'a',
- operation: { label: 'Top 5', dataType: 'string' as DataType, isBucketed: true },
- },
- {
- columnId: 'c',
- operation: { label: 'Count', dataType: 'number' as DataType, isBucketed: false },
- },
- ],
- changeType: 'unchanged',
- },
- state: {
- shape: 'pie',
- layers: [
- {
- layerId: 'first',
- layerType: layerTypes.DATA,
- groups: ['a', 'b'],
- metric: 'c',
-
- numberDisplay: 'hidden',
- categoryDisplay: 'inside',
- legendDisplay: 'show',
- percentDecimals: 0,
- legendMaxLines: 1,
- truncateLegend: true,
- nestedLegend: true,
- },
- ],
- },
- keptLayerIds: ['first'],
- };
-
- // test with 1 group
- expect(
- suggestions(meta).filter(({ hide, state }) => !hide && state.shape === 'mosaic')
- ).toMatchInlineSnapshot(`Array []`);
-
- meta.table.columns.push({
- columnId: 'b',
- operation: { label: 'Top 6', dataType: 'string' as DataType, isBucketed: true },
- });
-
- meta.table.columns.push({
- columnId: 'c',
- operation: { label: 'Top 7', dataType: 'string' as DataType, isBucketed: true },
- });
-
- // test with 3 groups
- expect(
- suggestions(meta).filter(({ hide, state }) => !hide && state.shape === 'mosaic')
).toMatchInlineSnapshot(`Array []`);
});
});
@@ -952,7 +861,7 @@ describe('suggestions', () => {
).toHaveLength(0);
});
- it('waffle type should be added only in case of 1 group', () => {
+ it('waffle type should be hidden from the suggestion list', () => {
expect(
suggestions({
table: {
@@ -971,14 +880,13 @@ describe('suggestions', () => {
changeType: 'unchanged',
},
state: {
- shape: 'waffle',
+ shape: 'pie',
layers: [
{
layerId: 'first',
layerType: layerTypes.DATA,
groups: ['a', 'b'],
metric: 'c',
-
numberDisplay: 'hidden',
categoryDisplay: 'inside',
legendDisplay: 'show',
@@ -993,61 +901,5 @@ describe('suggestions', () => {
}).filter(({ hide, state }) => !hide && state.shape === 'waffle')
).toMatchInlineSnapshot(`Array []`);
});
-
- it('waffle type should be added only in case of 1 group (negative test)', () => {
- const meta: Parameters[0] = {
- table: {
- layerId: 'first',
- isMultiRow: true,
- columns: [
- {
- columnId: 'c',
- operation: { label: 'Count', dataType: 'number' as DataType, isBucketed: false },
- },
- ],
- changeType: 'unchanged',
- },
- state: {
- shape: 'pie',
- layers: [
- {
- layerId: 'first',
- layerType: layerTypes.DATA,
- groups: ['a', 'b'],
- metric: 'c',
-
- numberDisplay: 'hidden',
- categoryDisplay: 'inside',
- legendDisplay: 'show',
- percentDecimals: 0,
- legendMaxLines: 1,
- truncateLegend: true,
- nestedLegend: true,
- },
- ],
- },
- keptLayerIds: ['first'],
- };
-
- // test with no group
- expect(
- suggestions(meta).filter(({ hide, state }) => !hide && state.shape === 'waffle')
- ).toMatchInlineSnapshot(`Array []`);
-
- meta.table.columns.push({
- columnId: 'b',
- operation: { label: 'Top 6', dataType: 'string' as DataType, isBucketed: true },
- });
-
- meta.table.columns.push({
- columnId: 'c',
- operation: { label: 'Top 7', dataType: 'string' as DataType, isBucketed: true },
- });
-
- // test with 2 groups
- expect(
- suggestions(meta).filter(({ hide, state }) => !hide && state.shape === 'waffle')
- ).toMatchInlineSnapshot(`Array []`);
- });
});
});
diff --git a/x-pack/plugins/lens/public/pie_visualization/suggestions.ts b/x-pack/plugins/lens/public/pie_visualization/suggestions.ts
index f638bfd908be4..248f4a82b1694 100644
--- a/x-pack/plugins/lens/public/pie_visualization/suggestions.ts
+++ b/x-pack/plugins/lens/public/pie_visualization/suggestions.ts
@@ -232,11 +232,7 @@ export function suggestions({
],
},
previewIcon: 'bullseye',
- hide:
- groups.length !== 2 ||
- table.changeType === 'reduced' ||
- hasIntervalScale(groups) ||
- (state && state.shape === 'mosaic'),
+ hide: true,
});
}
@@ -275,11 +271,7 @@ export function suggestions({
],
},
previewIcon: 'bullseye',
- hide:
- groups.length !== 1 ||
- table.changeType === 'reduced' ||
- hasIntervalScale(groups) ||
- (state && state.shape === 'waffle'),
+ hide: true,
});
}
diff --git a/x-pack/plugins/lens/public/pie_visualization/to_expression.ts b/x-pack/plugins/lens/public/pie_visualization/to_expression.ts
index e13fbf62708ee..57270337e67a4 100644
--- a/x-pack/plugins/lens/public/pie_visualization/to_expression.ts
+++ b/x-pack/plugins/lens/public/pie_visualization/to_expression.ts
@@ -5,10 +5,12 @@
* 2.0.
*/
-import { Ast } from '@kbn/interpreter/common';
-import { PaletteRegistry } from 'src/plugins/charts/public';
-import { Operation, DatasourcePublicAPI } from '../types';
+import type { Ast } from '@kbn/interpreter/common';
+import type { PaletteRegistry } from 'src/plugins/charts/public';
+import type { Operation, DatasourcePublicAPI } from '../types';
import { DEFAULT_PERCENT_DECIMALS } from './constants';
+import { shouldShowValuesInLegend } from './render_helpers';
+
import type { PieVisualizationState } from '../../common/expressions';
export function toExpression(
@@ -34,6 +36,7 @@ function expressionHelper(
const operations = layer.groups
.map((columnId) => ({ columnId, operation: datasource.getOperationForColumnId(columnId) }))
.filter((o): o is { columnId: string; operation: Operation } => !!o.operation);
+
if (!layer.metric || !operations.length) {
return null;
}
@@ -55,6 +58,7 @@ function expressionHelper(
categoryDisplay: [layer.categoryDisplay],
legendDisplay: [layer.legendDisplay],
legendPosition: [layer.legendPosition || 'right'],
+ showValuesInLegend: [shouldShowValuesInLegend(layer, state.shape)],
percentDecimals: [
state.shape === 'waffle'
? DEFAULT_PERCENT_DECIMALS
diff --git a/x-pack/plugins/lens/public/pie_visualization/toolbar.tsx b/x-pack/plugins/lens/public/pie_visualization/toolbar.tsx
index 195a72cca9fed..70ad4d8c07daa 100644
--- a/x-pack/plugins/lens/public/pie_visualization/toolbar.tsx
+++ b/x-pack/plugins/lens/public/pie_visualization/toolbar.tsx
@@ -6,7 +6,7 @@
*/
import './toolbar.scss';
-import React from 'react';
+import React, { useCallback } from 'react';
import { i18n } from '@kbn/i18n';
import {
EuiFlexGroup,
@@ -23,6 +23,7 @@ import type { PieVisualizationState, SharedPieLayerState } from '../../common/ex
import { VisualizationDimensionEditorProps, VisualizationToolbarProps } from '../types';
import { ToolbarPopover, LegendSettingsPopover, useDebouncedValue } from '../shared_components';
import { PalettePicker } from '../shared_components';
+import { shouldShowValuesInLegend } from './render_helpers';
const legendOptions: Array<{
value: SharedPieLayerState['legendDisplay'];
@@ -55,6 +56,67 @@ const legendOptions: Array<{
export function PieToolbar(props: VisualizationToolbarProps) {
const { state, setState } = props;
const layer = state.layers[0];
+
+ const onStateChange = useCallback(
+ (part: Record) => {
+ setState({
+ ...state,
+ layers: [{ ...layer, ...part }],
+ });
+ },
+ [layer, state, setState]
+ );
+
+ const onCategoryDisplayChange = useCallback(
+ (option) => onStateChange({ categoryDisplay: option }),
+ [onStateChange]
+ );
+
+ const onNumberDisplayChange = useCallback(
+ (option) => onStateChange({ numberDisplay: option }),
+ [onStateChange]
+ );
+
+ const onPercentDecimalsChange = useCallback(
+ (option) => {
+ onStateChange({ percentDecimals: option });
+ },
+ [onStateChange]
+ );
+
+ const onLegendDisplayChange = useCallback(
+ (optionId) => {
+ onStateChange({ legendDisplay: legendOptions.find(({ id }) => id === optionId)!.value });
+ },
+ [onStateChange]
+ );
+
+ const onLegendPositionChange = useCallback(
+ (id) => onStateChange({ legendPosition: id as Position }),
+ [onStateChange]
+ );
+
+ const onNestedLegendChange = useCallback(
+ (id) => onStateChange({ nestedLegend: !layer.nestedLegend }),
+ [layer, onStateChange]
+ );
+
+ const onTruncateLegendChange = useCallback(() => {
+ const current = layer.truncateLegend ?? true;
+ onStateChange({ truncateLegend: !current });
+ }, [layer, onStateChange]);
+
+ const onLegendMaxLinesChange = useCallback(
+ (val) => onStateChange({ legendMaxLines: val }),
+ [onStateChange]
+ );
+
+ const onValueInLegendChange = useCallback(() => {
+ onStateChange({
+ showValuesInLegend: !shouldShowValuesInLegend(layer, state.shape),
+ });
+ }, [layer, state.shape, onStateChange]);
+
if (!layer) {
return null;
}
@@ -87,12 +149,7 @@ export function PieToolbar(props: VisualizationToolbarProps {
- setState({
- ...state,
- layers: [{ ...layer, categoryDisplay: option }],
- });
- }}
+ onChange={onCategoryDisplayChange}
/>
) : null}
@@ -110,12 +167,7 @@ export function PieToolbar(props: VisualizationToolbarProps {
- setState({
- ...state,
- layers: [{ ...layer, numberDisplay: option }],
- });
- }}
+ onChange={onNumberDisplayChange}
/>
) : null}
@@ -131,59 +183,28 @@ export function PieToolbar(props: VisualizationToolbarProps
{
- setState({
- ...state,
- layers: [{ ...layer, percentDecimals: value }],
- });
- }}
+ setValue={onPercentDecimalsChange}
/>
{
- setState({
- ...state,
- layers: [
- {
- ...layer,
- legendDisplay: legendOptions.find(({ id }) => id === optionId)!.value,
- },
- ],
- });
- }}
+ onDisplayChange={onLegendDisplayChange}
+ valueInLegend={shouldShowValuesInLegend(layer, state.shape)}
+ renderValueInLegendSwitch={
+ 'showValues' in PartitionChartsMeta[state.shape]?.legend ?? false
+ }
+ onValueInLegendChange={onValueInLegendChange}
position={layer.legendPosition}
- onPositionChange={(id) => {
- setState({
- ...state,
- layers: [{ ...layer, legendPosition: id as Position }],
- });
- }}
+ onPositionChange={onLegendPositionChange}
renderNestedLegendSwitch
nestedLegend={!!layer.nestedLegend}
- onNestedLegendChange={() => {
- setState({
- ...state,
- layers: [{ ...layer, nestedLegend: !layer.nestedLegend }],
- });
- }}
+ onNestedLegendChange={onNestedLegendChange}
shouldTruncate={layer.truncateLegend ?? true}
- onTruncateLegendChange={() => {
- const current = layer.truncateLegend ?? true;
- setState({
- ...state,
- layers: [{ ...layer, truncateLegend: !current }],
- });
- }}
+ onTruncateLegendChange={onTruncateLegendChange}
maxLines={layer?.legendMaxLines}
- onMaxLinesChange={(val) => {
- setState({
- ...state,
- layers: [{ ...layer, legendMaxLines: val }],
- });
- }}
+ onMaxLinesChange={onLegendMaxLinesChange}
/>
);
diff --git a/x-pack/plugins/lens/public/pie_visualization/visualization.tsx b/x-pack/plugins/lens/public/pie_visualization/visualization.tsx
index 0eb56ce090aff..49a80b73da1c4 100644
--- a/x-pack/plugins/lens/public/pie_visualization/visualization.tsx
+++ b/x-pack/plugins/lens/public/pie_visualization/visualization.tsx
@@ -282,10 +282,7 @@ export const getPieVisualization = ({
warningMessages.push(
{state.shape},
- }}
+ defaultMessage="Waffle charts are unable to effectively display small field values. To display all field values, use the Data table or Treemap."
/>
);
}
diff --git a/x-pack/plugins/licensing/server/plugin.ts b/x-pack/plugins/licensing/server/plugin.ts
index 83379fe48ac9e..43c8135127871 100644
--- a/x-pack/plugins/licensing/server/plugin.ts
+++ b/x-pack/plugins/licensing/server/plugin.ts
@@ -177,10 +177,7 @@ export class LicensingPlugin implements Plugin): Promise => {
const client = isPromise(clusterClient) ? await clusterClient : clusterClient;
try {
- const { body: response } = await client.asInternalUser.xpack.info({
- // @ts-expect-error `accept_enterprise` is not present in the client definition
- accept_enterprise: true,
- });
+ const { body: response } = await client.asInternalUser.xpack.info();
const normalizedLicense =
response.license && response.license.type !== 'missing'
? normalizeServerLicense(response.license)
diff --git a/x-pack/plugins/ml/common/index.ts b/x-pack/plugins/ml/common/index.ts
index ea8ad43d6bb3b..c76b662df7a5a 100644
--- a/x-pack/plugins/ml/common/index.ts
+++ b/x-pack/plugins/ml/common/index.ts
@@ -15,3 +15,4 @@ export { isRuntimeMappings, isRuntimeField } from './util/runtime_field_utils';
export { extractErrorMessage } from './util/errors';
export type { RuntimeMappings } from './types/fields';
export { getDefaultCapabilities as getDefaultMlCapabilities } from './types/capabilities';
+export { DATAFEED_STATE, JOB_STATE } from './constants/states';
diff --git a/x-pack/plugins/ml/public/application/components/data_grid/column_chart.scss b/x-pack/plugins/ml/public/application/components/data_grid/column_chart.scss
index 756804a0e6aa0..551734bc2fcdc 100644
--- a/x-pack/plugins/ml/public/application/components/data_grid/column_chart.scss
+++ b/x-pack/plugins/ml/public/application/components/data_grid/column_chart.scss
@@ -25,8 +25,3 @@
min-width: $euiButtonMinWidth;
td { text-align: center }
}
-
-/* Override to align column header to bottom of cell when no chart is available */
-.mlDataGrid .euiDataGridHeaderCell__content {
- margin-top: auto;
-}
diff --git a/x-pack/plugins/ml/public/application/components/data_grid/data_grid.scss b/x-pack/plugins/ml/public/application/components/data_grid/data_grid.scss
index b445c82b35ff9..f9cc09ef8c425 100644
--- a/x-pack/plugins/ml/public/application/components/data_grid/data_grid.scss
+++ b/x-pack/plugins/ml/public/application/components/data_grid/data_grid.scss
@@ -1,11 +1,18 @@
.mlDataGrid {
+
.euiDataGridRowCell--boolean {
text-transform: none;
}
- // Override to align the sorting arrow at the bottom when histogram charts are enabled
- .euiDataGridHeaderCell .euiDataGridHeaderCell__sortingArrow {
- margin-top: auto;
- margin-bottom: 0;
+ // Overrides to align the sorting arrow, actions icon and the column header when no chart is available,
+ // to the bottom of the cell when histogram charts are enabled.
+ // Note that overrides have to be used as currently it is not possible to add a custom class name
+ // for the EuiDataGridHeaderCell - see https://github.com/elastic/eui/issues/5106
+ .euiDataGridHeaderCell {
+ .euiDataGridHeaderCell__sortingArrow,
+ .euiDataGridHeaderCell__icon,
+ .euiPopover {
+ margin-top: auto;
+ }
}
}
diff --git a/x-pack/plugins/ml/public/application/components/data_grid/data_grid.tsx b/x-pack/plugins/ml/public/application/components/data_grid/data_grid.tsx
index 5240dbb1ec474..a77f43e68daef 100644
--- a/x-pack/plugins/ml/public/application/components/data_grid/data_grid.tsx
+++ b/x-pack/plugins/ml/public/application/components/data_grid/data_grid.tsx
@@ -114,14 +114,6 @@ export const DataGrid: FC = memo(
// };
// };
- // If the charts are visible, hide the column actions icon.
- const columnsWithChartsActionized = columnsWithCharts.map((d) => {
- if (chartsVisible === true) {
- d.actions = false;
- }
- return d;
- });
-
const popOverContent = useMemo(() => {
return analysisType === ANALYSIS_CONFIG_TYPE.REGRESSION ||
analysisType === ANALYSIS_CONFIG_TYPE.CLASSIFICATION ||
@@ -341,7 +333,7 @@ export const DataGrid: FC = memo(
{
+ columns={columnsWithCharts.map((c) => {
c.initialWidth = 165;
return c;
})}
diff --git a/x-pack/plugins/ml/server/saved_objects/saved_objects.ts b/x-pack/plugins/ml/server/saved_objects/saved_objects.ts
index 004b5e8e554cc..f3061d3e38d7a 100644
--- a/x-pack/plugins/ml/server/saved_objects/saved_objects.ts
+++ b/x-pack/plugins/ml/server/saved_objects/saved_objects.ts
@@ -25,6 +25,10 @@ export function setupSavedObjects(savedObjects: SavedObjectsServiceSetup) {
savedObjects.registerType({
name: ML_MODULE_SAVED_OBJECT_TYPE,
hidden: false,
+ management: {
+ importableAndExportable: true,
+ visibleInManagement: false,
+ },
namespaceType: 'agnostic',
migrations,
mappings: mlModule,
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/embeddable/embeddable.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/embeddable/embeddable.tsx
index ba03640669330..e183194259c3a 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/embeddable/embeddable.tsx
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/embeddable/embeddable.tsx
@@ -26,7 +26,7 @@ export interface ExploratoryEmbeddableProps {
showCalculationMethod?: boolean;
axisTitlesVisibility?: XYState['axisTitlesVisibilitySettings'];
legendIsVisible?: boolean;
- dataTypesIndexPatterns?: Record;
+ dataTypesIndexPatterns?: Partial>;
reportConfigMap?: ReportConfigMap;
}
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/series_actions.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/series_actions.tsx
index 640e928b8ab98..074a9e1ca6780 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/series_actions.tsx
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/series_actions.tsx
@@ -112,7 +112,7 @@ const HIDE_SERIES_LABEL = i18n.translate('xpack.observability.seriesEditor.hide'
});
const COPY_SERIES_LABEL = i18n.translate('xpack.observability.seriesEditor.clone', {
- defaultMessage: 'Copy series',
+ defaultMessage: 'Duplicate series',
});
const VIEW_SAMPLE_DOCUMENTS_LABEL = i18n.translate(
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/series_name.test.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/series_name.test.tsx
index ccad461209313..cbd7efc42d964 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/series_name.test.tsx
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/series_name.test.tsx
@@ -7,10 +7,17 @@
import React from 'react';
import { fireEvent, screen, waitFor } from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
import { mockUxSeries, render } from '../../rtl_helpers';
import { SeriesName } from './series_name';
-describe.skip('SeriesChartTypesSelect', function () {
+// ensures that fields appropriately match to their label
+jest.mock('@elastic/eui/lib/services/accessibility/html_id_generator', () => ({
+ ...jest.requireActual('@elastic/eui/lib/services/accessibility/html_id_generator'),
+ htmlIdGenerator: () => () => `id-${Math.random()}`,
+}));
+
+describe('SeriesName', function () {
it('should render properly', async function () {
render();
@@ -20,7 +27,7 @@ describe.skip('SeriesChartTypesSelect', function () {
it('should display input when editing name', async function () {
render();
- let input = screen.queryByLabelText(mockUxSeries.name);
+ let input = screen.queryByTestId('exploratoryViewSeriesNameInput') as HTMLInputElement;
// read only
expect(input).not.toBeInTheDocument();
@@ -30,17 +37,52 @@ describe.skip('SeriesChartTypesSelect', function () {
fireEvent.click(editButton);
await waitFor(() => {
- input = screen.getByLabelText(mockUxSeries.name);
+ input = screen.getByTestId('exploratoryViewSeriesNameInput') as HTMLInputElement;
expect(input).toBeInTheDocument();
+ expect(input.value).toBe(mockUxSeries.name);
});
// toggle readonly
fireEvent.click(editButton);
await waitFor(() => {
- input = screen.getByLabelText(mockUxSeries.name);
+ input = screen.queryByTestId('exploratoryViewSeriesNameInput') as HTMLInputElement;
+
+ expect(screen.getByText(mockUxSeries.name)).toBeInTheDocument();
+ expect(input).not.toBeInTheDocument();
+ });
+ });
+
+ it('should save name on enter key', async function () {
+ const newName = '-test-new-name';
+ render();
+
+ let input = screen.queryByTestId('exploratoryViewSeriesNameInput') as HTMLInputElement;
+
+ // read only
+ expect(input).not.toBeInTheDocument();
+
+ const editButton = screen.getByRole('button');
+ // toggle editing
+ userEvent.click(editButton);
+
+ await waitFor(() => {
+ input = screen.getByTestId('exploratoryViewSeriesNameInput') as HTMLInputElement;
+
+ expect(input).toBeInTheDocument();
+ });
+
+ userEvent.click(input);
+ userEvent.type(input, newName);
+
+ // submit
+ userEvent.keyboard('{enter}');
+
+ await waitFor(() => {
+ input = screen.queryByTestId('exploratoryViewSeriesNameInput') as HTMLInputElement;
+ expect(screen.getByText(`${mockUxSeries.name}${newName}`)).toBeInTheDocument();
expect(input).not.toBeInTheDocument();
});
});
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/series_name.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/series_name.tsx
index cff30a2b35059..68a628e23292c 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/series_name.tsx
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/series_name.tsx
@@ -5,7 +5,7 @@
* 2.0.
*/
-import React, { useState, ChangeEvent, useEffect, useRef } from 'react';
+import React, { useState, ChangeEvent, useEffect, useRef, KeyboardEventHandler } from 'react';
import styled from 'styled-components';
import { i18n } from '@kbn/i18n';
import {
@@ -57,6 +57,12 @@ export function SeriesName({ series, seriesId }: Props) {
}
};
+ const onKeyDown: KeyboardEventHandler = (event) => {
+ if (event.key === 'Enter') {
+ setIsEditingEnabled(false);
+ }
+ };
+
useEffect(() => {
setValue(series.name);
}, [series.name]);
@@ -75,12 +81,14 @@ export function SeriesName({ series, seriesId }: Props) {
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/report_metric_options.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/report_metric_options.tsx
index 0c1f41437ede7..e3e63af94118e 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/report_metric_options.tsx
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/report_metric_options.tsx
@@ -169,7 +169,7 @@ const NO_DATA_AVAILABLE = i18n.translate('xpack.observability.expView.seriesEdit
const NO_PERMISSIONS = i18n.translate('xpack.observability.expView.seriesEditor.noPermissions', {
defaultMessage:
- "Unable to create Index Pattern. You don't have the required permission, please contact your admin.",
+ "Unable to create Data View. You don't have the required permission, please contact your admin.",
});
const REPORT_METRIC_TOOLTIP = i18n.translate(
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/utils/stringify_kueries.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/utils/stringify_kueries.ts
index afff4a333f7b1..29b0ac417f50f 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/utils/stringify_kueries.ts
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/utils/stringify_kueries.ts
@@ -19,11 +19,16 @@ const buildOrCondition = (values: string[]) => {
}
return `(${values.join(' or ')})`;
};
+
+function addSlashes(str: string) {
+ return (str + '').replace(/[\\"']/g, '\\$&').replace(/\u0000/g, '\\0');
+}
+
export const urlFiltersToKueryString = (urlFilters: UrlFilter[]): string => {
let kueryString = '';
urlFilters.forEach(({ field, values, notValues, wildcards, notWildcards }) => {
- const valuesT = values?.map((val) => `"${val}"`);
- const notValuesT = notValues?.map((val) => `"${val}"`);
+ const valuesT = values?.map((val) => `"${addSlashes(val)}"`);
+ const notValuesT = notValues?.map((val) => `"${addSlashes(val)}"`);
const wildcardsT = wildcards?.map((val) => `*${val}*`);
const notWildcardsT = notWildcards?.map((val) => `*${val}*`);
diff --git a/x-pack/plugins/reporting/common/constants.ts b/x-pack/plugins/reporting/common/constants.ts
index 65d196b6e068a..1fe37f86b037f 100644
--- a/x-pack/plugins/reporting/common/constants.ts
+++ b/x-pack/plugins/reporting/common/constants.ts
@@ -55,17 +55,6 @@ export const UI_SETTINGS_CSV_SEPARATOR = 'csv:separator';
export const UI_SETTINGS_CSV_QUOTE_VALUES = 'csv:quoteValues';
export const UI_SETTINGS_DATEFORMAT_TZ = 'dateFormat:tz';
-export const LAYOUT_TYPES = {
- CANVAS: 'canvas',
- PRESERVE_LAYOUT: 'preserve_layout',
- PRINT: 'print',
-};
-
-export const DEFAULT_VIEWPORT = {
- width: 1950,
- height: 1200,
-};
-
// Export Type Definitions
export const CSV_REPORT_TYPE = 'CSV';
export const CSV_JOB_TYPE = 'csv_searchsource';
diff --git a/x-pack/plugins/reporting/common/test/fixtures.ts b/x-pack/plugins/reporting/common/test/fixtures.ts
index c7489d54e9504..5cc6cf274c340 100644
--- a/x-pack/plugins/reporting/common/test/fixtures.ts
+++ b/x-pack/plugins/reporting/common/test/fixtures.ts
@@ -11,7 +11,6 @@ import type { ReportMock } from './types';
const buildMockReport = (baseObj: ReportMock) => ({
index: '.reporting-2020.04.12',
migration_version: '7.15.0',
- browser_type: 'chromium',
max_attempts: 1,
timeout: 300000,
created_by: 'elastic',
diff --git a/x-pack/plugins/reporting/common/types/base.ts b/x-pack/plugins/reporting/common/types/base.ts
index a44378979ac3c..234467a16921e 100644
--- a/x-pack/plugins/reporting/common/types/base.ts
+++ b/x-pack/plugins/reporting/common/types/base.ts
@@ -6,7 +6,7 @@
*/
import type { Ensure, SerializableRecord } from '@kbn/utility-types';
-import type { LayoutParams } from './layout';
+import type { LayoutParams } from '../../../screenshotting/common';
import { LocatorParams } from './url';
export type JobId = string;
diff --git a/x-pack/plugins/reporting/common/types/export_types/png.ts b/x-pack/plugins/reporting/common/types/export_types/png.ts
index 3b850b5bd8b33..5afde424127a1 100644
--- a/x-pack/plugins/reporting/common/types/export_types/png.ts
+++ b/x-pack/plugins/reporting/common/types/export_types/png.ts
@@ -5,7 +5,7 @@
* 2.0.
*/
-import type { LayoutParams } from '../layout';
+import type { LayoutParams } from '../../../../screenshotting/common';
import type { BaseParams, BasePayload } from '../base';
interface BaseParamsPNG {
diff --git a/x-pack/plugins/reporting/common/types/export_types/png_v2.ts b/x-pack/plugins/reporting/common/types/export_types/png_v2.ts
index c937d01ce0be1..1469437fe6199 100644
--- a/x-pack/plugins/reporting/common/types/export_types/png_v2.ts
+++ b/x-pack/plugins/reporting/common/types/export_types/png_v2.ts
@@ -6,7 +6,7 @@
*/
import type { LocatorParams } from '../url';
-import type { LayoutParams } from '../layout';
+import type { LayoutParams } from '../../../../screenshotting/common';
import type { BaseParams, BasePayload } from '../base';
// Job params: structure of incoming user request data
diff --git a/x-pack/plugins/reporting/common/types/export_types/printable_pdf.ts b/x-pack/plugins/reporting/common/types/export_types/printable_pdf.ts
index a424706430f2c..57e5a90595d5c 100644
--- a/x-pack/plugins/reporting/common/types/export_types/printable_pdf.ts
+++ b/x-pack/plugins/reporting/common/types/export_types/printable_pdf.ts
@@ -5,7 +5,7 @@
* 2.0.
*/
-import type { LayoutParams } from '../layout';
+import type { LayoutParams } from '../../../../screenshotting/common';
import type { BaseParams, BasePayload } from '../base';
interface BaseParamsPDF {
diff --git a/x-pack/plugins/reporting/common/types/export_types/printable_pdf_v2.ts b/x-pack/plugins/reporting/common/types/export_types/printable_pdf_v2.ts
index c9a7a2ce2331a..b3fbbc1653dfb 100644
--- a/x-pack/plugins/reporting/common/types/export_types/printable_pdf_v2.ts
+++ b/x-pack/plugins/reporting/common/types/export_types/printable_pdf_v2.ts
@@ -6,7 +6,7 @@
*/
import type { LocatorParams } from '../url';
-import type { LayoutParams } from '../layout';
+import type { LayoutParams } from '../../../../screenshotting/common';
import type { BaseParams, BasePayload } from '../base';
interface BaseParamsPDFV2 {
diff --git a/x-pack/plugins/reporting/common/types/index.ts b/x-pack/plugins/reporting/common/types/index.ts
index 8612400e8b390..056ef81e70a0a 100644
--- a/x-pack/plugins/reporting/common/types/index.ts
+++ b/x-pack/plugins/reporting/common/types/index.ts
@@ -5,11 +5,9 @@
* 2.0.
*/
-import type { Size, LayoutParams } from './layout';
import type { JobId, BaseParams, BaseParamsV2, BasePayload, BasePayloadV2 } from './base';
export type { JobId, BaseParams, BaseParamsV2, BasePayload, BasePayloadV2 };
-export type { Size, LayoutParams };
export type {
DownloadReportFn,
IlmPolicyMigrationStatus,
@@ -20,20 +18,6 @@ export type {
} from './url';
export * from './export_types';
-export interface PageSizeParams {
- pageMarginTop: number;
- pageMarginBottom: number;
- pageMarginWidth: number;
- tableBorderWidth: number;
- headingHeight: number;
- subheadingHeight: number;
-}
-
-export interface PdfImageSize {
- width: number;
- height?: number;
-}
-
export interface ReportDocumentHead {
_id: string;
_index: string;
@@ -83,7 +67,6 @@ export interface ReportSource {
*/
kibana_name?: string; // for troubleshooting
kibana_id?: string; // for troubleshooting
- browser_type?: string; // no longer used since chromium is the only option (used to allow phantomjs)
timeout?: number; // for troubleshooting: the actual comparison uses the config setting xpack.reporting.queue.timeout
max_attempts?: number; // for troubleshooting: the actual comparison uses the config setting xpack.reporting.capture.maxAttempts
started_at?: string; // timestamp in UTC
diff --git a/x-pack/plugins/reporting/common/types/layout.ts b/x-pack/plugins/reporting/common/types/layout.ts
deleted file mode 100644
index b22d6b59d0873..0000000000000
--- a/x-pack/plugins/reporting/common/types/layout.ts
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license 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 { Ensure, SerializableRecord } from '@kbn/utility-types';
-
-export type Size = Ensure<
- {
- width: number;
- height: number;
- },
- SerializableRecord
->;
-
-export type LayoutParams = Ensure<
- {
- id: string;
- dimensions?: Size;
- },
- SerializableRecord
->;
diff --git a/x-pack/plugins/reporting/kibana.json b/x-pack/plugins/reporting/kibana.json
index 4bddfae96756d..123c23e5e1c29 100644
--- a/x-pack/plugins/reporting/kibana.json
+++ b/x-pack/plugins/reporting/kibana.json
@@ -18,6 +18,7 @@
"uiActions",
"taskManager",
"embeddable",
+ "screenshotting",
"screenshotMode",
"share",
"features"
diff --git a/x-pack/plugins/reporting/public/lib/job.tsx b/x-pack/plugins/reporting/public/lib/job.tsx
index 8e2db6a9d998e..d24695b1041c7 100644
--- a/x-pack/plugins/reporting/public/lib/job.tsx
+++ b/x-pack/plugins/reporting/public/lib/job.tsx
@@ -51,7 +51,6 @@ export class Job {
public timeout: ReportSource['timeout'];
public kibana_name: ReportSource['kibana_name'];
public kibana_id: ReportSource['kibana_id'];
- public browser_type: ReportSource['browser_type'];
public size?: ReportOutput['size'];
public content_type?: TaskRunResult['content_type'];
@@ -80,7 +79,6 @@ export class Job {
this.timeout = report.timeout;
this.kibana_name = report.kibana_name;
this.kibana_id = report.kibana_id;
- this.browser_type = report.browser_type;
this.browserTimezone = report.payload.browserTimezone;
this.size = report.output?.size;
this.content_type = report.output?.content_type;
diff --git a/x-pack/plugins/reporting/public/management/components/report_info_flyout_content.tsx b/x-pack/plugins/reporting/public/management/components/report_info_flyout_content.tsx
index 25199c4abaa68..00ce9069d81ce 100644
--- a/x-pack/plugins/reporting/public/management/components/report_info_flyout_content.tsx
+++ b/x-pack/plugins/reporting/public/management/components/report_info_flyout_content.tsx
@@ -141,12 +141,6 @@ export const ReportInfoFlyoutContent: FunctionComponent = ({ info }) => {
}),
description: info.layout?.id || UNKNOWN,
},
- {
- title: i18n.translate('xpack.reporting.listing.infoPanel.browserTypeInfo', {
- defaultMessage: 'Browser type',
- }),
- description: info.browser_type || NA,
- },
];
const warnings = info.getWarnings();
diff --git a/x-pack/plugins/reporting/public/plugin.ts b/x-pack/plugins/reporting/public/plugin.ts
index fe80ed679c8ed..ea48bb253ad9f 100644
--- a/x-pack/plugins/reporting/public/plugin.ts
+++ b/x-pack/plugins/reporting/public/plugin.ts
@@ -18,6 +18,7 @@ import {
Plugin,
PluginInitializerContext,
} from 'src/core/public';
+import type { ScreenshottingSetup } from '../../screenshotting/public';
import { CONTEXT_MENU_TRIGGER } from '../../../../src/plugins/embeddable/public';
import {
FeatureCatalogueCategory,
@@ -73,6 +74,7 @@ export interface ReportingPublicPluginSetupDendencies {
management: ManagementSetup;
licensing: LicensingPluginSetup;
uiActions: UiActionsSetup;
+ screenshotting: ScreenshottingSetup;
share: SharePluginSetup;
}
@@ -145,6 +147,7 @@ export class ReportingPublicPlugin
home,
management,
licensing: { license$ }, // FIXME: 'license$' is deprecated
+ screenshotting,
share,
uiActions,
} = setupDeps;
@@ -203,7 +206,7 @@ export class ReportingPublicPlugin
id: 'reportingRedirect',
mount: async (params) => {
const { mountRedirectApp } = await import('./redirect');
- return mountRedirectApp({ ...params, share, apiClient });
+ return mountRedirectApp({ ...params, apiClient, screenshotting, share });
},
title: 'Reporting redirect app',
searchable: false,
diff --git a/x-pack/plugins/reporting/public/redirect/mount_redirect_app.tsx b/x-pack/plugins/reporting/public/redirect/mount_redirect_app.tsx
index eb34fc71cbf4e..fa658126efebc 100644
--- a/x-pack/plugins/reporting/public/redirect/mount_redirect_app.tsx
+++ b/x-pack/plugins/reporting/public/redirect/mount_redirect_app.tsx
@@ -10,6 +10,7 @@ import React from 'react';
import { EuiErrorBoundary } from '@elastic/eui';
import type { AppMountParameters } from 'kibana/public';
+import type { ScreenshottingSetup } from '../../../screenshotting/public';
import type { SharePluginSetup } from '../shared_imports';
import type { ReportingAPIClient } from '../lib/reporting_api_client';
@@ -17,13 +18,25 @@ import { RedirectApp } from './redirect_app';
interface MountParams extends AppMountParameters {
apiClient: ReportingAPIClient;
+ screenshotting: ScreenshottingSetup;
share: SharePluginSetup;
}
-export const mountRedirectApp = ({ element, apiClient, history, share }: MountParams) => {
+export const mountRedirectApp = ({
+ element,
+ apiClient,
+ history,
+ screenshotting,
+ share,
+}: MountParams) => {
render(
-
+
,
element
);
diff --git a/x-pack/plugins/reporting/public/redirect/redirect_app.tsx b/x-pack/plugins/reporting/public/redirect/redirect_app.tsx
index 4b271b17c5e85..9f0b3f51f2731 100644
--- a/x-pack/plugins/reporting/public/redirect/redirect_app.tsx
+++ b/x-pack/plugins/reporting/public/redirect/redirect_app.tsx
@@ -12,6 +12,7 @@ import { i18n } from '@kbn/i18n';
import { EuiCallOut, EuiCodeBlock } from '@elastic/eui';
import type { ScopedHistory } from 'src/core/public';
+import type { ScreenshottingSetup } from '../../../screenshotting/public';
import { REPORTING_REDIRECT_LOCATOR_STORE_KEY } from '../../common/constants';
import { LocatorParams } from '../../common/types';
@@ -24,6 +25,7 @@ import './redirect_app.scss';
interface Props {
apiClient: ReportingAPIClient;
history: ScopedHistory;
+ screenshotting: ScreenshottingSetup;
share: SharePluginSetup;
}
@@ -39,7 +41,9 @@ const i18nTexts = {
),
};
-export const RedirectApp: FunctionComponent = ({ share, apiClient }) => {
+type ReportingContext = Record;
+
+export const RedirectApp: FunctionComponent = ({ apiClient, screenshotting, share }) => {
const [error, setError] = useState();
useEffect(() => {
@@ -53,9 +57,8 @@ export const RedirectApp: FunctionComponent = ({ share, apiClient }) => {
const result = await apiClient.getInfo(jobId as string);
locatorParams = result?.locatorParams?.[0];
} else {
- locatorParams = (window as unknown as Record)[
- REPORTING_REDIRECT_LOCATOR_STORE_KEY
- ];
+ locatorParams =
+ screenshotting.getContext()?.[REPORTING_REDIRECT_LOCATOR_STORE_KEY];
}
if (!locatorParams) {
@@ -70,7 +73,7 @@ export const RedirectApp: FunctionComponent = ({ share, apiClient }) => {
throw e;
}
})();
- }, [share, apiClient]);
+ }, [apiClient, screenshotting, share]);
return (
diff --git a/x-pack/plugins/reporting/public/share_context_menu/index.ts b/x-pack/plugins/reporting/public/share_context_menu/index.ts
index b0d6f2e6a2b52..321a5a29281af 100644
--- a/x-pack/plugins/reporting/public/share_context_menu/index.ts
+++ b/x-pack/plugins/reporting/public/share_context_menu/index.ts
@@ -8,8 +8,8 @@
import * as Rx from 'rxjs';
import type { IUiSettingsClient, ToastsSetup } from 'src/core/public';
import { CoreStart } from 'src/core/public';
+import type { LayoutParams } from '../../../screenshotting/common';
import type { LicensingPluginSetup } from '../../../licensing/public';
-import type { LayoutParams } from '../../common/types';
import type { ReportingAPIClient } from '../lib/reporting_api_client';
export interface ExportPanelShareOpts {
diff --git a/x-pack/plugins/reporting/public/share_context_menu/screen_capture_panel_content.tsx b/x-pack/plugins/reporting/public/share_context_menu/screen_capture_panel_content.tsx
index de3cc89b31fd0..f9e2908c0f733 100644
--- a/x-pack/plugins/reporting/public/share_context_menu/screen_capture_panel_content.tsx
+++ b/x-pack/plugins/reporting/public/share_context_menu/screen_capture_panel_content.tsx
@@ -8,7 +8,7 @@
import { EuiFormRow, EuiSwitch, EuiSwitchEvent } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n-react';
import React, { Component } from 'react';
-import { LayoutParams } from '../../common/types';
+import type { LayoutParams } from '../../../screenshotting/common';
import { ReportingPanelContent, ReportingPanelProps } from './reporting_panel_content';
export interface Props extends ReportingPanelProps {
@@ -103,7 +103,7 @@ export class ScreenCapturePanelContent extends Component
{
this.setState({ useCanvasLayout: evt.target.checked, usePrintLayout: false });
};
- private getLayout = (): Required => {
+ private getLayout = (): LayoutParams => {
const { layout: outerLayout } = this.props.getJobParams();
let dimensions = outerLayout?.dimensions;
diff --git a/x-pack/plugins/reporting/server/browsers/chromium/driver_factory/index.test.ts b/x-pack/plugins/reporting/server/browsers/chromium/driver_factory/index.test.ts
deleted file mode 100644
index dae692fae8825..0000000000000
--- a/x-pack/plugins/reporting/server/browsers/chromium/driver_factory/index.test.ts
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import puppeteer from 'puppeteer';
-import * as Rx from 'rxjs';
-import { take } from 'rxjs/operators';
-import { HeadlessChromiumDriverFactory } from '.';
-import type { ReportingCore } from '../../..';
-import {
- createMockConfigSchema,
- createMockLevelLogger,
- createMockReportingCore,
-} from '../../../test_helpers';
-
-jest.mock('puppeteer');
-
-const mock = (browserDriverFactory: HeadlessChromiumDriverFactory) => {
- browserDriverFactory.getBrowserLogger = jest.fn(() => new Rx.Observable());
- browserDriverFactory.getProcessLogger = jest.fn(() => new Rx.Observable());
- browserDriverFactory.getPageExit = jest.fn(() => new Rx.Observable());
- return browserDriverFactory;
-};
-
-describe('class HeadlessChromiumDriverFactory', () => {
- let reporting: ReportingCore;
- const logger = createMockLevelLogger();
- const path = 'path/to/headless_shell';
-
- beforeEach(async () => {
- (puppeteer as jest.Mocked).launch.mockResolvedValue({
- newPage: jest.fn().mockResolvedValue({
- target: jest.fn(() => ({
- createCDPSession: jest.fn().mockResolvedValue({
- send: jest.fn(),
- }),
- })),
- emulateTimezone: jest.fn(),
- setDefaultTimeout: jest.fn(),
- }),
- close: jest.fn(),
- process: jest.fn(),
- } as unknown as puppeteer.Browser);
-
- reporting = await createMockReportingCore(
- createMockConfigSchema({
- capture: {
- browser: { chromium: { proxy: {} } },
- timeouts: { openUrl: 50000 },
- },
- })
- );
- });
-
- it('createPage returns browser driver and process exit observable', async () => {
- const factory = mock(new HeadlessChromiumDriverFactory(reporting, path, logger));
- const utils = await factory.createPage({}).pipe(take(1)).toPromise();
- expect(utils).toHaveProperty('driver');
- expect(utils).toHaveProperty('exit$');
- });
-
- it('createPage rejects if Puppeteer launch fails', async () => {
- (puppeteer as jest.Mocked).launch.mockRejectedValue(
- `Puppeteer Launch mock fail.`
- );
- const factory = mock(new HeadlessChromiumDriverFactory(reporting, path, logger));
- expect(() =>
- factory.createPage({}).pipe(take(1)).toPromise()
- ).rejects.toThrowErrorMatchingInlineSnapshot(
- `"Error spawning Chromium browser! Puppeteer Launch mock fail."`
- );
- });
-});
diff --git a/x-pack/plugins/reporting/server/browsers/chromium/driver_factory/index.ts b/x-pack/plugins/reporting/server/browsers/chromium/driver_factory/index.ts
deleted file mode 100644
index 2aef62f59985b..0000000000000
--- a/x-pack/plugins/reporting/server/browsers/chromium/driver_factory/index.ts
+++ /dev/null
@@ -1,268 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license 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 { getDataPath } from '@kbn/utils';
-import del from 'del';
-import apm from 'elastic-apm-node';
-import fs from 'fs';
-import path from 'path';
-import puppeteer from 'puppeteer';
-import * as Rx from 'rxjs';
-import { InnerSubscriber } from 'rxjs/internal/InnerSubscriber';
-import { ignoreElements, map, mergeMap, tap } from 'rxjs/operators';
-import { getChromiumDisconnectedError } from '../';
-import { ReportingCore } from '../../..';
-import { durationToNumber } from '../../../../common/schema_utils';
-import { CaptureConfig } from '../../../../server/types';
-import { LevelLogger } from '../../../lib';
-import { safeChildProcess } from '../../safe_child_process';
-import { HeadlessChromiumDriver } from '../driver';
-import { args } from './args';
-import { getMetrics } from './metrics';
-
-type BrowserConfig = CaptureConfig['browser']['chromium'];
-
-export class HeadlessChromiumDriverFactory {
- private binaryPath: string;
- private captureConfig: CaptureConfig;
- private browserConfig: BrowserConfig;
- private userDataDir: string;
- private getChromiumArgs: () => string[];
- private core: ReportingCore;
-
- constructor(core: ReportingCore, binaryPath: string, private logger: LevelLogger) {
- this.core = core;
- this.binaryPath = binaryPath;
- const config = core.getConfig();
- this.captureConfig = config.get('capture');
- this.browserConfig = this.captureConfig.browser.chromium;
-
- if (this.browserConfig.disableSandbox) {
- logger.warning(`Enabling the Chromium sandbox provides an additional layer of protection.`);
- }
-
- this.userDataDir = fs.mkdtempSync(path.join(getDataPath(), 'chromium-'));
- this.getChromiumArgs = () =>
- args({
- userDataDir: this.userDataDir,
- disableSandbox: this.browserConfig.disableSandbox,
- proxy: this.browserConfig.proxy,
- });
- }
-
- type = 'chromium';
-
- /*
- * Return an observable to objects which will drive screenshot capture for a page
- */
- createPage(
- { browserTimezone }: { browserTimezone?: string },
- pLogger = this.logger
- ): Rx.Observable<{ driver: HeadlessChromiumDriver; exit$: Rx.Observable }> {
- // FIXME: 'create' is deprecated
- return Rx.Observable.create(async (observer: InnerSubscriber) => {
- const logger = pLogger.clone(['browser-driver']);
- logger.info(`Creating browser page driver`);
-
- const chromiumArgs = this.getChromiumArgs();
- logger.debug(`Chromium launch args set to: ${chromiumArgs}`);
-
- let browser: puppeteer.Browser | null = null;
-
- try {
- browser = await puppeteer.launch({
- pipe: !this.browserConfig.inspect,
- userDataDir: this.userDataDir,
- executablePath: this.binaryPath,
- ignoreHTTPSErrors: true,
- handleSIGHUP: false,
- args: chromiumArgs,
- env: {
- TZ: browserTimezone,
- },
- });
- } catch (err) {
- observer.error(new Error(`Error spawning Chromium browser! ${err}`));
- return;
- }
-
- const page = await browser.newPage();
- const devTools = await page.target().createCDPSession();
-
- await devTools.send('Performance.enable', { timeDomain: 'timeTicks' });
- const startMetrics = await devTools.send('Performance.getMetrics');
-
- // Log version info for debugging / maintenance
- const versionInfo = await devTools.send('Browser.getVersion');
- logger.debug(`Browser version: ${JSON.stringify(versionInfo)}`);
-
- await page.emulateTimezone(browserTimezone);
-
- // Set the default timeout for all navigation methods to the openUrl timeout
- // All waitFor methods have their own timeout config passed in to them
- page.setDefaultTimeout(durationToNumber(this.captureConfig.timeouts.openUrl));
-
- logger.debug(`Browser page driver created`);
-
- const childProcess = {
- async kill() {
- try {
- if (devTools && startMetrics) {
- const endMetrics = await devTools.send('Performance.getMetrics');
- const { cpu, cpuInPercentage, memory, memoryInMegabytes } = getMetrics(
- startMetrics,
- endMetrics
- );
-
- apm.currentTransaction?.setLabel('cpu', cpu, false);
- apm.currentTransaction?.setLabel('memory', memory, false);
- logger.debug(
- `Chromium consumed CPU ${cpuInPercentage}% Memory ${memoryInMegabytes}MB`
- );
- }
- } catch (error) {
- logger.error(error);
- }
-
- try {
- await browser?.close();
- } catch (err) {
- // do not throw
- logger.error(err);
- }
- },
- };
- const { terminate$ } = safeChildProcess(logger, childProcess);
-
- // this is adding unsubscribe logic to our observer
- // so that if our observer unsubscribes, we terminate our child-process
- observer.add(() => {
- logger.debug(`The browser process observer has unsubscribed. Closing the browser...`);
- childProcess.kill(); // ignore async
- });
-
- // make the observer subscribe to terminate$
- observer.add(
- terminate$
- .pipe(
- tap((signal) => {
- logger.debug(`Termination signal received: ${signal}`);
- }),
- ignoreElements()
- )
- .subscribe(observer)
- );
-
- // taps the browser log streams and combine them to Kibana logs
- this.getBrowserLogger(page, logger).subscribe();
- this.getProcessLogger(browser, logger).subscribe();
-
- // HeadlessChromiumDriver: object to "drive" a browser page
- const driver = new HeadlessChromiumDriver(this.core, page, {
- inspect: !!this.browserConfig.inspect,
- networkPolicy: this.captureConfig.networkPolicy,
- });
-
- // Rx.Observable: stream to interrupt page capture
- const exit$ = this.getPageExit(browser, page);
-
- observer.next({ driver, exit$ });
-
- // unsubscribe logic makes a best-effort attempt to delete the user data directory used by chromium
- observer.add(() => {
- const userDataDir = this.userDataDir;
- logger.debug(`deleting chromium user data directory at [${userDataDir}]`);
- // the unsubscribe function isn't `async` so we're going to make our best effort at
- // deleting the userDataDir and if it fails log an error.
- del(userDataDir, { force: true }).catch((error) => {
- logger.error(`error deleting user data directory at [${userDataDir}]!`);
- logger.error(error);
- });
- });
- });
- }
-
- getBrowserLogger(page: puppeteer.Page, logger: LevelLogger): Rx.Observable {
- const consoleMessages$ = Rx.fromEvent(page, 'console').pipe(
- map((line) => {
- const formatLine = () => `{ text: "${line.text()?.trim()}", url: ${line.location()?.url} }`;
-
- if (line.type() === 'error') {
- logger.error(`Error in browser console: ${formatLine()}`, ['headless-browser-console']);
- } else {
- logger.debug(`Message in browser console: ${formatLine()}`, [
- `headless-browser-console:${line.type()}`,
- ]);
- }
- })
- );
-
- const uncaughtExceptionPageError$ = Rx.fromEvent(page, 'pageerror').pipe(
- map((err) => {
- logger.warning(
- i18n.translate('xpack.reporting.browsers.chromium.pageErrorDetected', {
- defaultMessage: `Reporting encountered an uncaught error on the page that will be ignored: {err}`,
- values: { err: err.toString() },
- })
- );
- })
- );
-
- const pageRequestFailed$ = Rx.fromEvent(page, 'requestfailed').pipe(
- map((req) => {
- const failure = req.failure && req.failure();
- if (failure) {
- logger.warning(
- `Request to [${req.url()}] failed! [${failure.errorText}]. This error will be ignored.`
- );
- }
- })
- );
-
- return Rx.merge(consoleMessages$, uncaughtExceptionPageError$, pageRequestFailed$);
- }
-
- getProcessLogger(browser: puppeteer.Browser, logger: LevelLogger): Rx.Observable {
- const childProcess = browser.process();
- // NOTE: The browser driver can not observe stdout and stderr of the child process
- // Puppeteer doesn't give a handle to the original ChildProcess object
- // See https://github.com/GoogleChrome/puppeteer/issues/1292#issuecomment-521470627
-
- if (childProcess == null) {
- throw new TypeError('childProcess is null or undefined!');
- }
-
- // just log closing of the process
- const processClose$ = Rx.fromEvent(childProcess, 'close').pipe(
- tap(() => {
- logger.debug('child process closed', ['headless-browser-process']);
- })
- );
-
- return processClose$; // ideally, this would also merge with observers for stdout and stderr
- }
-
- getPageExit(browser: puppeteer.Browser, page: puppeteer.Page) {
- const pageError$ = Rx.fromEvent(page, 'error').pipe(
- mergeMap((err) => {
- return Rx.throwError(
- i18n.translate('xpack.reporting.browsers.chromium.errorDetected', {
- defaultMessage: 'Reporting encountered an error: {err}',
- values: { err: err.toString() },
- })
- );
- })
- );
-
- const browserDisconnect$ = Rx.fromEvent(browser, 'disconnected').pipe(
- mergeMap(() => Rx.throwError(getChromiumDisconnectedError()))
- );
-
- return Rx.merge(pageError$, browserDisconnect$);
- }
-}
diff --git a/x-pack/plugins/reporting/server/browsers/chromium/driver_factory/start_logs.ts b/x-pack/plugins/reporting/server/browsers/chromium/driver_factory/start_logs.ts
deleted file mode 100644
index 1a739488bf6ed..0000000000000
--- a/x-pack/plugins/reporting/server/browsers/chromium/driver_factory/start_logs.ts
+++ /dev/null
@@ -1,144 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license 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 { spawn } from 'child_process';
-import del from 'del';
-import { mkdtempSync } from 'fs';
-import { uniq } from 'lodash';
-import os from 'os';
-import { join } from 'path';
-import { createInterface } from 'readline';
-import { getDataPath } from '@kbn/utils';
-import { fromEvent, merge, of, timer } from 'rxjs';
-import { catchError, map, reduce, takeUntil, tap } from 'rxjs/operators';
-import { ReportingCore } from '../../../';
-import { LevelLogger } from '../../../lib';
-import { ChromiumArchivePaths } from '../paths';
-import { args } from './args';
-
-const paths = new ChromiumArchivePaths();
-const browserLaunchTimeToWait = 5 * 1000;
-
-// Default args used by pptr
-// https://github.com/puppeteer/puppeteer/blob/13ea347/src/node/Launcher.ts#L168
-const defaultArgs = [
- '--disable-background-networking',
- '--enable-features=NetworkService,NetworkServiceInProcess',
- '--disable-background-timer-throttling',
- '--disable-backgrounding-occluded-windows',
- '--disable-breakpad',
- '--disable-client-side-phishing-detection',
- '--disable-component-extensions-with-background-pages',
- '--disable-default-apps',
- '--disable-dev-shm-usage',
- '--disable-extensions',
- '--disable-features=TranslateUI',
- '--disable-hang-monitor',
- '--disable-ipc-flooding-protection',
- '--disable-popup-blocking',
- '--disable-prompt-on-repost',
- '--disable-renderer-backgrounding',
- '--disable-sync',
- '--force-color-profile=srgb',
- '--metrics-recording-only',
- '--no-first-run',
- '--enable-automation',
- '--password-store=basic',
- '--use-mock-keychain',
- '--remote-debugging-port=0',
- '--headless',
-];
-
-export const browserStartLogs = (
- core: ReportingCore,
- logger: LevelLogger,
- overrideFlags: string[] = []
-) => {
- const config = core.getConfig();
- const proxy = config.get('capture', 'browser', 'chromium', 'proxy');
- const disableSandbox = config.get('capture', 'browser', 'chromium', 'disableSandbox');
- const userDataDir = mkdtempSync(join(getDataPath(), 'chromium-'));
-
- const platform = process.platform;
- const architecture = os.arch();
- const pkg = paths.find(platform, architecture);
- if (!pkg) {
- throw new Error(`Unsupported platform: ${platform}-${architecture}`);
- }
- const binaryPath = paths.getBinaryPath(pkg);
-
- const kbnArgs = args({
- userDataDir,
- disableSandbox,
- proxy,
- });
- const finalArgs = uniq([...defaultArgs, ...kbnArgs, ...overrideFlags]);
-
- // On non-windows platforms, `detached: true` makes child process a
- // leader of a new process group, making it possible to kill child
- // process tree with `.kill(-pid)` command. @see
- // https://nodejs.org/api/child_process.html#child_process_options_detached
- const browserProcess = spawn(binaryPath, finalArgs, {
- detached: process.platform !== 'win32',
- });
-
- const rl = createInterface({ input: browserProcess.stderr });
-
- const exit$ = fromEvent(browserProcess, 'exit').pipe(
- map((code) => {
- logger.error(`Browser exited abnormally, received code: ${code}`);
- return i18n.translate('xpack.reporting.diagnostic.browserCrashed', {
- defaultMessage: `Browser exited abnormally during startup`,
- });
- })
- );
-
- const error$ = fromEvent(browserProcess, 'error').pipe(
- map((err) => {
- logger.error(`Browser process threw an error on startup`);
- logger.error(err as string | Error);
- return i18n.translate('xpack.reporting.diagnostic.browserErrored', {
- defaultMessage: `Browser process threw an error on startup`,
- });
- })
- );
-
- const browserProcessLogger = logger.clone(['chromium-stderr']);
- const log$ = fromEvent(rl, 'line').pipe(
- tap((message: unknown) => {
- if (typeof message === 'string') {
- browserProcessLogger.info(message);
- }
- })
- );
-
- // Collect all events (exit, error and on log-lines), but let chromium keep spitting out
- // logs as sometimes it's "bind" successfully for remote connections, but later emit
- // a log indicative of an issue (for example, no default font found).
- return merge(exit$, error$, log$).pipe(
- takeUntil(timer(browserLaunchTimeToWait)),
- reduce((acc, curr) => `${acc}${curr}\n`, ''),
- tap(() => {
- if (browserProcess && browserProcess.pid && !browserProcess.killed) {
- browserProcess.kill('SIGKILL');
- logger.info(`Successfully sent 'SIGKILL' to browser process (PID: ${browserProcess.pid})`);
- }
- browserProcess.removeAllListeners();
- rl.removeAllListeners();
- rl.close();
- del(userDataDir, { force: true }).catch((error) => {
- logger.error(`Error deleting user data directory at [${userDataDir}]!`);
- logger.error(error);
- });
- }),
- catchError((error) => {
- logger.error(error);
- return of(error);
- })
- );
-};
diff --git a/x-pack/plugins/reporting/server/browsers/chromium/index.ts b/x-pack/plugins/reporting/server/browsers/chromium/index.ts
deleted file mode 100644
index e0d043f821ab4..0000000000000
--- a/x-pack/plugins/reporting/server/browsers/chromium/index.ts
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { i18n } from '@kbn/i18n';
-import { BrowserDownload } from '../';
-import { ReportingCore } from '../../../server';
-import { LevelLogger } from '../../lib';
-import { HeadlessChromiumDriverFactory } from './driver_factory';
-import { ChromiumArchivePaths } from './paths';
-
-export const chromium: BrowserDownload = {
- paths: new ChromiumArchivePaths(),
- createDriverFactory: (core: ReportingCore, binaryPath: string, logger: LevelLogger) =>
- new HeadlessChromiumDriverFactory(core, binaryPath, logger),
-};
-
-export const getChromiumDisconnectedError = () =>
- new Error(
- i18n.translate('xpack.reporting.screencapture.browserWasClosed', {
- defaultMessage: 'Browser was closed unexpectedly! Check the server logs for more info.',
- })
- );
-
-export const getDisallowedOutgoingUrlError = (interceptedUrl: string) =>
- new Error(
- i18n.translate('xpack.reporting.chromiumDriver.disallowedOutgoingUrl', {
- defaultMessage: `Received disallowed outgoing URL: "{interceptedUrl}". Failing the request and closing the browser.`,
- values: { interceptedUrl },
- })
- );
-
-export { ChromiumArchivePaths };
diff --git a/x-pack/plugins/reporting/server/browsers/download/download.test.ts b/x-pack/plugins/reporting/server/browsers/download/download.test.ts
deleted file mode 100644
index 688a746826e54..0000000000000
--- a/x-pack/plugins/reporting/server/browsers/download/download.test.ts
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { createHash } from 'crypto';
-import del from 'del';
-import { readFileSync } from 'fs';
-import { resolve as resolvePath } from 'path';
-import { Readable } from 'stream';
-import { LevelLogger } from '../../lib';
-import { download } from './download';
-
-const TEMP_DIR = resolvePath(__dirname, '__tmp__');
-const TEMP_FILE = resolvePath(TEMP_DIR, 'foo/bar/download');
-
-class ReadableOf extends Readable {
- constructor(private readonly responseBody: string) {
- super();
- }
-
- _read() {
- this.push(this.responseBody);
- this.push(null);
- }
-}
-
-jest.mock('axios');
-const request: jest.Mock = jest.requireMock('axios').request;
-
-const mockLogger = {
- error: jest.fn(),
- warn: jest.fn(),
- info: jest.fn(),
-} as unknown as LevelLogger;
-
-test('downloads the url to the path', async () => {
- const BODY = 'abdcefg';
- request.mockImplementationOnce(async () => {
- return {
- data: new ReadableOf(BODY),
- };
- });
-
- await download('url', TEMP_FILE, mockLogger);
- expect(readFileSync(TEMP_FILE, 'utf8')).toEqual(BODY);
-});
-
-test('returns the md5 hex hash of the http body', async () => {
- const BODY = 'foobar';
- const HASH = createHash('md5').update(BODY).digest('hex');
- request.mockImplementationOnce(async () => {
- return {
- data: new ReadableOf(BODY),
- };
- });
-
- const returned = await download('url', TEMP_FILE, mockLogger);
- expect(returned).toEqual(HASH);
-});
-
-test('throws if request emits an error', async () => {
- request.mockImplementationOnce(async () => {
- throw new Error('foo');
- });
-
- return expect(download('url', TEMP_FILE, mockLogger)).rejects.toThrow('foo');
-});
-
-afterEach(async () => await del(TEMP_DIR));
diff --git a/x-pack/plugins/reporting/server/browsers/download/ensure_downloaded.test.ts b/x-pack/plugins/reporting/server/browsers/download/ensure_downloaded.test.ts
deleted file mode 100644
index 9db128c019ac0..0000000000000
--- a/x-pack/plugins/reporting/server/browsers/download/ensure_downloaded.test.ts
+++ /dev/null
@@ -1,120 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import path from 'path';
-import mockFs from 'mock-fs';
-import { existsSync, readdirSync } from 'fs';
-import { chromium } from '../chromium';
-import { download } from './download';
-import { md5 } from './checksum';
-import { ensureBrowserDownloaded } from './ensure_downloaded';
-import { LevelLogger } from '../../lib';
-
-jest.mock('./checksum');
-jest.mock('./download');
-
-// https://github.com/elastic/kibana/issues/115881
-describe.skip('ensureBrowserDownloaded', () => {
- let logger: jest.Mocked;
-
- beforeEach(() => {
- logger = {
- debug: jest.fn(),
- error: jest.fn(),
- warning: jest.fn(),
- } as unknown as typeof logger;
-
- (md5 as jest.MockedFunction).mockImplementation(
- async (packagePath) =>
- chromium.paths.packages.find(
- (packageInfo) => chromium.paths.resolvePath(packageInfo) === packagePath
- )?.archiveChecksum ?? 'some-md5'
- );
-
- (download as jest.MockedFunction).mockImplementation(
- async (_url, packagePath) =>
- chromium.paths.packages.find(
- (packageInfo) => chromium.paths.resolvePath(packageInfo) === packagePath
- )?.archiveChecksum ?? 'some-md5'
- );
-
- mockFs();
- });
-
- afterEach(() => {
- mockFs.restore();
- jest.resetAllMocks();
- });
-
- it('should remove unexpected files', async () => {
- const unexpectedPath1 = `${chromium.paths.archivesPath}/unexpected1`;
- const unexpectedPath2 = `${chromium.paths.archivesPath}/unexpected2`;
-
- mockFs({
- [unexpectedPath1]: 'test',
- [unexpectedPath2]: 'test',
- });
-
- await ensureBrowserDownloaded(logger);
-
- expect(existsSync(unexpectedPath1)).toBe(false);
- expect(existsSync(unexpectedPath2)).toBe(false);
- });
-
- it('should reject when download fails', async () => {
- (download as jest.MockedFunction).mockRejectedValueOnce(
- new Error('some error')
- );
-
- await expect(ensureBrowserDownloaded(logger)).rejects.toBeInstanceOf(Error);
- });
-
- it('should reject when downloaded md5 hash is different', async () => {
- (download as jest.MockedFunction).mockResolvedValue('random-md5');
-
- await expect(ensureBrowserDownloaded(logger)).rejects.toBeInstanceOf(Error);
- });
-
- describe('when archives are already present', () => {
- beforeEach(() => {
- mockFs(
- Object.fromEntries(
- chromium.paths.packages.map((packageInfo) => [
- chromium.paths.resolvePath(packageInfo),
- '',
- ])
- )
- );
- });
-
- it('should not download again', async () => {
- await ensureBrowserDownloaded(logger);
-
- expect(download).not.toHaveBeenCalled();
- const paths = [
- readdirSync(path.resolve(chromium.paths.archivesPath + '/x64')),
- readdirSync(path.resolve(chromium.paths.archivesPath + '/arm64')),
- ];
-
- expect(paths).toEqual([
- expect.arrayContaining([
- 'chrome-win.zip',
- 'chromium-70f5d88-linux_x64.zip',
- 'chromium-d163fd7-darwin_x64.zip',
- ]),
- expect.arrayContaining(['chromium-70f5d88-linux_arm64.zip']),
- ]);
- });
-
- it('should download again if md5 hash different', async () => {
- (md5 as jest.MockedFunction).mockResolvedValueOnce('random-md5');
- await ensureBrowserDownloaded(logger);
-
- expect(download).toHaveBeenCalledTimes(1);
- });
- });
-});
diff --git a/x-pack/plugins/reporting/server/browsers/download/ensure_downloaded.ts b/x-pack/plugins/reporting/server/browsers/download/ensure_downloaded.ts
deleted file mode 100644
index 2766b404f1dd1..0000000000000
--- a/x-pack/plugins/reporting/server/browsers/download/ensure_downloaded.ts
+++ /dev/null
@@ -1,101 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { existsSync } from 'fs';
-import del from 'del';
-import { BrowserDownload, chromium } from '../';
-import { GenericLevelLogger } from '../../lib/level_logger';
-import { md5 } from './checksum';
-import { download } from './download';
-
-/**
- * Check for the downloaded archive of each requested browser type and
- * download them if they are missing or their checksum is invalid
- */
-export async function ensureBrowserDownloaded(logger: GenericLevelLogger) {
- await ensureDownloaded([chromium], logger);
-}
-
-/**
- * Clears the unexpected files in the browsers archivesPath
- * and ensures that all packages/archives are downloaded and
- * that their checksums match the declared value
- */
-async function ensureDownloaded(browsers: BrowserDownload[], logger: GenericLevelLogger) {
- await Promise.all(
- browsers.map(async ({ paths: pSet }) => {
- const removedFiles = await del(`${pSet.archivesPath}/**/*`, {
- force: true,
- onlyFiles: true,
- ignore: pSet.getAllArchiveFilenames(),
- });
-
- removedFiles.forEach((path) => {
- logger.warning(`Deleting unexpected file ${path}`);
- });
-
- const invalidChecksums: string[] = [];
- await Promise.all(
- pSet.packages.map(async (p) => {
- const { archiveFilename, archiveChecksum } = p;
- if (archiveFilename && archiveChecksum) {
- const path = pSet.resolvePath(p);
- const pathExists = existsSync(path);
-
- let foundChecksum: string;
- try {
- foundChecksum = await md5(path).catch();
- } catch {
- foundChecksum = 'MISSING';
- }
-
- if (pathExists && foundChecksum === archiveChecksum) {
- logger.debug(`Browser archive for ${p.platform}/${p.architecture} found in ${path} `);
- return;
- }
-
- if (!pathExists) {
- logger.warning(
- `Browser archive for ${p.platform}/${p.architecture} not found in ${path}.`
- );
- }
- if (foundChecksum !== archiveChecksum) {
- logger.warning(
- `Browser archive checksum for ${p.platform}/${p.architecture} ` +
- `is ${foundChecksum} but ${archiveChecksum} was expected.`
- );
- }
-
- const url = pSet.getDownloadUrl(p);
- try {
- const downloadedChecksum = await download(url, path, logger);
- if (downloadedChecksum !== archiveChecksum) {
- logger.warning(
- `Invalid checksum for ${p.platform}/${p.architecture}: ` +
- `expected ${archiveChecksum} got ${downloadedChecksum}`
- );
- invalidChecksums.push(`${url} => ${path}`);
- }
- } catch (err) {
- throw new Error(`Failed to download ${url}: ${err}`);
- }
- }
- })
- );
-
- if (invalidChecksums.length) {
- const err = new Error(
- `Error downloading browsers, checksums incorrect for:\n - ${invalidChecksums.join(
- '\n - '
- )}`
- );
- logger.error(err);
- throw err;
- }
- })
- );
-}
diff --git a/x-pack/plugins/reporting/server/browsers/index.ts b/x-pack/plugins/reporting/server/browsers/index.ts
deleted file mode 100644
index be5c85a6e9581..0000000000000
--- a/x-pack/plugins/reporting/server/browsers/index.ts
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { first } from 'rxjs/operators';
-import { ReportingCore } from '../';
-import { LevelLogger } from '../lib';
-import { chromium, ChromiumArchivePaths } from './chromium';
-import { HeadlessChromiumDriverFactory } from './chromium/driver_factory';
-import { installBrowser } from './install';
-
-export { chromium } from './chromium';
-export { HeadlessChromiumDriver } from './chromium/driver';
-export { HeadlessChromiumDriverFactory } from './chromium/driver_factory';
-
-type CreateDriverFactory = (
- core: ReportingCore,
- binaryPath: string,
- logger: LevelLogger
-) => HeadlessChromiumDriverFactory;
-
-export interface BrowserDownload {
- createDriverFactory: CreateDriverFactory;
- paths: ChromiumArchivePaths;
-}
-
-export const initializeBrowserDriverFactory = async (core: ReportingCore, logger: LevelLogger) => {
- const chromiumLogger = logger.clone(['chromium']);
- const { binaryPath$ } = installBrowser(chromiumLogger);
- const binaryPath = await binaryPath$.pipe(first()).toPromise();
- return chromium.createDriverFactory(core, binaryPath, chromiumLogger);
-};
diff --git a/x-pack/plugins/reporting/server/browsers/install.ts b/x-pack/plugins/reporting/server/browsers/install.ts
deleted file mode 100644
index 0441bbcfb5306..0000000000000
--- a/x-pack/plugins/reporting/server/browsers/install.ts
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import del from 'del';
-import os from 'os';
-import path from 'path';
-import * as Rx from 'rxjs';
-import { GenericLevelLogger } from '../lib/level_logger';
-import { ChromiumArchivePaths } from './chromium';
-import { ensureBrowserDownloaded } from './download';
-import { md5 } from './download/checksum';
-import { extract } from './extract';
-
-/**
- * "install" a browser by type into installs path by extracting the downloaded
- * archive. If there is an error extracting the archive an `ExtractError` is thrown
- */
-export function installBrowser(
- logger: GenericLevelLogger,
- chromiumPath: string = path.resolve(__dirname, '../../chromium'),
- platform: string = process.platform,
- architecture: string = os.arch()
-): { binaryPath$: Rx.Subject } {
- const binaryPath$ = new Rx.Subject();
-
- const paths = new ChromiumArchivePaths();
- const pkg = paths.find(platform, architecture);
-
- if (!pkg) {
- throw new Error(`Unsupported platform: ${platform}-${architecture}`);
- }
-
- const backgroundInstall = async () => {
- const binaryPath = paths.getBinaryPath(pkg);
- const binaryChecksum = await md5(binaryPath).catch(() => '');
-
- if (binaryChecksum !== pkg.binaryChecksum) {
- logger.warning(
- `Found browser binary checksum for ${pkg.platform}/${pkg.architecture} ` +
- `is ${binaryChecksum} but ${pkg.binaryChecksum} was expected. Re-installing...`
- );
- try {
- await del(chromiumPath);
- } catch (err) {
- logger.error(err);
- }
-
- try {
- await ensureBrowserDownloaded(logger);
- const archive = path.join(paths.archivesPath, pkg.architecture, pkg.archiveFilename);
- logger.info(`Extracting [${archive}] to [${chromiumPath}]`);
- await extract(archive, chromiumPath);
- } catch (err) {
- logger.error(err);
- }
- }
-
- logger.info(`Browser executable: ${binaryPath}`);
-
- binaryPath$.next(binaryPath); // subscribers wait for download and extract to complete
- };
-
- backgroundInstall();
-
- return {
- binaryPath$,
- };
-}
diff --git a/x-pack/plugins/reporting/server/config/__snapshots__/schema.test.ts.snap b/x-pack/plugins/reporting/server/config/__snapshots__/schema.test.ts.snap
index a384550f18462..65f3c45fb2255 100644
--- a/x-pack/plugins/reporting/server/config/__snapshots__/schema.test.ts.snap
+++ b/x-pack/plugins/reporting/server/config/__snapshots__/schema.test.ts.snap
@@ -3,52 +3,8 @@
exports[`Reporting Config Schema context {"dev":false,"dist":false} produces correct config 1`] = `
Object {
"capture": Object {
- "browser": Object {
- "autoDownload": true,
- "chromium": Object {
- "proxy": Object {
- "enabled": false,
- },
- },
- "type": "chromium",
- },
"loadDelay": "PT3S",
"maxAttempts": 1,
- "networkPolicy": Object {
- "enabled": true,
- "rules": Array [
- Object {
- "allow": true,
- "host": undefined,
- "protocol": "http:",
- },
- Object {
- "allow": true,
- "host": undefined,
- "protocol": "https:",
- },
- Object {
- "allow": true,
- "host": undefined,
- "protocol": "ws:",
- },
- Object {
- "allow": true,
- "host": undefined,
- "protocol": "wss:",
- },
- Object {
- "allow": true,
- "host": undefined,
- "protocol": "data:",
- },
- Object {
- "allow": false,
- "host": undefined,
- "protocol": undefined,
- },
- ],
- },
"timeouts": Object {
"openUrl": "PT1M",
"renderComplete": "PT30S",
@@ -101,53 +57,8 @@ Object {
exports[`Reporting Config Schema context {"dev":false,"dist":true} produces correct config 1`] = `
Object {
"capture": Object {
- "browser": Object {
- "autoDownload": false,
- "chromium": Object {
- "inspect": false,
- "proxy": Object {
- "enabled": false,
- },
- },
- "type": "chromium",
- },
"loadDelay": "PT3S",
"maxAttempts": 3,
- "networkPolicy": Object {
- "enabled": true,
- "rules": Array [
- Object {
- "allow": true,
- "host": undefined,
- "protocol": "http:",
- },
- Object {
- "allow": true,
- "host": undefined,
- "protocol": "https:",
- },
- Object {
- "allow": true,
- "host": undefined,
- "protocol": "ws:",
- },
- Object {
- "allow": true,
- "host": undefined,
- "protocol": "wss:",
- },
- Object {
- "allow": true,
- "host": undefined,
- "protocol": "data:",
- },
- Object {
- "allow": false,
- "host": undefined,
- "protocol": undefined,
- },
- ],
- },
"timeouts": Object {
"openUrl": "PT1M",
"renderComplete": "PT30S",
diff --git a/x-pack/plugins/reporting/server/config/create_config.test.ts b/x-pack/plugins/reporting/server/config/create_config.test.ts
index 3c5ecdc1dab0b..fd8180bd46a05 100644
--- a/x-pack/plugins/reporting/server/config/create_config.test.ts
+++ b/x-pack/plugins/reporting/server/config/create_config.test.ts
@@ -77,13 +77,6 @@ describe('Reporting server createConfig$', () => {
expect(result).toMatchInlineSnapshot(`
Object {
- "capture": Object {
- "browser": Object {
- "chromium": Object {
- "disableSandbox": true,
- },
- },
- },
"csv": Object {},
"encryptionKey": "iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii",
"index": ".reporting",
@@ -106,47 +99,6 @@ describe('Reporting server createConfig$', () => {
expect(mockLogger.warn).not.toHaveBeenCalled();
});
- it('uses user-provided disableSandbox: false', async () => {
- mockInitContext = coreMock.createPluginInitializerContext(
- createMockConfigSchema({
- encryptionKey: '888888888888888888888888888888888',
- capture: { browser: { chromium: { disableSandbox: false } } },
- })
- );
- const mockConfig$ = createMockConfig(mockInitContext);
- const result = await createConfig$(mockCoreSetup, mockConfig$, mockLogger).toPromise();
-
- expect(result.capture.browser.chromium).toMatchObject({ disableSandbox: false });
- expect(mockLogger.warn).not.toHaveBeenCalled();
- });
-
- it('uses user-provided disableSandbox: true', async () => {
- mockInitContext = coreMock.createPluginInitializerContext(
- createMockConfigSchema({
- encryptionKey: '888888888888888888888888888888888',
- capture: { browser: { chromium: { disableSandbox: true } } },
- })
- );
- const mockConfig$ = createMockConfig(mockInitContext);
- const result = await createConfig$(mockCoreSetup, mockConfig$, mockLogger).toPromise();
-
- expect(result.capture.browser.chromium).toMatchObject({ disableSandbox: true });
- expect(mockLogger.warn).not.toHaveBeenCalled();
- });
-
- it('provides a default for disableSandbox', async () => {
- mockInitContext = coreMock.createPluginInitializerContext(
- createMockConfigSchema({
- encryptionKey: '888888888888888888888888888888888',
- })
- );
- const mockConfig$ = createMockConfig(mockInitContext);
- const result = await createConfig$(mockCoreSetup, mockConfig$, mockLogger).toPromise();
-
- expect(result.capture.browser.chromium).toMatchObject({ disableSandbox: expect.any(Boolean) });
- expect(mockLogger.warn).not.toHaveBeenCalled();
- });
-
it.each(['0', '0.0', '0.0.0', '0.0.0.0', '0000:0000:0000:0000:0000:0000:0000:0000', '::'])(
`apply failover logic when hostname is given as "%s"`,
async (hostname) => {
diff --git a/x-pack/plugins/reporting/server/config/create_config.ts b/x-pack/plugins/reporting/server/config/create_config.ts
index 5de54a43582ab..2ac225ec4576a 100644
--- a/x-pack/plugins/reporting/server/config/create_config.ts
+++ b/x-pack/plugins/reporting/server/config/create_config.ts
@@ -7,17 +7,15 @@
import crypto from 'crypto';
import ipaddr from 'ipaddr.js';
-import { sum, upperFirst } from 'lodash';
+import { sum } from 'lodash';
import { Observable } from 'rxjs';
-import { map, mergeMap } from 'rxjs/operators';
+import { map } from 'rxjs/operators';
import { CoreSetup } from 'src/core/server';
import { LevelLogger } from '../lib';
-import { getDefaultChromiumSandboxDisabled } from './default_chromium_sandbox_disabled';
import { ReportingConfigType } from './schema';
/*
* Set up dynamic config defaults
- * - xpack.capture.browser.chromium.disableSandbox
* - xpack.kibanaServer
* - xpack.reporting.encryptionKey
*/
@@ -71,41 +69,6 @@ export function createConfig$(
protocol: kibanaServerProtocol,
},
};
- }),
- mergeMap(async (config) => {
- if (config.capture.browser.chromium.disableSandbox != null) {
- // disableSandbox was set by user
- return { ...config };
- }
-
- // disableSandbox was not set by user, apply default for OS
- const { os, disableSandbox } = await getDefaultChromiumSandboxDisabled();
- const osName = [os.os, os.dist, os.release].filter(Boolean).map(upperFirst).join(' ');
-
- logger.debug(`Running on OS: '{osName}'`);
-
- if (disableSandbox === true) {
- logger.warn(
- `Chromium sandbox provides an additional layer of protection, but is not supported for ${osName} OS.` +
- ` Automatically setting 'xpack.reporting.capture.browser.chromium.disableSandbox: true'.`
- );
- } else {
- logger.info(
- `Chromium sandbox provides an additional layer of protection, and is supported for ${osName} OS.` +
- ` Automatically enabling Chromium sandbox.`
- );
- }
-
- return {
- ...config,
- capture: {
- ...config.capture,
- browser: {
- ...config.capture.browser,
- chromium: { ...config.capture.browser.chromium, disableSandbox },
- },
- },
- };
})
);
}
diff --git a/x-pack/plugins/reporting/server/config/default_chromium_sandbox_disabled.test.ts b/x-pack/plugins/reporting/server/config/default_chromium_sandbox_disabled.test.ts
deleted file mode 100644
index 6ca75b7a1701b..0000000000000
--- a/x-pack/plugins/reporting/server/config/default_chromium_sandbox_disabled.test.ts
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor 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('getos', () => {
- return jest.fn();
-});
-
-import { getDefaultChromiumSandboxDisabled } from './default_chromium_sandbox_disabled';
-import getos from 'getos';
-
-interface TestObject {
- os: string;
- dist?: string;
- release?: string;
-}
-
-function defaultTest(os: TestObject, expectedDefault: boolean) {
- test(`${expectedDefault ? 'disabled' : 'enabled'} on ${JSON.stringify(os)}`, async () => {
- (getos as jest.Mock).mockImplementation((cb) => cb(null, os));
- const actualDefault = await getDefaultChromiumSandboxDisabled();
- expect(actualDefault.disableSandbox).toBe(expectedDefault);
- });
-}
-
-defaultTest({ os: 'win32' }, false);
-defaultTest({ os: 'darwin' }, false);
-defaultTest({ os: 'linux', dist: 'Centos', release: '7.0' }, true);
-defaultTest({ os: 'linux', dist: 'Red Hat Linux', release: '7.0' }, true);
-defaultTest({ os: 'linux', dist: 'Ubuntu Linux', release: '14.04' }, false);
-defaultTest({ os: 'linux', dist: 'Ubuntu Linux', release: '16.04' }, false);
-defaultTest({ os: 'linux', dist: 'SUSE Linux', release: '11' }, false);
-defaultTest({ os: 'linux', dist: 'SUSE Linux', release: '12' }, false);
-defaultTest({ os: 'linux', dist: 'SUSE Linux', release: '42.0' }, false);
-defaultTest({ os: 'linux', dist: 'Debian', release: '8' }, true);
-defaultTest({ os: 'linux', dist: 'Debian', release: '9' }, true);
diff --git a/x-pack/plugins/reporting/server/config/index.ts b/x-pack/plugins/reporting/server/config/index.ts
index 711e930484e01..963895d1fe583 100644
--- a/x-pack/plugins/reporting/server/config/index.ts
+++ b/x-pack/plugins/reporting/server/config/index.ts
@@ -19,6 +19,7 @@ export const config: PluginConfigDescriptor = {
schema: ConfigSchema,
deprecations: ({ unused }) => [
unused('capture.browser.chromium.maxScreenshotDimension', { level: 'warning' }), // unused since 7.8
+ unused('capture.browser.type'),
unused('poll.jobCompletionNotifier.intervalErrorMultiplier', { level: 'warning' }), // unused since 7.10
unused('poll.jobsRefresh.intervalErrorMultiplier', { level: 'warning' }), // unused since 7.10
unused('capture.viewport', { level: 'warning' }), // deprecated as unused since 7.16
@@ -72,7 +73,6 @@ export const config: PluginConfigDescriptor = {
capture: {
maxAttempts: true,
timeouts: { openUrl: true, renderComplete: true, waitForElements: true },
- networkPolicy: false, // show as [redacted]
zoom: true,
},
csv: { maxSizeBytes: true, scroll: { size: true, duration: true } },
diff --git a/x-pack/plugins/reporting/server/config/schema.test.ts b/x-pack/plugins/reporting/server/config/schema.test.ts
index c49490be87a15..3af7a4e5cfe4c 100644
--- a/x-pack/plugins/reporting/server/config/schema.test.ts
+++ b/x-pack/plugins/reporting/server/config/schema.test.ts
@@ -55,47 +55,12 @@ describe('Reporting Config Schema', () => {
).toBe('qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq');
expect(ConfigSchema.validate({ encryptionKey: 'weaksauce' }).encryptionKey).toBe('weaksauce');
-
- // disableSandbox
- expect(
- ConfigSchema.validate({ capture: { browser: { chromium: { disableSandbox: true } } } })
- .capture.browser.chromium
- ).toMatchObject({ disableSandbox: true, proxy: { enabled: false } });
-
// kibanaServer
expect(
ConfigSchema.validate({ kibanaServer: { hostname: 'Frodo' } }).kibanaServer
).toMatchObject({ hostname: 'Frodo' });
});
- it('allows setting a wildcard for chrome proxy bypass', () => {
- expect(
- ConfigSchema.validate({
- capture: {
- browser: {
- chromium: {
- proxy: {
- enabled: true,
- server: 'http://example.com:8080',
- bypass: ['*.example.com', '*bar.example.com', 'bats.example.com'],
- },
- },
- },
- },
- }).capture.browser.chromium.proxy
- ).toMatchInlineSnapshot(`
- Object {
- "bypass": Array [
- "*.example.com",
- "*bar.example.com",
- "bats.example.com",
- ],
- "enabled": true,
- "server": "http://example.com:8080",
- }
- `);
- });
-
it.each(['0', '0.0', '0.0.0'])(
`fails to validate "kibanaServer.hostname" with an invalid hostname: "%s"`,
(address) => {
diff --git a/x-pack/plugins/reporting/server/config/schema.ts b/x-pack/plugins/reporting/server/config/schema.ts
index 4c56fc4c6db60..c031ed4f94f9d 100644
--- a/x-pack/plugins/reporting/server/config/schema.ts
+++ b/x-pack/plugins/reporting/server/config/schema.ts
@@ -46,20 +46,6 @@ const QueueSchema = schema.object({
}),
});
-const RulesSchema = schema.object({
- allow: schema.boolean(),
- host: schema.maybe(schema.string()),
- protocol: schema.maybe(
- schema.string({
- validate(value) {
- if (!/:$/.test(value)) {
- return 'must end in colon';
- }
- },
- })
- ),
-});
-
const CaptureSchema = schema.object({
timeouts: schema.object({
openUrl: schema.oneOf([schema.number(), schema.duration()], {
@@ -72,56 +58,10 @@ const CaptureSchema = schema.object({
defaultValue: moment.duration({ seconds: 30 }),
}),
}),
- networkPolicy: schema.object({
- enabled: schema.boolean({ defaultValue: true }),
- rules: schema.arrayOf(RulesSchema, {
- defaultValue: [
- { host: undefined, allow: true, protocol: 'http:' },
- { host: undefined, allow: true, protocol: 'https:' },
- { host: undefined, allow: true, protocol: 'ws:' },
- { host: undefined, allow: true, protocol: 'wss:' },
- { host: undefined, allow: true, protocol: 'data:' },
- { host: undefined, allow: false, protocol: undefined }, // Default action is to deny!
- ],
- }),
- }),
zoom: schema.number({ defaultValue: 2 }),
loadDelay: schema.oneOf([schema.number(), schema.duration()], {
defaultValue: moment.duration({ seconds: 3 }),
}),
- browser: schema.object({
- autoDownload: schema.conditional(
- schema.contextRef('dist'),
- true,
- schema.boolean({ defaultValue: false }),
- schema.boolean({ defaultValue: true })
- ),
- chromium: schema.object({
- inspect: schema.conditional(
- schema.contextRef('dist'),
- true,
- schema.boolean({ defaultValue: false }),
- schema.maybe(schema.never())
- ),
- disableSandbox: schema.maybe(schema.boolean()), // default value is dynamic in createConfig$
- proxy: schema.object({
- enabled: schema.boolean({ defaultValue: false }),
- server: schema.conditional(
- schema.siblingRef('enabled'),
- true,
- schema.uri({ scheme: ['http', 'https'] }),
- schema.maybe(schema.never())
- ),
- bypass: schema.conditional(
- schema.siblingRef('enabled'),
- true,
- schema.arrayOf(schema.string()),
- schema.maybe(schema.never())
- ),
- }),
- }),
- type: schema.string({ defaultValue: 'chromium' }),
- }),
maxAttempts: schema.conditional(
schema.contextRef('dist'),
true,
diff --git a/x-pack/plugins/reporting/server/core.ts b/x-pack/plugins/reporting/server/core.ts
index 43aefb73aebb9..63900db4016b5 100644
--- a/x-pack/plugins/reporting/server/core.ts
+++ b/x-pack/plugins/reporting/server/core.ts
@@ -7,8 +7,8 @@
import Hapi from '@hapi/hapi';
import * as Rx from 'rxjs';
-import { filter, first, map, take } from 'rxjs/operators';
-import { ScreenshotModePluginSetup } from 'src/plugins/screenshot_mode/server';
+import { filter, first, map, switchMap, take } from 'rxjs/operators';
+import type { ScreenshottingStart, ScreenshotResult } from '../../screenshotting/server';
import {
BasePath,
IClusterClient,
@@ -28,13 +28,14 @@ import { SecurityPluginSetup } from '../../security/server';
import { DEFAULT_SPACE_ID } from '../../spaces/common/constants';
import { SpacesPluginSetup } from '../../spaces/server';
import { TaskManagerSetupContract, TaskManagerStartContract } from '../../task_manager/server';
+import { REPORTING_REDIRECT_LOCATOR_STORE_KEY } from '../common/constants';
+import { durationToNumber } from '../common/schema_utils';
import { ReportingConfig, ReportingSetup } from './';
-import { HeadlessChromiumDriverFactory } from './browsers/chromium/driver_factory';
import { ReportingConfigType } from './config';
import { checkLicense, getExportTypesRegistry, LevelLogger } from './lib';
import { ReportingStore } from './lib/store';
import { ExecuteReportTask, MonitorReportsTask, ReportTaskParams } from './lib/tasks';
-import { ReportingPluginRouter } from './types';
+import { ReportingPluginRouter, ScreenshotOptions } from './types';
export interface ReportingInternalSetup {
basePath: Pick;
@@ -44,13 +45,11 @@ export interface ReportingInternalSetup {
security?: SecurityPluginSetup;
spaces?: SpacesPluginSetup;
taskManager: TaskManagerSetupContract;
- screenshotMode: ScreenshotModePluginSetup;
logger: LevelLogger;
status: StatusServiceSetup;
}
export interface ReportingInternalStart {
- browserDriverFactory: HeadlessChromiumDriverFactory;
store: ReportingStore;
savedObjects: SavedObjectsServiceStart;
uiSettings: UiSettingsServiceStart;
@@ -58,6 +57,7 @@ export interface ReportingInternalStart {
data: DataPluginStart;
taskManager: TaskManagerStartContract;
logger: LevelLogger;
+ screenshotting: ScreenshottingStart;
}
export class ReportingCore {
@@ -253,18 +253,6 @@ export class ReportingCore {
.toPromise();
}
- private getScreenshotModeDep() {
- return this.getPluginSetupDeps().screenshotMode;
- }
-
- public getEnableScreenshotMode() {
- return this.getScreenshotModeDep().setScreenshotModeEnabled;
- }
-
- public getSetScreenshotLayout() {
- return this.getScreenshotModeDep().setScreenshotLayout;
- }
-
/*
* Gives synchronous access to the setupDeps
*/
@@ -350,6 +338,35 @@ export class ReportingCore {
return startDeps.esClient;
}
+ public getScreenshots(options: ScreenshotOptions): Rx.Observable {
+ return Rx.defer(() => this.getPluginStartDeps()).pipe(
+ switchMap(({ screenshotting }) => {
+ const config = this.getConfig();
+ return screenshotting.getScreenshots({
+ ...options,
+
+ timeouts: {
+ loadDelay: durationToNumber(config.get('capture', 'loadDelay')),
+ openUrl: durationToNumber(config.get('capture', 'timeouts', 'openUrl')),
+ waitForElements: durationToNumber(config.get('capture', 'timeouts', 'waitForElements')),
+ renderComplete: durationToNumber(config.get('capture', 'timeouts', 'renderComplete')),
+ },
+
+ layout: {
+ zoom: config.get('capture', 'zoom'),
+ ...options.layout,
+ },
+
+ urls: options.urls.map((url) =>
+ typeof url === 'string'
+ ? url
+ : [url[0], { [REPORTING_REDIRECT_LOCATOR_STORE_KEY]: url[1] }]
+ ),
+ });
+ })
+ );
+ }
+
public trackReport(reportId: string) {
this.executing.add(reportId);
}
diff --git a/x-pack/plugins/reporting/server/export_types/common/generate_png.ts b/x-pack/plugins/reporting/server/export_types/common/generate_png.ts
index c5e70a6c93eff..8c83e0ae73527 100644
--- a/x-pack/plugins/reporting/server/export_types/common/generate_png.ts
+++ b/x-pack/plugins/reporting/server/export_types/common/generate_png.ts
@@ -8,70 +8,60 @@
import apm from 'elastic-apm-node';
import * as Rx from 'rxjs';
import { finalize, map, tap } from 'rxjs/operators';
+import { LayoutTypes } from '../../../../screenshotting/common';
import { REPORTING_TRANSACTION_TYPE } from '../../../common/constants';
import { ReportingCore } from '../../';
-import { UrlOrUrlLocatorTuple } from '../../../common/types';
+import { ScreenshotOptions } from '../../types';
import { LevelLogger } from '../../lib';
-import { LayoutParams, LayoutSelectorDictionary, PreserveLayout } from '../../lib/layouts';
-import { getScreenshots$, ScreenshotResults } from '../../lib/screenshots';
-import { ConditionalHeaders } from '../common';
-export async function generatePngObservableFactory(reporting: ReportingCore) {
- const config = reporting.getConfig();
- const captureConfig = config.get('capture');
- const { browserDriverFactory } = await reporting.getPluginStartDeps();
-
- return function generatePngObservable(
- logger: LevelLogger,
- urlOrUrlLocatorTuple: UrlOrUrlLocatorTuple,
- browserTimezone: string | undefined,
- conditionalHeaders: ConditionalHeaders,
- layoutParams: LayoutParams & { selectors?: Partial }
- ): Rx.Observable<{ buffer: Buffer; warnings: string[] }> {
- const apmTrans = apm.startTransaction('generate-png', REPORTING_TRANSACTION_TYPE);
- const apmLayout = apmTrans?.startSpan('create-layout', 'setup');
- if (!layoutParams || !layoutParams.dimensions) {
- throw new Error(`LayoutParams.Dimensions is undefined.`);
- }
- const layout = new PreserveLayout(layoutParams.dimensions, layoutParams.selectors);
+export function generatePngObservable(
+ reporting: ReportingCore,
+ logger: LevelLogger,
+ options: ScreenshotOptions
+): Rx.Observable<{ buffer: Buffer; warnings: string[] }> {
+ const apmTrans = apm.startTransaction('generate-png', REPORTING_TRANSACTION_TYPE);
+ const apmLayout = apmTrans?.startSpan('create-layout', 'setup');
+ if (!options.layout.dimensions) {
+ throw new Error(`LayoutParams.Dimensions is undefined.`);
+ }
+ const layout = {
+ id: LayoutTypes.PRESERVE_LAYOUT,
+ ...options.layout,
+ };
- if (apmLayout) apmLayout.end();
+ apmLayout?.end();
- const apmScreenshots = apmTrans?.startSpan('screenshots-pipeline', 'setup');
- let apmBuffer: typeof apm.currentSpan;
- const screenshots$ = getScreenshots$(captureConfig, browserDriverFactory, {
- logger,
- urlsOrUrlLocatorTuples: [urlOrUrlLocatorTuple],
- conditionalHeaders,
- layout,
- browserTimezone,
- }).pipe(
- tap(() => {
- apmScreenshots?.end();
- apmBuffer = apmTrans?.startSpan('get-buffer', 'output') ?? null;
- }),
- map((results: ScreenshotResults[]) => ({
- buffer: results[0].screenshots[0].data,
- warnings: results.reduce((found, current) => {
- if (current.error) {
- found.push(current.error.message);
- }
- if (current.renderErrors) {
- found.push(...current.renderErrors);
- }
- return found;
- }, [] as string[]),
- })),
- tap(({ buffer }) => {
- logger.debug(`PNG buffer byte length: ${buffer.byteLength}`);
- apmTrans?.setLabel('byte-length', buffer.byteLength, false);
- }),
- finalize(() => {
- apmBuffer?.end();
- apmTrans?.end();
- })
- );
+ const apmScreenshots = apmTrans?.startSpan('screenshots-pipeline', 'setup');
+ let apmBuffer: typeof apm.currentSpan;
- return screenshots$;
- };
+ return reporting.getScreenshots({ ...options, layout }).pipe(
+ tap(({ metrics$ }) => {
+ metrics$.subscribe(({ cpu, memory }) => {
+ apmTrans?.setLabel('cpu', cpu, false);
+ apmTrans?.setLabel('memory', memory, false);
+ });
+ apmScreenshots?.end();
+ apmBuffer = apmTrans?.startSpan('get-buffer', 'output') ?? null;
+ }),
+ map(({ results }) => ({
+ buffer: results[0].screenshots[0].data,
+ warnings: results.reduce((found, current) => {
+ if (current.error) {
+ found.push(current.error.message);
+ }
+ if (current.renderErrors) {
+ found.push(...current.renderErrors);
+ }
+ return found;
+ }, [] as string[]),
+ })),
+ tap(({ buffer }) => {
+ logger.debug(`PNG buffer byte length: ${buffer.byteLength}`);
+ apmTrans?.setLabel('byte-length', buffer.byteLength, false);
+ }),
+ finalize(() => {
+ apmBuffer?.end();
+ apmTrans?.end();
+ })
+ );
}
diff --git a/x-pack/plugins/reporting/server/export_types/common/index.ts b/x-pack/plugins/reporting/server/export_types/common/index.ts
index c35dcb5344e21..501de48e0450a 100644
--- a/x-pack/plugins/reporting/server/export_types/common/index.ts
+++ b/x-pack/plugins/reporting/server/export_types/common/index.ts
@@ -10,7 +10,7 @@ export { getConditionalHeaders } from './get_conditional_headers';
export { getFullUrls } from './get_full_urls';
export { omitBlockedHeaders } from './omit_blocked_headers';
export { validateUrls } from './validate_urls';
-export { generatePngObservableFactory } from './generate_png';
+export { generatePngObservable } from './generate_png';
export { getCustomLogo } from './get_custom_logo';
export interface TimeRangeParams {
diff --git a/x-pack/plugins/reporting/server/export_types/common/pdf/get_template.ts b/x-pack/plugins/reporting/server/export_types/common/pdf/get_template.ts
index 58ddeb51e7a4f..0c7fedc8f7b7e 100644
--- a/x-pack/plugins/reporting/server/export_types/common/pdf/get_template.ts
+++ b/x-pack/plugins/reporting/server/export_types/common/pdf/get_template.ts
@@ -13,12 +13,12 @@ import {
StyleDictionary,
TDocumentDefinitions,
} from 'pdfmake/interfaces';
-import { LayoutInstance } from '../../../lib/layouts';
+import type { Layout } from '../../../../../screenshotting/server';
import { REPORTING_TABLE_LAYOUT } from './get_doc_options';
import { getFont } from './get_font';
export function getTemplate(
- layout: LayoutInstance,
+ layout: Layout,
logo: string | undefined,
title: string,
tableBorderWidth: number,
diff --git a/x-pack/plugins/reporting/server/export_types/common/pdf/index.test.ts b/x-pack/plugins/reporting/server/export_types/common/pdf/index.test.ts
index 74a247d4568ab..2df98c6c79357 100644
--- a/x-pack/plugins/reporting/server/export_types/common/pdf/index.test.ts
+++ b/x-pack/plugins/reporting/server/export_types/common/pdf/index.test.ts
@@ -5,8 +5,7 @@
* 2.0.
*/
-import { PreserveLayout, PrintLayout } from '../../../lib/layouts';
-import { createMockConfig, createMockConfigSchema } from '../../../test_helpers';
+import { createMockLayout } from '../../../../../screenshotting/server/layouts/mock';
import { PdfMaker } from './';
const imageBase64 = Buffer.from(
@@ -16,66 +15,22 @@ const imageBase64 = Buffer.from(
// FLAKY: https://github.com/elastic/kibana/issues/118484
describe.skip('PdfMaker', () => {
- it('makes PDF using PrintLayout mode', async () => {
- const config = createMockConfig(createMockConfigSchema());
- const layout = new PrintLayout(config.get('capture'));
- const pdf = new PdfMaker(layout, undefined);
+ let layout: ReturnType;
+ let pdf: PdfMaker;
- expect(pdf.setTitle('the best PDF in the world')).toBe(undefined);
- expect([
- pdf.addImage(imageBase64, { title: 'first viz', description: '☃️' }),
- pdf.addImage(imageBase64, { title: 'second viz', description: '❄️' }),
- ]).toEqual([undefined, undefined]);
-
- const { _layout: testLayout, _title: testTitle } = pdf as unknown as {
- _layout: object;
- _title: string;
- };
- expect(testLayout).toMatchObject({
- captureConfig: { browser: { chromium: { disableSandbox: true } } }, // NOTE: irrelevant data?
- groupCount: 2,
- id: 'print',
- selectors: {
- itemsCountAttribute: 'data-shared-items-count',
- renderComplete: '[data-shared-item]',
- screenshot: '[data-shared-item]',
- timefilterDurationAttribute: 'data-shared-timefilter-duration',
- },
- });
- expect(testTitle).toBe('the best PDF in the world');
-
- // generate buffer
- pdf.generate();
- const result = await pdf.getBuffer();
- expect(Buffer.isBuffer(result)).toBe(true);
+ beforeEach(() => {
+ layout = createMockLayout();
+ pdf = new PdfMaker(layout, undefined);
});
- it('makes PDF using PreserveLayout mode', async () => {
- const layout = new PreserveLayout({ width: 400, height: 300 });
- const pdf = new PdfMaker(layout, undefined);
+ describe('getBuffer', () => {
+ it('should generate PDF buffer', async () => {
+ pdf.setTitle('the best PDF in the world');
+ pdf.addImage(imageBase64, { title: 'first viz', description: '☃️' });
+ pdf.addImage(imageBase64, { title: 'second viz', description: '❄️' });
+ pdf.generate();
- expect(pdf.setTitle('the finest PDF in the world')).toBe(undefined);
- expect(pdf.addImage(imageBase64, { title: 'cool times', description: '☃️' })).toBe(undefined);
-
- const { _layout: testLayout, _title: testTitle } = pdf as unknown as {
- _layout: object;
- _title: string;
- };
- expect(testLayout).toMatchObject({
- groupCount: 1,
- id: 'preserve_layout',
- selectors: {
- itemsCountAttribute: 'data-shared-items-count',
- renderComplete: '[data-shared-item]',
- screenshot: '[data-shared-items-container]',
- timefilterDurationAttribute: 'data-shared-timefilter-duration',
- },
+ await expect(pdf.getBuffer()).resolves.toBeInstanceOf(Buffer);
});
- expect(testTitle).toBe('the finest PDF in the world');
-
- // generate buffer
- pdf.generate();
- const result = await pdf.getBuffer();
- expect(Buffer.isBuffer(result)).toBe(true);
});
});
diff --git a/x-pack/plugins/reporting/server/export_types/common/pdf/index.ts b/x-pack/plugins/reporting/server/export_types/common/pdf/index.ts
index 0cd054d3e3709..d6c0ec9dd844c 100644
--- a/x-pack/plugins/reporting/server/export_types/common/pdf/index.ts
+++ b/x-pack/plugins/reporting/server/export_types/common/pdf/index.ts
@@ -12,7 +12,7 @@ import _ from 'lodash';
import path from 'path';
import Printer from 'pdfmake';
import { Content, ContentImage, ContentText } from 'pdfmake/interfaces';
-import { LayoutInstance } from '../../../lib/layouts';
+import type { Layout } from '../../../../../screenshotting/server';
import { getDocOptions, REPORTING_TABLE_LAYOUT } from './get_doc_options';
import { getFont } from './get_font';
import { getTemplate } from './get_template';
@@ -21,14 +21,14 @@ const assetPath = path.resolve(__dirname, '..', '..', 'common', 'assets');
const tableBorderWidth = 1;
export class PdfMaker {
- private _layout: LayoutInstance;
+ private _layout: Layout;
private _logo: string | undefined;
private _title: string;
private _content: Content[];
private _printer: Printer;
private _pdfDoc: PDFKit.PDFDocument | undefined;
- constructor(layout: LayoutInstance, logo: string | undefined) {
+ constructor(layout: Layout, logo: string | undefined) {
const fontPath = (filename: string) => path.resolve(assetPath, 'fonts', filename);
const fonts = {
Roboto: {
diff --git a/x-pack/plugins/reporting/server/export_types/png/execute_job/index.test.ts b/x-pack/plugins/reporting/server/export_types/png/execute_job/index.test.ts
index ed4709d501b43..7356da4da3a11 100644
--- a/x-pack/plugins/reporting/server/export_types/png/execute_job/index.test.ts
+++ b/x-pack/plugins/reporting/server/export_types/png/execute_job/index.test.ts
@@ -15,11 +15,11 @@ import {
createMockConfigSchema,
createMockReportingCore,
} from '../../../test_helpers';
-import { generatePngObservableFactory } from '../../common';
+import { generatePngObservable } from '../../common';
import { TaskPayloadPNG } from '../types';
import { runTaskFnFactory } from './';
-jest.mock('../../common/generate_png', () => ({ generatePngObservableFactory: jest.fn() }));
+jest.mock('../../common/generate_png');
let content: string;
let mockReporting: ReportingCore;
@@ -61,16 +61,13 @@ beforeEach(async () => {
mockReporting = await createMockReportingCore(mockReportingConfig);
mockReporting.setConfig(createMockConfig(mockReportingConfig));
-
- (generatePngObservableFactory as jest.Mock).mockReturnValue(jest.fn());
});
-afterEach(() => (generatePngObservableFactory as jest.Mock).mockReset());
+afterEach(() => (generatePngObservable as jest.Mock).mockReset());
test(`passes browserTimezone to generatePng`, async () => {
const encryptedHeaders = await encryptHeaders({});
- const generatePngObservable = (await generatePngObservableFactory(mockReporting)) as jest.Mock;
- generatePngObservable.mockReturnValue(Rx.of({ buffer: Buffer.from('') }));
+ (generatePngObservable as jest.Mock).mockReturnValue(Rx.of({ buffer: Buffer.from('') }));
const runTask = await runTaskFnFactory(mockReporting, getMockLogger());
const browserTimezone = 'UTC';
@@ -85,42 +82,24 @@ test(`passes browserTimezone to generatePng`, async () => {
stream
);
- expect(generatePngObservable.mock.calls).toMatchInlineSnapshot(`
- Array [
- Array [
- LevelLogger {
- "_logger": Object {
- "get": [MockFunction],
- },
- "_tags": Array [
- "PNG",
- "execute",
- "pngJobId",
- ],
- "warning": [Function],
- },
- "localhost:80undefined/app/kibana#/something",
- "UTC",
- Object {
- "conditions": Object {
- "basePath": undefined,
- "hostname": "localhost",
- "port": 80,
- "protocol": undefined,
- },
- "headers": Object {},
- },
- undefined,
- ],
- ]
- `);
+ expect(generatePngObservable).toHaveBeenCalledWith(
+ expect.anything(),
+ expect.anything(),
+ expect.objectContaining({
+ urls: ['localhost:80undefined/app/kibana#/something'],
+ browserTimezone: 'UTC',
+ conditionalHeaders: expect.objectContaining({
+ conditions: expect.any(Object),
+ headers: {},
+ }),
+ })
+ );
});
test(`returns content_type of application/png`, async () => {
const runTask = await runTaskFnFactory(mockReporting, getMockLogger());
const encryptedHeaders = await encryptHeaders({});
- const generatePngObservable = await generatePngObservableFactory(mockReporting);
(generatePngObservable as jest.Mock).mockReturnValue(Rx.of({ buffer: Buffer.from('foo') }));
const { content_type: contentType } = await runTask(
@@ -134,7 +113,6 @@ test(`returns content_type of application/png`, async () => {
test(`returns content of generatePng`, async () => {
const testContent = 'raw string from get_screenhots';
- const generatePngObservable = await generatePngObservableFactory(mockReporting);
(generatePngObservable as jest.Mock).mockReturnValue(Rx.of({ buffer: Buffer.from(testContent) }));
const runTask = await runTaskFnFactory(mockReporting, getMockLogger());
diff --git a/x-pack/plugins/reporting/server/export_types/png/execute_job/index.ts b/x-pack/plugins/reporting/server/export_types/png/execute_job/index.ts
index 2446e7a7d1c51..e6cbfb45eb095 100644
--- a/x-pack/plugins/reporting/server/export_types/png/execute_job/index.ts
+++ b/x-pack/plugins/reporting/server/export_types/png/execute_job/index.ts
@@ -7,7 +7,7 @@
import apm from 'elastic-apm-node';
import * as Rx from 'rxjs';
-import { catchError, finalize, map, mergeMap, takeUntil, tap } from 'rxjs/operators';
+import { finalize, map, mergeMap, takeUntil, tap } from 'rxjs/operators';
import { PNG_JOB_TYPE, REPORTING_TRANSACTION_TYPE } from '../../../../common/constants';
import { TaskRunResult } from '../../../lib/tasks';
import { RunTaskFn, RunTaskFnFactory } from '../../../types';
@@ -16,7 +16,7 @@ import {
getConditionalHeaders,
getFullUrls,
omitBlockedHeaders,
- generatePngObservableFactory,
+ generatePngObservable,
} from '../../common';
import { TaskPayloadPNG } from '../types';
@@ -25,40 +25,35 @@ export const runTaskFnFactory: RunTaskFnFactory> =
const config = reporting.getConfig();
const encryptionKey = config.get('encryptionKey');
- return async function runTask(jobId, job, cancellationToken, stream) {
+ return function runTask(jobId, job, cancellationToken, stream) {
const apmTrans = apm.startTransaction('execute-job-png', REPORTING_TRANSACTION_TYPE);
const apmGetAssets = apmTrans?.startSpan('get-assets', 'setup');
let apmGeneratePng: { end: () => void } | null | undefined;
- const generatePngObservable = await generatePngObservableFactory(reporting);
const jobLogger = parentLogger.clone([PNG_JOB_TYPE, 'execute', jobId]);
const process$: Rx.Observable = Rx.of(1).pipe(
mergeMap(() => decryptJobHeaders(encryptionKey, job.headers, jobLogger)),
map((decryptedHeaders) => omitBlockedHeaders(decryptedHeaders)),
map((filteredHeaders) => getConditionalHeaders(config, filteredHeaders)),
mergeMap((conditionalHeaders) => {
- const urls = getFullUrls(config, job);
- const hashUrl = urls[0];
- if (apmGetAssets) apmGetAssets.end();
+ const [url] = getFullUrls(config, job);
+ apmGetAssets?.end();
apmGeneratePng = apmTrans?.startSpan('generate-png-pipeline', 'execute');
- return generatePngObservable(
- jobLogger,
- hashUrl,
- job.browserTimezone,
+
+ return generatePngObservable(reporting, jobLogger, {
conditionalHeaders,
- job.layout
- );
+ urls: [url],
+ browserTimezone: job.browserTimezone,
+ layout: job.layout,
+ });
}),
tap(({ buffer }) => stream.write(buffer)),
map(({ warnings }) => ({
content_type: 'image/png',
warnings,
})),
- catchError((err) => {
- jobLogger.error(err);
- return Rx.throwError(err);
- }),
+ tap({ error: (error) => jobLogger.error(error) }),
finalize(() => apmGeneratePng?.end())
);
diff --git a/x-pack/plugins/reporting/server/export_types/png_v2/execute_job.test.ts b/x-pack/plugins/reporting/server/export_types/png_v2/execute_job.test.ts
index ba076f98996b1..783c8f8e8f880 100644
--- a/x-pack/plugins/reporting/server/export_types/png_v2/execute_job.test.ts
+++ b/x-pack/plugins/reporting/server/export_types/png_v2/execute_job.test.ts
@@ -16,11 +16,11 @@ import {
createMockConfigSchema,
createMockReportingCore,
} from '../../test_helpers';
-import { generatePngObservableFactory } from '../common';
+import { generatePngObservable } from '../common';
import { runTaskFnFactory } from './execute_job';
import { TaskPayloadPNGV2 } from './types';
-jest.mock('../common/generate_png', () => ({ generatePngObservableFactory: jest.fn() }));
+jest.mock('../common/generate_png');
let content: string;
let mockReporting: ReportingCore;
@@ -62,16 +62,13 @@ beforeEach(async () => {
mockReporting = await createMockReportingCore(mockReportingConfig);
mockReporting.setConfig(createMockConfig(mockReportingConfig));
-
- (generatePngObservableFactory as jest.Mock).mockReturnValue(jest.fn());
});
-afterEach(() => (generatePngObservableFactory as jest.Mock).mockReset());
+afterEach(() => (generatePngObservable as jest.Mock).mockReset());
test(`passes browserTimezone to generatePng`, async () => {
const encryptedHeaders = await encryptHeaders({});
- const generatePngObservable = (await generatePngObservableFactory(mockReporting)) as jest.Mock;
- generatePngObservable.mockReturnValue(Rx.of({ buffer: Buffer.from('') }));
+ (generatePngObservable as jest.Mock).mockReturnValue(Rx.of({ buffer: Buffer.from('') }));
const runTask = await runTaskFnFactory(mockReporting, getMockLogger());
const browserTimezone = 'UTC';
@@ -87,49 +84,29 @@ test(`passes browserTimezone to generatePng`, async () => {
stream
);
- expect(generatePngObservable.mock.calls).toMatchInlineSnapshot(`
- Array [
- Array [
- LevelLogger {
- "_logger": Object {
- "get": [MockFunction],
- },
- "_tags": Array [
- "PNGV2",
- "execute",
- "pngJobId",
- ],
- "warning": [Function],
- },
- Array [
- "localhost:80undefined/app/reportingRedirect?forceNow=test",
- Object {
- "id": "test",
- "params": Object {},
- "version": "test",
- },
+ expect(generatePngObservable).toHaveBeenCalledWith(
+ expect.anything(),
+ expect.anything(),
+ expect.objectContaining({
+ urls: [
+ [
+ 'localhost:80undefined/app/reportingRedirect?forceNow=test',
+ { id: 'test', params: {}, version: 'test' },
],
- "UTC",
- Object {
- "conditions": Object {
- "basePath": undefined,
- "hostname": "localhost",
- "port": 80,
- "protocol": undefined,
- },
- "headers": Object {},
- },
- undefined,
],
- ]
- `);
+ browserTimezone: 'UTC',
+ conditionalHeaders: expect.objectContaining({
+ conditions: expect.any(Object),
+ headers: {},
+ }),
+ })
+ );
});
test(`returns content_type of application/png`, async () => {
const runTask = await runTaskFnFactory(mockReporting, getMockLogger());
const encryptedHeaders = await encryptHeaders({});
- const generatePngObservable = await generatePngObservableFactory(mockReporting);
(generatePngObservable as jest.Mock).mockReturnValue(Rx.of({ buffer: Buffer.from('foo') }));
const { content_type: contentType } = await runTask(
@@ -146,7 +123,6 @@ test(`returns content_type of application/png`, async () => {
test(`returns content of generatePng getBuffer base64 encoded`, async () => {
const testContent = 'raw string from get_screenhots';
- const generatePngObservable = await generatePngObservableFactory(mockReporting);
(generatePngObservable as jest.Mock).mockReturnValue(Rx.of({ buffer: Buffer.from(testContent) }));
const runTask = await runTaskFnFactory(mockReporting, getMockLogger());
diff --git a/x-pack/plugins/reporting/server/export_types/png_v2/execute_job.ts b/x-pack/plugins/reporting/server/export_types/png_v2/execute_job.ts
index 00652309b88c1..a8ab6c4355000 100644
--- a/x-pack/plugins/reporting/server/export_types/png_v2/execute_job.ts
+++ b/x-pack/plugins/reporting/server/export_types/png_v2/execute_job.ts
@@ -7,7 +7,7 @@
import apm from 'elastic-apm-node';
import * as Rx from 'rxjs';
-import { catchError, finalize, map, mergeMap, takeUntil, tap } from 'rxjs/operators';
+import { finalize, map, mergeMap, takeUntil, tap } from 'rxjs/operators';
import { PNG_JOB_TYPE_V2, REPORTING_TRANSACTION_TYPE } from '../../../common/constants';
import { TaskRunResult } from '../../lib/tasks';
import { RunTaskFn, RunTaskFnFactory } from '../../types';
@@ -15,7 +15,7 @@ import {
decryptJobHeaders,
getConditionalHeaders,
omitBlockedHeaders,
- generatePngObservableFactory,
+ generatePngObservable,
} from '../common';
import { getFullRedirectAppUrl } from '../common/v2/get_full_redirect_app_url';
import { TaskPayloadPNGV2 } from './types';
@@ -25,12 +25,11 @@ export const runTaskFnFactory: RunTaskFnFactory> =
const config = reporting.getConfig();
const encryptionKey = config.get('encryptionKey');
- return async function runTask(jobId, job, cancellationToken, stream) {
+ return function runTask(jobId, job, cancellationToken, stream) {
const apmTrans = apm.startTransaction('execute-job-png-v2', REPORTING_TRANSACTION_TYPE);
const apmGetAssets = apmTrans?.startSpan('get-assets', 'setup');
let apmGeneratePng: { end: () => void } | null | undefined;
- const generatePngObservable = await generatePngObservableFactory(reporting);
const jobLogger = parentLogger.clone([PNG_JOB_TYPE_V2, 'execute', jobId]);
const process$: Rx.Observable = Rx.of(1).pipe(
mergeMap(() => decryptJobHeaders(encryptionKey, job.headers, jobLogger)),
@@ -41,25 +40,21 @@ export const runTaskFnFactory: RunTaskFnFactory> =
const [locatorParams] = job.locatorParams;
apmGetAssets?.end();
-
apmGeneratePng = apmTrans?.startSpan('generate-png-pipeline', 'execute');
- return generatePngObservable(
- jobLogger,
- [url, locatorParams],
- job.browserTimezone,
+
+ return generatePngObservable(reporting, jobLogger, {
conditionalHeaders,
- job.layout
- );
+ browserTimezone: job.browserTimezone,
+ layout: job.layout,
+ urls: [[url, locatorParams]],
+ });
}),
tap(({ buffer }) => stream.write(buffer)),
map(({ warnings }) => ({
content_type: 'image/png',
warnings,
})),
- catchError((err) => {
- jobLogger.error(err);
- return Rx.throwError(err);
- }),
+ tap({ error: (error) => jobLogger.error(error) }),
finalize(() => apmGeneratePng?.end())
);
diff --git a/x-pack/plugins/reporting/server/export_types/printable_pdf/execute_job/index.test.ts b/x-pack/plugins/reporting/server/export_types/printable_pdf/execute_job/index.test.ts
index 02f9c93929ea1..eb02097ec7924 100644
--- a/x-pack/plugins/reporting/server/export_types/printable_pdf/execute_job/index.test.ts
+++ b/x-pack/plugins/reporting/server/export_types/printable_pdf/execute_job/index.test.ts
@@ -5,18 +5,18 @@
* 2.0.
*/
-jest.mock('../lib/generate_pdf', () => ({ generatePdfObservableFactory: jest.fn() }));
-
import * as Rx from 'rxjs';
import { Writable } from 'stream';
import { ReportingCore } from '../../../';
import { CancellationToken } from '../../../../common';
import { cryptoFactory, LevelLogger } from '../../../lib';
import { createMockConfigSchema, createMockReportingCore } from '../../../test_helpers';
-import { generatePdfObservableFactory } from '../lib/generate_pdf';
+import { generatePdfObservable } from '../lib/generate_pdf';
import { TaskPayloadPDF } from '../types';
import { runTaskFnFactory } from './';
+jest.mock('../lib/generate_pdf');
+
let content: string;
let mockReporting: ReportingCore;
let stream: jest.Mocked;
@@ -56,16 +56,13 @@ beforeEach(async () => {
};
const mockSchema = createMockConfigSchema(reportingConfig);
mockReporting = await createMockReportingCore(mockSchema);
-
- (generatePdfObservableFactory as jest.Mock).mockReturnValue(jest.fn());
});
-afterEach(() => (generatePdfObservableFactory as jest.Mock).mockReset());
+afterEach(() => (generatePdfObservable as jest.Mock).mockReset());
test(`passes browserTimezone to generatePdf`, async () => {
const encryptedHeaders = await encryptHeaders({});
- const generatePdfObservable = (await generatePdfObservableFactory(mockReporting)) as jest.Mock;
- generatePdfObservable.mockReturnValue(Rx.of({ buffer: Buffer.from('') }));
+ (generatePdfObservable as jest.Mock).mockReturnValue(Rx.of({ buffer: Buffer.from('') }));
const runTask = runTaskFnFactory(mockReporting, getMockLogger());
const browserTimezone = 'UTC';
@@ -81,8 +78,13 @@ test(`passes browserTimezone to generatePdf`, async () => {
stream
);
- const tzParam = generatePdfObservable.mock.calls[0][3];
- expect(tzParam).toBe('UTC');
+ expect(generatePdfObservable).toHaveBeenCalledWith(
+ expect.anything(),
+ expect.anything(),
+ expect.anything(),
+ expect.objectContaining({ browserTimezone: 'UTC' }),
+ undefined
+ );
});
test(`returns content_type of application/pdf`, async () => {
@@ -90,7 +92,6 @@ test(`returns content_type of application/pdf`, async () => {
const runTask = runTaskFnFactory(mockReporting, logger);
const encryptedHeaders = await encryptHeaders({});
- const generatePdfObservable = await generatePdfObservableFactory(mockReporting);
(generatePdfObservable as jest.Mock).mockReturnValue(Rx.of({ buffer: Buffer.from('') }));
const { content_type: contentType } = await runTask(
@@ -104,7 +105,6 @@ test(`returns content_type of application/pdf`, async () => {
test(`returns content of generatePdf getBuffer base64 encoded`, async () => {
const testContent = 'test content';
- const generatePdfObservable = await generatePdfObservableFactory(mockReporting);
(generatePdfObservable as jest.Mock).mockReturnValue(Rx.of({ buffer: Buffer.from(testContent) }));
const runTask = runTaskFnFactory(mockReporting, getMockLogger());
diff --git a/x-pack/plugins/reporting/server/export_types/printable_pdf/execute_job/index.ts b/x-pack/plugins/reporting/server/export_types/printable_pdf/execute_job/index.ts
index 2358333bbe7ef..f301b3e1e6ef2 100644
--- a/x-pack/plugins/reporting/server/export_types/printable_pdf/execute_job/index.ts
+++ b/x-pack/plugins/reporting/server/export_types/printable_pdf/execute_job/index.ts
@@ -18,7 +18,7 @@ import {
omitBlockedHeaders,
getCustomLogo,
} from '../../common';
-import { generatePdfObservableFactory } from '../lib/generate_pdf';
+import { generatePdfObservable } from '../lib/generate_pdf';
import { TaskPayloadPDF } from '../types';
export const runTaskFnFactory: RunTaskFnFactory> =
@@ -32,8 +32,6 @@ export const runTaskFnFactory: RunTaskFnFactory> =
const apmGetAssets = apmTrans?.startSpan('get-assets', 'setup');
let apmGeneratePdf: { end: () => void } | null | undefined;
- const generatePdfObservable = await generatePdfObservableFactory(reporting);
-
const process$: Rx.Observable = Rx.of(1).pipe(
mergeMap(() => decryptJobHeaders(encryptionKey, job.headers, jobLogger)),
map((decryptedHeaders) => omitBlockedHeaders(decryptedHeaders)),
@@ -49,12 +47,15 @@ export const runTaskFnFactory: RunTaskFnFactory> =
apmGeneratePdf = apmTrans?.startSpan('generate-pdf-pipeline', 'execute');
return generatePdfObservable(
+ reporting,
jobLogger,
title,
- urls,
- browserTimezone,
- conditionalHeaders,
- layout,
+ {
+ urls,
+ browserTimezone,
+ conditionalHeaders,
+ layout,
+ },
logo
);
}),
diff --git a/x-pack/plugins/reporting/server/export_types/printable_pdf/lib/generate_pdf.ts b/x-pack/plugins/reporting/server/export_types/printable_pdf/lib/generate_pdf.ts
index dce6ea678bded..5bf087fecd10a 100644
--- a/x-pack/plugins/reporting/server/export_types/printable_pdf/lib/generate_pdf.ts
+++ b/x-pack/plugins/reporting/server/export_types/printable_pdf/lib/generate_pdf.ts
@@ -8,16 +8,15 @@
import { groupBy } from 'lodash';
import * as Rx from 'rxjs';
import { mergeMap } from 'rxjs/operators';
+import { ScreenshotResult } from '../../../../../screenshotting/server';
import { ReportingCore } from '../../../';
import { LevelLogger } from '../../../lib';
-import { createLayout, LayoutParams } from '../../../lib/layouts';
-import { getScreenshots$, ScreenshotResults } from '../../../lib/screenshots';
-import { ConditionalHeaders } from '../../common';
+import { ScreenshotOptions } from '../../../types';
import { PdfMaker } from '../../common/pdf';
import { getTracker } from './tracker';
-const getTimeRange = (urlScreenshots: ScreenshotResults[]) => {
- const grouped = groupBy(urlScreenshots.map((u) => u.timeRange));
+const getTimeRange = (urlScreenshots: ScreenshotResult['results']) => {
+ const grouped = groupBy(urlScreenshots.map(({ timeRange }) => timeRange));
const values = Object.values(grouped);
if (values.length === 1) {
return values[0][0];
@@ -26,97 +25,80 @@ const getTimeRange = (urlScreenshots: ScreenshotResults[]) => {
return null;
};
-export async function generatePdfObservableFactory(reporting: ReportingCore) {
- const config = reporting.getConfig();
- const captureConfig = config.get('capture');
- const { browserDriverFactory } = await reporting.getPluginStartDeps();
-
- return function generatePdfObservable(
- logger: LevelLogger,
- title: string,
- urls: string[],
- browserTimezone: string | undefined,
- conditionalHeaders: ConditionalHeaders,
- layoutParams: LayoutParams,
- logo?: string
- ): Rx.Observable<{ buffer: Buffer | null; warnings: string[] }> {
- const tracker = getTracker();
- tracker.startLayout();
-
- const layout = createLayout(captureConfig, layoutParams);
- logger.debug(`Layout: width=${layout.width} height=${layout.height}`);
- tracker.endLayout();
-
- tracker.startScreenshots();
- const screenshots$ = getScreenshots$(captureConfig, browserDriverFactory, {
- logger,
- urlsOrUrlLocatorTuples: urls,
- conditionalHeaders,
- layout,
- browserTimezone,
- }).pipe(
- mergeMap(async (results: ScreenshotResults[]) => {
- tracker.endScreenshots();
-
- tracker.startSetup();
- const pdfOutput = new PdfMaker(layout, logo);
- if (title) {
- const timeRange = getTimeRange(results);
- title += timeRange ? ` - ${timeRange}` : '';
- pdfOutput.setTitle(title);
- }
- tracker.endSetup();
-
- results.forEach((r) => {
- r.screenshots.forEach((screenshot) => {
- logger.debug(`Adding image to PDF. Image size: ${screenshot.data.byteLength}`); // prettier-ignore
- tracker.startAddImage();
- tracker.endAddImage();
- pdfOutput.addImage(screenshot.data, {
- title: screenshot.title ?? undefined,
- description: screenshot.description ?? undefined,
- });
+export function generatePdfObservable(
+ reporting: ReportingCore,
+ logger: LevelLogger,
+ title: string,
+ options: ScreenshotOptions,
+ logo?: string
+): Rx.Observable<{ buffer: Buffer | null; warnings: string[] }> {
+ const tracker = getTracker();
+ tracker.startScreenshots();
+
+ return reporting.getScreenshots(options).pipe(
+ mergeMap(async ({ layout, metrics$, results }) => {
+ metrics$.subscribe(({ cpu, memory }) => {
+ tracker.setCpuUsage(cpu);
+ tracker.setMemoryUsage(memory);
+ });
+ tracker.endScreenshots();
+ tracker.startSetup();
+
+ const pdfOutput = new PdfMaker(layout, logo);
+ if (title) {
+ const timeRange = getTimeRange(results);
+ title += timeRange ? ` - ${timeRange}` : '';
+ pdfOutput.setTitle(title);
+ }
+ tracker.endSetup();
+
+ results.forEach((r) => {
+ r.screenshots.forEach((screenshot) => {
+ logger.debug(`Adding image to PDF. Image size: ${screenshot.data.byteLength}`); // prettier-ignore
+ tracker.startAddImage();
+ tracker.endAddImage();
+ pdfOutput.addImage(screenshot.data, {
+ title: screenshot.title ?? undefined,
+ description: screenshot.description ?? undefined,
});
});
-
- let buffer: Buffer | null = null;
- try {
- tracker.startCompile();
- logger.debug(`Compiling PDF using "${layout.id}" layout...`);
- pdfOutput.generate();
- tracker.endCompile();
-
- tracker.startGetBuffer();
- logger.debug(`Generating PDF Buffer...`);
- buffer = await pdfOutput.getBuffer();
-
- const byteLength = buffer?.byteLength ?? 0;
- logger.debug(`PDF buffer byte length: ${byteLength}`);
- tracker.setByteLength(byteLength);
-
- tracker.endGetBuffer();
- } catch (err) {
- logger.error(`Could not generate the PDF buffer!`);
- logger.error(err);
- }
-
- tracker.end();
-
- return {
- buffer,
- warnings: results.reduce((found, current) => {
- if (current.error) {
- found.push(current.error.message);
- }
- if (current.renderErrors) {
- found.push(...current.renderErrors);
- }
- return found;
- }, [] as string[]),
- };
- })
- );
-
- return screenshots$;
- };
+ });
+
+ let buffer: Buffer | null = null;
+ try {
+ tracker.startCompile();
+ logger.debug(`Compiling PDF using "${layout.id}" layout...`);
+ pdfOutput.generate();
+ tracker.endCompile();
+
+ tracker.startGetBuffer();
+ logger.debug(`Generating PDF Buffer...`);
+ buffer = await pdfOutput.getBuffer();
+
+ const byteLength = buffer?.byteLength ?? 0;
+ logger.debug(`PDF buffer byte length: ${byteLength}`);
+ tracker.setByteLength(byteLength);
+
+ tracker.endGetBuffer();
+ } catch (err) {
+ logger.error(`Could not generate the PDF buffer!`);
+ logger.error(err);
+ }
+
+ tracker.end();
+
+ return {
+ buffer,
+ warnings: results.reduce((found, current) => {
+ if (current.error) {
+ found.push(current.error.message);
+ }
+ if (current.renderErrors) {
+ found.push(...current.renderErrors);
+ }
+ return found;
+ }, [] as string[]),
+ };
+ })
+ );
}
diff --git a/x-pack/plugins/reporting/server/export_types/printable_pdf/lib/tracker.ts b/x-pack/plugins/reporting/server/export_types/printable_pdf/lib/tracker.ts
index 3d720ccade546..d1cf2b96817d2 100644
--- a/x-pack/plugins/reporting/server/export_types/printable_pdf/lib/tracker.ts
+++ b/x-pack/plugins/reporting/server/export_types/printable_pdf/lib/tracker.ts
@@ -10,8 +10,8 @@ import { REPORTING_TRANSACTION_TYPE } from '../../../../common/constants';
interface PdfTracker {
setByteLength: (byteLength: number) => void;
- startLayout: () => void;
- endLayout: () => void;
+ setCpuUsage: (cpu: number) => void;
+ setMemoryUsage: (memory: number) => void;
startScreenshots: () => void;
endScreenshots: () => void;
startSetup: () => void;
@@ -35,7 +35,6 @@ interface ApmSpan {
export function getTracker(): PdfTracker {
const apmTrans = apm.startTransaction('generate-pdf', REPORTING_TRANSACTION_TYPE);
- let apmLayout: ApmSpan | null = null;
let apmScreenshots: ApmSpan | null = null;
let apmSetup: ApmSpan | null = null;
let apmAddImage: ApmSpan | null = null;
@@ -43,12 +42,6 @@ export function getTracker(): PdfTracker {
let apmGetBuffer: ApmSpan | null = null;
return {
- startLayout() {
- apmLayout = apmTrans?.startSpan('create-layout', SPANTYPE_SETUP) || null;
- },
- endLayout() {
- if (apmLayout) apmLayout.end();
- },
startScreenshots() {
apmScreenshots = apmTrans?.startSpan('screenshots-pipeline', SPANTYPE_SETUP) || null;
},
@@ -82,6 +75,12 @@ export function getTracker(): PdfTracker {
setByteLength(byteLength: number) {
apmTrans?.setLabel('byte-length', byteLength, false);
},
+ setCpuUsage(cpu: number) {
+ apmTrans?.setLabel('cpu', cpu, false);
+ },
+ setMemoryUsage(memory: number) {
+ apmTrans?.setLabel('memory', memory, false);
+ },
end() {
if (apmTrans) apmTrans.end();
},
diff --git a/x-pack/plugins/reporting/server/export_types/printable_pdf_v2/execute_job.test.ts b/x-pack/plugins/reporting/server/export_types/printable_pdf_v2/execute_job.test.ts
index 197bd3866b8f6..9a73595ff32da 100644
--- a/x-pack/plugins/reporting/server/export_types/printable_pdf_v2/execute_job.test.ts
+++ b/x-pack/plugins/reporting/server/export_types/printable_pdf_v2/execute_job.test.ts
@@ -5,7 +5,7 @@
* 2.0.
*/
-jest.mock('./lib/generate_pdf', () => ({ generatePdfObservableFactory: jest.fn() }));
+jest.mock('./lib/generate_pdf');
import * as Rx from 'rxjs';
import { Writable } from 'stream';
@@ -15,7 +15,7 @@ import { LocatorParams } from '../../../common/types';
import { cryptoFactory, LevelLogger } from '../../lib';
import { createMockConfigSchema, createMockReportingCore } from '../../test_helpers';
import { runTaskFnFactory } from './execute_job';
-import { generatePdfObservableFactory } from './lib/generate_pdf';
+import { generatePdfObservable } from './lib/generate_pdf';
import { TaskPayloadPDFV2 } from './types';
let content: string;
@@ -61,16 +61,13 @@ beforeEach(async () => {
};
const mockSchema = createMockConfigSchema(reportingConfig);
mockReporting = await createMockReportingCore(mockSchema);
-
- (generatePdfObservableFactory as jest.Mock).mockReturnValue(jest.fn());
});
-afterEach(() => (generatePdfObservableFactory as jest.Mock).mockReset());
+afterEach(() => (generatePdfObservable as jest.Mock).mockReset());
test(`passes browserTimezone to generatePdf`, async () => {
const encryptedHeaders = await encryptHeaders({});
- const generatePdfObservable = (await generatePdfObservableFactory(mockReporting)) as jest.Mock;
- generatePdfObservable.mockReturnValue(Rx.of(Buffer.from('')));
+ (generatePdfObservable as jest.Mock).mockReturnValue(Rx.of(Buffer.from('')));
const runTask = runTaskFnFactory(mockReporting, getMockLogger());
const browserTimezone = 'UTC';
@@ -87,8 +84,15 @@ test(`passes browserTimezone to generatePdf`, async () => {
stream
);
- const tzParam = generatePdfObservable.mock.calls[0][4];
- expect(tzParam).toBe('UTC');
+ expect(generatePdfObservable).toHaveBeenCalledWith(
+ expect.anything(),
+ expect.anything(),
+ expect.anything(),
+ expect.anything(),
+ expect.anything(),
+ expect.objectContaining({ browserTimezone: 'UTC' }),
+ undefined
+ );
});
test(`returns content_type of application/pdf`, async () => {
@@ -96,7 +100,6 @@ test(`returns content_type of application/pdf`, async () => {
const runTask = runTaskFnFactory(mockReporting, logger);
const encryptedHeaders = await encryptHeaders({});
- const generatePdfObservable = await generatePdfObservableFactory(mockReporting);
(generatePdfObservable as jest.Mock).mockReturnValue(Rx.of({ buffer: Buffer.from('') }));
const { content_type: contentType } = await runTask(
@@ -110,7 +113,6 @@ test(`returns content_type of application/pdf`, async () => {
test(`returns content of generatePdf getBuffer base64 encoded`, async () => {
const testContent = 'test content';
- const generatePdfObservable = await generatePdfObservableFactory(mockReporting);
(generatePdfObservable as jest.Mock).mockReturnValue(Rx.of({ buffer: Buffer.from(testContent) }));
const runTask = runTaskFnFactory(mockReporting, getMockLogger());
diff --git a/x-pack/plugins/reporting/server/export_types/printable_pdf_v2/execute_job.ts b/x-pack/plugins/reporting/server/export_types/printable_pdf_v2/execute_job.ts
index b1b6f3f79aee3..890c0c9cde731 100644
--- a/x-pack/plugins/reporting/server/export_types/printable_pdf_v2/execute_job.ts
+++ b/x-pack/plugins/reporting/server/export_types/printable_pdf_v2/execute_job.ts
@@ -17,7 +17,7 @@ import {
omitBlockedHeaders,
getCustomLogo,
} from '../common';
-import { generatePdfObservableFactory } from './lib/generate_pdf';
+import { generatePdfObservable } from './lib/generate_pdf';
import { TaskPayloadPDFV2 } from './types';
export const runTaskFnFactory: RunTaskFnFactory> =
@@ -31,8 +31,6 @@ export const runTaskFnFactory: RunTaskFnFactory> =
const apmGetAssets = apmTrans?.startSpan('get-assets', 'setup');
let apmGeneratePdf: { end: () => void } | null | undefined;
- const generatePdfObservable = await generatePdfObservableFactory(reporting);
-
const process$: Rx.Observable = Rx.of(1).pipe(
mergeMap(() => decryptJobHeaders(encryptionKey, job.headers, jobLogger)),
map((decryptedHeaders) => omitBlockedHeaders(decryptedHeaders)),
@@ -46,13 +44,16 @@ export const runTaskFnFactory: RunTaskFnFactory> =
apmGeneratePdf = apmTrans?.startSpan('generate-pdf-pipeline', 'execute');
return generatePdfObservable(
+ reporting,
jobLogger,
job,
title,
locatorParams,
- browserTimezone,
- conditionalHeaders,
- layout,
+ {
+ browserTimezone,
+ conditionalHeaders,
+ layout,
+ },
logo
);
}),
diff --git a/x-pack/plugins/reporting/server/export_types/printable_pdf_v2/lib/generate_pdf.ts b/x-pack/plugins/reporting/server/export_types/printable_pdf_v2/lib/generate_pdf.ts
index b44e2ca4441eb..3d790beb41b39 100644
--- a/x-pack/plugins/reporting/server/export_types/printable_pdf_v2/lib/generate_pdf.ts
+++ b/x-pack/plugins/reporting/server/export_types/printable_pdf_v2/lib/generate_pdf.ts
@@ -5,21 +5,20 @@
* 2.0.
*/
-import { groupBy, zip } from 'lodash';
+import { groupBy } from 'lodash';
import * as Rx from 'rxjs';
import { mergeMap } from 'rxjs/operators';
import { ReportingCore } from '../../../';
import { LocatorParams, UrlOrUrlLocatorTuple } from '../../../../common/types';
import { LevelLogger } from '../../../lib';
-import { createLayout, LayoutParams } from '../../../lib/layouts';
-import { getScreenshots$, ScreenshotResults } from '../../../lib/screenshots';
-import { ConditionalHeaders } from '../../common';
+import { ScreenshotResult } from '../../../../../screenshotting/server';
+import { ScreenshotOptions } from '../../../types';
import { PdfMaker } from '../../common/pdf';
import { getFullRedirectAppUrl } from '../../common/v2/get_full_redirect_app_url';
import type { TaskPayloadPDFV2 } from '../types';
import { getTracker } from './tracker';
-const getTimeRange = (urlScreenshots: ScreenshotResults[]) => {
+const getTimeRange = (urlScreenshots: ScreenshotResult['results']) => {
const grouped = groupBy(urlScreenshots.map((u) => u.timeRange));
const values = Object.values(grouped);
if (values.length === 1) {
@@ -29,106 +28,92 @@ const getTimeRange = (urlScreenshots: ScreenshotResults[]) => {
return null;
};
-export async function generatePdfObservableFactory(reporting: ReportingCore) {
- const config = reporting.getConfig();
- const captureConfig = config.get('capture');
- const { browserDriverFactory } = await reporting.getPluginStartDeps();
-
- return function generatePdfObservable(
- logger: LevelLogger,
- job: TaskPayloadPDFV2,
- title: string,
- locatorParams: LocatorParams[],
- browserTimezone: string | undefined,
- conditionalHeaders: ConditionalHeaders,
- layoutParams: LayoutParams,
- logo?: string
- ): Rx.Observable<{ buffer: Buffer | null; warnings: string[] }> {
- const tracker = getTracker();
- tracker.startLayout();
-
- const layout = createLayout(captureConfig, layoutParams);
- logger.debug(`Layout: width=${layout.width} height=${layout.height}`);
- tracker.endLayout();
-
- tracker.startScreenshots();
-
- /**
- * For each locator we get the relative URL to the redirect app
- */
- const urls = locatorParams.map(() =>
- getFullRedirectAppUrl(reporting.getConfig(), job.spaceId, job.forceNow)
- );
-
- const screenshots$ = getScreenshots$(captureConfig, browserDriverFactory, {
- logger,
- urlsOrUrlLocatorTuples: zip(urls, locatorParams) as UrlOrUrlLocatorTuple[],
- conditionalHeaders,
- layout,
- browserTimezone,
- }).pipe(
- mergeMap(async (results: ScreenshotResults[]) => {
- tracker.endScreenshots();
-
- tracker.startSetup();
- const pdfOutput = new PdfMaker(layout, logo);
- if (title) {
- const timeRange = getTimeRange(results);
- title += timeRange ? ` - ${timeRange}` : '';
- pdfOutput.setTitle(title);
- }
- tracker.endSetup();
-
- results.forEach((r) => {
- r.screenshots.forEach((screenshot) => {
- logger.debug(`Adding image to PDF. Image base64 size: ${screenshot.data.byteLength}`); // prettier-ignore
- tracker.startAddImage();
- tracker.endAddImage();
- pdfOutput.addImage(screenshot.data, {
- title: screenshot.title ?? undefined,
- description: screenshot.description ?? undefined,
- });
+export function generatePdfObservable(
+ reporting: ReportingCore,
+ logger: LevelLogger,
+ job: TaskPayloadPDFV2,
+ title: string,
+ locatorParams: LocatorParams[],
+ options: Omit,
+ logo?: string
+): Rx.Observable<{ buffer: Buffer | null; warnings: string[] }> {
+ const tracker = getTracker();
+ tracker.startScreenshots();
+
+ /**
+ * For each locator we get the relative URL to the redirect app
+ */
+ const urls = locatorParams.map((locator) => [
+ getFullRedirectAppUrl(reporting.getConfig(), job.spaceId, job.forceNow),
+ locator,
+ ]) as UrlOrUrlLocatorTuple[];
+
+ const screenshots$ = reporting.getScreenshots({ ...options, urls }).pipe(
+ mergeMap(async ({ layout, metrics$, results }) => {
+ metrics$.subscribe(({ cpu, memory }) => {
+ tracker.setCpuUsage(cpu);
+ tracker.setMemoryUsage(memory);
+ });
+ tracker.endScreenshots();
+ tracker.startSetup();
+
+ const pdfOutput = new PdfMaker(layout, logo);
+ if (title) {
+ const timeRange = getTimeRange(results);
+ title += timeRange ? ` - ${timeRange}` : '';
+ pdfOutput.setTitle(title);
+ }
+ tracker.endSetup();
+
+ results.forEach((r) => {
+ r.screenshots.forEach((screenshot) => {
+ logger.debug(`Adding image to PDF. Image base64 size: ${screenshot.data.byteLength}`); // prettier-ignore
+ tracker.startAddImage();
+ tracker.endAddImage();
+ pdfOutput.addImage(screenshot.data, {
+ title: screenshot.title ?? undefined,
+ description: screenshot.description ?? undefined,
});
});
-
- let buffer: Buffer | null = null;
- try {
- tracker.startCompile();
- logger.debug(`Compiling PDF using "${layout.id}" layout...`);
- pdfOutput.generate();
- tracker.endCompile();
-
- tracker.startGetBuffer();
- logger.debug(`Generating PDF Buffer...`);
- buffer = await pdfOutput.getBuffer();
-
- const byteLength = buffer?.byteLength ?? 0;
- logger.debug(`PDF buffer byte length: ${byteLength}`);
- tracker.setByteLength(byteLength);
-
- tracker.endGetBuffer();
- } catch (err) {
- logger.error(`Could not generate the PDF buffer!`);
- logger.error(err);
- }
-
- tracker.end();
-
- return {
- buffer,
- warnings: results.reduce((found, current) => {
- if (current.error) {
- found.push(current.error.message);
- }
- if (current.renderErrors) {
- found.push(...current.renderErrors);
- }
- return found;
- }, [] as string[]),
- };
- })
- );
-
- return screenshots$;
- };
+ });
+
+ let buffer: Buffer | null = null;
+ try {
+ tracker.startCompile();
+ logger.debug(`Compiling PDF using "${layout.id}" layout...`);
+ pdfOutput.generate();
+ tracker.endCompile();
+
+ tracker.startGetBuffer();
+ logger.debug(`Generating PDF Buffer...`);
+ buffer = await pdfOutput.getBuffer();
+
+ const byteLength = buffer?.byteLength ?? 0;
+ logger.debug(`PDF buffer byte length: ${byteLength}`);
+ tracker.setByteLength(byteLength);
+
+ tracker.endGetBuffer();
+ } catch (err) {
+ logger.error(`Could not generate the PDF buffer!`);
+ logger.error(err);
+ }
+
+ tracker.end();
+
+ return {
+ buffer,
+ warnings: results.reduce((found, current) => {
+ if (current.error) {
+ found.push(current.error.message);
+ }
+ if (current.renderErrors) {
+ found.push(...current.renderErrors);
+ }
+ return found;
+ }, [] as string[]),
+ };
+ })
+ );
+
+ return screenshots$;
}
diff --git a/x-pack/plugins/reporting/server/export_types/printable_pdf_v2/lib/tracker.ts b/x-pack/plugins/reporting/server/export_types/printable_pdf_v2/lib/tracker.ts
index 3d720ccade546..d1cf2b96817d2 100644
--- a/x-pack/plugins/reporting/server/export_types/printable_pdf_v2/lib/tracker.ts
+++ b/x-pack/plugins/reporting/server/export_types/printable_pdf_v2/lib/tracker.ts
@@ -10,8 +10,8 @@ import { REPORTING_TRANSACTION_TYPE } from '../../../../common/constants';
interface PdfTracker {
setByteLength: (byteLength: number) => void;
- startLayout: () => void;
- endLayout: () => void;
+ setCpuUsage: (cpu: number) => void;
+ setMemoryUsage: (memory: number) => void;
startScreenshots: () => void;
endScreenshots: () => void;
startSetup: () => void;
@@ -35,7 +35,6 @@ interface ApmSpan {
export function getTracker(): PdfTracker {
const apmTrans = apm.startTransaction('generate-pdf', REPORTING_TRANSACTION_TYPE);
- let apmLayout: ApmSpan | null = null;
let apmScreenshots: ApmSpan | null = null;
let apmSetup: ApmSpan | null = null;
let apmAddImage: ApmSpan | null = null;
@@ -43,12 +42,6 @@ export function getTracker(): PdfTracker {
let apmGetBuffer: ApmSpan | null = null;
return {
- startLayout() {
- apmLayout = apmTrans?.startSpan('create-layout', SPANTYPE_SETUP) || null;
- },
- endLayout() {
- if (apmLayout) apmLayout.end();
- },
startScreenshots() {
apmScreenshots = apmTrans?.startSpan('screenshots-pipeline', SPANTYPE_SETUP) || null;
},
@@ -82,6 +75,12 @@ export function getTracker(): PdfTracker {
setByteLength(byteLength: number) {
apmTrans?.setLabel('byte-length', byteLength, false);
},
+ setCpuUsage(cpu: number) {
+ apmTrans?.setLabel('cpu', cpu, false);
+ },
+ setMemoryUsage(memory: number) {
+ apmTrans?.setLabel('memory', memory, false);
+ },
end() {
if (apmTrans) apmTrans.end();
},
diff --git a/x-pack/plugins/reporting/server/lib/content_stream.ts b/x-pack/plugins/reporting/server/lib/content_stream.ts
index 9719ac57b119c..b8ae7d0a17670 100644
--- a/x-pack/plugins/reporting/server/lib/content_stream.ts
+++ b/x-pack/plugins/reporting/server/lib/content_stream.ts
@@ -66,7 +66,9 @@ export class ContentStream extends Duplex {
return Math.floor(max / 2);
}
- private buffer = Buffer.from('');
+ private buffers: Buffer[] = [];
+ private bytesBuffered = 0;
+
private bytesRead = 0;
private chunksRead = 0;
private chunksWritten = 0;
@@ -249,8 +251,43 @@ export class ContentStream extends Duplex {
});
}
- private async flush(size = this.buffer.byteLength) {
- const chunk = this.buffer.slice(0, size);
+ private async flush(size = this.bytesBuffered) {
+ const buffersToFlush: Buffer[] = [];
+ let bytesToFlush = 0;
+
+ /*
+ Loop over each buffer, keeping track of how many bytes we have added
+ to the array of buffers to be flushed. The array of buffers to be flushed
+ contains buffers by reference, not copies. This avoids putting pressure on
+ the CPU for copying buffers or for gc activity. Please profile performance
+ with a large byte configuration and a large number of records (900k+)
+ before changing this code. Config used at time of writing:
+
+ xpack.reporting:
+ csv.maxSizeBytes: 500000000
+ csv.scroll.size: 1000
+
+ At the moment this can put memory pressure on Kibana. Up to 1,1 GB in a dev
+ build. It is not recommended to have overly large max size bytes but we
+ need this code to be as performant as possible.
+ */
+ while (this.buffers.length) {
+ const remainder = size - bytesToFlush;
+ if (remainder <= 0) {
+ break;
+ }
+ const buffer = this.buffers.shift()!;
+ const chunkedBuffer = buffer.slice(0, remainder);
+ buffersToFlush.push(chunkedBuffer);
+ bytesToFlush += chunkedBuffer.byteLength;
+
+ if (buffer.byteLength > remainder) {
+ this.buffers.unshift(buffer.slice(remainder));
+ }
+ }
+
+ // We call Buffer.concat with the fewest number of buffers possible
+ const chunk = Buffer.concat(buffersToFlush);
const content = this.encode(chunk);
if (!this.chunksWritten) {
@@ -265,22 +302,21 @@ export class ContentStream extends Duplex {
}
this.bytesWritten += chunk.byteLength;
- this.buffer = this.buffer.slice(size);
+ this.bytesBuffered -= bytesToFlush;
}
private async flushAllFullChunks() {
const maxChunkSize = await this.getMaxChunkSize();
- while (this.buffer.byteLength >= maxChunkSize) {
+ while (this.bytesBuffered >= maxChunkSize && this.buffers.length) {
await this.flush(maxChunkSize);
}
}
_write(chunk: Buffer | string, encoding: BufferEncoding, callback: Callback) {
- this.buffer = Buffer.concat([
- this.buffer,
- Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk, encoding),
- ]);
+ const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk, encoding);
+ this.bytesBuffered += buffer.byteLength;
+ this.buffers.push(buffer);
this.flushAllFullChunks()
.then(() => callback())
diff --git a/x-pack/plugins/reporting/server/lib/layouts/create_layout.ts b/x-pack/plugins/reporting/server/lib/layouts/create_layout.ts
deleted file mode 100644
index f62ee6ab720c3..0000000000000
--- a/x-pack/plugins/reporting/server/lib/layouts/create_layout.ts
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { LAYOUT_TYPES } from '../../../common/constants';
-import { CaptureConfig } from '../../types';
-import { LayoutInstance, LayoutParams, LayoutTypes } from './';
-import { CanvasLayout } from './canvas_layout';
-import { PreserveLayout } from './preserve_layout';
-import { PrintLayout } from './print_layout';
-
-export function createLayout(
- captureConfig: CaptureConfig,
- layoutParams?: LayoutParams
-): LayoutInstance {
- if (layoutParams && layoutParams.dimensions && layoutParams.id === LAYOUT_TYPES.PRESERVE_LAYOUT) {
- return new PreserveLayout(layoutParams.dimensions);
- }
-
- if (layoutParams && layoutParams.dimensions && layoutParams.id === LayoutTypes.CANVAS) {
- return new CanvasLayout(layoutParams.dimensions);
- }
-
- // layoutParams is optional as PrintLayout doesn't use it
- return new PrintLayout(captureConfig);
-}
diff --git a/x-pack/plugins/reporting/server/lib/layouts/index.ts b/x-pack/plugins/reporting/server/lib/layouts/index.ts
deleted file mode 100644
index daff568ab0067..0000000000000
--- a/x-pack/plugins/reporting/server/lib/layouts/index.ts
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { LevelLogger } from '../';
-import { Size } from '../../../common/types';
-import { HeadlessChromiumDriver } from '../../browsers';
-import type { Layout } from './layout';
-
-export interface LayoutSelectorDictionary {
- screenshot: string;
- renderComplete: string;
- renderError: string;
- renderErrorAttribute: string;
- itemsCountAttribute: string;
- timefilterDurationAttribute: string;
-}
-
-export type { LayoutParams, PageSizeParams, PdfImageSize, Size } from '../../../common/types';
-export { CanvasLayout } from './canvas_layout';
-export { createLayout } from './create_layout';
-export type { Layout } from './layout';
-export { PreserveLayout } from './preserve_layout';
-export { PrintLayout } from './print_layout';
-
-export const LayoutTypes = {
- PRESERVE_LAYOUT: 'preserve_layout',
- PRINT: 'print',
- CANVAS: 'canvas', // no margins or branding in the layout
-};
-
-export const getDefaultLayoutSelectors = (): LayoutSelectorDictionary => ({
- screenshot: '[data-shared-items-container]',
- renderComplete: '[data-shared-item]',
- renderError: '[data-render-error]',
- renderErrorAttribute: 'data-render-error',
- itemsCountAttribute: 'data-shared-items-count',
- timefilterDurationAttribute: 'data-shared-timefilter-duration',
-});
-
-interface LayoutSelectors {
- // Fields that are not part of Layout: the instances
- // independently implement these fields on their own
- selectors: LayoutSelectorDictionary;
- positionElements?: (browser: HeadlessChromiumDriver, logger: LevelLogger) => Promise;
-}
-
-export type LayoutInstance = Layout & LayoutSelectors & Partial;
diff --git a/x-pack/plugins/reporting/server/lib/screenshots/get_number_of_items.test.ts b/x-pack/plugins/reporting/server/lib/screenshots/get_number_of_items.test.ts
deleted file mode 100644
index f160fcb8b27ad..0000000000000
--- a/x-pack/plugins/reporting/server/lib/screenshots/get_number_of_items.test.ts
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { set } from 'lodash';
-import { durationToNumber } from '../../../common/schema_utils';
-import { HeadlessChromiumDriver } from '../../browsers';
-import {
- createMockBrowserDriverFactory,
- createMockConfig,
- createMockConfigSchema,
- createMockLayoutInstance,
- createMockLevelLogger,
- createMockReportingCore,
-} from '../../test_helpers';
-import { CaptureConfig } from '../../types';
-import { LayoutInstance } from '../layouts';
-import { LevelLogger } from '../level_logger';
-import { getNumberOfItems } from './get_number_of_items';
-
-describe('getNumberOfItems', () => {
- let captureConfig: CaptureConfig;
- let layout: LayoutInstance;
- let logger: jest.Mocked;
- let browser: HeadlessChromiumDriver;
- let timeout: number;
-
- beforeEach(async () => {
- const schema = createMockConfigSchema(set({}, 'capture.timeouts.waitForElements', 0));
- const config = createMockConfig(schema);
- const core = await createMockReportingCore(schema);
-
- captureConfig = config.get('capture');
- layout = createMockLayoutInstance(captureConfig);
- logger = createMockLevelLogger();
- timeout = durationToNumber(captureConfig.timeouts.waitForElements);
-
- await createMockBrowserDriverFactory(core, logger, {
- evaluate: jest.fn(
- async unknown>({
- fn,
- args,
- }: {
- fn: T;
- args: Parameters;
- }) => fn(...args)
- ),
- getCreatePage: (driver) => {
- browser = driver;
-
- return jest.fn();
- },
- });
- });
-
- afterEach(() => {
- document.body.innerHTML = '';
- });
-
- it('should determine the number of items by attribute', async () => {
- document.body.innerHTML = `
-
- `;
-
- await expect(getNumberOfItems(timeout, browser, layout, logger)).resolves.toBe(10);
- });
-
- it('should determine the number of items by selector ', async () => {
- document.body.innerHTML = `
-
-
-
- `;
-
- await expect(getNumberOfItems(timeout, browser, layout, logger)).resolves.toBe(3);
- });
-
- it('should fall back to the selector when the attribute is empty', async () => {
- document.body.innerHTML = `
-
-
-
- `;
-
- await expect(getNumberOfItems(timeout, browser, layout, logger)).resolves.toBe(2);
- });
-});
diff --git a/x-pack/plugins/reporting/server/lib/screenshots/get_render_errors.test.ts b/x-pack/plugins/reporting/server/lib/screenshots/get_render_errors.test.ts
deleted file mode 100644
index d29c0936bfceb..0000000000000
--- a/x-pack/plugins/reporting/server/lib/screenshots/get_render_errors.test.ts
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { HeadlessChromiumDriver } from '../../browsers';
-import {
- createMockBrowserDriverFactory,
- createMockConfig,
- createMockConfigSchema,
- createMockLayoutInstance,
- createMockLevelLogger,
- createMockReportingCore,
-} from '../../test_helpers';
-import { CaptureConfig } from '../../types';
-import { LayoutInstance } from '../layouts';
-import { LevelLogger } from '../level_logger';
-import { getRenderErrors } from './get_render_errors';
-
-describe('getRenderErrors', () => {
- let captureConfig: CaptureConfig;
- let layout: LayoutInstance;
- let logger: jest.Mocked;
- let browser: HeadlessChromiumDriver;
-
- beforeEach(async () => {
- const schema = createMockConfigSchema();
- const config = createMockConfig(schema);
- const core = await createMockReportingCore(schema);
-
- captureConfig = config.get('capture');
- layout = createMockLayoutInstance(captureConfig);
- logger = createMockLevelLogger();
-
- await createMockBrowserDriverFactory(core, logger, {
- evaluate: jest.fn(
- async unknown>({
- fn,
- args,
- }: {
- fn: T;
- args: Parameters;
- }) => fn(...args)
- ),
- getCreatePage: (driver) => {
- browser = driver;
-
- return jest.fn();
- },
- });
- });
-
- afterEach(() => {
- document.body.innerHTML = '';
- });
-
- it('should extract the error messages', async () => {
- document.body.innerHTML = `
-
-
-
-
- `;
-
- await expect(getRenderErrors(browser, layout, logger)).resolves.toEqual([
- 'a test error',
- 'a test error',
- 'a test error',
- 'a test error',
- ]);
- });
-
- it('should extract the error messages, even when there are none', async () => {
- document.body.innerHTML = `
-
- `;
-
- await expect(getRenderErrors(browser, layout, logger)).resolves.toEqual(undefined);
- });
-});
diff --git a/x-pack/plugins/reporting/server/lib/screenshots/get_time_range.test.ts b/x-pack/plugins/reporting/server/lib/screenshots/get_time_range.test.ts
deleted file mode 100644
index 003d1dc254a2a..0000000000000
--- a/x-pack/plugins/reporting/server/lib/screenshots/get_time_range.test.ts
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { HeadlessChromiumDriver } from '../../browsers';
-import {
- createMockBrowserDriverFactory,
- createMockConfig,
- createMockConfigSchema,
- createMockLayoutInstance,
- createMockLevelLogger,
- createMockReportingCore,
-} from '../../test_helpers';
-import { LayoutInstance } from '../layouts';
-import { LevelLogger } from '../level_logger';
-import { getTimeRange } from './get_time_range';
-
-describe('getTimeRange', () => {
- let layout: LayoutInstance;
- let logger: jest.Mocked;
- let browser: HeadlessChromiumDriver;
-
- beforeEach(async () => {
- const schema = createMockConfigSchema();
- const config = createMockConfig(schema);
- const captureConfig = config.get('capture');
- const core = await createMockReportingCore(schema);
-
- layout = createMockLayoutInstance(captureConfig);
- logger = createMockLevelLogger();
-
- await createMockBrowserDriverFactory(core, logger, {
- evaluate: jest.fn(
- async unknown>({
- fn,
- args,
- }: {
- fn: T;
- args: Parameters;
- }) => fn(...args)
- ),
- getCreatePage: (driver) => {
- browser = driver;
-
- return jest.fn();
- },
- });
- });
-
- afterEach(() => {
- document.body.innerHTML = '';
- });
-
- it('should return null when there is no duration element', async () => {
- await expect(getTimeRange(browser, layout, logger)).resolves.toBeNull();
- });
-
- it('should return null when duration attrbute is empty', async () => {
- document.body.innerHTML = `
-
- `;
-
- await expect(getTimeRange(browser, layout, logger)).resolves.toBeNull();
- });
-
- it('should return duration', async () => {
- document.body.innerHTML = `
-
- `;
-
- await expect(getTimeRange(browser, layout, logger)).resolves.toBe('10');
- });
-});
diff --git a/x-pack/plugins/reporting/server/lib/screenshots/index.ts b/x-pack/plugins/reporting/server/lib/screenshots/index.ts
deleted file mode 100644
index 2b8a0d6207a9b..0000000000000
--- a/x-pack/plugins/reporting/server/lib/screenshots/index.ts
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { LevelLogger } from '../';
-import { UrlOrUrlLocatorTuple } from '../../../common/types';
-import { ConditionalHeaders } from '../../export_types/common';
-import { LayoutInstance } from '../layouts';
-
-export { getScreenshots$ } from './observable';
-
-export interface PhaseInstance {
- timeoutValue: number;
- configValue: string;
- label: string;
-}
-
-export interface PhaseTimeouts {
- openUrl: PhaseInstance;
- waitForElements: PhaseInstance;
- renderComplete: PhaseInstance;
- loadDelay: number;
-}
-
-export interface ScreenshotObservableOpts {
- logger: LevelLogger;
- urlsOrUrlLocatorTuples: UrlOrUrlLocatorTuple[];
- conditionalHeaders: ConditionalHeaders;
- layout: LayoutInstance;
- browserTimezone?: string;
-}
-
-export interface AttributesMap {
- [key: string]: string | null;
-}
-
-export interface ElementPosition {
- boundingClientRect: {
- // modern browsers support x/y, but older ones don't
- top: number;
- left: number;
- width: number;
- height: number;
- };
- scroll: {
- x: number;
- y: number;
- };
-}
-
-export interface ElementsPositionAndAttribute {
- position: ElementPosition;
- attributes: AttributesMap;
-}
-
-export interface Screenshot {
- data: Buffer;
- title: string | null;
- description: string | null;
-}
-
-export interface PageSetupResults {
- elementsPositionAndAttributes: ElementsPositionAndAttribute[] | null;
- timeRange: string | null;
- error?: Error;
-}
-
-export interface ScreenshotResults {
- timeRange: string | null;
- screenshots: Screenshot[];
- error?: Error;
-
- /**
- * Individual visualizations might encounter errors at runtime. If there are any they are added to this
- * field. Any text captured here is intended to be shown to the user for debugging purposes, reporting
- * does no further sanitization on these strings.
- */
- renderErrors?: string[];
- elementsPositionAndAttributes?: ElementsPositionAndAttribute[]; // NOTE: for testing
-}
diff --git a/x-pack/plugins/reporting/server/lib/screenshots/observable.test.ts b/x-pack/plugins/reporting/server/lib/screenshots/observable.test.ts
deleted file mode 100644
index 3071ecb54dc26..0000000000000
--- a/x-pack/plugins/reporting/server/lib/screenshots/observable.test.ts
+++ /dev/null
@@ -1,490 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor 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('puppeteer', () => ({
- launch: () => ({
- // Fixme needs event emitters
- newPage: () => ({
- emulateTimezone: jest.fn(),
- setDefaultTimeout: jest.fn(),
- }),
- process: jest.fn(),
- close: jest.fn(),
- }),
-}));
-
-import moment from 'moment';
-import * as Rx from 'rxjs';
-import { ReportingCore } from '../..';
-import { HeadlessChromiumDriver } from '../../browsers';
-import { ConditionalHeaders } from '../../export_types/common';
-import {
- createMockBrowserDriverFactory,
- createMockConfig,
- createMockConfigSchema,
- createMockLayoutInstance,
- createMockLevelLogger,
- createMockReportingCore,
-} from '../../test_helpers';
-import * as contexts from './constants';
-import { getScreenshots$ } from './';
-
-/*
- * Mocks
- */
-const logger = createMockLevelLogger();
-
-const mockSchema = createMockConfigSchema({
- capture: {
- loadDelay: moment.duration(2, 's'),
- timeouts: {
- openUrl: moment.duration(2, 'm'),
- waitForElements: moment.duration(20, 's'),
- renderComplete: moment.duration(10, 's'),
- },
- },
-});
-const mockConfig = createMockConfig(mockSchema);
-const captureConfig = mockConfig.get('capture');
-const mockLayout = createMockLayoutInstance(captureConfig);
-
-let core: ReportingCore;
-
-/*
- * Tests
- */
-describe('Screenshot Observable Pipeline', () => {
- let mockBrowserDriverFactory: any;
-
- beforeEach(async () => {
- core = await createMockReportingCore(mockSchema);
- mockBrowserDriverFactory = await createMockBrowserDriverFactory(core, logger, {});
- });
-
- it('pipelines a single url into screenshot and timeRange', async () => {
- const result = await getScreenshots$(captureConfig, mockBrowserDriverFactory, {
- logger,
- urlsOrUrlLocatorTuples: ['/welcome/home/start/index.htm'],
- conditionalHeaders: {} as ConditionalHeaders,
- layout: mockLayout,
- browserTimezone: 'UTC',
- }).toPromise();
-
- expect(result).toMatchInlineSnapshot(`
- Array [
- Object {
- "elementsPositionAndAttributes": Array [
- Object {
- "attributes": Object {
- "description": "Default ",
- "title": "Default Mock Title",
- },
- "position": Object {
- "boundingClientRect": Object {
- "height": 600,
- "left": 0,
- "top": 0,
- "width": 800,
- },
- "scroll": Object {
- "x": 0,
- "y": 0,
- },
- },
- },
- ],
- "error": undefined,
- "screenshots": Array [
- Object {
- "data": Object {
- "data": Array [
- 115,
- 99,
- 114,
- 101,
- 101,
- 110,
- 115,
- 104,
- 111,
- 116,
- ],
- "type": "Buffer",
- },
- "description": "Default ",
- "title": "Default Mock Title",
- },
- ],
- "timeRange": "Default GetTimeRange Result",
- },
- ]
- `);
- });
-
- it('pipelines multiple urls into', async () => {
- // mock implementations
- const mockScreenshot = jest.fn(async () => Buffer.from('some screenshots'));
- const mockOpen = jest.fn();
-
- // mocks
- mockBrowserDriverFactory = await createMockBrowserDriverFactory(core, logger, {
- screenshot: mockScreenshot,
- open: mockOpen,
- });
-
- // test
- const result = await getScreenshots$(captureConfig, mockBrowserDriverFactory, {
- logger,
- urlsOrUrlLocatorTuples: [
- '/welcome/home/start/index2.htm',
- '/welcome/home/start/index.php3?page=./home.php',
- ],
- conditionalHeaders: {} as ConditionalHeaders,
- layout: mockLayout,
- browserTimezone: 'UTC',
- }).toPromise();
-
- expect(result).toMatchInlineSnapshot(`
- Array [
- Object {
- "elementsPositionAndAttributes": Array [
- Object {
- "attributes": Object {
- "description": "Default ",
- "title": "Default Mock Title",
- },
- "position": Object {
- "boundingClientRect": Object {
- "height": 600,
- "left": 0,
- "top": 0,
- "width": 800,
- },
- "scroll": Object {
- "x": 0,
- "y": 0,
- },
- },
- },
- ],
- "error": undefined,
- "screenshots": Array [
- Object {
- "data": Object {
- "data": Array [
- 115,
- 111,
- 109,
- 101,
- 32,
- 115,
- 99,
- 114,
- 101,
- 101,
- 110,
- 115,
- 104,
- 111,
- 116,
- 115,
- ],
- "type": "Buffer",
- },
- "description": "Default ",
- "title": "Default Mock Title",
- },
- ],
- "timeRange": "Default GetTimeRange Result",
- },
- Object {
- "elementsPositionAndAttributes": Array [
- Object {
- "attributes": Object {
- "description": "Default ",
- "title": "Default Mock Title",
- },
- "position": Object {
- "boundingClientRect": Object {
- "height": 600,
- "left": 0,
- "top": 0,
- "width": 800,
- },
- "scroll": Object {
- "x": 0,
- "y": 0,
- },
- },
- },
- ],
- "error": undefined,
- "screenshots": Array [
- Object {
- "data": Object {
- "data": Array [
- 115,
- 111,
- 109,
- 101,
- 32,
- 115,
- 99,
- 114,
- 101,
- 101,
- 110,
- 115,
- 104,
- 111,
- 116,
- 115,
- ],
- "type": "Buffer",
- },
- "description": "Default ",
- "title": "Default Mock Title",
- },
- ],
- "timeRange": "Default GetTimeRange Result",
- },
- ]
- `);
-
- // ensures the correct selectors are waited on for multi URL jobs
- expect(mockOpen.mock.calls.length).toBe(2);
-
- const firstSelector = mockOpen.mock.calls[0][1].waitForSelector;
- expect(firstSelector).toBe('.kbnAppWrapper');
-
- const secondSelector = mockOpen.mock.calls[1][1].waitForSelector;
- expect(secondSelector).toBe('[data-shared-page="2"]');
- });
-
- describe('error handling', () => {
- it('recovers if waitForSelector fails', async () => {
- // mock implementations
- const mockWaitForSelector = jest.fn().mockImplementation((selectorArg: string) => {
- throw new Error('Mock error!');
- });
-
- // mocks
- mockBrowserDriverFactory = await createMockBrowserDriverFactory(core, logger, {
- waitForSelector: mockWaitForSelector,
- });
-
- // test
- const getScreenshot = async () => {
- return await getScreenshots$(captureConfig, mockBrowserDriverFactory, {
- logger,
- urlsOrUrlLocatorTuples: [
- '/welcome/home/start/index2.htm',
- '/welcome/home/start/index.php3?page=./home.php3',
- ],
- conditionalHeaders: {} as ConditionalHeaders,
- layout: mockLayout,
- browserTimezone: 'UTC',
- }).toPromise();
- };
-
- await expect(getScreenshot()).resolves.toMatchInlineSnapshot(`
- Array [
- Object {
- "elementsPositionAndAttributes": Array [
- Object {
- "attributes": Object {},
- "position": Object {
- "boundingClientRect": Object {
- "height": 100,
- "left": 0,
- "top": 0,
- "width": 100,
- },
- "scroll": Object {
- "x": 0,
- "y": 0,
- },
- },
- },
- ],
- "error": [Error: The "wait for elements" phase encountered an error: Error: An error occurred when trying to read the page for visualization panel info: Error: Mock error!],
- "screenshots": Array [
- Object {
- "data": Object {
- "data": Array [
- 115,
- 99,
- 114,
- 101,
- 101,
- 110,
- 115,
- 104,
- 111,
- 116,
- ],
- "type": "Buffer",
- },
- "description": undefined,
- "title": undefined,
- },
- ],
- "timeRange": null,
- },
- Object {
- "elementsPositionAndAttributes": Array [
- Object {
- "attributes": Object {},
- "position": Object {
- "boundingClientRect": Object {
- "height": 100,
- "left": 0,
- "top": 0,
- "width": 100,
- },
- "scroll": Object {
- "x": 0,
- "y": 0,
- },
- },
- },
- ],
- "error": [Error: The "wait for elements" phase encountered an error: Error: An error occurred when trying to read the page for visualization panel info: Error: Mock error!],
- "screenshots": Array [
- Object {
- "data": Object {
- "data": Array [
- 115,
- 99,
- 114,
- 101,
- 101,
- 110,
- 115,
- 104,
- 111,
- 116,
- ],
- "type": "Buffer",
- },
- "description": undefined,
- "title": undefined,
- },
- ],
- "timeRange": null,
- },
- ]
- `);
- });
-
- it('observes page exit', async () => {
- // mocks
- const mockGetCreatePage = (driver: HeadlessChromiumDriver) =>
- jest
- .fn()
- .mockImplementation(() =>
- Rx.of({ driver, exit$: Rx.throwError('Instant timeout has fired!') })
- );
-
- const mockWaitForSelector = jest.fn().mockImplementation((selectorArg: string) => {
- return Rx.never().toPromise();
- });
-
- mockBrowserDriverFactory = await createMockBrowserDriverFactory(core, logger, {
- getCreatePage: mockGetCreatePage,
- waitForSelector: mockWaitForSelector,
- });
-
- // test
- const getScreenshot = async () => {
- return await getScreenshots$(captureConfig, mockBrowserDriverFactory, {
- logger,
- urlsOrUrlLocatorTuples: ['/welcome/home/start/index.php3?page=./home.php3'],
- conditionalHeaders: {} as ConditionalHeaders,
- layout: mockLayout,
- browserTimezone: 'UTC',
- }).toPromise();
- };
-
- await expect(getScreenshot()).rejects.toMatchInlineSnapshot(`"Instant timeout has fired!"`);
- });
-
- it(`uses defaults for element positions and size when Kibana page is not ready`, async () => {
- // mocks
- const mockBrowserEvaluate = jest.fn();
- mockBrowserEvaluate.mockImplementation(() => {
- const lastCallIndex = mockBrowserEvaluate.mock.calls.length - 1;
- const { context: mockCall } = mockBrowserEvaluate.mock.calls[lastCallIndex][1];
-
- if (mockCall === contexts.CONTEXT_ELEMENTATTRIBUTES) {
- return Promise.resolve(null);
- } else {
- return Promise.resolve();
- }
- });
- mockBrowserDriverFactory = await createMockBrowserDriverFactory(core, logger, {
- evaluate: mockBrowserEvaluate,
- });
- mockLayout.getViewport = () => null;
-
- const screenshots = await getScreenshots$(captureConfig, mockBrowserDriverFactory, {
- logger,
- urlsOrUrlLocatorTuples: ['/welcome/home/start/index.php3?page=./home.php3'],
- conditionalHeaders: {} as ConditionalHeaders,
- layout: mockLayout,
- browserTimezone: 'UTC',
- }).toPromise();
-
- expect(screenshots).toMatchInlineSnapshot(`
- Array [
- Object {
- "elementsPositionAndAttributes": Array [
- Object {
- "attributes": Object {},
- "position": Object {
- "boundingClientRect": Object {
- "height": 1200,
- "left": 0,
- "top": 0,
- "width": 1800,
- },
- "scroll": Object {
- "x": 0,
- "y": 0,
- },
- },
- },
- ],
- "error": undefined,
- "screenshots": Array [
- Object {
- "data": Object {
- "data": Array [
- 115,
- 99,
- 114,
- 101,
- 101,
- 110,
- 115,
- 104,
- 111,
- 116,
- ],
- "type": "Buffer",
- },
- "description": undefined,
- "title": undefined,
- },
- ],
- "timeRange": undefined,
- },
- ]
- `);
- });
- });
-});
diff --git a/x-pack/plugins/reporting/server/lib/screenshots/observable.ts b/x-pack/plugins/reporting/server/lib/screenshots/observable.ts
deleted file mode 100644
index 8ba2a125a5504..0000000000000
--- a/x-pack/plugins/reporting/server/lib/screenshots/observable.ts
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import apm from 'elastic-apm-node';
-import * as Rx from 'rxjs';
-import { catchError, concatMap, first, mergeMap, take, takeUntil, toArray } from 'rxjs/operators';
-import { durationToNumber } from '../../../common/schema_utils';
-import { REPORTING_TRANSACTION_TYPE } from '../../../common/constants';
-import { HeadlessChromiumDriverFactory } from '../../browsers';
-import { CaptureConfig } from '../../types';
-import {
- ElementPosition,
- ElementsPositionAndAttribute,
- PageSetupResults,
- ScreenshotObservableOpts,
- ScreenshotResults,
-} from './';
-import { ScreenshotObservableHandler } from './observable_handler';
-
-export type { ElementPosition, ElementsPositionAndAttribute, ScreenshotResults };
-
-const getTimeouts = (captureConfig: CaptureConfig) => ({
- openUrl: {
- timeoutValue: durationToNumber(captureConfig.timeouts.openUrl),
- configValue: `xpack.reporting.capture.timeouts.openUrl`,
- label: 'open URL',
- },
- waitForElements: {
- timeoutValue: durationToNumber(captureConfig.timeouts.waitForElements),
- configValue: `xpack.reporting.capture.timeouts.waitForElements`,
- label: 'wait for elements',
- },
- renderComplete: {
- timeoutValue: durationToNumber(captureConfig.timeouts.renderComplete),
- configValue: `xpack.reporting.capture.timeouts.renderComplete`,
- label: 'render complete',
- },
- loadDelay: durationToNumber(captureConfig.loadDelay),
-});
-
-export function getScreenshots$(
- captureConfig: CaptureConfig,
- browserDriverFactory: HeadlessChromiumDriverFactory,
- opts: ScreenshotObservableOpts
-): Rx.Observable {
- const apmTrans = apm.startTransaction('screenshot-pipeline', REPORTING_TRANSACTION_TYPE);
- const apmCreatePage = apmTrans?.startSpan('create-page', 'wait');
- const { browserTimezone, logger } = opts;
-
- return browserDriverFactory.createPage({ browserTimezone }, logger).pipe(
- mergeMap(({ driver, exit$ }) => {
- apmCreatePage?.end();
- exit$.subscribe({ error: () => apmTrans?.end() });
-
- const screen = new ScreenshotObservableHandler(driver, opts, getTimeouts(captureConfig));
-
- return Rx.from(opts.urlsOrUrlLocatorTuples).pipe(
- concatMap((urlOrUrlLocatorTuple, index) =>
- screen.setupPage(index, urlOrUrlLocatorTuple, apmTrans).pipe(
- catchError((err) => {
- screen.checkPageIsOpen(); // this fails the job if the browser has closed
-
- logger.error(err);
- return Rx.of({ ...defaultSetupResult, error: err }); // allow failover screenshot capture
- }),
- takeUntil(exit$),
- screen.getScreenshots()
- )
- ),
- take(opts.urlsOrUrlLocatorTuples.length),
- toArray()
- );
- }),
- first()
- );
-}
-
-const defaultSetupResult: PageSetupResults = {
- elementsPositionAndAttributes: null,
- timeRange: null,
-};
diff --git a/x-pack/plugins/reporting/server/lib/screenshots/observable_handler.test.ts b/x-pack/plugins/reporting/server/lib/screenshots/observable_handler.test.ts
deleted file mode 100644
index cb0a513992722..0000000000000
--- a/x-pack/plugins/reporting/server/lib/screenshots/observable_handler.test.ts
+++ /dev/null
@@ -1,160 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import * as Rx from 'rxjs';
-import { first, map } from 'rxjs/operators';
-import { HeadlessChromiumDriver } from '../../browsers';
-import { ReportingConfigType } from '../../config';
-import { ConditionalHeaders } from '../../export_types/common';
-import {
- createMockBrowserDriverFactory,
- createMockConfigSchema,
- createMockLayoutInstance,
- createMockLevelLogger,
- createMockReportingCore,
-} from '../../test_helpers';
-import { LayoutInstance } from '../layouts';
-import { PhaseTimeouts, ScreenshotObservableOpts } from './';
-import { ScreenshotObservableHandler } from './observable_handler';
-
-const logger = createMockLevelLogger();
-
-describe('ScreenshotObservableHandler', () => {
- let captureConfig: ReportingConfigType['capture'];
- let layout: LayoutInstance;
- let conditionalHeaders: ConditionalHeaders;
- let opts: ScreenshotObservableOpts;
- let timeouts: PhaseTimeouts;
- let driver: HeadlessChromiumDriver;
-
- beforeAll(async () => {
- captureConfig = {
- timeouts: {
- openUrl: 30000,
- waitForElements: 30000,
- renderComplete: 30000,
- },
- loadDelay: 5000,
- } as unknown as typeof captureConfig;
-
- layout = createMockLayoutInstance(captureConfig);
-
- conditionalHeaders = {
- headers: { testHeader: 'testHeadValue' },
- conditions: {} as unknown as ConditionalHeaders['conditions'],
- };
-
- opts = {
- conditionalHeaders,
- layout,
- logger,
- urlsOrUrlLocatorTuples: [],
- };
-
- timeouts = {
- openUrl: {
- timeoutValue: 60000,
- configValue: `xpack.reporting.capture.timeouts.openUrl`,
- label: 'open URL',
- },
- waitForElements: {
- timeoutValue: 30000,
- configValue: `xpack.reporting.capture.timeouts.waitForElements`,
- label: 'wait for elements',
- },
- renderComplete: {
- timeoutValue: 60000,
- configValue: `xpack.reporting.capture.timeouts.renderComplete`,
- label: 'render complete',
- },
- loadDelay: 5000,
- };
- });
-
- beforeEach(async () => {
- const reporting = await createMockReportingCore(createMockConfigSchema());
- const driverFactory = await createMockBrowserDriverFactory(reporting, logger);
- ({ driver } = await driverFactory.createPage({}, logger).pipe(first()).toPromise());
- driver.isPageOpen = jest.fn().mockImplementation(() => true);
- });
-
- describe('waitUntil', () => {
- it('catches TimeoutError and references the timeout config in a custom message', async () => {
- const screenshots = new ScreenshotObservableHandler(driver, opts, timeouts);
- const test$ = Rx.interval(1000).pipe(
- screenshots.waitUntil({
- timeoutValue: 200,
- configValue: 'test.config.value',
- label: 'Test Config',
- })
- );
-
- const testPipeline = () => test$.toPromise();
- await expect(testPipeline).rejects.toMatchInlineSnapshot(
- `[Error: The "Test Config" phase took longer than 0.2 seconds. You may need to increase "test.config.value"]`
- );
- });
-
- it('catches other Errors and explains where they were thrown', async () => {
- const screenshots = new ScreenshotObservableHandler(driver, opts, timeouts);
- const test$ = Rx.throwError(new Error(`Test Error to Throw`)).pipe(
- screenshots.waitUntil({
- timeoutValue: 200,
- configValue: 'test.config.value',
- label: 'Test Config',
- })
- );
-
- const testPipeline = () => test$.toPromise();
- await expect(testPipeline).rejects.toMatchInlineSnapshot(
- `[Error: The "Test Config" phase encountered an error: Error: Test Error to Throw]`
- );
- });
-
- it('is a pass-through if there is no Error', async () => {
- const screenshots = new ScreenshotObservableHandler(driver, opts, timeouts);
- const test$ = Rx.of('nice to see you').pipe(
- screenshots.waitUntil({
- timeoutValue: 20,
- configValue: 'xxxxxxxxxxxxxxxxx',
- label: 'xxxxxxxxxxx',
- })
- );
-
- await expect(test$.toPromise()).resolves.toBe(`nice to see you`);
- });
- });
-
- describe('checkPageIsOpen', () => {
- it('throws a decorated Error when page is not open', async () => {
- driver.isPageOpen = jest.fn().mockImplementation(() => false);
- const screenshots = new ScreenshotObservableHandler(driver, opts, timeouts);
- const test$ = Rx.of(234455).pipe(
- map((input) => {
- screenshots.checkPageIsOpen();
- return input;
- })
- );
-
- await expect(test$.toPromise()).rejects.toMatchInlineSnapshot(
- `[Error: Browser was closed unexpectedly! Check the server logs for more info.]`
- );
- });
-
- it('is a pass-through when the page is open', async () => {
- const screenshots = new ScreenshotObservableHandler(driver, opts, timeouts);
- const test$ = Rx.of(234455).pipe(
- map((input) => {
- screenshots.checkPageIsOpen();
- return input;
- })
- );
-
- await expect(test$.toPromise()).resolves.toBe(234455);
- });
- });
-});
diff --git a/x-pack/plugins/reporting/server/lib/screenshots/observable_handler.ts b/x-pack/plugins/reporting/server/lib/screenshots/observable_handler.ts
deleted file mode 100644
index c241a529818fa..0000000000000
--- a/x-pack/plugins/reporting/server/lib/screenshots/observable_handler.ts
+++ /dev/null
@@ -1,197 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import apm from 'elastic-apm-node';
-import * as Rx from 'rxjs';
-import { catchError, mergeMap, switchMapTo, timeoutWith } from 'rxjs/operators';
-import { numberToDuration } from '../../../common/schema_utils';
-import { UrlOrUrlLocatorTuple } from '../../../common/types';
-import { HeadlessChromiumDriver } from '../../browsers';
-import { getChromiumDisconnectedError } from '../../browsers/chromium';
-import {
- PageSetupResults,
- PhaseInstance,
- PhaseTimeouts,
- ScreenshotObservableOpts,
- ScreenshotResults,
-} from './';
-import { getElementPositionAndAttributes } from './get_element_position_data';
-import { getNumberOfItems } from './get_number_of_items';
-import { getRenderErrors } from './get_render_errors';
-import { getScreenshots } from './get_screenshots';
-import { getTimeRange } from './get_time_range';
-import { injectCustomCss } from './inject_css';
-import { openUrl } from './open_url';
-import { waitForRenderComplete } from './wait_for_render';
-import { waitForVisualizations } from './wait_for_visualizations';
-
-export class ScreenshotObservableHandler {
- private conditionalHeaders: ScreenshotObservableOpts['conditionalHeaders'];
- private layout: ScreenshotObservableOpts['layout'];
- private logger: ScreenshotObservableOpts['logger'];
-
- constructor(
- private readonly driver: HeadlessChromiumDriver,
- opts: ScreenshotObservableOpts,
- private timeouts: PhaseTimeouts
- ) {
- this.conditionalHeaders = opts.conditionalHeaders;
- this.layout = opts.layout;
- this.logger = opts.logger;
- }
-
- /*
- * Decorates a TimeoutError with context of the phase that has timed out.
- */
- public waitUntil(phase: PhaseInstance) {
- const { timeoutValue, label, configValue } = phase;
-
- return (source: Rx.Observable) =>
- source.pipe(
- catchError((error) => {
- throw new Error(`The "${label}" phase encountered an error: ${error}`);
- }),
- timeoutWith(
- timeoutValue,
- Rx.throwError(
- new Error(
- `The "${label}" phase took longer than ${numberToDuration(
- timeoutValue
- ).asSeconds()} seconds. You may need to increase "${configValue}"`
- )
- )
- )
- );
- }
-
- private openUrl(index: number, urlOrUrlLocatorTuple: UrlOrUrlLocatorTuple) {
- return Rx.defer(() =>
- openUrl(
- this.timeouts.openUrl.timeoutValue,
- this.driver,
- index,
- urlOrUrlLocatorTuple,
- this.conditionalHeaders,
- this.layout,
- this.logger
- )
- ).pipe(this.waitUntil(this.timeouts.openUrl));
- }
-
- private waitForElements() {
- const driver = this.driver;
- const waitTimeout = this.timeouts.waitForElements.timeoutValue;
-
- return Rx.defer(() => getNumberOfItems(waitTimeout, driver, this.layout, this.logger)).pipe(
- mergeMap((itemsCount) => {
- // set the viewport to the dimentions from the job, to allow elements to flow into the expected layout
- const viewport = this.layout.getViewport(itemsCount) || getDefaultViewPort();
-
- return Rx.forkJoin([
- driver.setViewport(viewport, this.logger),
- waitForVisualizations(waitTimeout, driver, itemsCount, this.layout, this.logger),
- ]);
- }),
- this.waitUntil(this.timeouts.waitForElements)
- );
- }
-
- private completeRender(apmTrans: apm.Transaction | null) {
- const driver = this.driver;
- const layout = this.layout;
- const logger = this.logger;
-
- return Rx.defer(async () => {
- // Waiting till _after_ elements have rendered before injecting our CSS
- // allows for them to be displayed properly in many cases
- await injectCustomCss(driver, layout, logger);
-
- const apmPositionElements = apmTrans?.startSpan('position-elements', 'correction');
- // position panel elements for print layout
- await layout.positionElements?.(driver, logger);
- apmPositionElements?.end();
-
- await waitForRenderComplete(this.timeouts.loadDelay, driver, layout, logger);
- }).pipe(
- mergeMap(() =>
- Rx.forkJoin({
- timeRange: getTimeRange(driver, layout, logger),
- elementsPositionAndAttributes: getElementPositionAndAttributes(driver, layout, logger),
- renderErrors: getRenderErrors(driver, layout, logger),
- })
- ),
- this.waitUntil(this.timeouts.renderComplete)
- );
- }
-
- public setupPage(
- index: number,
- urlOrUrlLocatorTuple: UrlOrUrlLocatorTuple,
- apmTrans: apm.Transaction | null
- ) {
- return this.openUrl(index, urlOrUrlLocatorTuple).pipe(
- switchMapTo(this.waitForElements()),
- switchMapTo(this.completeRender(apmTrans))
- );
- }
-
- public getScreenshots() {
- return (withRenderComplete: Rx.Observable) =>
- withRenderComplete.pipe(
- mergeMap(async (data: PageSetupResults): Promise => {
- this.checkPageIsOpen(); // fail the report job if the browser has closed
-
- const elements =
- data.elementsPositionAndAttributes ??
- getDefaultElementPosition(this.layout.getViewport(1));
- const screenshots = await getScreenshots(this.driver, elements, this.logger);
- const { timeRange, error: setupError } = data;
-
- return {
- timeRange,
- screenshots,
- error: setupError,
- elementsPositionAndAttributes: elements,
- };
- })
- );
- }
-
- public checkPageIsOpen() {
- if (!this.driver.isPageOpen()) {
- throw getChromiumDisconnectedError();
- }
- }
-}
-
-const DEFAULT_SCREENSHOT_CLIP_HEIGHT = 1200;
-const DEFAULT_SCREENSHOT_CLIP_WIDTH = 1800;
-
-const getDefaultElementPosition = (dimensions: { height?: number; width?: number } | null) => {
- const height = dimensions?.height || DEFAULT_SCREENSHOT_CLIP_HEIGHT;
- const width = dimensions?.width || DEFAULT_SCREENSHOT_CLIP_WIDTH;
-
- return [
- {
- position: {
- boundingClientRect: { top: 0, left: 0, height, width },
- scroll: { x: 0, y: 0 },
- },
- attributes: {},
- },
- ];
-};
-
-/*
- * If Kibana is showing a non-HTML error message, the viewport might not be
- * provided by the browser.
- */
-const getDefaultViewPort = () => ({
- height: DEFAULT_SCREENSHOT_CLIP_HEIGHT,
- width: DEFAULT_SCREENSHOT_CLIP_WIDTH,
- zoom: 1,
-});
diff --git a/x-pack/plugins/reporting/server/lib/store/mapping.ts b/x-pack/plugins/reporting/server/lib/store/mapping.ts
index a43b4494fe913..667648d3372c5 100644
--- a/x-pack/plugins/reporting/server/lib/store/mapping.ts
+++ b/x-pack/plugins/reporting/server/lib/store/mapping.ts
@@ -34,7 +34,6 @@ export const mapping = {
},
},
},
- browser_type: { type: 'keyword' },
migration_version: { type: 'keyword' }, // new field (7.14) to distinguish reports that were scheduled with Task Manager
jobtype: { type: 'keyword' },
payload: { type: 'object', enabled: false },
diff --git a/x-pack/plugins/reporting/server/lib/store/report.test.ts b/x-pack/plugins/reporting/server/lib/store/report.test.ts
index f9cd413b3e5a7..f6cbbade4df7b 100644
--- a/x-pack/plugins/reporting/server/lib/store/report.test.ts
+++ b/x-pack/plugins/reporting/server/lib/store/report.test.ts
@@ -13,7 +13,6 @@ describe('Class Report', () => {
_index: '.reporting-test-index-12345',
jobtype: 'test-report',
created_by: 'created_by_test_string',
- browser_type: 'browser_type_test_string',
max_attempts: 50,
payload: {
headers: 'payload_test_field',
@@ -28,7 +27,6 @@ describe('Class Report', () => {
expect(report.toReportSource()).toMatchObject({
attempts: 0,
- browser_type: 'browser_type_test_string',
completed_at: undefined,
created_by: 'created_by_test_string',
jobtype: 'test-report',
@@ -49,7 +47,6 @@ describe('Class Report', () => {
});
expect(report.toApiJSON()).toMatchObject({
attempts: 0,
- browser_type: 'browser_type_test_string',
created_by: 'created_by_test_string',
index: '.reporting-test-index-12345',
jobtype: 'test-report',
@@ -68,7 +65,6 @@ describe('Class Report', () => {
_index: '.reporting-test-index-12345',
jobtype: 'test-report',
created_by: 'created_by_test_string',
- browser_type: 'browser_type_test_string',
max_attempts: 50,
payload: {
headers: 'payload_test_field',
@@ -91,7 +87,6 @@ describe('Class Report', () => {
expect(report.toReportSource()).toMatchObject({
attempts: 0,
- browser_type: 'browser_type_test_string',
completed_at: undefined,
created_by: 'created_by_test_string',
jobtype: 'test-report',
@@ -113,7 +108,6 @@ describe('Class Report', () => {
});
expect(report.toApiJSON()).toMatchObject({
attempts: 0,
- browser_type: 'browser_type_test_string',
completed_at: undefined,
created_by: 'created_by_test_string',
id: '12342p9o387549o2345',
diff --git a/x-pack/plugins/reporting/server/lib/store/report.ts b/x-pack/plugins/reporting/server/lib/store/report.ts
index 2f802334eb6ff..67f1ccdea5db8 100644
--- a/x-pack/plugins/reporting/server/lib/store/report.ts
+++ b/x-pack/plugins/reporting/server/lib/store/report.ts
@@ -38,7 +38,6 @@ export class Report implements Partial {
public readonly payload: ReportSource['payload'];
public readonly meta: ReportSource['meta'];
- public readonly browser_type: ReportSource['browser_type'];
public readonly status: ReportSource['status'];
public readonly attempts: ReportSource['attempts'];
@@ -82,7 +81,6 @@ export class Report implements Partial {
this.max_attempts = opts.max_attempts;
this.attempts = opts.attempts || 0;
this.timeout = opts.timeout;
- this.browser_type = opts.browser_type;
this.process_expiration = opts.process_expiration;
this.started_at = opts.started_at;
@@ -125,7 +123,6 @@ export class Report implements Partial {
meta: this.meta,
timeout: this.timeout,
max_attempts: this.max_attempts,
- browser_type: this.browser_type,
status: this.status,
attempts: this.attempts,
started_at: this.started_at,
@@ -170,7 +167,6 @@ export class Report implements Partial {
meta: this.meta,
timeout: this.timeout,
max_attempts: this.max_attempts,
- browser_type: this.browser_type,
status: this.status,
attempts: this.attempts,
started_at: this.started_at,
diff --git a/x-pack/plugins/reporting/server/lib/store/store.test.ts b/x-pack/plugins/reporting/server/lib/store/store.test.ts
index a28197d261ba2..c67dc3fa2d992 100644
--- a/x-pack/plugins/reporting/server/lib/store/store.test.ts
+++ b/x-pack/plugins/reporting/server/lib/store/store.test.ts
@@ -193,7 +193,6 @@ describe('ReportingStore', () => {
status: 'pending',
meta: { testMeta: 'meta' } as any,
payload: { testPayload: 'payload' } as any,
- browser_type: 'browser type string',
attempts: 0,
max_attempts: 1,
timeout: 30000,
@@ -214,7 +213,6 @@ describe('ReportingStore', () => {
"_primary_term": 1234,
"_seq_no": 5678,
"attempts": 0,
- "browser_type": "browser type string",
"completed_at": undefined,
"created_at": "some time",
"created_by": "some security person",
@@ -247,7 +245,6 @@ describe('ReportingStore', () => {
_primary_term: 10002,
jobtype: 'test-report',
created_by: 'created_by_test_string',
- browser_type: 'browser_type_test_string',
max_attempts: 50,
payload: {
title: 'test report',
@@ -279,7 +276,6 @@ describe('ReportingStore', () => {
_primary_term: 10002,
jobtype: 'test-report',
created_by: 'created_by_test_string',
- browser_type: 'browser_type_test_string',
max_attempts: 50,
payload: {
title: 'test report',
@@ -310,7 +306,6 @@ describe('ReportingStore', () => {
_primary_term: 10002,
jobtype: 'test-report',
created_by: 'created_by_test_string',
- browser_type: 'browser_type_test_string',
max_attempts: 50,
payload: {
title: 'test report',
@@ -341,7 +336,6 @@ describe('ReportingStore', () => {
_primary_term: 10002,
jobtype: 'test-report',
created_by: 'created_by_test_string',
- browser_type: 'browser_type_test_string',
max_attempts: 50,
payload: {
title: 'test report',
@@ -385,7 +379,6 @@ describe('ReportingStore', () => {
_primary_term: 10002,
jobtype: 'test-report-2',
created_by: 'created_by_test_string',
- browser_type: 'browser_type_test_string',
status: 'processing',
process_expiration: '2002',
max_attempts: 3,
diff --git a/x-pack/plugins/reporting/server/lib/store/store.ts b/x-pack/plugins/reporting/server/lib/store/store.ts
index 43f57da8c21f7..7ddef6d66e275 100644
--- a/x-pack/plugins/reporting/server/lib/store/store.ts
+++ b/x-pack/plugins/reporting/server/lib/store/store.ts
@@ -24,7 +24,6 @@ import { MIGRATION_VERSION } from './report';
export type ReportProcessingFields = Required<{
kibana_id: Report['kibana_id'];
kibana_name: Report['kibana_name'];
- browser_type: Report['browser_type'];
attempts: Report['attempts'];
started_at: Report['started_at'];
max_attempts: Report['max_attempts'];
@@ -252,7 +251,6 @@ export class ReportingStore {
_primary_term: document._primary_term,
jobtype: document._source?.jobtype,
attempts: document._source?.attempts,
- browser_type: document._source?.browser_type,
created_at: document._source?.created_at,
created_by: document._source?.created_by,
max_attempts: document._source?.max_attempts,
diff --git a/x-pack/plugins/reporting/server/lib/tasks/execute_report.ts b/x-pack/plugins/reporting/server/lib/tasks/execute_report.ts
index 5f885ad127b43..b725c31da398d 100644
--- a/x-pack/plugins/reporting/server/lib/tasks/execute_report.ts
+++ b/x-pack/plugins/reporting/server/lib/tasks/execute_report.ts
@@ -159,7 +159,6 @@ export class ExecuteReportTask implements ReportingTask {
const doc: ReportProcessingFields = {
kibana_id: this.kibanaId,
kibana_name: this.kibanaName,
- browser_type: this.config.capture.browser.type,
attempts: report.attempts + 1,
max_attempts: maxAttempts,
started_at: startTime,
diff --git a/x-pack/plugins/reporting/server/plugin.test.ts b/x-pack/plugins/reporting/server/plugin.test.ts
index 9a2acc4a51202..4c04eb0c004e5 100644
--- a/x-pack/plugins/reporting/server/plugin.test.ts
+++ b/x-pack/plugins/reporting/server/plugin.test.ts
@@ -5,16 +5,6 @@
* 2.0.
*/
-jest.mock('./browsers/install', () => ({
- installBrowser: jest.fn().mockImplementation(() => ({
- binaryPath$: {
- pipe: jest.fn().mockImplementation(() => ({
- toPromise: () => Promise.resolve(),
- })),
- },
- })),
-}));
-
import { coreMock } from 'src/core/server/mocks';
import { featuresPluginMock } from '../../features/server/mocks';
import { TaskManagerSetupContract } from '../../task_manager/server';
diff --git a/x-pack/plugins/reporting/server/plugin.ts b/x-pack/plugins/reporting/server/plugin.ts
index 8969a698a8ce4..0a2318daded02 100644
--- a/x-pack/plugins/reporting/server/plugin.ts
+++ b/x-pack/plugins/reporting/server/plugin.ts
@@ -8,7 +8,6 @@
import type { CoreSetup, CoreStart, Plugin, PluginInitializerContext } from 'src/core/server';
import { PLUGIN_ID } from '../common/constants';
import { ReportingCore } from './';
-import { initializeBrowserDriverFactory } from './browsers';
import { buildConfig, registerUiSettings, ReportingConfigType } from './config';
import { registerDeprecations } from './deprecations';
import { LevelLogger, ReportingStore } from './lib';
@@ -35,7 +34,7 @@ export class ReportingPlugin
public setup(core: CoreSetup, plugins: ReportingSetupDeps) {
const { http } = core;
- const { screenshotMode, features, licensing, security, spaces, taskManager } = plugins;
+ const { features, licensing, security, spaces, taskManager } = plugins;
const reportingCore = new ReportingCore(this.logger, this.initContext);
@@ -53,7 +52,6 @@ export class ReportingPlugin
const router = http.createRouter();
const basePath = http.basePath;
reportingCore.pluginSetup({
- screenshotMode,
features,
licensing,
basePath,
@@ -98,11 +96,9 @@ export class ReportingPlugin
(async () => {
await reportingCore.pluginSetsUp();
- const browserDriverFactory = await initializeBrowserDriverFactory(reportingCore, this.logger);
const store = new ReportingStore(reportingCore, this.logger);
await reportingCore.pluginStart({
- browserDriverFactory,
savedObjects: core.savedObjects,
uiSettings: core.uiSettings,
store,
@@ -110,6 +106,7 @@ export class ReportingPlugin
data: plugins.data,
taskManager: plugins.taskManager,
logger: this.logger,
+ screenshotting: plugins.screenshotting,
});
// Note: this must be called after ReportingCore.pluginStart
diff --git a/x-pack/plugins/reporting/server/routes/diagnostic/browser.test.ts b/x-pack/plugins/reporting/server/routes/diagnostic/browser.test.ts
index a27ce6a49b1a2..47dae7f96daa4 100644
--- a/x-pack/plugins/reporting/server/routes/diagnostic/browser.test.ts
+++ b/x-pack/plugins/reporting/server/routes/diagnostic/browser.test.ts
@@ -6,11 +6,10 @@
*/
import { UnwrapPromise } from '@kbn/utility-types';
-import { spawn } from 'child_process';
-import { createInterface } from 'readline';
import { setupServer } from 'src/core/server/test_utils';
import supertest from 'supertest';
import * as Rx from 'rxjs';
+import type { ScreenshottingStart } from '../../../../screenshotting/server';
import { ReportingCore } from '../..';
import {
createMockConfigSchema,
@@ -21,17 +20,11 @@ import {
import type { ReportingRequestHandlerContext } from '../../types';
import { registerDiagnoseBrowser } from './browser';
-jest.mock('child_process');
-jest.mock('readline');
-
type SetupServerReturn = UnwrapPromise>;
const devtoolMessage = 'DevTools listening on (ws://localhost:4000)';
const fontNotFoundMessage = 'Could not find the default font';
-const wait = (ms: number): Rx.Observable<0> =>
- Rx.from(new Promise<0>((resolve) => setTimeout(() => resolve(0), ms)));
-
describe('POST /diagnose/browser', () => {
jest.setTimeout(6000);
const reportingSymbol = Symbol('reporting');
@@ -40,12 +33,11 @@ describe('POST /diagnose/browser', () => {
let server: SetupServerReturn['server'];
let httpSetup: SetupServerReturn['httpSetup'];
let core: ReportingCore;
- const mockedSpawn: any = spawn;
- const mockedCreateInterface: any = createInterface;
+ let screenshotting: jest.Mocked;
const config = createMockConfigSchema({
queue: { timeout: 120000 },
- capture: { browser: { chromium: { proxy: { enabled: false } } } },
+ capture: {},
});
beforeEach(async () => {
@@ -56,9 +48,6 @@ describe('POST /diagnose/browser', () => {
() => ({ usesUiCapabilities: () => false })
);
- // Make all uses of 'Rx.timer' return an observable that completes in 50ms
- jest.spyOn(Rx, 'timer').mockImplementation(() => wait(50));
-
core = await createMockReportingCore(
config,
createMockPluginSetup({
@@ -67,21 +56,7 @@ describe('POST /diagnose/browser', () => {
})
);
- mockedSpawn.mockImplementation(() => ({
- removeAllListeners: jest.fn(),
- kill: jest.fn(),
- pid: 123,
- stderr: 'stderr',
- addEventListener: jest.fn(),
- removeEventListener: jest.fn(),
- }));
-
- mockedCreateInterface.mockImplementation(() => ({
- addEventListener: jest.fn(),
- removeEventListener: jest.fn(),
- removeAllListeners: jest.fn(),
- close: jest.fn(),
- }));
+ screenshotting = (await core.getPluginStartDeps()).screenshotting as typeof screenshotting;
});
afterEach(async () => {
@@ -94,12 +69,7 @@ describe('POST /diagnose/browser', () => {
await server.start();
- mockedCreateInterface.mockImplementation(() => ({
- addEventListener: (_e: string, cb: any) => setTimeout(() => cb(devtoolMessage), 0),
- removeEventListener: jest.fn(),
- removeAllListeners: jest.fn(),
- close: jest.fn(),
- }));
+ screenshotting.diagnose.mockReturnValue(Rx.of(devtoolMessage));
return supertest(httpSetup.server.listener)
.post('/api/reporting/diagnose/browser')
@@ -115,20 +85,7 @@ describe('POST /diagnose/browser', () => {
registerDiagnoseBrowser(core, mockLogger);
await server.start();
-
- mockedCreateInterface.mockImplementation(() => ({
- addEventListener: (_e: string, cb: any) => setTimeout(() => cb(logs), 0),
- removeEventListener: jest.fn(),
- removeAllListeners: jest.fn(),
- close: jest.fn(),
- }));
-
- mockedSpawn.mockImplementation(() => ({
- removeAllListeners: jest.fn(),
- kill: jest.fn(),
- addEventListener: jest.fn(),
- removeEventListener: jest.fn(),
- }));
+ screenshotting.diagnose.mockReturnValue(Rx.of(logs));
return supertest(httpSetup.server.listener)
.post('/api/reporting/diagnose/browser')
@@ -139,8 +96,7 @@ describe('POST /diagnose/browser', () => {
"help": Array [
"The browser couldn't locate a default font. Please see https://www.elastic.co/guide/en/kibana/current/reporting-troubleshooting.html#reporting-troubleshooting-system-dependencies to fix this issue.",
],
- "logs": "Could not find the default font
- ",
+ "logs": "Could not find the default font",
"success": false,
}
`);
@@ -151,23 +107,7 @@ describe('POST /diagnose/browser', () => {
registerDiagnoseBrowser(core, mockLogger);
await server.start();
-
- mockedCreateInterface.mockImplementation(() => ({
- addEventListener: (_e: string, cb: any) => {
- setTimeout(() => cb(devtoolMessage), 0);
- setTimeout(() => cb(fontNotFoundMessage), 0);
- },
- removeEventListener: jest.fn(),
- removeAllListeners: jest.fn(),
- close: jest.fn(),
- }));
-
- mockedSpawn.mockImplementation(() => ({
- removeAllListeners: jest.fn(),
- kill: jest.fn(),
- addEventListener: jest.fn(),
- removeEventListener: jest.fn(),
- }));
+ screenshotting.diagnose.mockReturnValue(Rx.of(`${devtoolMessage}\n${fontNotFoundMessage}`));
return supertest(httpSetup.server.listener)
.post('/api/reporting/diagnose/browser')
@@ -179,89 +119,10 @@ describe('POST /diagnose/browser', () => {
"The browser couldn't locate a default font. Please see https://www.elastic.co/guide/en/kibana/current/reporting-troubleshooting.html#reporting-troubleshooting-system-dependencies to fix this issue.",
],
"logs": "DevTools listening on (ws://localhost:4000)
- Could not find the default font
- ",
+ Could not find the default font",
"success": false,
}
`);
});
});
-
- it('logs a message when the browser starts, but then crashes', async () => {
- registerDiagnoseBrowser(core, mockLogger);
-
- await server.start();
-
- mockedCreateInterface.mockImplementation(() => ({
- addEventListener: (_e: string, cb: any) => {
- setTimeout(() => cb(fontNotFoundMessage), 0);
- },
- removeEventListener: jest.fn(),
- removeAllListeners: jest.fn(),
- close: jest.fn(),
- }));
-
- mockedSpawn.mockImplementation(() => ({
- removeAllListeners: jest.fn(),
- kill: jest.fn(),
- addEventListener: (e: string, cb: any) => {
- if (e === 'exit') {
- setTimeout(() => cb(), 5);
- }
- },
- removeEventListener: jest.fn(),
- }));
-
- return supertest(httpSetup.server.listener)
- .post('/api/reporting/diagnose/browser')
- .expect(200)
- .then(({ body }) => {
- const helpArray = [...body.help];
- helpArray.sort();
- expect(helpArray).toMatchInlineSnapshot(`
- Array [
- "The browser couldn't locate a default font. Please see https://www.elastic.co/guide/en/kibana/current/reporting-troubleshooting.html#reporting-troubleshooting-system-dependencies to fix this issue.",
- ]
- `);
- expect(body.logs).toMatch(/Could not find the default font/);
- expect(body.logs).toMatch(/Browser exited abnormally during startup/);
- expect(body.success).toBe(false);
- });
- });
-
- it('cleans up process and subscribers', async () => {
- registerDiagnoseBrowser(core, mockLogger);
-
- await server.start();
- const killMock = jest.fn();
- const spawnListenersMock = jest.fn();
- const createInterfaceListenersMock = jest.fn();
- const createInterfaceCloseMock = jest.fn();
-
- mockedSpawn.mockImplementation(() => ({
- removeAllListeners: spawnListenersMock,
- kill: killMock,
- pid: 123,
- stderr: 'stderr',
- addEventListener: jest.fn(),
- removeEventListener: jest.fn(),
- }));
-
- mockedCreateInterface.mockImplementation(() => ({
- addEventListener: (_e: string, cb: any) => setTimeout(() => cb(devtoolMessage), 0),
- removeEventListener: jest.fn(),
- removeAllListeners: createInterfaceListenersMock,
- close: createInterfaceCloseMock,
- }));
-
- return supertest(httpSetup.server.listener)
- .post('/api/reporting/diagnose/browser')
- .expect(200)
- .then(() => {
- expect(killMock.mock.calls.length).toBe(1);
- expect(spawnListenersMock.mock.calls.length).toBe(1);
- expect(createInterfaceListenersMock.mock.calls.length).toBe(1);
- expect(createInterfaceCloseMock.mock.calls.length).toBe(1);
- });
- });
});
diff --git a/x-pack/plugins/reporting/server/routes/diagnostic/browser.ts b/x-pack/plugins/reporting/server/routes/diagnostic/browser.ts
index 7793fc658c535..f68df294b4118 100644
--- a/x-pack/plugins/reporting/server/routes/diagnostic/browser.ts
+++ b/x-pack/plugins/reporting/server/routes/diagnostic/browser.ts
@@ -8,7 +8,6 @@
import { i18n } from '@kbn/i18n';
import { ReportingCore } from '../..';
import { API_DIAGNOSE_URL } from '../../../common/constants';
-import { browserStartLogs } from '../../browsers/chromium/driver_factory/start_logs';
import { LevelLogger as Logger } from '../../lib';
import { authorizedUserPreRouting } from '../lib/authorized_user_pre_routing';
import { DiagnosticResponse } from './';
@@ -52,7 +51,8 @@ export const registerDiagnoseBrowser = (reporting: ReportingCore, logger: Logger
},
authorizedUserPreRouting(reporting, async (_user, _context, _req, res) => {
try {
- const logs = await browserStartLogs(reporting, logger).toPromise();
+ const { screenshotting } = await reporting.getPluginStartDeps();
+ const logs = await screenshotting.diagnose().toPromise();
const knownIssues = Object.keys(logsToHelpMap) as Array;
const boundSuccessfully = logs.includes(`DevTools listening on`);
diff --git a/x-pack/plugins/reporting/server/routes/diagnostic/screenshot.test.ts b/x-pack/plugins/reporting/server/routes/diagnostic/screenshot.test.ts
index dd543707fe66a..4bc33d20d6fcf 100644
--- a/x-pack/plugins/reporting/server/routes/diagnostic/screenshot.test.ts
+++ b/x-pack/plugins/reporting/server/routes/diagnostic/screenshot.test.ts
@@ -20,7 +20,7 @@ import type { ReportingRequestHandlerContext } from '../../types';
jest.mock('../../export_types/common/generate_png');
-import { generatePngObservableFactory } from '../../export_types/common';
+import { generatePngObservable } from '../../export_types/common';
type SetupServerReturn = UnwrapPromise>;
@@ -31,12 +31,12 @@ describe('POST /diagnose/screenshot', () => {
let core: ReportingCore;
const setScreenshotResponse = (resp: object | Error) => {
- const generateMock = Promise.resolve(() => ({
+ const generateMock = {
pipe: () => ({
toPromise: () => (resp instanceof Error ? Promise.reject(resp) : Promise.resolve(resp)),
}),
- }));
- (generatePngObservableFactory as jest.Mock).mockResolvedValue(generateMock);
+ };
+ (generatePngObservable as jest.Mock).mockReturnValue(generateMock);
};
const config = createMockConfigSchema({ queue: { timeout: 120000 } });
diff --git a/x-pack/plugins/reporting/server/routes/diagnostic/screenshot.ts b/x-pack/plugins/reporting/server/routes/diagnostic/screenshot.ts
index f2002dd945882..2d5a254045104 100644
--- a/x-pack/plugins/reporting/server/routes/diagnostic/screenshot.ts
+++ b/x-pack/plugins/reporting/server/routes/diagnostic/screenshot.ts
@@ -9,7 +9,7 @@ import { i18n } from '@kbn/i18n';
import { ReportingCore } from '../..';
import { APP_WRAPPER_CLASS } from '../../../../../../src/core/server';
import { API_DIAGNOSE_URL } from '../../../common/constants';
-import { omitBlockedHeaders, generatePngObservableFactory } from '../../export_types/common';
+import { omitBlockedHeaders, generatePngObservable } from '../../export_types/common';
import { getAbsoluteUrlFactory } from '../../export_types/common/get_absolute_url';
import { LevelLogger as Logger } from '../../lib';
import { authorizedUserPreRouting } from '../lib/authorized_user_pre_routing';
@@ -25,7 +25,6 @@ export const registerDiagnoseScreenshot = (reporting: ReportingCore, logger: Log
validate: {},
},
authorizedUserPreRouting(reporting, async (_user, _context, req, res) => {
- const generatePngObservable = await generatePngObservableFactory(reporting);
const config = reporting.getConfig();
const decryptedHeaders = req.headers as Record;
const [basePath, protocol, hostname, port] = [
@@ -40,7 +39,6 @@ export const registerDiagnoseScreenshot = (reporting: ReportingCore, logger: Log
// Hack the layout to make the base/login page work
const layout = {
- id: 'png',
dimensions: {
width: 1440,
height: 2024,
@@ -53,7 +51,7 @@ export const registerDiagnoseScreenshot = (reporting: ReportingCore, logger: Log
},
};
- const headers = {
+ const conditionalHeaders = {
headers: omitBlockedHeaders(decryptedHeaders),
conditions: {
hostname,
@@ -63,7 +61,12 @@ export const registerDiagnoseScreenshot = (reporting: ReportingCore, logger: Log
},
};
- return generatePngObservable(logger, hashUrl, 'America/Los_Angeles', headers, layout)
+ return generatePngObservable(reporting, logger, {
+ conditionalHeaders,
+ layout,
+ browserTimezone: 'America/Los_Angeles',
+ urls: [hashUrl],
+ })
.pipe()
.toPromise()
.then((screenshot) => {
diff --git a/x-pack/plugins/reporting/server/routes/lib/request_handler.test.ts b/x-pack/plugins/reporting/server/routes/lib/request_handler.test.ts
index 5900a151f92da..6d73a3ec7ee74 100644
--- a/x-pack/plugins/reporting/server/routes/lib/request_handler.test.ts
+++ b/x-pack/plugins/reporting/server/routes/lib/request_handler.test.ts
@@ -103,7 +103,6 @@ describe('Handle request to generate', () => {
"_primary_term": undefined,
"_seq_no": undefined,
"attempts": 0,
- "browser_type": undefined,
"completed_at": undefined,
"created_by": "testymcgee",
"jobtype": "printable_pdf",
@@ -180,7 +179,6 @@ describe('Handle request to generate', () => {
expect(snapObj).toMatchInlineSnapshot(`
Object {
"attempts": 0,
- "browser_type": undefined,
"completed_at": undefined,
"created_by": "testymcgee",
"index": ".reporting-foo-index-234",
diff --git a/x-pack/plugins/reporting/server/test_helpers/create_mock_browserdriverfactory.ts b/x-pack/plugins/reporting/server/test_helpers/create_mock_browserdriverfactory.ts
deleted file mode 100644
index d42fb73b447a5..0000000000000
--- a/x-pack/plugins/reporting/server/test_helpers/create_mock_browserdriverfactory.ts
+++ /dev/null
@@ -1,146 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import moment from 'moment';
-import { Page } from 'puppeteer';
-import * as Rx from 'rxjs';
-import { ReportingCore } from '..';
-import { chromium, HeadlessChromiumDriver, HeadlessChromiumDriverFactory } from '../browsers';
-import { LevelLogger } from '../lib';
-import { ElementsPositionAndAttribute } from '../lib/screenshots';
-import * as contexts from '../lib/screenshots/constants';
-import { CaptureConfig } from '../types';
-
-interface CreateMockBrowserDriverFactoryOpts {
- evaluate: jest.Mock, any[]>;
- waitForSelector: jest.Mock, any[]>;
- waitFor: jest.Mock, any[]>;
- screenshot: jest.Mock, any[]>;
- open: jest.Mock, any[]>;
- getCreatePage: (driver: HeadlessChromiumDriver) => jest.Mock;
-}
-
-const mockSelectors = {
- renderComplete: 'renderedSelector',
- itemsCountAttribute: 'itemsSelector',
- screenshot: 'screenshotSelector',
- timefilterDurationAttribute: 'timefilterDurationSelector',
- toastHeader: 'toastHeaderSelector',
-};
-
-const getMockElementsPositionAndAttributes = (
- title: string,
- description: string
-): ElementsPositionAndAttribute[] => [
- {
- position: {
- boundingClientRect: { top: 0, left: 0, width: 800, height: 600 },
- scroll: { x: 0, y: 0 },
- },
- attributes: { title, description },
- },
-];
-
-const mockWaitForSelector = jest.fn();
-mockWaitForSelector.mockImplementation((selectorArg: string) => {
- const { renderComplete, itemsCountAttribute, toastHeader } = mockSelectors;
- if (selectorArg === `${renderComplete},[${itemsCountAttribute}]`) {
- return Promise.resolve(true);
- } else if (selectorArg === toastHeader) {
- return Rx.never().toPromise();
- }
- throw new Error(selectorArg);
-});
-const mockBrowserEvaluate = jest.fn();
-mockBrowserEvaluate.mockImplementation(() => {
- const lastCallIndex = mockBrowserEvaluate.mock.calls.length - 1;
- const { context: mockCall } = mockBrowserEvaluate.mock.calls[lastCallIndex][1];
-
- if (mockCall === contexts.CONTEXT_SKIPTELEMETRY) {
- return Promise.resolve();
- }
- if (mockCall === contexts.CONTEXT_GETNUMBEROFITEMS) {
- return Promise.resolve(1);
- }
- if (mockCall === contexts.CONTEXT_INJECTCSS) {
- return Promise.resolve();
- }
- if (mockCall === contexts.CONTEXT_WAITFORRENDER) {
- return Promise.resolve();
- }
- if (mockCall === contexts.CONTEXT_GETTIMERANGE) {
- return Promise.resolve('Default GetTimeRange Result');
- }
- if (mockCall === contexts.CONTEXT_ELEMENTATTRIBUTES) {
- return Promise.resolve(getMockElementsPositionAndAttributes('Default Mock Title', 'Default '));
- }
- if (mockCall === contexts.CONTEXT_GETRENDERERRORS) {
- return Promise.resolve();
- }
- throw new Error(mockCall);
-});
-const mockScreenshot = jest.fn(async () => Buffer.from('screenshot'));
-const getCreatePage = (driver: HeadlessChromiumDriver) =>
- jest.fn().mockImplementation(() => Rx.of({ driver, exit$: Rx.never() }));
-
-const defaultOpts: CreateMockBrowserDriverFactoryOpts = {
- evaluate: mockBrowserEvaluate,
- waitForSelector: mockWaitForSelector,
- waitFor: jest.fn(),
- screenshot: mockScreenshot,
- open: jest.fn(),
- getCreatePage,
-};
-
-export const createMockBrowserDriverFactory = async (
- core: ReportingCore,
- logger: LevelLogger,
- opts: Partial = {}
-): Promise => {
- const captureConfig: CaptureConfig = {
- timeouts: {
- openUrl: moment.duration(60, 's'),
- waitForElements: moment.duration(30, 's'),
- renderComplete: moment.duration(30, 's'),
- },
- browser: {
- type: 'chromium',
- chromium: {
- inspect: false,
- disableSandbox: false,
- proxy: { enabled: false, server: undefined, bypass: undefined },
- },
- autoDownload: false,
- },
- networkPolicy: { enabled: true, rules: [] },
- loadDelay: moment.duration(2, 's'),
- zoom: 2,
- maxAttempts: 1,
- };
-
- const binaryPath = '/usr/local/share/common/secure/super_awesome_binary';
- const mockBrowserDriverFactory = chromium.createDriverFactory(core, binaryPath, logger);
- const mockPage = { setViewport: () => {} } as unknown as Page;
- const mockBrowserDriver = new HeadlessChromiumDriver(core, mockPage, {
- inspect: true,
- networkPolicy: captureConfig.networkPolicy,
- });
-
- // mock the driver methods as either default mocks or passed-in
- mockBrowserDriver.waitForSelector = opts.waitForSelector ? opts.waitForSelector : defaultOpts.waitForSelector; // prettier-ignore
- mockBrowserDriver.waitFor = opts.waitFor ? opts.waitFor : defaultOpts.waitFor;
- mockBrowserDriver.evaluate = opts.evaluate ? opts.evaluate : defaultOpts.evaluate;
- mockBrowserDriver.screenshot = opts.screenshot ? opts.screenshot : defaultOpts.screenshot;
- mockBrowserDriver.open = opts.open ? opts.open : defaultOpts.open;
- mockBrowserDriver.isPageOpen = () => true;
-
- mockBrowserDriverFactory.createPage = opts.getCreatePage
- ? opts.getCreatePage(mockBrowserDriver)
- : getCreatePage(mockBrowserDriver);
-
- return mockBrowserDriverFactory;
-};
diff --git a/x-pack/plugins/reporting/server/test_helpers/create_mock_reportingplugin.ts b/x-pack/plugins/reporting/server/test_helpers/create_mock_reportingplugin.ts
index c05b2c54aeabf..0569ea1400555 100644
--- a/x-pack/plugins/reporting/server/test_helpers/create_mock_reportingplugin.ts
+++ b/x-pack/plugins/reporting/server/test_helpers/create_mock_reportingplugin.ts
@@ -7,7 +7,6 @@
jest.mock('../routes');
jest.mock('../usage');
-jest.mock('../browsers');
import _ from 'lodash';
import * as Rx from 'rxjs';
@@ -18,24 +17,15 @@ import { FieldFormatsRegistry } from 'src/plugins/field_formats/common';
import { ReportingConfig, ReportingCore } from '../';
import { featuresPluginMock } from '../../../features/server/mocks';
import { securityMock } from '../../../security/server/mocks';
+// eslint-disable-next-line @kbn/eslint/no-restricted-paths
+import { createMockScreenshottingStart } from '../../../screenshotting/server/mock';
import { taskManagerMock } from '../../../task_manager/server/mocks';
-import {
- chromium,
- HeadlessChromiumDriverFactory,
- initializeBrowserDriverFactory,
-} from '../browsers';
import { ReportingConfigType } from '../config';
import { ReportingInternalSetup, ReportingInternalStart } from '../core';
import { ReportingStore } from '../lib';
import { setFieldFormats } from '../services';
import { createMockLevelLogger } from './create_mock_levellogger';
-(
- initializeBrowserDriverFactory as jest.Mock>
-).mockImplementation(() => Promise.resolve({} as HeadlessChromiumDriverFactory));
-
-(chromium as any).createDriverFactory.mockImplementation(() => ({}));
-
export const createMockPluginSetup = (setupMock?: any): ReportingInternalSetup => {
return {
features: featuresPluginMock.createSetup(),
@@ -63,7 +53,6 @@ export const createMockPluginStart = (
: createMockReportingStore();
return {
- browserDriverFactory: startMock.browserDriverFactory,
esClient: elasticsearchServiceMock.createClusterClient(),
savedObjects: startMock.savedObjects || { getScopedClient: jest.fn() },
uiSettings: startMock.uiSettings || { asScopedToClient: () => ({ get: jest.fn() }) },
@@ -74,6 +63,7 @@ export const createMockPluginStart = (
ensureScheduled: jest.fn(),
} as any,
logger: createMockLevelLogger(),
+ screenshotting: startMock.screenshotting || createMockScreenshottingStart(),
...startMock,
};
};
@@ -102,14 +92,6 @@ export const createMockConfigSchema = (
port: 80,
...overrides.kibanaServer,
},
- capture: {
- browser: {
- chromium: {
- disableSandbox: true,
- },
- },
- ...overrides.capture,
- },
queue: {
indexInterval: 'week',
pollEnabled: true,
diff --git a/x-pack/plugins/reporting/server/test_helpers/index.ts b/x-pack/plugins/reporting/server/test_helpers/index.ts
index fe8c92d928af5..667c85c24a35d 100644
--- a/x-pack/plugins/reporting/server/test_helpers/index.ts
+++ b/x-pack/plugins/reporting/server/test_helpers/index.ts
@@ -5,8 +5,6 @@
* 2.0.
*/
-export { createMockBrowserDriverFactory } from './create_mock_browserdriverfactory';
-export { createMockLayoutInstance } from './create_mock_layoutinstance';
export { createMockLevelLogger } from './create_mock_levellogger';
export {
createMockConfig,
diff --git a/x-pack/plugins/reporting/server/types.ts b/x-pack/plugins/reporting/server/types.ts
index af9a973b0bb45..3b1e819f0863c 100644
--- a/x-pack/plugins/reporting/server/types.ts
+++ b/x-pack/plugins/reporting/server/types.ts
@@ -11,13 +11,17 @@ import { DataPluginStart } from 'src/plugins/data/server/plugin';
import { ScreenshotModePluginSetup } from 'src/plugins/screenshot_mode/server';
import { UsageCollectionSetup } from 'src/plugins/usage_collection/server';
import { Writable } from 'stream';
+import type {
+ ScreenshottingStart,
+ ScreenshotOptions as BaseScreenshotOptions,
+} from '../../screenshotting/server';
import { PluginSetupContract as FeaturesPluginSetup } from '../../features/server';
import { LicensingPluginSetup } from '../../licensing/server';
import { AuthenticatedUser, SecurityPluginSetup } from '../../security/server';
import { SpacesPluginSetup } from '../../spaces/server';
import { TaskManagerSetupContract, TaskManagerStartContract } from '../../task_manager/server';
import { CancellationToken } from '../common';
-import { BaseParams, BasePayload, TaskRunResult } from '../common/types';
+import { BaseParams, BasePayload, TaskRunResult, UrlOrUrlLocatorTuple } from '../common/types';
import { ReportingConfigType } from './config';
import { ReportingCore } from './core';
import { LevelLogger } from './lib';
@@ -39,6 +43,7 @@ export interface ReportingSetupDeps {
export interface ReportingStartDeps {
data: DataPluginStart;
+ screenshotting: ScreenshottingStart;
taskManager: TaskManagerStartContract;
}
@@ -109,3 +114,10 @@ export interface ReportingRequestHandlerContext {
* @internal
*/
export type ReportingPluginRouter = IRouter;
+
+/**
+ * @internal
+ */
+export interface ScreenshotOptions extends Omit {
+ urls: UrlOrUrlLocatorTuple[];
+}
diff --git a/x-pack/plugins/reporting/server/usage/__snapshots__/reporting_usage_collector.test.ts.snap b/x-pack/plugins/reporting/server/usage/__snapshots__/reporting_usage_collector.test.ts.snap
index 2017ae0be59c7..78bb9ab6df51f 100644
--- a/x-pack/plugins/reporting/server/usage/__snapshots__/reporting_usage_collector.test.ts.snap
+++ b/x-pack/plugins/reporting/server/usage/__snapshots__/reporting_usage_collector.test.ts.snap
@@ -129,9 +129,6 @@ Object {
"available": Object {
"type": "boolean",
},
- "browser_type": Object {
- "type": "keyword",
- },
"csv": Object {
"app": Object {
"canvas workpad": Object {
@@ -1973,7 +1970,6 @@ Object {
},
"_all": 9,
"available": true,
- "browser_type": undefined,
"csv": Object {
"app": Object {
"canvas workpad": 0,
@@ -2243,7 +2239,6 @@ Object {
},
"_all": 0,
"available": true,
- "browser_type": undefined,
"csv": Object {
"app": Object {
"canvas workpad": 0,
@@ -2492,7 +2487,6 @@ Object {
},
"_all": 4,
"available": true,
- "browser_type": undefined,
"csv": Object {
"app": Object {
"canvas workpad": 0,
@@ -2768,7 +2762,6 @@ Object {
},
"_all": 11,
"available": true,
- "browser_type": undefined,
"csv": Object {
"app": Object {
"canvas workpad": 0,
diff --git a/x-pack/plugins/reporting/server/usage/get_reporting_usage.ts b/x-pack/plugins/reporting/server/usage/get_reporting_usage.ts
index 73a4920b350e3..59387923e3755 100644
--- a/x-pack/plugins/reporting/server/usage/get_reporting_usage.ts
+++ b/x-pack/plugins/reporting/server/usage/get_reporting_usage.ts
@@ -206,10 +206,6 @@ export async function getReportingUsage(
.search(params)
.then(({ body: response }) => handleResponse(response))
.then((usage: Partial): ReportingUsageType => {
- // Allow this to explicitly throw an exception if/when this config is deprecated,
- // because we shouldn't collect browserType in that case!
- const browserType = config.get('capture', 'browser', 'type');
-
const exportTypesHandler = getExportTypesHandler(exportTypesRegistry);
const availability = exportTypesHandler.getAvailability(
featureAvailability
@@ -219,7 +215,6 @@ export async function getReportingUsage(
return {
available: true,
- browser_type: browserType,
enabled: true,
last7Days: getExportStats(last7Days, availability, exportTypesHandler),
...getExportStats(all, availability, exportTypesHandler),
diff --git a/x-pack/plugins/reporting/server/usage/schema.ts b/x-pack/plugins/reporting/server/usage/schema.ts
index 9580ddb935dfb..fc464903edaee 100644
--- a/x-pack/plugins/reporting/server/usage/schema.ts
+++ b/x-pack/plugins/reporting/server/usage/schema.ts
@@ -92,7 +92,6 @@ const rangeStatsSchema: MakeSchemaFrom = {
export const reportingSchema: MakeSchemaFrom = {
...rangeStatsSchema,
available: { type: 'boolean' },
- browser_type: { type: 'keyword' },
enabled: { type: 'boolean' },
last7Days: rangeStatsSchema,
};
diff --git a/x-pack/plugins/reporting/server/usage/types.ts b/x-pack/plugins/reporting/server/usage/types.ts
index 856d3ad10cb26..e6695abc8da74 100644
--- a/x-pack/plugins/reporting/server/usage/types.ts
+++ b/x-pack/plugins/reporting/server/usage/types.ts
@@ -129,7 +129,6 @@ export type RangeStats = JobTypes & {
export type ReportingUsageType = RangeStats & {
available: boolean;
- browser_type: string;
enabled: boolean;
last7Days: RangeStats;
};
diff --git a/x-pack/plugins/reporting/tsconfig.json b/x-pack/plugins/reporting/tsconfig.json
index 3e58450565720..4e09708915f95 100644
--- a/x-pack/plugins/reporting/tsconfig.json
+++ b/x-pack/plugins/reporting/tsconfig.json
@@ -26,6 +26,7 @@
{ "path": "../../../src/plugins/field_formats/tsconfig.json" },
{ "path": "../features/tsconfig.json" },
{ "path": "../licensing/tsconfig.json" },
+ { "path": "../screenshotting/tsconfig.json" },
{ "path": "../security/tsconfig.json" },
{ "path": "../spaces/tsconfig.json" },
]
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 1ea85e5a5434e..114d54eb7b4bb 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
@@ -2925,6 +2925,16 @@ export const ecsFieldMap = {
array: false,
required: false,
},
+ 'threat.enrichments.feed': {
+ type: 'object',
+ array: false,
+ required: false,
+ },
+ 'threat.enrichments.feed.name': {
+ type: 'keyword',
+ array: false,
+ required: false,
+ },
'threat.enrichments.matched.atomic': {
type: 'keyword',
array: false,
diff --git a/x-pack/plugins/rule_registry/server/rule_data_plugin_service/resource_installer.mock.ts b/x-pack/plugins/rule_registry/server/rule_data_plugin_service/resource_installer.mock.ts
new file mode 100644
index 0000000000000..0b3940b936424
--- /dev/null
+++ b/x-pack/plugins/rule_registry/server/rule_data_plugin_service/resource_installer.mock.ts
@@ -0,0 +1,24 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license 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 { PublicMethodsOf } from '@kbn/utility-types';
+import { ResourceInstaller } from './resource_installer';
+
+type Schema = PublicMethodsOf;
+export type ResourceInstallerMock = jest.Mocked;
+const createResourceInstallerMock = () => {
+ return {
+ installCommonResources: jest.fn(),
+ installIndexLevelResources: jest.fn(),
+ installAndUpdateNamespaceLevelResources: jest.fn(),
+ };
+};
+
+export const resourceInstallerMock: {
+ create: () => ResourceInstallerMock;
+} = {
+ create: createResourceInstallerMock,
+};
diff --git a/x-pack/plugins/rule_registry/server/rule_data_plugin_service/rule_data_plugin_service.test.ts b/x-pack/plugins/rule_registry/server/rule_data_plugin_service/rule_data_plugin_service.test.ts
new file mode 100644
index 0000000000000..4d217d1a181e9
--- /dev/null
+++ b/x-pack/plugins/rule_registry/server/rule_data_plugin_service/rule_data_plugin_service.test.ts
@@ -0,0 +1,109 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { loggerMock } from '@kbn/logging/mocks';
+import { RuleDataService } from './rule_data_plugin_service';
+import { elasticsearchServiceMock } from 'src/core/server/mocks';
+import { AlertConsumers } from '@kbn/rule-data-utils/alerts_as_data_rbac';
+import { Dataset } from './index_options';
+import { RuleDataClient } from '../rule_data_client/rule_data_client';
+import { createRuleDataClientMock as mockCreateRuleDataClient } from '../rule_data_client/rule_data_client.mock';
+
+jest.mock('../rule_data_client/rule_data_client', () => ({
+ RuleDataClient: jest.fn().mockImplementation(() => mockCreateRuleDataClient()),
+}));
+
+describe('ruleDataPluginService', () => {
+ beforeEach(() => {
+ jest.resetAllMocks();
+ });
+
+ describe('isRegistrationContextDisabled', () => {
+ it('should return true', async () => {
+ const mockClusterClient = elasticsearchServiceMock.createElasticsearchClient();
+ const getClusterClient = jest.fn(() => Promise.resolve(mockClusterClient));
+
+ const ruleDataService = new RuleDataService({
+ logger: loggerMock.create(),
+ getClusterClient,
+ kibanaVersion: '8.1.0',
+ isWriteEnabled: true,
+ disabledRegistrationContexts: ['observability.logs'],
+ isWriterCacheEnabled: true,
+ });
+ expect(ruleDataService.isRegistrationContextDisabled('observability.logs')).toBe(true);
+ });
+
+ it('should return false', async () => {
+ const mockClusterClient = elasticsearchServiceMock.createElasticsearchClient();
+ const getClusterClient = jest.fn(() => Promise.resolve(mockClusterClient));
+
+ const ruleDataService = new RuleDataService({
+ logger: loggerMock.create(),
+ getClusterClient,
+ kibanaVersion: '8.1.0',
+ isWriteEnabled: true,
+ disabledRegistrationContexts: ['observability.logs'],
+ isWriterCacheEnabled: true,
+ });
+ expect(ruleDataService.isRegistrationContextDisabled('observability.apm')).toBe(false);
+ });
+ });
+
+ describe('isWriteEnabled', () => {
+ it('should return true', async () => {
+ const mockClusterClient = elasticsearchServiceMock.createElasticsearchClient();
+ const getClusterClient = jest.fn(() => Promise.resolve(mockClusterClient));
+
+ const ruleDataService = new RuleDataService({
+ logger: loggerMock.create(),
+ getClusterClient,
+ kibanaVersion: '8.1.0',
+ isWriteEnabled: true,
+ disabledRegistrationContexts: ['observability.logs'],
+ isWriterCacheEnabled: true,
+ });
+
+ expect(ruleDataService.isWriteEnabled('observability.logs')).toBe(false);
+ });
+ });
+
+ describe('initializeIndex', () => {
+ it('calls RuleDataClient', async () => {
+ const mockClusterClient = elasticsearchServiceMock.createElasticsearchClient();
+ const getClusterClient = jest.fn(() => Promise.resolve(mockClusterClient));
+
+ const ruleDataService = new RuleDataService({
+ logger: loggerMock.create(),
+ getClusterClient,
+ kibanaVersion: '8.1.0',
+ isWriteEnabled: true,
+ disabledRegistrationContexts: ['observability.logs'],
+ isWriterCacheEnabled: true,
+ });
+ const indexOptions = {
+ feature: AlertConsumers.LOGS,
+ registrationContext: 'observability.logs',
+ dataset: Dataset.alerts,
+ componentTemplateRefs: [],
+ componentTemplates: [
+ {
+ name: 'mappings',
+ },
+ ],
+ };
+ await ruleDataService.initializeService();
+ await ruleDataService.initializeIndex(indexOptions);
+ expect(RuleDataClient).toHaveBeenCalled();
+ expect(RuleDataClient).toHaveBeenCalledWith(
+ expect.objectContaining({
+ indexInfo: expect.objectContaining({ baseName: '.alerts-observability.logs.alerts' }),
+ })
+ );
+ });
+ });
+});
diff --git a/x-pack/plugins/screenshotting/README.md b/x-pack/plugins/screenshotting/README.md
new file mode 100644
index 0000000000000..3439f06dff8e5
--- /dev/null
+++ b/x-pack/plugins/screenshotting/README.md
@@ -0,0 +1,11 @@
+# Kibana Screenshotting
+
+This plugin provides functionality to take screenshots of the Kibana pages.
+It uses Chromium and Puppeteer underneath to run the browser in headless mode.
+
+## API
+
+The plugin exposes most of the functionality in the start contract.
+The Chromium download and setup is happening during the setup stage.
+
+To learn more about the public API, please use automatically generated API reference or generated TypeDoc comments.
diff --git a/x-pack/plugins/screenshotting/common/context.ts b/x-pack/plugins/screenshotting/common/context.ts
new file mode 100644
index 0000000000000..c47f8706533b8
--- /dev/null
+++ b/x-pack/plugins/screenshotting/common/context.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.
+ */
+
+/**
+ * Screenshot context.
+ * This is a serializable object that can be passed from the screenshotting backend and then deserialized on the target page.
+ */
+export type Context = Record;
+
+/**
+ * @interal
+ */
+export const SCREENSHOTTING_CONTEXT_KEY = '__SCREENSHOTTING_CONTEXT_KEY__';
diff --git a/x-pack/plugins/reporting/server/browsers/chromium/driver/index.ts b/x-pack/plugins/screenshotting/common/index.ts
similarity index 66%
rename from x-pack/plugins/reporting/server/browsers/chromium/driver/index.ts
rename to x-pack/plugins/screenshotting/common/index.ts
index afd31608d5a6e..04296dd5426b5 100644
--- a/x-pack/plugins/reporting/server/browsers/chromium/driver/index.ts
+++ b/x-pack/plugins/screenshotting/common/index.ts
@@ -5,4 +5,6 @@
* 2.0.
*/
-export { HeadlessChromiumDriver } from './chromium_driver';
+export type { Context } from './context';
+export type { LayoutParams } from './layout';
+export { LayoutTypes } from './layout';
diff --git a/x-pack/plugins/screenshotting/common/layout.ts b/x-pack/plugins/screenshotting/common/layout.ts
new file mode 100644
index 0000000000000..aade05eeea04e
--- /dev/null
+++ b/x-pack/plugins/screenshotting/common/layout.ts
@@ -0,0 +1,75 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license 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 { Ensure, SerializableRecord } from '@kbn/utility-types';
+
+/**
+ * @internal
+ */
+export type Size = Ensure<
+ {
+ /**
+ * Layout width.
+ */
+ width: number;
+
+ /**
+ * Layout height.
+ */
+ height: number;
+ },
+ SerializableRecord
+>;
+
+/**
+ * @internal
+ */
+export interface LayoutSelectorDictionary {
+ screenshot: string;
+ renderComplete: string;
+ renderError: string;
+ renderErrorAttribute: string;
+ itemsCountAttribute: string;
+ timefilterDurationAttribute: string;
+}
+
+/**
+ * Screenshot layout parameters.
+ */
+export type LayoutParams = Ensure<
+ {
+ /**
+ * Unique layout name.
+ */
+ id?: string;
+
+ /**
+ * Layout sizing.
+ */
+ dimensions?: Size;
+
+ /**
+ * Element selectors determining the page state.
+ */
+ selectors?: Partial;
+
+ /**
+ * Page zoom.
+ */
+ zoom?: number;
+ },
+ SerializableRecord
+>;
+
+/**
+ * Supported layout types.
+ */
+export const LayoutTypes = {
+ PRESERVE_LAYOUT: 'preserve_layout',
+ PRINT: 'print',
+ CANVAS: 'canvas', // no margins or branding in the layout
+};
diff --git a/x-pack/plugins/screenshotting/jest.config.js b/x-pack/plugins/screenshotting/jest.config.js
new file mode 100644
index 0000000000000..a02d667f86a19
--- /dev/null
+++ b/x-pack/plugins/screenshotting/jest.config.js
@@ -0,0 +1,15 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+module.exports = {
+ preset: '@kbn/test',
+ rootDir: '../../..',
+ roots: ['/x-pack/plugins/screenshotting'],
+ coverageDirectory: '/target/kibana-coverage/jest/x-pack/plugins/screenshotting',
+ coverageReporters: ['text', 'html'],
+ collectCoverageFrom: ['/x-pack/plugins/screenshotting/server/**/*.{ts}'],
+};
diff --git a/x-pack/plugins/screenshotting/kibana.json b/x-pack/plugins/screenshotting/kibana.json
new file mode 100644
index 0000000000000..32446551627e0
--- /dev/null
+++ b/x-pack/plugins/screenshotting/kibana.json
@@ -0,0 +1,14 @@
+{
+ "id": "screenshotting",
+ "version": "8.0.0",
+ "kibanaVersion": "kibana",
+ "owner": {
+ "name": "Kibana Reporting Services",
+ "githubTeam": "kibana-reporting-services"
+ },
+ "description": "Kibana Screenshotting Plugin",
+ "requiredPlugins": ["screenshotMode"],
+ "configPath": ["xpack", "screenshotting"],
+ "server": true,
+ "ui": true
+}
diff --git a/x-pack/plugins/screenshotting/public/context_storage.ts b/x-pack/plugins/screenshotting/public/context_storage.ts
new file mode 100644
index 0000000000000..76a2cf231cf83
--- /dev/null
+++ b/x-pack/plugins/screenshotting/public/context_storage.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 { Context, SCREENSHOTTING_CONTEXT_KEY } from '../common/context';
+
+declare global {
+ interface Window {
+ [SCREENSHOTTING_CONTEXT_KEY]?: Context;
+ }
+}
+
+export class ContextStorage {
+ get(): T {
+ return (window[SCREENSHOTTING_CONTEXT_KEY] ?? {}) as T;
+ }
+}
diff --git a/x-pack/plugins/screenshotting/public/index.ts b/x-pack/plugins/screenshotting/public/index.ts
new file mode 100644
index 0000000000000..659dbc81917a7
--- /dev/null
+++ b/x-pack/plugins/screenshotting/public/index.ts
@@ -0,0 +1,18 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { ScreenshottingPlugin } from './plugin';
+
+/**
+ * Screenshotting plugin entry point.
+ */
+export function plugin(...args: ConstructorParameters) {
+ return new ScreenshottingPlugin(...args);
+}
+
+export { LayoutTypes } from '../common';
+export type { ScreenshottingSetup, ScreenshottingStart } from './plugin';
diff --git a/x-pack/plugins/screenshotting/public/plugin.tsx b/x-pack/plugins/screenshotting/public/plugin.tsx
new file mode 100755
index 0000000000000..4ba5046b8a881
--- /dev/null
+++ b/x-pack/plugins/screenshotting/public/plugin.tsx
@@ -0,0 +1,42 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license 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 { Plugin } from 'src/core/public';
+import { ContextStorage } from './context_storage';
+
+/**
+ * Setup public contract.
+ */
+export interface ScreenshottingSetup {
+ /**
+ * Gathers screenshot context that has been set on the backend.
+ */
+ getContext: ContextStorage['get'];
+}
+
+/**
+ * Start public contract.
+ */
+export type ScreenshottingStart = ScreenshottingSetup;
+
+export class ScreenshottingPlugin implements Plugin {
+ private contextStorage = new ContextStorage();
+
+ setup(): ScreenshottingSetup {
+ return {
+ getContext: () => this.contextStorage.get(),
+ };
+ }
+
+ start(): ScreenshottingStart {
+ return {
+ getContext: () => this.contextStorage.get(),
+ };
+ }
+
+ stop() {}
+}
diff --git a/x-pack/plugins/reporting/server/browsers/chromium/driver/chromium_driver.ts b/x-pack/plugins/screenshotting/server/browsers/chromium/driver.ts
similarity index 75%
rename from x-pack/plugins/reporting/server/browsers/chromium/driver/chromium_driver.ts
rename to x-pack/plugins/screenshotting/server/browsers/chromium/driver.ts
index 0f2572ff2b2e4..245572efe9348 100644
--- a/x-pack/plugins/reporting/server/browsers/chromium/driver/chromium_driver.ts
+++ b/x-pack/plugins/screenshotting/server/browsers/chromium/driver.ts
@@ -8,22 +8,56 @@
import { i18n } from '@kbn/i18n';
import { map, truncate } from 'lodash';
import open from 'opn';
-import puppeteer, { ElementHandle, EvaluateFn, SerializableOrJSHandle } from 'puppeteer';
+import puppeteer, { ElementHandle, EvaluateFn, Page, SerializableOrJSHandle } from 'puppeteer';
import { parse as parseUrl } from 'url';
-import type { LocatorParams } from '../../../../common/types';
-import { REPORTING_REDIRECT_LOCATOR_STORE_KEY } from '../../../../common/constants';
-import { getDisallowedOutgoingUrlError } from '../';
-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 { Layout, ViewZoomWidthHeight } from '../../../lib/layouts/layout';
-import { ElementPosition } from '../../../lib/screenshots';
-import { allowRequest, NetworkPolicy } from '../../network_policy';
-
-export interface ChromiumDriverOptions {
- inspect: boolean;
- networkPolicy: NetworkPolicy;
+import { Logger } from 'src/core/server';
+import type { Layout } from 'src/plugins/screenshot_mode/common';
+import {
+ KBN_SCREENSHOT_MODE_HEADER,
+ ScreenshotModePluginSetup,
+} from '../../../../../../src/plugins/screenshot_mode/server';
+import { Context, SCREENSHOTTING_CONTEXT_KEY } from '../../../common/context';
+import { ConfigType } from '../../config';
+import { allowRequest } from '../network_policy';
+
+export interface ConditionalHeadersConditions {
+ protocol: string;
+ hostname: string;
+ port: number;
+ basePath: string;
+}
+
+export interface ConditionalHeaders {
+ headers: Record;
+ conditions: ConditionalHeadersConditions;
+}
+
+export interface ElementPosition {
+ boundingClientRect: {
+ // modern browsers support x/y, but older ones don't
+ top: number;
+ left: number;
+ width: number;
+ height: number;
+ };
+ scroll: {
+ x: number;
+ y: number;
+ };
+}
+
+export interface Viewport {
+ zoom: number;
+ width: number;
+ height: number;
+}
+
+interface OpenOptions {
+ conditionalHeaders: ConditionalHeaders;
+ context?: Context;
+ waitForSelector: string;
+ timeout: number;
+ layout?: Layout;
}
interface WaitForSelectorOpts {
@@ -56,28 +90,30 @@ interface InterceptedRequest {
const WAIT_FOR_DELAY_MS: number = 100;
-export class HeadlessChromiumDriver {
- private readonly page: puppeteer.Page;
- private readonly inspect: boolean;
- private readonly networkPolicy: NetworkPolicy;
+function getDisallowedOutgoingUrlError(interceptedUrl: string) {
+ return new Error(
+ i18n.translate('xpack.screenshotting.chromiumDriver.disallowedOutgoingUrl', {
+ defaultMessage: `Received disallowed outgoing URL: "{interceptedUrl}". Failing the request and closing the browser.`,
+ values: { interceptedUrl },
+ })
+ );
+}
+/**
+ * @internal
+ */
+export class HeadlessChromiumDriver {
private listenersAttached = false;
private interceptedCount = 0;
- private core: ReportingCore;
constructor(
- core: ReportingCore,
- page: puppeteer.Page,
- { inspect, networkPolicy }: ChromiumDriverOptions
- ) {
- this.core = core;
- this.page = page;
- this.inspect = inspect;
- this.networkPolicy = networkPolicy;
- }
+ private screenshotMode: ScreenshotModePluginSetup,
+ private config: ConfigType,
+ private readonly page: Page
+ ) {}
private allowRequest(url: string) {
- return !this.networkPolicy.enabled || allowRequest(url, this.networkPolicy.rules);
+ return !this.config.networkPolicy.enabled || allowRequest(url, this.config.networkPolicy.rules);
}
private truncateUrl(url: string) {
@@ -90,22 +126,16 @@ export class HeadlessChromiumDriver {
/*
* Call Page.goto and wait to see the Kibana DOM content
*/
- public async open(
+ async open(
url: string,
{
conditionalHeaders,
+ context,
+ layout,
waitForSelector: pageLoadSelector,
timeout,
- locator,
- layout,
- }: {
- conditionalHeaders: ConditionalHeaders;
- waitForSelector: string;
- timeout: number;
- locator?: LocatorParams;
- layout?: Layout;
- },
- logger: LevelLogger
+ }: OpenOptions,
+ logger: Logger
): Promise {
logger.info(`opening url ${url}`);
@@ -116,13 +146,9 @@ export class HeadlessChromiumDriver {
* Integrate with the screenshot mode plugin contract by calling this function before any other
* scripts have run on the browser page.
*/
- await this.page.evaluateOnNewDocument(this.core.getEnableScreenshotMode());
-
- if (layout) {
- await this.page.evaluateOnNewDocument(this.core.getSetScreenshotLayout(), layout.id);
- }
+ await this.page.evaluateOnNewDocument(this.screenshotMode.setScreenshotModeEnabled);
- if (locator) {
+ if (context) {
await this.page.evaluateOnNewDocument(
(key: string, value: unknown) => {
Object.defineProperty(window, key, {
@@ -132,18 +158,20 @@ export class HeadlessChromiumDriver {
value,
});
},
- REPORTING_REDIRECT_LOCATOR_STORE_KEY,
- locator
+ SCREENSHOTTING_CONTEXT_KEY,
+ context
);
}
- await this.page.setRequestInterception(true);
+ if (layout) {
+ await this.page.evaluateOnNewDocument(this.screenshotMode.setScreenshotLayout, layout);
+ }
+ await this.page.setRequestInterception(true);
this.registerListeners(conditionalHeaders, logger);
-
await this.page.goto(url, { waitUntil: 'domcontentloaded' });
- if (this.inspect) {
+ if (this.config.browser.chromium.inspect) {
await this.launchDebugger();
}
@@ -159,14 +187,14 @@ export class HeadlessChromiumDriver {
/*
* Let modules poll if Chrome is still running so they can short circuit if needed
*/
- public isPageOpen() {
+ isPageOpen() {
return !this.page.isClosed();
}
/*
* Call Page.screenshot and return a base64-encoded string of the image
*/
- public async screenshot(elementPosition: ElementPosition): Promise {
+ async screenshot(elementPosition: ElementPosition): Promise {
const { boundingClientRect, scroll } = elementPosition;
const screenshot = await this.page.screenshot({
clip: {
@@ -188,32 +216,28 @@ export class HeadlessChromiumDriver {
return undefined;
}
- public async evaluate(
- { fn, args = [] }: EvaluateOpts,
- meta: EvaluateMetaOpts,
- logger: LevelLogger
- ) {
+ evaluate({ fn, args = [] }: EvaluateOpts, meta: EvaluateMetaOpts, logger: Logger): Promise {
logger.debug(`evaluate ${meta.context}`);
- const result = await this.page.evaluate(fn, ...args);
- return result;
+
+ return this.page.evaluate(fn, ...args);
}
- public async waitForSelector(
+ async waitForSelector(
selector: string,
opts: WaitForSelectorOpts,
context: EvaluateMetaOpts,
- logger: LevelLogger
+ logger: Logger
): Promise> {
const { timeout } = opts;
logger.debug(`waitForSelector ${selector}`);
- const resp = await this.page.waitForSelector(selector, { timeout }); // override default 30000ms
+ const response = await this.page.waitForSelector(selector, { timeout }); // override default 30000ms
- if (!resp) {
+ if (!response) {
throw new Error(`Failure in waitForSelector: void response! Context: ${context.context}`);
}
logger.debug(`waitForSelector ${selector} resolved`);
- return resp;
+ return response;
}
public async waitFor({
@@ -228,9 +252,9 @@ export class HeadlessChromiumDriver {
await this.page.waitForFunction(fn, { timeout, polling: WAIT_FOR_DELAY_MS }, ...args);
}
- public async setViewport(
- { width: _width, height: _height, zoom }: ViewZoomWidthHeight,
- logger: LevelLogger
+ async setViewport(
+ { width: _width, height: _height, zoom }: Viewport,
+ logger: Logger
): Promise {
const width = Math.floor(_width);
const height = Math.floor(_height);
@@ -245,7 +269,7 @@ export class HeadlessChromiumDriver {
});
}
- private registerListeners(conditionalHeaders: ConditionalHeaders, logger: LevelLogger) {
+ private registerListeners(conditionalHeaders: ConditionalHeaders, logger: Logger) {
if (this.listenersAttached) {
return;
}
@@ -300,10 +324,13 @@ export class HeadlessChromiumDriver {
});
} catch (err) {
logger.error(
- i18n.translate('xpack.reporting.chromiumDriver.failedToCompleteRequestUsingHeaders', {
- defaultMessage: 'Failed to complete a request using headers: {error}',
- values: { error: err },
- })
+ i18n.translate(
+ 'xpack.screenshotting.chromiumDriver.failedToCompleteRequestUsingHeaders',
+ {
+ defaultMessage: 'Failed to complete a request using headers: {error}',
+ values: { error: err },
+ }
+ )
);
}
} else {
@@ -313,7 +340,7 @@ export class HeadlessChromiumDriver {
await client.send('Fetch.continueRequest', { requestId });
} catch (err) {
logger.error(
- i18n.translate('xpack.reporting.chromiumDriver.failedToCompleteRequest', {
+ i18n.translate('xpack.screenshotting.chromiumDriver.failedToCompleteRequest', {
defaultMessage: 'Failed to complete a request: {error}',
values: { error: err },
})
diff --git a/x-pack/plugins/reporting/server/browsers/chromium/driver_factory/args.ts b/x-pack/plugins/screenshotting/server/browsers/chromium/driver_factory/args.ts
similarity index 81%
rename from x-pack/plugins/reporting/server/browsers/chromium/driver_factory/args.ts
rename to x-pack/plugins/screenshotting/server/browsers/chromium/driver_factory/args.ts
index 07ae13fa31849..e5985082b3c1c 100644
--- a/x-pack/plugins/reporting/server/browsers/chromium/driver_factory/args.ts
+++ b/x-pack/plugins/screenshotting/server/browsers/chromium/driver_factory/args.ts
@@ -5,18 +5,23 @@
* 2.0.
*/
-import { CaptureConfig } from '../../../../server/types';
-import { DEFAULT_VIEWPORT } from '../../../../common/constants';
+import type { ConfigType } from '../../../config';
-type BrowserConfig = CaptureConfig['browser']['chromium'];
+interface Viewport {
+ height: number;
+ width: number;
+}
+
+type Proxy = ConfigType['browser']['chromium']['proxy'];
interface LaunchArgs {
userDataDir: string;
- disableSandbox: BrowserConfig['disableSandbox'];
- proxy: BrowserConfig['proxy'];
+ viewport?: Viewport;
+ disableSandbox?: boolean;
+ proxy: Proxy;
}
-export const args = ({ userDataDir, disableSandbox, proxy: proxyConfig }: LaunchArgs) => {
+export const args = ({ userDataDir, disableSandbox, viewport, proxy: proxyConfig }: LaunchArgs) => {
const flags = [
// Disable built-in Google Translate service
'--disable-translate',
@@ -41,14 +46,17 @@ export const args = ({ userDataDir, disableSandbox, proxy: proxyConfig }: Launch
'--disable-gpu',
'--headless',
'--hide-scrollbars',
- // NOTE: setting the window size does NOT set the viewport size: viewport and window size are different.
- // The viewport may later need to be resized depending on the position of the clip area.
- // These numbers come from the job parameters, so this is a close guess.
- `--window-size=${Math.floor(DEFAULT_VIEWPORT.width)},${Math.floor(DEFAULT_VIEWPORT.height)}`,
// allow screenshot clip region to go outside of the viewport
`--mainFrameClipsContent=false`,
];
+ if (viewport) {
+ // NOTE: setting the window size does NOT set the viewport size: viewport and window size are different.
+ // The viewport may later need to be resized depending on the position of the clip area.
+ // These numbers come from the job parameters, so this is a close guess.
+ flags.push(`--window-size=${Math.floor(viewport.width)},${Math.floor(viewport.height)}`);
+ }
+
if (proxyConfig.enabled) {
flags.push(`--proxy-server=${proxyConfig.server}`);
if (proxyConfig.bypass) {
diff --git a/x-pack/plugins/screenshotting/server/browsers/chromium/driver_factory/index.test.ts b/x-pack/plugins/screenshotting/server/browsers/chromium/driver_factory/index.test.ts
new file mode 100644
index 0000000000000..23e276541465a
--- /dev/null
+++ b/x-pack/plugins/screenshotting/server/browsers/chromium/driver_factory/index.test.ts
@@ -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 puppeteer from 'puppeteer';
+import * as Rx from 'rxjs';
+import { take } from 'rxjs/operators';
+import type { Logger } from 'src/core/server';
+import type { ScreenshotModePluginSetup } from 'src/plugins/screenshot_mode/server';
+import { ConfigType } from '../../../config';
+import { HeadlessChromiumDriverFactory } from '.';
+
+jest.mock('puppeteer');
+
+describe('HeadlessChromiumDriverFactory', () => {
+ const path = 'path/to/headless_shell';
+ const config = {
+ browser: {
+ chromium: {
+ proxy: {},
+ },
+ },
+ } as ConfigType;
+ let logger: jest.Mocked;
+ let screenshotMode: jest.Mocked;
+ let factory: HeadlessChromiumDriverFactory;
+
+ beforeEach(async () => {
+ logger = {
+ debug: jest.fn(),
+ error: jest.fn(),
+ info: jest.fn(),
+ warn: jest.fn(),
+ get: jest.fn(() => logger),
+ } as unknown as typeof logger;
+ screenshotMode = {} as unknown as typeof screenshotMode;
+
+ (puppeteer as jest.Mocked).launch.mockResolvedValue({
+ newPage: jest.fn().mockResolvedValue({
+ target: jest.fn(() => ({
+ createCDPSession: jest.fn().mockResolvedValue({
+ send: jest.fn(),
+ }),
+ })),
+ emulateTimezone: jest.fn(),
+ setDefaultTimeout: jest.fn(),
+ }),
+ close: jest.fn(),
+ process: jest.fn(),
+ } as unknown as puppeteer.Browser);
+
+ factory = new HeadlessChromiumDriverFactory(screenshotMode, config, logger, path);
+ jest.spyOn(factory, 'getBrowserLogger').mockReturnValue(Rx.EMPTY);
+ jest.spyOn(factory, 'getProcessLogger').mockReturnValue(Rx.EMPTY);
+ jest.spyOn(factory, 'getPageExit').mockReturnValue(Rx.EMPTY);
+ });
+
+ describe('createPage', () => {
+ it('returns browser driver and process exit observable', async () => {
+ await expect(
+ factory.createPage({ openUrlTimeout: 0 }).pipe(take(1)).toPromise()
+ ).resolves.toEqual(
+ expect.objectContaining({
+ driver: expect.anything(),
+ exit$: expect.anything(),
+ })
+ );
+ });
+
+ it('rejects if Puppeteer launch fails', async () => {
+ (puppeteer as jest.Mocked).launch.mockRejectedValue(
+ `Puppeteer Launch mock fail.`
+ );
+ expect(() =>
+ factory.createPage({ openUrlTimeout: 0 }).pipe(take(1)).toPromise()
+ ).rejects.toThrowErrorMatchingInlineSnapshot(
+ `"Error spawning Chromium browser! Puppeteer Launch mock fail."`
+ );
+ });
+ });
+});
diff --git a/x-pack/plugins/screenshotting/server/browsers/chromium/driver_factory/index.ts b/x-pack/plugins/screenshotting/server/browsers/chromium/driver_factory/index.ts
new file mode 100644
index 0000000000000..e9656013140c2
--- /dev/null
+++ b/x-pack/plugins/screenshotting/server/browsers/chromium/driver_factory/index.ts
@@ -0,0 +1,379 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license 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 { getDataPath } from '@kbn/utils';
+import { spawn } from 'child_process';
+import del from 'del';
+import fs from 'fs';
+import { uniq } from 'lodash';
+import path from 'path';
+import puppeteer, { Browser, ConsoleMessage, HTTPRequest, Page } from 'puppeteer';
+import { createInterface } from 'readline';
+import * as Rx from 'rxjs';
+import { InnerSubscriber } from 'rxjs/internal/InnerSubscriber';
+import { catchError, ignoreElements, map, mergeMap, reduce, takeUntil, tap } from 'rxjs/operators';
+import type { Logger } from 'src/core/server';
+import type { ScreenshotModePluginSetup } from 'src/plugins/screenshot_mode/server';
+import { ConfigType } from '../../../config';
+import { getChromiumDisconnectedError } from '../';
+import { safeChildProcess } from '../../safe_child_process';
+import { HeadlessChromiumDriver } from '../driver';
+import { args } from './args';
+import { getMetrics, PerformanceMetrics } from './metrics';
+
+interface CreatePageOptions {
+ browserTimezone?: string;
+ openUrlTimeout: number;
+}
+
+interface CreatePageResult {
+ driver: HeadlessChromiumDriver;
+ exit$: Rx.Observable;
+ metrics$: Rx.Observable;
+}
+
+export const DEFAULT_VIEWPORT = {
+ width: 1950,
+ height: 1200,
+};
+
+// Default args used by pptr
+// https://github.com/puppeteer/puppeteer/blob/13ea347/src/node/Launcher.ts#L168
+const DEFAULT_ARGS = [
+ '--disable-background-networking',
+ '--enable-features=NetworkService,NetworkServiceInProcess',
+ '--disable-background-timer-throttling',
+ '--disable-backgrounding-occluded-windows',
+ '--disable-breakpad',
+ '--disable-client-side-phishing-detection',
+ '--disable-component-extensions-with-background-pages',
+ '--disable-default-apps',
+ '--disable-dev-shm-usage',
+ '--disable-extensions',
+ '--disable-features=TranslateUI',
+ '--disable-hang-monitor',
+ '--disable-ipc-flooding-protection',
+ '--disable-popup-blocking',
+ '--disable-prompt-on-repost',
+ '--disable-renderer-backgrounding',
+ '--disable-sync',
+ '--force-color-profile=srgb',
+ '--metrics-recording-only',
+ '--no-first-run',
+ '--enable-automation',
+ '--password-store=basic',
+ '--use-mock-keychain',
+ '--remote-debugging-port=0',
+ '--headless',
+];
+
+const DIAGNOSTIC_TIME = 5 * 1000;
+
+export class HeadlessChromiumDriverFactory {
+ private userDataDir = fs.mkdtempSync(path.join(getDataPath(), 'chromium-'));
+ type = 'chromium';
+
+ constructor(
+ private screenshotMode: ScreenshotModePluginSetup,
+ private config: ConfigType,
+ private logger: Logger,
+ private binaryPath: string
+ ) {
+ if (this.config.browser.chromium.disableSandbox) {
+ logger.warn(`Enabling the Chromium sandbox provides an additional layer of protection.`);
+ }
+ }
+
+ private getChromiumArgs() {
+ return args({
+ userDataDir: this.userDataDir,
+ disableSandbox: this.config.browser.chromium.disableSandbox,
+ proxy: this.config.browser.chromium.proxy,
+ viewport: DEFAULT_VIEWPORT,
+ });
+ }
+
+ /*
+ * Return an observable to objects which will drive screenshot capture for a page
+ */
+ createPage(
+ { browserTimezone, openUrlTimeout }: CreatePageOptions,
+ pLogger = this.logger
+ ): Rx.Observable {
+ // FIXME: 'create' is deprecated
+ return Rx.Observable.create(async (observer: InnerSubscriber) => {
+ const logger = pLogger.get('browser-driver');
+ logger.info(`Creating browser page driver`);
+
+ const chromiumArgs = this.getChromiumArgs();
+ logger.debug(`Chromium launch args set to: ${chromiumArgs}`);
+
+ let browser: Browser | undefined;
+
+ try {
+ browser = await puppeteer.launch({
+ pipe: !this.config.browser.chromium.inspect,
+ userDataDir: this.userDataDir,
+ executablePath: this.binaryPath,
+ ignoreHTTPSErrors: true,
+ handleSIGHUP: false,
+ args: chromiumArgs,
+ env: {
+ TZ: browserTimezone,
+ },
+ });
+ } catch (err) {
+ observer.error(new Error(`Error spawning Chromium browser! ${err}`));
+ return;
+ }
+
+ const page = await browser.newPage();
+ const devTools = await page.target().createCDPSession();
+
+ await devTools.send('Performance.enable', { timeDomain: 'timeTicks' });
+ const startMetrics = await devTools.send('Performance.getMetrics');
+ const metrics$ = new Rx.Subject();
+
+ // Log version info for debugging / maintenance
+ const versionInfo = await devTools.send('Browser.getVersion');
+ logger.debug(`Browser version: ${JSON.stringify(versionInfo)}`);
+
+ await page.emulateTimezone(browserTimezone);
+
+ // Set the default timeout for all navigation methods to the openUrl timeout
+ // All waitFor methods have their own timeout config passed in to them
+ page.setDefaultTimeout(openUrlTimeout);
+
+ logger.debug(`Browser page driver created`);
+
+ const childProcess = {
+ async kill() {
+ try {
+ if (devTools && startMetrics) {
+ const endMetrics = await devTools.send('Performance.getMetrics');
+ const metrics = getMetrics(startMetrics, endMetrics);
+ const { cpuInPercentage, memoryInMegabytes } = metrics;
+
+ metrics$.next(metrics);
+ logger.debug(
+ `Chromium consumed CPU ${cpuInPercentage}% Memory ${memoryInMegabytes}MB`
+ );
+ }
+ } catch (error) {
+ logger.error(error);
+ } finally {
+ metrics$.complete();
+ }
+
+ try {
+ await browser?.close();
+ } catch (err) {
+ // do not throw
+ logger.error(err);
+ }
+ },
+ };
+ const { terminate$ } = safeChildProcess(logger, childProcess);
+
+ // this is adding unsubscribe logic to our observer
+ // so that if our observer unsubscribes, we terminate our child-process
+ observer.add(() => {
+ logger.debug(`The browser process observer has unsubscribed. Closing the browser...`);
+ childProcess.kill(); // ignore async
+ });
+
+ // make the observer subscribe to terminate$
+ observer.add(
+ terminate$
+ .pipe(
+ tap((signal) => {
+ logger.debug(`Termination signal received: ${signal}`);
+ }),
+ ignoreElements()
+ )
+ .subscribe(observer)
+ );
+
+ // taps the browser log streams and combine them to Kibana logs
+ this.getBrowserLogger(page, logger).subscribe();
+ this.getProcessLogger(browser, logger).subscribe();
+
+ // HeadlessChromiumDriver: object to "drive" a browser page
+ const driver = new HeadlessChromiumDriver(this.screenshotMode, this.config, page);
+
+ // Rx.Observable: stream to interrupt page capture
+ const exit$ = this.getPageExit(browser, page);
+
+ observer.next({ driver, exit$, metrics$: metrics$.asObservable() });
+
+ // unsubscribe logic makes a best-effort attempt to delete the user data directory used by chromium
+ observer.add(() => {
+ const userDataDir = this.userDataDir;
+ logger.debug(`deleting chromium user data directory at [${userDataDir}]`);
+ // the unsubscribe function isn't `async` so we're going to make our best effort at
+ // deleting the userDataDir and if it fails log an error.
+ del(userDataDir, { force: true }).catch((error) => {
+ logger.error(`error deleting user data directory at [${userDataDir}]!`);
+ logger.error(error);
+ });
+ });
+ });
+ }
+
+ getBrowserLogger(page: Page, logger: Logger): Rx.Observable {
+ const consoleMessages$ = Rx.fromEvent