diff --git a/.buildkite/scripts/steps/storybooks/build_and_upload.js b/.buildkite/scripts/steps/storybooks/build_and_upload.js index 0fdf24d87ffad..89958fe08d6cc 100644 --- a/.buildkite/scripts/steps/storybooks/build_and_upload.js +++ b/.buildkite/scripts/steps/storybooks/build_and_upload.js @@ -8,6 +8,7 @@ const STORYBOOKS = [ 'canvas', 'codeeditor', 'ci_composite', + 'custom_integrations', 'url_template_editor', 'dashboard', 'dashboard_enhanced', diff --git a/.i18nrc.json b/.i18nrc.json index 294c7a576ebb4..46d2f8c6a23bf 100644 --- a/.i18nrc.json +++ b/.i18nrc.json @@ -19,6 +19,7 @@ "home": "src/plugins/home", "flot": "packages/kbn-ui-shared-deps-src/src/flot_charts", "charts": "src/plugins/charts", + "customIntegrations": "src/plugins/custom_integrations", "esUi": "src/plugins/es_ui_shared", "devTools": "src/plugins/dev_tools", "expressions": "src/plugins/expressions", diff --git a/docs/apm/troubleshooting.asciidoc b/docs/apm/troubleshooting.asciidoc index 4a62f71528676..6e0c3b1decda8 100644 --- a/docs/apm/troubleshooting.asciidoc +++ b/docs/apm/troubleshooting.asciidoc @@ -72,8 +72,6 @@ then the index template will not be set up automatically. Instead, you'll need t *Using a custom index names* This problem can also occur if you've customized the index name that you write APM data to. -The default index name that APM writes events to can be found -{apm-server-ref}/elasticsearch-output.html#index-option-es[here]. If you change the default, you must also configure the `setup.template.name` and `setup.template.pattern` options. See {apm-server-ref}/configuration-template.html[Load the Elasticsearch index template]. If the Elasticsearch index template has already been successfully loaded to the index, diff --git a/docs/dev-tools/grokdebugger/index.asciidoc b/docs/dev-tools/grokdebugger/index.asciidoc index 934452c54ccca..6a809c13fcb93 100644 --- a/docs/dev-tools/grokdebugger/index.asciidoc +++ b/docs/dev-tools/grokdebugger/index.asciidoc @@ -9,21 +9,22 @@ structure it. Grok is good for parsing syslog, apache, and other webserver logs, mysql logs, and in general, any log format that is written for human consumption. -Grok patterns are supported in the ingest node -{ref}/grok-processor.html[grok processor] and the Logstash -{logstash-ref}/plugins-filters-grok.html[grok filter]. See -{logstash-ref}/plugins-filters-grok.html#_grok_basics[grok basics] -for more information on the syntax for a grok pattern. - -The Elastic Stack ships -with more than 120 reusable grok patterns. See -https://github.com/elastic/elasticsearch/tree/master/libs/grok/src/main/resources/patterns[Ingest node grok patterns] and https://github.com/logstash-plugins/logstash-patterns-core/tree/master/patterns[Logstash grok patterns] -for the complete list of patterns. +Grok patterns are supported in {es} {ref}/runtime.html[runtime fields], the {es} +{ref}/grok-processor.html[grok ingest processor], and the {ls} +{logstash-ref}/plugins-filters-grok.html[grok filter]. For syntax, see +{ref}/grok.html[Grokking grok]. + +The {stack} ships with more than 120 reusable grok patterns. For a complete +list of patterns, see +https://github.com/elastic/elasticsearch/tree/master/libs/grok/src/main/resources/patterns[{es} +grok patterns] and +https://github.com/logstash-plugins/logstash-patterns-core/tree/master/patterns[{ls} +grok patterns]. Because -ingest node and Logstash share the same grok implementation and pattern +{es} and {ls} share the same grok implementation and pattern libraries, any grok pattern that you create in the *Grok Debugger* will work -in ingest node and Logstash. +in both {es} and {ls}. [float] [[grokdebugger-getting-started]] diff --git a/docs/developer/plugin-list.asciidoc b/docs/developer/plugin-list.asciidoc index 7f7041f7815cd..cbf46801fa86f 100644 --- a/docs/developer/plugin-list.asciidoc +++ b/docs/developer/plugin-list.asciidoc @@ -458,7 +458,7 @@ the infrastructure monitoring use-case within Kibana. |{kib-repo}blob/{branch}/x-pack/plugins/ingest_pipelines/README.md[ingestPipelines] -|The ingest_pipelines plugin provides Kibana support for Elasticsearch's ingest nodes. Please refer to the Elasticsearch documentation for more details. +|The ingest_pipelines plugin provides Kibana support for Elasticsearch's ingest pipelines. |{kib-repo}blob/{branch}/x-pack/plugins/lens/readme.md[lens] 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 f40f52db55de9..ab0f2d0ee5a17 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 @@ -234,5 +234,17 @@ readonly links: { 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; + }; }; ``` 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 2499227d20ad4..f0fe058c403ed 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 apm: {
readonly kibanaSettings: string;
readonly supportedServiceMaps: string;
readonly customLinks: string;
readonly droppedTransactionSpans: string;
readonly upgrading: string;
readonly metaData: string;
};
readonly canvas: {
readonly guide: string;
};
readonly dashboard: {
readonly guide: string;
readonly drilldowns: string;
readonly drilldownsTriggerPicker: string;
readonly urlDrilldownTemplateSyntax: string;
readonly urlDrilldownVariables: string;
};
readonly discover: Record<string, string>;
readonly filebeat: {
readonly base: string;
readonly installation: string;
readonly configuration: string;
readonly elasticsearchOutput: string;
readonly elasticsearchModule: string;
readonly startup: string;
readonly exportedFields: string;
readonly suricataModule: string;
readonly zeekModule: string;
};
readonly auditbeat: {
readonly base: string;
readonly auditdModule: string;
readonly systemModule: string;
};
readonly metricbeat: {
readonly base: string;
readonly configure: string;
readonly httpEndpoint: string;
readonly install: string;
readonly start: string;
};
readonly enterpriseSearch: {
readonly base: string;
readonly appSearchBase: string;
readonly workplaceSearchBase: string;
};
readonly heartbeat: {
readonly base: string;
};
readonly libbeat: {
readonly getStarted: string;
};
readonly logstash: {
readonly base: string;
};
readonly functionbeat: {
readonly base: string;
};
readonly winlogbeat: {
readonly base: string;
};
readonly aggs: {
readonly composite: string;
readonly composite_missing_bucket: string;
readonly date_histogram: string;
readonly date_range: string;
readonly date_format_pattern: string;
readonly filter: string;
readonly filters: string;
readonly geohash_grid: string;
readonly histogram: string;
readonly ip_range: string;
readonly range: string;
readonly significant_terms: string;
readonly terms: string;
readonly 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: 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 query: {
readonly eql: string;
readonly kueryQuerySyntax: string;
readonly luceneQuerySyntax: string;
readonly percolate: string;
readonly queryDsl: string;
readonly autocompleteChanges: 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: Record<string, string>;
readonly monitoring: Record<string, string>;
readonly security: Readonly<{
apiKeyServiceSettings: string;
clusterPrivileges: string;
elasticsearchSettings: string;
elasticsearchEnableSecurity: string;
indicesPrivileges: string;
kibanaTLS: string;
kibanaPrivileges: string;
mappingRoles: string;
mappingRolesFieldRules: string;
runAsPrivilege: 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<{
guide: string;
fleetServer: string;
fleetServerAddFleetServer: string;
settings: string;
settingsFleetServerHostSettings: string;
troubleshooting: string;
elasticAgent: string;
datastreams: string;
datastreamsNamingScheme: string;
upgradeElasticAgent: string;
upgradeElasticAgent712lower: string;
}>;
readonly ecs: {
readonly guide: string;
};
} | | +| [links](./kibana-plugin-core-public.doclinksstart.links.md) | {
readonly settings: string;
readonly apm: {
readonly kibanaSettings: string;
readonly supportedServiceMaps: string;
readonly customLinks: string;
readonly droppedTransactionSpans: string;
readonly upgrading: string;
readonly metaData: string;
};
readonly canvas: {
readonly guide: string;
};
readonly dashboard: {
readonly guide: string;
readonly drilldowns: string;
readonly drilldownsTriggerPicker: string;
readonly urlDrilldownTemplateSyntax: string;
readonly urlDrilldownVariables: string;
};
readonly discover: Record<string, string>;
readonly filebeat: {
readonly base: string;
readonly installation: string;
readonly configuration: string;
readonly elasticsearchOutput: string;
readonly elasticsearchModule: string;
readonly startup: string;
readonly exportedFields: string;
readonly suricataModule: string;
readonly zeekModule: string;
};
readonly auditbeat: {
readonly base: string;
readonly auditdModule: string;
readonly systemModule: string;
};
readonly metricbeat: {
readonly base: string;
readonly configure: string;
readonly httpEndpoint: string;
readonly install: string;
readonly start: string;
};
readonly enterpriseSearch: {
readonly base: string;
readonly appSearchBase: string;
readonly workplaceSearchBase: string;
};
readonly heartbeat: {
readonly base: string;
};
readonly libbeat: {
readonly getStarted: string;
};
readonly logstash: {
readonly base: string;
};
readonly functionbeat: {
readonly base: string;
};
readonly winlogbeat: {
readonly base: string;
};
readonly aggs: {
readonly composite: string;
readonly composite_missing_bucket: string;
readonly date_histogram: string;
readonly date_range: string;
readonly date_format_pattern: string;
readonly filter: string;
readonly filters: string;
readonly geohash_grid: string;
readonly histogram: string;
readonly ip_range: string;
readonly range: string;
readonly significant_terms: string;
readonly terms: string;
readonly 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: 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 query: {
readonly eql: string;
readonly kueryQuerySyntax: string;
readonly luceneQuerySyntax: string;
readonly percolate: string;
readonly queryDsl: string;
readonly autocompleteChanges: 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: Record<string, string>;
readonly monitoring: Record<string, string>;
readonly security: Readonly<{
apiKeyServiceSettings: string;
clusterPrivileges: string;
elasticsearchSettings: string;
elasticsearchEnableSecurity: string;
indicesPrivileges: string;
kibanaTLS: string;
kibanaPrivileges: string;
mappingRoles: string;
mappingRolesFieldRules: string;
runAsPrivilege: 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<{
guide: string;
fleetServer: string;
fleetServerAddFleetServer: string;
settings: string;
settingsFleetServerHostSettings: string;
troubleshooting: string;
elasticAgent: string;
datastreams: string;
datastreamsNamingScheme: string;
upgradeElasticAgent: string;
upgradeElasticAgent712lower: 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;
};
} | | diff --git a/docs/migration/migrate_8_0.asciidoc b/docs/migration/migrate_8_0.asciidoc index 4c0c335b3c33e..60a65580501a6 100644 --- a/docs/migration/migrate_8_0.asciidoc +++ b/docs/migration/migrate_8_0.asciidoc @@ -364,4 +364,34 @@ Configuration management tools and automation will need to be updated to use the === `server.xsrf.token` is no longer valid *Details:* The deprecated `server.xsrf.token` setting in the `kibana.yml` file has been removed. +[float] +=== `newsfeed.defaultLanguage` is no longer valid +*Details:* Specifying a default language to retrieve newsfeed items is no longer supported. + +*Impact:* Newsfeed items will be retrieved based on the browser locale and fallback to 'en' if an item does not have a translation for the locale. Configure {kibana-ref}/i18n-settings-kb.html#general-i18n-settings-kb[`i18n.locale`] to override the default behavior. + +[float] +=== `xpack.banners.placement` has changed value +*Details:* `xpack.banners.placement: 'header'` setting in `kibana.yml` has changed value. + +*Impact:* Use {kibana-ref}/banners-settings-kb.html#banners-settings-kb[`xpack.banners.placement: 'top'`] instead. + +[float] +=== `cpu.cgroup.path.override` is no longer valid +*Details:* The deprecated `cpu.cgroup.path.override` setting is no longer supported. + +*Impact:* Configure {kibana-ref}/settings.html#ops-cGroupOverrides-cpuPath[`ops.cGroupOverrides.cpuPath`] instead. + +[float] +=== `cpuacct.cgroup.path.override` is no longer valid +*Details:* The deprecated `cpuacct.cgroup.path.override` setting is no longer supported. + +*Impact:* Configure {kibana-ref}/settings.html#ops-cGroupOverrides-cpuAcctPath[`ops.cGroupOverrides.cpuAcctPath`] instead. + +[float] +=== `server.xsrf.whitelist` is no longer valid +*Details:* The deprecated `server.xsrf.whitelist` setting is no longer supported. + +*Impact:* Use {kibana-ref}/settings.html#settings-xsrf-allowlist[`server.xsrf.allowlist`] instead. + // end::notable-breaking-changes[] diff --git a/docs/redirects.asciidoc b/docs/redirects.asciidoc index 97a87506f2337..d5bc2ccd8ef7d 100644 --- a/docs/redirects.asciidoc +++ b/docs/redirects.asciidoc @@ -293,7 +293,7 @@ This content has moved. Refer to <>. This content has moved. Refer to <>. [role="exclude",id="ingest-node-pipelines"] -== Ingest Node Pipelines +== Ingest Pipelines This content has moved. Refer to {ref}/ingest.html[Ingest pipelines]. diff --git a/docs/settings/alert-action-settings.asciidoc b/docs/settings/alert-action-settings.asciidoc index 6dfda0a30a030..3a94e652d2ea0 100644 --- a/docs/settings/alert-action-settings.asciidoc +++ b/docs/settings/alert-action-settings.asciidoc @@ -31,11 +31,6 @@ Be sure to back up the encryption key value somewhere safe, as your alerting rul [[action-settings]] ==== Action settings -`xpack.actions.enabled`:: -deprecated:[7.16.0,"In 8.0 and later, this setting will no longer be supported."] -Feature toggle that enables Actions in {kib}. -If `false`, all features dependent on Actions are disabled, including the *Observability* and *Security* apps. Default: `true`. - `xpack.actions.allowedHosts` {ess-icon}:: A list of hostnames that {kib} is allowed to connect to when built-in actions are triggered. It defaults to `[*]`, allowing any host, but keep in mind the potential for SSRF attacks when hosts are not explicitly added to the allowed hosts. An empty list `[]` can be used to block built-in actions from making any external connections. + diff --git a/docs/settings/reporting-settings.asciidoc b/docs/settings/reporting-settings.asciidoc index 694f8c53f6745..560f2d850c6d5 100644 --- a/docs/settings/reporting-settings.asciidoc +++ b/docs/settings/reporting-settings.asciidoc @@ -11,7 +11,6 @@ You can configure `xpack.reporting` settings in your `kibana.yml` to: * <> * <> -* <> * <> * <> * <> @@ -47,33 +46,6 @@ The static encryption key for reporting. Use an alphanumeric text string that is xpack.reporting.encryptionKey: "something_secret" -------------------------------------------------------------------------------- -[float] -[[report-indices]] -==== Reporting index setting - - - -`xpack.reporting.index`:: -deprecated:[7.11.0,This setting will be removed in 8.0.0.] Multitenancy by changing `kibana.index` is unsupported starting in 8.0.0. For more details, refer to https://ela.st/kbn-remove-legacy-multitenancy[8.0 Breaking Changes]. When you divide workspaces in an Elastic cluster using multiple {kib} instances with a different `kibana.index` setting per instance, you must set a unique `xpack.reporting.index` setting per `kibana.index`. Otherwise, report generation periodically fails if a report is queued through an instance with one `kibana.index` setting, and an instance with a different `kibana.index` attempts to claim the job. Reporting uses a weekly index in {es} to store the reporting job and the report content. The index is automatically created if it does not already exist. Configure a unique value for `xpack.reporting.index`, beginning with `.reporting-`, for every {kib} instance that has a unique <> setting. Defaults to `.reporting`. - -{kib} instance A: -[source,yaml] --------------------------------------------------------------------------------- -kibana.index: ".kibana-a" -xpack.reporting.index: ".reporting-a" -xpack.reporting.encryptionKey: "something_secret" --------------------------------------------------------------------------------- - -{kib} instance B: -[source,yaml] --------------------------------------------------------------------------------- -kibana.index: ".kibana-b" -xpack.reporting.index: ".reporting-b" -xpack.reporting.encryptionKey: "something_secret" --------------------------------------------------------------------------------- - -NOTE: If security is enabled, the `xpack.reporting.index` setting should begin with `.reporting-` for the `kibana_system` role to have the necessary privileges over the index. - [float] [[reporting-kibana-server-settings]] ==== {kib} server settings diff --git a/docs/setup/configuring-reporting.asciidoc b/docs/setup/configuring-reporting.asciidoc index 6d209092d3338..38bf2955fb56e 100644 --- a/docs/setup/configuring-reporting.asciidoc +++ b/docs/setup/configuring-reporting.asciidoc @@ -148,56 +148,6 @@ reporting_user: - "cn=Bill Murray,dc=example,dc=com" -------------------------------------------------------------------------------- -[float] -==== Grant access with a custom index - -If you are using a custom index, the `xpack.reporting.index` setting must begin with `.reporting-*`. The default {kib} system user has `all` privileges against the `.reporting-*` pattern of indices. - -If you use a different pattern for the `xpack.reporting.index` setting, you must create a custom `kibana_system` user with appropriate access to the index. - -NOTE: In the next major version of Kibana, granting access with a custom index is unsupported. - -. Create the reporting role. - -.. Open the main menu, then click *Stack Management*. - -.. Click *Roles > Create role*. - -. Specify the role settings. - -.. Enter the *Role name*. For example, `custom-reporting-user`. - -.. From the *Indices* dropdown, select the custom index. - -.. From the *Privileges* dropdown, select *all*. - -.. Click *Add Kibana privilege*. - -.. Select one or more *Spaces* that you want to grant reporting privileges to. - -.. Click *Customize*, then click *Analytics*. - -.. Next to each application you want to grant reporting privileges to, click *All*. - -.. Click *Add {kib} privilege*, then click *Create role*. - -. Assign the reporting role to a user. - -.. Open the main menu, then click *Stack Management*. - -.. Click *Users*, then click the user you want to assign the reporting role to. - -.. From the *Roles* dropdown, select *kibana_system* and *custom-reporting-user*. - -.. Click *Update user*. - -. Configure {kib} to use the new account. -+ -[source,js] --------------------------------------------------------------------------------- -elasticsearch.username: 'custom_kibana_system' --------------------------------------------------------------------------------- - [float] [[securing-reporting]] === Secure the reporting endpoints diff --git a/docs/setup/settings.asciidoc b/docs/setup/settings.asciidoc index c098fb697de04..7a85411065db6 100644 --- a/docs/setup/settings.asciidoc +++ b/docs/setup/settings.asciidoc @@ -26,16 +26,6 @@ Toggling this causes the server to regenerate assets on the next startup, which may cause a delay before pages start being served. Set to `false` to disable Console. *Default: `true`* -| `cpu.cgroup.path.override:` - | deprecated:[7.10.0,"In 8.0 and later, this setting will no longer be supported."] - This setting has been renamed to - <>. - -| `cpuacct.cgroup.path.override:` - | deprecated:[7.10.0,"In 8.0 and later, this setting will no longer be supported."] - This setting has been renamed to - <>. - | `csp.rules:` | deprecated:[7.14.0,"In 8.0 and later, this setting will no longer be supported."] A https://w3c.github.io/webappsec-csp/[Content Security Policy] template diff --git a/docs/user/management.asciidoc b/docs/user/management.asciidoc index 4e5f70db9aef6..1f38d50e2d0bd 100644 --- a/docs/user/management.asciidoc +++ b/docs/user/management.asciidoc @@ -17,7 +17,7 @@ Consult your administrator if you do not have the appropriate access. [cols="50, 50"] |=== -| {ref}/ingest.html[Ingest Node Pipelines] +| {ref}/ingest.html[Ingest Pipelines] | Create and manage ingest pipelines that let you perform common transformations and enrichments on your data. diff --git a/docs/user/monitoring/monitoring-metricbeat.asciidoc b/docs/user/monitoring/monitoring-metricbeat.asciidoc index 5ef3b8177a9c5..101377e047588 100644 --- a/docs/user/monitoring/monitoring-metricbeat.asciidoc +++ b/docs/user/monitoring/monitoring-metricbeat.asciidoc @@ -189,8 +189,9 @@ If you configured the monitoring cluster to use encrypted communications, you must access it via HTTPS. For example, use a `hosts` setting like `https://es-mon-1:9200`. -IMPORTANT: The {es} {monitor-features} use ingest pipelines, therefore the -cluster that stores the monitoring data must have at least one ingest node. +IMPORTANT: The {es} {monitor-features} use ingest pipelines. The +cluster that stores the monitoring data must have at least one node with the +`ingest` role. If the {es} {security-features} are enabled on the monitoring cluster, you must provide a valid user ID and password so that {metricbeat} can send metrics diff --git a/packages/kbn-utils/src/path/index.ts b/packages/kbn-utils/src/path/index.ts index 9ee699c22c30c..15d6a3eddf01e 100644 --- a/packages/kbn-utils/src/path/index.ts +++ b/packages/kbn-utils/src/path/index.ts @@ -15,14 +15,12 @@ const isString = (v: any): v is string => typeof v === 'string'; const CONFIG_PATHS = [ process.env.KBN_PATH_CONF && join(process.env.KBN_PATH_CONF, 'kibana.yml'), - process.env.KIBANA_PATH_CONF && join(process.env.KIBANA_PATH_CONF, 'kibana.yml'), // deprecated join(REPO_ROOT, 'config/kibana.yml'), '/etc/kibana/kibana.yml', ].filter(isString); const CONFIG_DIRECTORIES = [ process.env.KBN_PATH_CONF, - process.env.KIBANA_PATH_CONF, // deprecated join(REPO_ROOT, 'config'), '/etc/kibana', ].filter(isString); diff --git a/src/core/public/doc_links/doc_links_service.ts b/src/core/public/doc_links/doc_links_service.ts index 0fe1347d299f9..624d6d10992cd 100644 --- a/src/core/public/doc_links/doc_links_service.ts +++ b/src/core/public/doc_links/doc_links_service.ts @@ -470,6 +470,19 @@ export class DocLinksService { ecs: { guide: `${ELASTIC_WEBSITE_URL}guide/en/ecs/current/index.html`, }, + clients: { + /** Changes to these URLs must also be synched in src/plugins/custom_integrations/server/language_clients/index.ts */ + guide: `${ELASTIC_WEBSITE_URL}guide/en/elasticsearch/client/index.html`, + goOverview: `${ELASTIC_WEBSITE_URL}guide/en/elasticsearch/client/go-api/${DOC_LINK_VERSION}/overview.html`, + javaIndex: `${ELASTIC_WEBSITE_URL}guide/en/elasticsearch/client/java-api-client/${DOC_LINK_VERSION}/index.html`, + jsIntro: `${ELASTIC_WEBSITE_URL}guide/en/elasticsearch/client/javascript-api/${DOC_LINK_VERSION}/introduction.html`, + netGuide: `${ELASTIC_WEBSITE_URL}guide/en/elasticsearch/client/net-api/${DOC_LINK_VERSION}/index.html`, + perlGuide: `${ELASTIC_WEBSITE_URL}guide/en/elasticsearch/client/perl-api/${DOC_LINK_VERSION}/index.html`, + phpGuide: `${ELASTIC_WEBSITE_URL}guide/en/elasticsearch/client/php-api/${DOC_LINK_VERSION}/index.html`, + pythonGuide: `${ELASTIC_WEBSITE_URL}guide/en/elasticsearch/client/net-api/${DOC_LINK_VERSION}/index.html`, + rubyOverview: `${ELASTIC_WEBSITE_URL}guide/en/elasticsearch/client/ruby-api/${DOC_LINK_VERSION}/ruby_client.html`, + rustGuide: `${ELASTIC_WEBSITE_URL}guide/en/elasticsearch/client/rust-api/${DOC_LINK_VERSION}/index.html`, + }, }, }); } @@ -706,5 +719,17 @@ export interface DocLinksStart { 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; + }; }; } diff --git a/src/core/public/public.api.md b/src/core/public/public.api.md index eace9c4011942..26c438dedec87 100644 --- a/src/core/public/public.api.md +++ b/src/core/public/public.api.md @@ -699,6 +699,18 @@ export interface DocLinksStart { 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; + }; }; } diff --git a/src/core/server/config/deprecation/core_deprecations.test.ts b/src/core/server/config/deprecation/core_deprecations.test.ts index 4e99f46ea05ff..95e23561a9378 100644 --- a/src/core/server/config/deprecation/core_deprecations.test.ts +++ b/src/core/server/config/deprecation/core_deprecations.test.ts @@ -19,38 +19,6 @@ describe('core deprecations', () => { process.env = { ...initialEnv }; }); - describe('kibanaPathConf', () => { - it('logs a warning if KIBANA_PATH_CONF environ variable is set', () => { - process.env.KIBANA_PATH_CONF = 'somepath'; - const { messages } = applyCoreDeprecations(); - expect(messages).toMatchInlineSnapshot(` - Array [ - "Environment variable \\"KIBANA_PATH_CONF\\" is deprecated. It has been replaced with \\"KBN_PATH_CONF\\" pointing to a config folder", - ] - `); - }); - - it('does not log a warning if KIBANA_PATH_CONF environ variable is unset', () => { - delete process.env.KIBANA_PATH_CONF; - const { messages } = applyCoreDeprecations(); - expect(messages).toHaveLength(0); - }); - }); - - describe('xsrfDeprecation', () => { - it('logs a warning if server.xsrf.whitelist is set', () => { - const { migrated, messages } = applyCoreDeprecations({ - server: { xsrf: { whitelist: ['/path'] } }, - }); - expect(migrated.server.xsrf.allowlist).toEqual(['/path']); - expect(messages).toMatchInlineSnapshot(` - Array [ - "Setting \\"server.xsrf.whitelist\\" has been replaced by \\"server.xsrf.allowlist\\"", - ] - `); - }); - }); - describe('server.cors', () => { it('renames server.cors to server.cors.enabled', () => { const { migrated } = applyCoreDeprecations({ @@ -58,8 +26,9 @@ describe('core deprecations', () => { }); expect(migrated.server.cors).toEqual({ enabled: true }); }); + it('logs a warning message about server.cors renaming', () => { - const { messages } = applyCoreDeprecations({ + const { messages, levels } = applyCoreDeprecations({ server: { cors: true }, }); expect(messages).toMatchInlineSnapshot(` @@ -67,7 +36,13 @@ describe('core deprecations', () => { "\\"server.cors\\" is deprecated and has been replaced by \\"server.cors.enabled\\"", ] `); + expect(levels).toMatchInlineSnapshot(` + Array [ + "warning", + ] + `); }); + it('does not log deprecation message when server.cors.enabled set', () => { const { migrated, messages } = applyCoreDeprecations({ server: { cors: { enabled: true } }, diff --git a/src/core/server/config/deprecation/core_deprecations.ts b/src/core/server/config/deprecation/core_deprecations.ts index 674812bd0957b..4e5f711fe9f3a 100644 --- a/src/core/server/config/deprecation/core_deprecations.ts +++ b/src/core/server/config/deprecation/core_deprecations.ts @@ -8,19 +8,6 @@ import { ConfigDeprecationProvider, ConfigDeprecation } from '@kbn/config'; -const kibanaPathConf: ConfigDeprecation = (settings, fromPath, addDeprecation) => { - if (process.env?.KIBANA_PATH_CONF) { - addDeprecation({ - message: `Environment variable "KIBANA_PATH_CONF" is deprecated. It has been replaced with "KBN_PATH_CONF" pointing to a config folder`, - correctiveActions: { - manualSteps: [ - 'Use "KBN_PATH_CONF" instead of "KIBANA_PATH_CONF" to point to a config folder.', - ], - }, - }); - } -}; - const rewriteBasePathDeprecation: ConfigDeprecation = (settings, fromPath, addDeprecation) => { if (settings.server?.basePath && !settings.server?.rewriteBasePath) { addDeprecation({ @@ -44,6 +31,7 @@ const rewriteCorsSettings: ConfigDeprecation = (settings, fromPath, addDeprecati if (typeof corsSettings === 'boolean') { addDeprecation({ message: '"server.cors" is deprecated and has been replaced by "server.cors.enabled"', + level: 'warning', correctiveActions: { manualSteps: [ `Replace "server.cors: ${corsSettings}" with "server.cors.enabled: ${corsSettings}"`, @@ -114,11 +102,7 @@ const cspRulesDeprecation: ConfigDeprecation = (settings, fromPath, addDeprecati }; export const coreDeprecationProvider: ConfigDeprecationProvider = ({ rename, unusedFromRoot }) => [ - rename('cpu.cgroup.path.override', 'ops.cGroupOverrides.cpuPath'), - rename('cpuacct.cgroup.path.override', 'ops.cGroupOverrides.cpuAcctPath'), - rename('server.xsrf.whitelist', 'server.xsrf.allowlist'), rewriteCorsSettings, - kibanaPathConf, rewriteBasePathDeprecation, cspRulesDeprecation, ]; diff --git a/src/core/server/config/test_utils.ts b/src/core/server/config/test_utils.ts index e3f9ca7eb29f2..592ea0981682f 100644 --- a/src/core/server/config/test_utils.ts +++ b/src/core/server/config/test_utils.ts @@ -16,6 +16,7 @@ function collectDeprecations( ) { const deprecations = provider(configDeprecationFactory); const deprecationMessages: string[] = []; + const deprecationLevels: string[] = []; const { config: migrated } = applyDeprecations( settings, deprecations.map((deprecation) => ({ @@ -23,11 +24,14 @@ function collectDeprecations( path, })), () => - ({ message }) => - deprecationMessages.push(message) + ({ message, level }) => { + deprecationMessages.push(message); + deprecationLevels.push(level ?? ''); + } ); return { messages: deprecationMessages, + levels: deprecationLevels, migrated, }; } diff --git a/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker b/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker index 89e9e3b117d77..dd5b66af9ef21 100755 --- a/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker +++ b/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker @@ -26,8 +26,6 @@ kibana_vars=( console.enabled console.proxyConfig console.proxyFilter - cpu.cgroup.path.override - cpuacct.cgroup.path.override csp.rules csp.strict csp.warnLegacyBrowsers @@ -175,7 +173,6 @@ kibana_vars=( server.uuid server.xsrf.allowlist server.xsrf.disableProtection - server.xsrf.whitelist status.allowAnonymous status.v6ApiFormat telemetry.allowChangingOptInStatus @@ -194,7 +191,6 @@ kibana_vars=( vis_type_vega.enableExternalUrls xpack.actions.allowedHosts xpack.actions.customHostSettings - xpack.actions.enabled xpack.actions.enabledActionTypes xpack.actions.maxResponseContentLength xpack.actions.preconfigured @@ -252,7 +248,6 @@ kibana_vars=( xpack.discoverEnhanced.actions.exploreDataInContextMenu.enabled xpack.encryptedSavedObjects.encryptionKey xpack.encryptedSavedObjects.keyRotation.decryptionOnlyKeys - xpack.event_log.enabled xpack.event_log.indexEntries xpack.event_log.logEntries xpack.fleet.agentPolicies @@ -319,7 +314,6 @@ kibana_vars=( xpack.reporting.csv.useByteOrderMarkEncoding xpack.reporting.enabled xpack.reporting.encryptionKey - xpack.reporting.index xpack.reporting.kibanaApp xpack.reporting.kibanaServer.hostname xpack.reporting.kibanaServer.port @@ -385,7 +379,6 @@ kibana_vars=( xpack.securitySolution.prebuiltRulesFromSavedObjects xpack.spaces.enabled xpack.spaces.maxSpaces - xpack.task_manager.enabled xpack.task_manager.index xpack.task_manager.max_attempts xpack.task_manager.max_poll_inactivity_cycles @@ -427,7 +420,7 @@ umask 0002 # paths. Therefore, Kibana provides a mechanism to override # reading the cgroup path from /proc/self/cgroup and instead uses the # cgroup path defined the configuration properties -# cpu.cgroup.path.override and cpuacct.cgroup.path.override. +# ops.cGroupOverrides.cpuPath and ops.cGroupOverrides.cpuAcctPath. # Therefore, we set this value here so that cgroup statistics are # available for the container this process will run in. diff --git a/src/dev/storybook/aliases.ts b/src/dev/storybook/aliases.ts index a61a2618d6428..c04f0d4f9320f 100644 --- a/src/dev/storybook/aliases.ts +++ b/src/dev/storybook/aliases.ts @@ -12,6 +12,7 @@ export const storybookAliases = { canvas: 'x-pack/plugins/canvas/storybook', codeeditor: 'src/plugins/kibana_react/public/code_editor/.storybook', ci_composite: '.ci/.storybook', + custom_integrations: 'src/plugins/custom_integrations/storybook', url_template_editor: 'src/plugins/kibana_react/public/url_template_editor/.storybook', dashboard: 'src/plugins/dashboard/.storybook', dashboard_enhanced: 'x-pack/plugins/dashboard_enhanced/.storybook', diff --git a/src/plugins/custom_integrations/kibana.json b/src/plugins/custom_integrations/kibana.json index 3a78270d9ef09..cd58c1aec1ecb 100755 --- a/src/plugins/custom_integrations/kibana.json +++ b/src/plugins/custom_integrations/kibana.json @@ -12,5 +12,8 @@ "extraPublicDirs": [ "common" ], + "requiredPlugins": [ + "presentationUtil" + ], "optionalPlugins": [] } diff --git a/src/plugins/custom_integrations/public/components/index.tsx b/src/plugins/custom_integrations/public/components/index.tsx new file mode 100644 index 0000000000000..cfbec7d6d5ae5 --- /dev/null +++ b/src/plugins/custom_integrations/public/components/index.tsx @@ -0,0 +1,31 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 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 React, { Suspense, ComponentType, ReactElement, Ref } from 'react'; +import { EuiLoadingSpinner, EuiErrorBoundary } from '@elastic/eui'; + +/** + * A HOC which supplies React.Suspense with a fallback component, and a `EuiErrorBoundary` to contain errors. + * @param Component A component deferred by `React.lazy` + * @param fallback A fallback component to render while things load; default is `EuiLoadingSpinner` + */ +export const withSuspense =

( + Component: ComponentType

, + fallback: ReactElement | null = +) => + React.forwardRef((props: P, ref: Ref) => { + return ( + + + + + + ); + }); + +export const LazyReplacementCard = React.lazy(() => import('./replacement_card')); diff --git a/src/plugins/custom_integrations/public/components/replacement_card/index.ts b/src/plugins/custom_integrations/public/components/replacement_card/index.ts new file mode 100644 index 0000000000000..631dc1fcb2ba2 --- /dev/null +++ b/src/plugins/custom_integrations/public/components/replacement_card/index.ts @@ -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 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 { ReplacementCard } from './replacement_card'; + +export { ReplacementCard, Props } from './replacement_card'; + +// required for dynamic import using React.lazy() +// eslint-disable-next-line import/no-default-export +export default ReplacementCard; diff --git a/src/plugins/custom_integrations/public/components/replacement_card/replacement_card.component.tsx b/src/plugins/custom_integrations/public/components/replacement_card/replacement_card.component.tsx new file mode 100644 index 0000000000000..f66d13fb911b5 --- /dev/null +++ b/src/plugins/custom_integrations/public/components/replacement_card/replacement_card.component.tsx @@ -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 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. + */ +/** @jsx jsx */ + +import { css, jsx } from '@emotion/react'; + +import { + htmlIdGenerator, + EuiButton, + EuiFlexGroup, + EuiFlexItem, + EuiPanel, + EuiText, + EuiAccordion, + EuiLink, + useEuiTheme, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; + +import { CustomIntegration } from '../../../common'; +import { usePlatformService } from '../../services'; + +export interface Props { + replacements: Array>; +} + +// TODO - clintandrewhall: should use doc-links service +const URL_COMPARISON = 'https://ela.st/beats-agent-comparison'; + +const idGenerator = htmlIdGenerator('replacementCard'); +const alsoAvailable = i18n.translate('customIntegrations.components.replacementAccordionLabel', { + defaultMessage: 'Also available in Beats', +}); + +const link = ( + + + +); + +/** + * A pure component, an accordion panel which can display information about replacements for a given EPR module. + */ +export const ReplacementCard = ({ replacements }: Props) => { + const { euiTheme } = useEuiTheme(); + const { getAbsolutePath } = usePlatformService(); + + if (replacements.length === 0) { + return null; + } + + const buttons = replacements.map((replacement) => ( + + + + {replacement.title} + + + + )); + + return ( +

+ + + + + + + + + + + {buttons} + + + + + +
+ ); +}; diff --git a/src/plugins/custom_integrations/public/components/replacement_card/replacement_card.stories.tsx b/src/plugins/custom_integrations/public/components/replacement_card/replacement_card.stories.tsx new file mode 100644 index 0000000000000..8fa0674c9b467 --- /dev/null +++ b/src/plugins/custom_integrations/public/components/replacement_card/replacement_card.stories.tsx @@ -0,0 +1,68 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 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 React from 'react'; +import { Meta } from '@storybook/react'; + +import { ReplacementCard as ConnectedComponent } from './replacement_card'; +import { ReplacementCard as PureComponent } from './replacement_card.component'; + +export default { + title: 'Replacement Card', + description: + 'An accordion panel which can display information about Beats alternatives to a given EPR module, (if available)', + decorators: [ + (storyFn, { globals }) => ( +
+ {storyFn()} +
+ ), + ], +} as Meta; + +interface Args { + eprPackageName: string; +} + +const args: Args = { + eprPackageName: 'nginx', +}; + +const argTypes = { + eprPackageName: { + control: { + type: 'radio', + options: ['nginx', 'okta', 'aws', 'apache'], + }, + }, +}; + +export function ReplacementCard({ eprPackageName }: Args) { + return ; +} + +ReplacementCard.args = args; +ReplacementCard.argTypes = argTypes; + +export function Component() { + return ( + + ); +} diff --git a/src/plugins/custom_integrations/public/components/replacement_card/replacement_card.tsx b/src/plugins/custom_integrations/public/components/replacement_card/replacement_card.tsx new file mode 100644 index 0000000000000..3e829270773a6 --- /dev/null +++ b/src/plugins/custom_integrations/public/components/replacement_card/replacement_card.tsx @@ -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 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 React from 'react'; +import useAsync from 'react-use/lib/useAsync'; +import { useFindService } from '../../services'; + +import { ReplacementCard as Component } from './replacement_card.component'; + +export interface Props { + eprPackageName: string; +} + +/** + * A data-connected component which can query about Beats-based replacement options for a given EPR module. + */ +export const ReplacementCard = ({ eprPackageName }: Props) => { + const { findReplacementIntegrations } = useFindService(); + const integrations = useAsync(async () => { + return await findReplacementIntegrations({ shipper: 'beats', eprPackageName }); + }, [eprPackageName]); + + const { loading, value: replacements } = integrations; + + if (loading || !replacements || replacements.length === 0) { + return null; + } + + return ; +}; diff --git a/src/plugins/custom_integrations/public/index.ts b/src/plugins/custom_integrations/public/index.ts index 9e979dd6692bc..91da75c634a44 100755 --- a/src/plugins/custom_integrations/public/index.ts +++ b/src/plugins/custom_integrations/public/index.ts @@ -13,4 +13,8 @@ import { CustomIntegrationsPlugin } from './plugin'; export function plugin() { return new CustomIntegrationsPlugin(); } + export { CustomIntegrationsSetup, CustomIntegrationsStart } from './types'; + +export { withSuspense, LazyReplacementCard } from './components'; +export { filterCustomIntegrations } from './services/find'; diff --git a/src/plugins/custom_integrations/public/mocks.ts b/src/plugins/custom_integrations/public/mocks.ts index 2e6bc491c2c5c..a8fedbbb712b2 100644 --- a/src/plugins/custom_integrations/public/mocks.ts +++ b/src/plugins/custom_integrations/public/mocks.ts @@ -6,7 +6,11 @@ * Side Public License, v 1. */ -import { CustomIntegrationsSetup } from './types'; +import { pluginServices } from './services'; +import { PluginServiceRegistry } from '../../presentation_util/public'; +import { CustomIntegrationsSetup, CustomIntegrationsStart } from './types'; +import { CustomIntegrationsServices } from './services'; +import { providers } from './services/stub'; function createCustomIntegrationsSetup(): jest.Mocked { const mock: jest.Mocked = { @@ -16,6 +20,17 @@ function createCustomIntegrationsSetup(): jest.Mocked { return mock; } +function createCustomIntegrationsStart(): jest.Mocked { + const registry = new PluginServiceRegistry(providers); + pluginServices.setRegistry(registry.start({})); + const ContextProvider = pluginServices.getContextProvider(); + + return { + ContextProvider: jest.fn(ContextProvider), + }; +} + export const customIntegrationsMock = { createSetup: createCustomIntegrationsSetup, + createStart: createCustomIntegrationsStart, }; diff --git a/src/plugins/custom_integrations/public/plugin.ts b/src/plugins/custom_integrations/public/plugin.ts index 7ea7a829e8072..a3470fefba46c 100755 --- a/src/plugins/custom_integrations/public/plugin.ts +++ b/src/plugins/custom_integrations/public/plugin.ts @@ -7,13 +7,20 @@ */ import { CoreSetup, CoreStart, Plugin } from 'src/core/public'; -import { CustomIntegrationsSetup, CustomIntegrationsStart } from './types'; +import { + CustomIntegrationsSetup, + CustomIntegrationsStart, + CustomIntegrationsStartDependencies, +} from './types'; import { CustomIntegration, ROUTES_APPEND_CUSTOM_INTEGRATIONS, ROUTES_REPLACEMENT_CUSTOM_INTEGRATIONS, } from '../common'; +import { pluginServices } from './services'; +import { pluginServiceRegistry } from './services/kibana'; + export class CustomIntegrationsPlugin implements Plugin { @@ -30,8 +37,14 @@ export class CustomIntegrationsPlugin }; } - public start(core: CoreStart): CustomIntegrationsStart { - return {}; + public start( + coreStart: CoreStart, + startPlugins: CustomIntegrationsStartDependencies + ): CustomIntegrationsStart { + pluginServices.setRegistry(pluginServiceRegistry.start({ coreStart, startPlugins })); + return { + ContextProvider: pluginServices.getContextProvider(), + }; } public stop() {} diff --git a/src/plugins/custom_integrations/public/services/find.test.ts b/src/plugins/custom_integrations/public/services/find.test.ts new file mode 100644 index 0000000000000..df52c22313b68 --- /dev/null +++ b/src/plugins/custom_integrations/public/services/find.test.ts @@ -0,0 +1,95 @@ +/* + * Copyright 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. + */ +/* + * Copyright 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 { filterCustomIntegrations } from './find'; +import { CustomIntegration } from '../../common'; + +describe('Custom Integrations Find Service', () => { + const integrations: CustomIntegration[] = [ + { + id: 'foo', + title: 'Foo', + description: 'test integration', + type: 'ui_link', + uiInternalPath: '/path/to/foo', + isBeta: false, + icons: [], + categories: ['aws', 'cloud'], + shipper: 'tests', + }, + { + id: 'bar', + title: 'Bar', + description: 'test integration', + type: 'ui_link', + uiInternalPath: '/path/to/bar', + isBeta: false, + icons: [], + categories: ['aws'], + shipper: 'other', + eprOverlap: 'eprValue', + }, + { + id: 'bar', + title: 'Bar', + description: 'test integration', + type: 'ui_link', + uiInternalPath: '/path/to/bar', + isBeta: false, + icons: [], + categories: ['cloud'], + shipper: 'other', + eprOverlap: 'eprValue', + }, + { + id: 'baz', + title: 'Baz', + description: 'test integration', + type: 'ui_link', + uiInternalPath: '/path/to/baz', + isBeta: false, + icons: [], + categories: ['cloud'], + shipper: 'tests', + eprOverlap: 'eprOtherValue', + }, + ]; + + describe('filterCustomIntegrations', () => { + test('filters on shipper', () => { + let result = filterCustomIntegrations(integrations, { shipper: 'other' }); + expect(result.length).toBe(2); + result = filterCustomIntegrations(integrations, { shipper: 'tests' }); + expect(result.length).toBe(2); + result = filterCustomIntegrations(integrations, { shipper: 'foobar' }); + expect(result.length).toBe(0); + }); + test('filters on eprOverlap', () => { + let result = filterCustomIntegrations(integrations, { eprPackageName: 'eprValue' }); + expect(result.length).toBe(2); + result = filterCustomIntegrations(integrations, { eprPackageName: 'eprOtherValue' }); + expect(result.length).toBe(1); + result = filterCustomIntegrations(integrations, { eprPackageName: 'otherValue' }); + expect(result.length).toBe(0); + }); + test('filters on categories and shipper, eprOverlap', () => { + const result = filterCustomIntegrations(integrations, { + shipper: 'other', + eprPackageName: 'eprValue', + }); + expect(result.length).toBe(2); + }); + }); +}); diff --git a/src/plugins/custom_integrations/public/services/find.ts b/src/plugins/custom_integrations/public/services/find.ts new file mode 100644 index 0000000000000..4e69327c351b4 --- /dev/null +++ b/src/plugins/custom_integrations/public/services/find.ts @@ -0,0 +1,46 @@ +/* + * Copyright 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 { CustomIntegration } from '../../common'; + +interface FindParams { + eprPackageName?: string; + shipper?: string; +} + +/** + * A plugin service that finds and returns custom integrations. + */ +export interface CustomIntegrationsFindService { + findReplacementIntegrations(params?: FindParams): Promise; + findAppendedIntegrations(params?: FindParams): Promise; +} + +/** + * Filter a set of integrations by eprPackageName, and/or shipper. + */ +export const filterCustomIntegrations = ( + integrations: CustomIntegration[], + { eprPackageName, shipper }: FindParams = {} +) => { + if (!eprPackageName && !shipper) { + return integrations; + } + + let result = integrations; + + if (eprPackageName) { + result = result.filter((integration) => integration.eprOverlap === eprPackageName); + } + + if (shipper) { + result = result.filter((integration) => integration.shipper === shipper); + } + + return result; +}; diff --git a/src/plugins/custom_integrations/public/services/index.ts b/src/plugins/custom_integrations/public/services/index.ts new file mode 100644 index 0000000000000..8a257ee1a2cd7 --- /dev/null +++ b/src/plugins/custom_integrations/public/services/index.ts @@ -0,0 +1,36 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 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 { PluginServices } from '../../../presentation_util/public'; + +import { CustomIntegrationsFindService } from './find'; +import { CustomIntegrationsPlatformService } from './platform'; + +/** + * Services used by the custom integrations plugin. + */ +export interface CustomIntegrationsServices { + find: CustomIntegrationsFindService; + platform: CustomIntegrationsPlatformService; +} + +/** + * The `PluginServices` object for the custom integrations plugin. + * @see /src/plugins/presentation_util/public/services/create/index.ts + */ +export const pluginServices = new PluginServices(); + +/** + * A React hook that provides connections to the `CustomIntegrationsFindService`. + */ +export const useFindService = () => (() => pluginServices.getHooks().find.useService())(); + +/** + * A React hook that provides connections to the `CustomIntegrationsPlatformService`. + */ +export const usePlatformService = () => (() => pluginServices.getHooks().platform.useService())(); diff --git a/src/plugins/custom_integrations/public/services/kibana/find.ts b/src/plugins/custom_integrations/public/services/kibana/find.ts new file mode 100644 index 0000000000000..5fc7626baa1e1 --- /dev/null +++ b/src/plugins/custom_integrations/public/services/kibana/find.ts @@ -0,0 +1,46 @@ +/* + * Copyright 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 { + CustomIntegration, + ROUTES_APPEND_CUSTOM_INTEGRATIONS, + ROUTES_REPLACEMENT_CUSTOM_INTEGRATIONS, +} from '../../../common'; +import { KibanaPluginServiceFactory } from '../../../../presentation_util/public'; + +import { CustomIntegrationsStartDependencies } from '../../types'; +import { CustomIntegrationsFindService, filterCustomIntegrations } from '../find'; + +/** + * A type definition for a factory to produce the `CustomIntegrationsFindService` for use in Kibana. + * @see /src/plugins/presentation_util/public/services/create/factory.ts + */ +export type CustomIntegrationsFindServiceFactory = KibanaPluginServiceFactory< + CustomIntegrationsFindService, + CustomIntegrationsStartDependencies +>; + +/** + * A factory to produce the `CustomIntegrationsFindService` for use in Kibana. + */ +export const findServiceFactory: CustomIntegrationsFindServiceFactory = ({ coreStart }) => ({ + findAppendedIntegrations: async (params) => { + const integrations: CustomIntegration[] = await coreStart.http.get( + ROUTES_APPEND_CUSTOM_INTEGRATIONS + ); + + return filterCustomIntegrations(integrations, params); + }, + findReplacementIntegrations: async (params) => { + const replacements: CustomIntegration[] = await coreStart.http.get( + ROUTES_REPLACEMENT_CUSTOM_INTEGRATIONS + ); + + return filterCustomIntegrations(replacements, params); + }, +}); diff --git a/src/plugins/custom_integrations/public/services/kibana/index.ts b/src/plugins/custom_integrations/public/services/kibana/index.ts new file mode 100644 index 0000000000000..d3cf27b9bc7c0 --- /dev/null +++ b/src/plugins/custom_integrations/public/services/kibana/index.ts @@ -0,0 +1,44 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { + PluginServiceProviders, + PluginServiceProvider, + PluginServiceRegistry, + KibanaPluginServiceParams, +} from '../../../../presentation_util/public'; + +import { CustomIntegrationsServices } from '..'; +import { CustomIntegrationsStartDependencies } from '../../types'; + +import { findServiceFactory } from './find'; +import { platformServiceFactory } from './platform'; + +export { findServiceFactory } from './find'; +export { platformServiceFactory } from './platform'; + +/** + * A set of `PluginServiceProvider`s for use in Kibana. + * @see /src/plugins/presentation_util/public/services/create/provider.tsx + */ +export const pluginServiceProviders: PluginServiceProviders< + CustomIntegrationsServices, + KibanaPluginServiceParams +> = { + find: new PluginServiceProvider(findServiceFactory), + platform: new PluginServiceProvider(platformServiceFactory), +}; + +/** + * A `PluginServiceRegistry` for use in Kibana. + * @see /src/plugins/presentation_util/public/services/create/registry.tsx + */ +export const pluginServiceRegistry = new PluginServiceRegistry< + CustomIntegrationsServices, + KibanaPluginServiceParams +>(pluginServiceProviders); diff --git a/src/plugins/custom_integrations/public/services/kibana/platform.ts b/src/plugins/custom_integrations/public/services/kibana/platform.ts new file mode 100644 index 0000000000000..e6fe89b68c975 --- /dev/null +++ b/src/plugins/custom_integrations/public/services/kibana/platform.ts @@ -0,0 +1,31 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 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 { KibanaPluginServiceFactory } from '../../../../presentation_util/public'; + +import type { CustomIntegrationsPlatformService } from '../platform'; +import type { CustomIntegrationsStartDependencies } from '../../types'; + +/** + * A type definition for a factory to produce the `CustomIntegrationsPlatformService` for use in Kibana. + * @see /src/plugins/presentation_util/public/services/create/factory.ts + */ +export type CustomIntegrationsPlatformServiceFactory = KibanaPluginServiceFactory< + CustomIntegrationsPlatformService, + CustomIntegrationsStartDependencies +>; + +/** + * A factory to produce the `CustomIntegrationsPlatformService` for use in Kibana. + */ +export const platformServiceFactory: CustomIntegrationsPlatformServiceFactory = ({ + coreStart, +}) => ({ + getBasePath: coreStart.http.basePath.get, + getAbsolutePath: (path: string): string => coreStart.http.basePath.prepend(`${path}`), +}); diff --git a/src/plugins/custom_integrations/public/services/platform.ts b/src/plugins/custom_integrations/public/services/platform.ts new file mode 100644 index 0000000000000..0eb9c7d5c3c10 --- /dev/null +++ b/src/plugins/custom_integrations/public/services/platform.ts @@ -0,0 +1,12 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export interface CustomIntegrationsPlatformService { + getBasePath: () => string; + getAbsolutePath: (path: string) => string; +} diff --git a/src/plugins/custom_integrations/public/services/storybook/index.ts b/src/plugins/custom_integrations/public/services/storybook/index.ts new file mode 100644 index 0000000000000..4dfed1b37e294 --- /dev/null +++ b/src/plugins/custom_integrations/public/services/storybook/index.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 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 { + PluginServiceProviders, + PluginServiceProvider, + PluginServiceRegistry, +} from '../../../../presentation_util/public'; + +import { CustomIntegrationsServices } from '..'; +import { findServiceFactory } from '../stub/find'; +import { platformServiceFactory } from '../stub/platform'; + +export { findServiceFactory } from '../stub/find'; +export { platformServiceFactory } from '../stub/platform'; + +/** + * A set of `PluginServiceProvider`s for use in Storybook. + * @see /src/plugins/presentation_util/public/services/create/provider.tsx + */ +export const providers: PluginServiceProviders = { + find: new PluginServiceProvider(findServiceFactory), + platform: new PluginServiceProvider(platformServiceFactory), +}; + +/** + * A `PluginServiceRegistry` for use in Storybook. + * @see /src/plugins/presentation_util/public/services/create/registry.tsx + */ +export const registry = new PluginServiceRegistry(providers); diff --git a/src/plugins/custom_integrations/public/services/stub/find.ts b/src/plugins/custom_integrations/public/services/stub/find.ts new file mode 100644 index 0000000000000..08def4e63471d --- /dev/null +++ b/src/plugins/custom_integrations/public/services/stub/find.ts @@ -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 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 { PluginServiceFactory } from '../../../../presentation_util/public'; + +import { CustomIntegrationsFindService, filterCustomIntegrations } from '../find'; + +/** + * A type definition for a factory to produce the `CustomIntegrationsFindService` with stubbed output. + * @see /src/plugins/presentation_util/public/services/create/factory.ts + */ +export type CustomIntegrationsFindServiceFactory = + PluginServiceFactory; + +/** + * A factory to produce the `CustomIntegrationsFindService` with stubbed output. + */ +export const findServiceFactory: CustomIntegrationsFindServiceFactory = () => ({ + findAppendedIntegrations: async (params) => { + const { integrations } = await import('./fixtures/integrations'); + return filterCustomIntegrations(integrations, params); + }, + findReplacementIntegrations: async (params) => { + const { integrations } = await import('./fixtures/integrations'); + return filterCustomIntegrations(integrations, params); + }, +}); diff --git a/src/plugins/custom_integrations/public/services/stub/fixtures/integrations.ts b/src/plugins/custom_integrations/public/services/stub/fixtures/integrations.ts new file mode 100644 index 0000000000000..7553deada9e26 --- /dev/null +++ b/src/plugins/custom_integrations/public/services/stub/fixtures/integrations.ts @@ -0,0 +1,1884 @@ +/* + * Copyright 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 { CustomIntegration } from '../../../../common'; + +export const integrations: CustomIntegration[] = [ + { + type: 'ui_link', + id: 'System logs', + title: 'System logs', + categories: ['os_system', 'security'], + uiInternalPath: '/app/home#/tutorial/systemLogs', + description: 'Collect system logs of common Unix/Linux based distributions.', + icons: [ + { + type: 'eui', + src: 'logoLogging', + }, + ], + shipper: 'beats', + eprOverlap: 'system', + isBeta: false, + }, + { + type: 'ui_link', + id: 'System metrics', + title: 'System metrics', + categories: ['os_system', 'security'], + uiInternalPath: '/app/home#/tutorial/systemMetrics', + description: 'Collect CPU, memory, network, and disk statistics from the host.', + icons: [ + { + type: 'svg', + src: '/dqo/plugins/home/assets/logos/system.svg', + }, + ], + shipper: 'beats', + eprOverlap: 'system', + isBeta: false, + }, + { + type: 'ui_link', + id: 'Apache logs', + title: 'Apache logs', + categories: ['web'], + uiInternalPath: '/app/home#/tutorial/apacheLogs', + description: 'Collect and parse access and error logs created by the Apache HTTP server.', + icons: [ + { + type: 'eui', + src: 'logoApache', + }, + ], + shipper: 'beats', + eprOverlap: 'apache', + isBeta: false, + }, + { + type: 'ui_link', + id: 'Apache metrics', + title: 'Apache metrics', + categories: ['web'], + uiInternalPath: '/app/home#/tutorial/apacheMetrics', + description: 'Fetch internal metrics from the Apache 2 HTTP server.', + icons: [ + { + type: 'eui', + src: 'logoApache', + }, + ], + shipper: 'beats', + eprOverlap: 'apache', + isBeta: false, + }, + { + type: 'ui_link', + id: 'Elasticsearch logs', + title: 'Elasticsearch logs', + categories: ['containers', 'os_system'], + uiInternalPath: '/app/home#/tutorial/elasticsearchLogs', + description: 'Collect and parse logs created by Elasticsearch.', + icons: [ + { + type: 'eui', + src: 'logoElasticsearch', + }, + ], + shipper: 'beats', + eprOverlap: 'elasticsearch', + isBeta: false, + }, + { + type: 'ui_link', + id: 'IIS logs', + title: 'IIS logs', + categories: ['web'], + uiInternalPath: '/app/home#/tutorial/iisLogs', + description: 'Collect and parse access and error logs created by the IIS HTTP server.', + icons: [ + { + type: 'svg', + src: '/dqo/plugins/home/assets/logos/iis.svg', + }, + ], + shipper: 'beats', + eprOverlap: 'iis', + isBeta: false, + }, + { + type: 'ui_link', + id: 'Kafka logs', + title: 'Kafka logs', + categories: ['message_queue'], + uiInternalPath: '/app/home#/tutorial/kafkaLogs', + description: 'Collect and parse logs created by Kafka.', + icons: [ + { + type: 'eui', + src: 'logoKafka', + }, + ], + shipper: 'beats', + eprOverlap: 'kafka', + isBeta: false, + }, + { + type: 'ui_link', + id: 'Logstash logs', + title: 'Logstash logs', + categories: ['custom'], + uiInternalPath: '/app/home#/tutorial/logstashLogs', + description: 'Collect Logstash main and slow logs.', + icons: [ + { + type: 'eui', + src: 'logoLogstash', + }, + ], + shipper: 'beats', + eprOverlap: 'logstash', + isBeta: false, + }, + { + type: 'ui_link', + id: 'Nginx logs', + title: 'Nginx logs', + categories: ['web', 'security'], + uiInternalPath: '/app/home#/tutorial/nginxLogs', + description: 'Collect and parse access and error logs created by the Nginx HTTP server.', + icons: [ + { + type: 'eui', + src: 'logoNginx', + }, + ], + shipper: 'beats', + eprOverlap: 'nginx', + isBeta: false, + }, + { + type: 'ui_link', + id: 'Nginx metrics', + title: 'Nginx metrics', + categories: ['web', 'security'], + uiInternalPath: '/app/home#/tutorial/nginxMetrics', + description: 'Fetch internal metrics from the Nginx HTTP server.', + icons: [ + { + type: 'eui', + src: 'logoNginx', + }, + ], + shipper: 'beats', + eprOverlap: 'nginx', + isBeta: false, + }, + { + type: 'ui_link', + id: 'MySQL logs', + title: 'MySQL logs', + categories: ['datastore'], + uiInternalPath: '/app/home#/tutorial/mysqlLogs', + description: 'Collect and parse error and slow logs created by MySQL.', + icons: [ + { + type: 'eui', + src: 'logoMySQL', + }, + ], + shipper: 'beats', + eprOverlap: 'mysql', + isBeta: false, + }, + { + type: 'ui_link', + id: 'MySQL metrics', + title: 'MySQL metrics', + categories: ['datastore'], + uiInternalPath: '/app/home#/tutorial/mysqlMetrics', + description: 'Fetch internal metrics from MySQL.', + icons: [ + { + type: 'eui', + src: 'logoMySQL', + }, + ], + shipper: 'beats', + eprOverlap: 'mysql', + isBeta: false, + }, + { + type: 'ui_link', + id: 'MongoDB metrics', + title: 'MongoDB metrics', + categories: ['datastore'], + uiInternalPath: '/app/home#/tutorial/mongodbMetrics', + description: 'Fetch internal metrics from MongoDB.', + icons: [ + { + type: 'eui', + src: 'logoMongodb', + }, + ], + shipper: 'beats', + eprOverlap: 'mongodb', + isBeta: false, + }, + { + type: 'ui_link', + id: 'Osquery logs', + title: 'Osquery logs', + categories: ['security', 'os_system'], + uiInternalPath: '/app/home#/tutorial/osqueryLogs', + description: 'Collect osquery logs in JSON format.', + icons: [ + { + type: 'svg', + src: '/dqo/plugins/home/assets/logos/osquery.svg', + }, + ], + shipper: 'beats', + eprOverlap: 'osquery', + isBeta: false, + }, + { + type: 'ui_link', + id: 'PHP-FPM metrics', + title: 'PHP-FPM metrics', + categories: ['security'], + uiInternalPath: '/app/home#/tutorial/phpfpmMetrics', + description: 'Fetch internal metrics from PHP-FPM.', + icons: [ + { + type: 'eui', + src: 'logoPhp', + }, + ], + shipper: 'beats', + eprOverlap: 'php_fpm', + isBeta: false, + }, + { + type: 'ui_link', + id: 'PostgreSQL metrics', + title: 'PostgreSQL metrics', + categories: ['datastore'], + uiInternalPath: '/app/home#/tutorial/postgresqlMetrics', + description: 'Fetch internal metrics from PostgreSQL.', + icons: [ + { + type: 'eui', + src: 'logoPostgres', + }, + ], + shipper: 'beats', + eprOverlap: 'postgresql', + isBeta: false, + }, + { + type: 'ui_link', + id: 'PostgreSQL logs', + title: 'PostgreSQL logs', + categories: ['datastore'], + uiInternalPath: '/app/home#/tutorial/postgresqlLogs', + description: 'Collect and parse error and slow logs created by PostgreSQL.', + icons: [ + { + type: 'eui', + src: 'logoPostgres', + }, + ], + shipper: 'beats', + eprOverlap: 'postgresql', + isBeta: false, + }, + { + type: 'ui_link', + id: 'RabbitMQ metrics', + title: 'RabbitMQ metrics', + categories: ['message_queue'], + uiInternalPath: '/app/home#/tutorial/rabbitmqMetrics', + description: 'Fetch internal metrics from the RabbitMQ server.', + icons: [ + { + type: 'eui', + src: 'logoRabbitmq', + }, + ], + shipper: 'beats', + eprOverlap: 'rabbitmq', + isBeta: false, + }, + { + type: 'ui_link', + id: 'Redis logs', + title: 'Redis logs', + categories: ['datastore', 'message_queue'], + uiInternalPath: '/app/home#/tutorial/redisLogs', + description: 'Collect and parse error and slow logs created by Redis.', + icons: [ + { + type: 'eui', + src: 'logoRedis', + }, + ], + shipper: 'beats', + eprOverlap: 'redis', + isBeta: false, + }, + { + type: 'ui_link', + id: 'Redis metrics', + title: 'Redis metrics', + categories: ['datastore', 'message_queue'], + uiInternalPath: '/app/home#/tutorial/redisMetrics', + description: 'Fetch internal metrics from Redis.', + icons: [ + { + type: 'eui', + src: 'logoRedis', + }, + ], + shipper: 'beats', + eprOverlap: 'redis', + isBeta: false, + }, + { + type: 'ui_link', + id: 'Suricata logs', + title: 'Suricata logs', + categories: ['network', 'security'], + uiInternalPath: '/app/home#/tutorial/suricataLogs', + description: 'Collect Suricata IDS/IPS/NSM logs.', + icons: [ + { + type: 'svg', + src: '/dqo/plugins/home/assets/logos/suricata.svg', + }, + ], + shipper: 'beats', + eprOverlap: 'suricata', + isBeta: false, + }, + { + type: 'ui_link', + id: 'Docker metrics', + title: 'Docker metrics', + categories: ['containers', 'os_system'], + uiInternalPath: '/app/home#/tutorial/dockerMetrics', + description: 'Fetch metrics about your Docker containers.', + icons: [ + { + type: 'eui', + src: 'logoDocker', + }, + ], + shipper: 'beats', + eprOverlap: 'docker', + isBeta: false, + }, + { + type: 'ui_link', + id: 'Kubernetes metrics', + title: 'Kubernetes metrics', + categories: ['containers', 'kubernetes'], + uiInternalPath: '/app/home#/tutorial/kubernetesMetrics', + description: 'Fetch metrics from your Kubernetes installation.', + icons: [ + { + type: 'eui', + src: 'logoKubernetes', + }, + ], + shipper: 'beats', + eprOverlap: 'kubernetes', + isBeta: false, + }, + { + type: 'ui_link', + id: 'uWSGI metrics', + title: 'uWSGI metrics', + categories: ['web', 'security'], + uiInternalPath: '/app/home#/tutorial/uwsgiMetrics', + description: 'Fetch internal metrics from the uWSGI server.', + icons: [ + { + type: 'svg', + src: '/dqo/plugins/home/assets/logos/uwsgi.svg', + }, + ], + shipper: 'beats', + eprOverlap: 'uwsgi', + isBeta: false, + }, + { + type: 'ui_link', + id: 'NetFlow / IPFIX Collector', + title: 'NetFlow / IPFIX Collector', + categories: ['network', 'security'], + uiInternalPath: '/app/home#/tutorial/netflowLogs', + description: 'Collect NetFlow and IPFIX flow records.', + icons: [ + { + type: 'eui', + src: 'logoBeats', + }, + ], + shipper: 'beats', + eprOverlap: 'netflow', + isBeta: false, + }, + { + type: 'ui_link', + id: 'Traefik logs', + title: 'Traefik logs', + categories: ['web', 'security'], + uiInternalPath: '/app/home#/tutorial/traefikLogs', + description: 'Collect Traefik access logs.', + icons: [ + { + type: 'svg', + src: '/dqo/plugins/home/assets/logos/traefik.svg', + }, + ], + shipper: 'beats', + eprOverlap: 'traefik', + isBeta: false, + }, + { + type: 'ui_link', + id: 'Ceph metrics', + title: 'Ceph metrics', + categories: ['network', 'security'], + uiInternalPath: '/app/home#/tutorial/cephMetrics', + description: 'Fetch internal metrics from the Ceph server.', + icons: [ + { + type: 'eui', + src: 'logoCeph', + }, + ], + shipper: 'beats', + eprOverlap: 'ceph', + isBeta: false, + }, + { + type: 'ui_link', + id: 'Aerospike metrics', + title: 'Aerospike metrics', + categories: ['web'], + uiInternalPath: '/app/home#/tutorial/aerospikeMetrics', + description: 'Fetch internal metrics from the Aerospike server.', + icons: [ + { + type: 'eui', + src: 'logoAerospike', + }, + ], + shipper: 'beats', + eprOverlap: 'aerospike', + isBeta: false, + }, + { + type: 'ui_link', + id: 'Couchbase metrics', + title: 'Couchbase metrics', + categories: ['security', 'network', 'web'], + uiInternalPath: '/app/home#/tutorial/couchbaseMetrics', + description: 'Fetch internal metrics from Couchbase.', + icons: [ + { + type: 'eui', + src: 'logoCouchbase', + }, + ], + shipper: 'beats', + eprOverlap: 'couchbase', + isBeta: false, + }, + { + type: 'ui_link', + id: 'Dropwizard metrics', + title: 'Dropwizard metrics', + categories: ['elastic_stack', 'datastore'], + uiInternalPath: '/app/home#/tutorial/dropwizardMetrics', + description: 'Fetch internal metrics from Dropwizard Java application.', + icons: [ + { + type: 'eui', + src: 'logoDropwizard', + }, + ], + shipper: 'beats', + eprOverlap: 'dropwizard', + isBeta: false, + }, + { + type: 'ui_link', + id: 'Elasticsearch metrics', + title: 'Elasticsearch metrics', + categories: ['elastic_stack', 'datastore'], + uiInternalPath: '/app/home#/tutorial/elasticsearchMetrics', + description: 'Fetch internal metrics from Elasticsearch.', + icons: [ + { + type: 'eui', + src: 'logoElasticsearch', + }, + ], + shipper: 'beats', + eprOverlap: 'elasticsearch', + isBeta: false, + }, + { + type: 'ui_link', + id: 'Etcd metrics', + title: 'Etcd metrics', + categories: ['elastic_stack', 'datastore'], + uiInternalPath: '/app/home#/tutorial/etcdMetrics', + description: 'Fetch internal metrics from the Etcd server.', + icons: [ + { + type: 'eui', + src: 'logoEtcd', + }, + ], + shipper: 'beats', + eprOverlap: 'etcd', + isBeta: false, + }, + { + type: 'ui_link', + id: 'HAProxy metrics', + title: 'HAProxy metrics', + categories: ['network', 'web'], + uiInternalPath: '/app/home#/tutorial/haproxyMetrics', + description: 'Fetch internal metrics from the HAProxy server.', + icons: [ + { + type: 'eui', + src: 'logoHAproxy', + }, + ], + shipper: 'beats', + eprOverlap: 'haproxy', + isBeta: false, + }, + { + type: 'ui_link', + id: 'Kafka metrics', + title: 'Kafka metrics', + categories: ['message_queue'], + uiInternalPath: '/app/home#/tutorial/kafkaMetrics', + description: 'Fetch internal metrics from the Kafka server.', + icons: [ + { + type: 'eui', + src: 'logoKafka', + }, + ], + shipper: 'beats', + eprOverlap: 'kafka', + isBeta: false, + }, + { + type: 'ui_link', + id: 'Kibana metrics', + title: 'Kibana metrics', + categories: ['message_queue'], + uiInternalPath: '/app/home#/tutorial/kibanaMetrics', + description: 'Fetch internal metrics from Kibana.', + icons: [ + { + type: 'eui', + src: 'logoKibana', + }, + ], + shipper: 'beats', + eprOverlap: 'kibana', + isBeta: false, + }, + { + type: 'ui_link', + id: 'Memcached metrics', + title: 'Memcached metrics', + categories: ['custom'], + uiInternalPath: '/app/home#/tutorial/memcachedMetrics', + description: 'Fetch internal metrics from the Memcached server.', + icons: [ + { + type: 'eui', + src: 'logoMemcached', + }, + ], + shipper: 'beats', + eprOverlap: 'memcached', + isBeta: false, + }, + { + type: 'ui_link', + id: 'Munin metrics', + title: 'Munin metrics', + categories: ['datastore'], + uiInternalPath: '/app/home#/tutorial/muninMetrics', + description: 'Fetch internal metrics from the Munin server.', + icons: [ + { + type: 'svg', + src: '/dqo/plugins/home/assets/logos/munin.svg', + }, + ], + shipper: 'beats', + eprOverlap: 'munin', + isBeta: false, + }, + { + type: 'ui_link', + id: 'vSphere metrics', + title: 'vSphere metrics', + categories: ['web', 'security'], + uiInternalPath: '/app/home#/tutorial/vsphereMetrics', + description: 'Fetch internal metrics from vSphere.', + icons: [ + { + type: 'svg', + src: '/dqo/plugins/home/assets/logos/vsphere.svg', + }, + ], + shipper: 'beats', + eprOverlap: 'vsphere', + isBeta: false, + }, + { + type: 'ui_link', + id: 'Windows metrics', + title: 'Windows metrics', + categories: ['os_system', 'security'], + uiInternalPath: '/app/home#/tutorial/windowsMetrics', + description: 'Fetch internal metrics from Windows.', + icons: [ + { + type: 'eui', + src: 'logoWindows', + }, + ], + shipper: 'beats', + eprOverlap: 'windows', + isBeta: false, + }, + { + type: 'ui_link', + id: 'Windows Event Log', + title: 'Windows Event Log', + categories: ['os_system', 'security'], + uiInternalPath: '/app/home#/tutorial/windowsEventLogs', + description: 'Fetch logs from the Windows Event Log.', + icons: [ + { + type: 'eui', + src: 'logoWindows', + }, + ], + shipper: 'beats', + eprOverlap: 'windows', + isBeta: false, + }, + { + type: 'ui_link', + id: 'Golang metrics', + title: 'Golang metrics', + categories: ['google_cloud', 'cloud', 'network', 'security'], + uiInternalPath: '/app/home#/tutorial/golangMetrics', + description: 'Fetch internal metrics from a Golang app.', + icons: [ + { + type: 'eui', + src: 'logoGolang', + }, + ], + shipper: 'beats', + eprOverlap: 'golang', + isBeta: false, + }, + { + type: 'ui_link', + id: 'Logstash metrics', + title: 'Logstash metrics', + categories: ['custom'], + uiInternalPath: '/app/home#/tutorial/logstashMetrics', + description: 'Fetch internal metrics from a Logstash server.', + icons: [ + { + type: 'eui', + src: 'logoLogstash', + }, + ], + shipper: 'beats', + eprOverlap: 'logstash', + isBeta: false, + }, + { + type: 'ui_link', + id: 'Prometheus metrics', + title: 'Prometheus metrics', + categories: ['monitoring', 'datastore'], + uiInternalPath: '/app/home#/tutorial/prometheusMetrics', + description: 'Fetch metrics from a Prometheus exporter.', + icons: [ + { + type: 'eui', + src: 'logoPrometheus', + }, + ], + shipper: 'beats', + eprOverlap: 'prometheus', + isBeta: false, + }, + { + type: 'ui_link', + id: 'Zookeeper metrics', + title: 'Zookeeper metrics', + categories: ['datastore', 'config_management'], + uiInternalPath: '/app/home#/tutorial/zookeeperMetrics', + description: 'Fetch internal metrics from a Zookeeper server.', + icons: [ + { + type: 'svg', + src: '/dqo/plugins/home/assets/logos/zookeeper.svg', + }, + ], + shipper: 'beats', + eprOverlap: 'zookeeper', + isBeta: false, + }, + { + type: 'ui_link', + id: 'Uptime Monitors', + title: 'Uptime Monitors', + categories: ['web', 'security'], + uiInternalPath: '/app/home#/tutorial/uptimeMonitors', + description: 'Monitor services for their availability', + icons: [ + { + type: 'eui', + src: 'uptimeApp', + }, + ], + shipper: 'beats', + eprOverlap: 'uptime', + isBeta: false, + }, + { + type: 'ui_link', + id: 'AWS Cloudwatch logs', + title: 'AWS Cloudwatch logs', + categories: ['security', 'network', 'web'], + uiInternalPath: '/app/home#/tutorial/cloudwatchLogs', + description: 'Collect Cloudwatch logs with Functionbeat.', + icons: [ + { + type: 'eui', + src: 'logoAWS', + }, + ], + shipper: 'beats', + eprOverlap: 'aws', + isBeta: false, + }, + { + type: 'ui_link', + id: 'AWS metrics', + title: 'AWS metrics', + categories: ['aws', 'cloud', 'datastore', 'security', 'network'], + uiInternalPath: '/app/home#/tutorial/awsMetrics', + description: 'Fetch monitoring metrics for EC2 instances from the AWS APIs and Cloudwatch.', + icons: [ + { + type: 'eui', + src: 'logoAWS', + }, + ], + shipper: 'beats', + eprOverlap: 'aws', + isBeta: false, + }, + { + type: 'ui_link', + id: 'Microsoft SQL Server Metrics', + title: 'Microsoft SQL Server Metrics', + categories: ['datastore'], + uiInternalPath: '/app/home#/tutorial/mssqlMetrics', + description: 'Fetch monitoring metrics from a Microsoft SQL Server instance', + icons: [ + { + type: 'svg', + src: '/dqo/plugins/home/assets/logos/mssql.svg', + }, + ], + shipper: 'beats', + eprOverlap: 'mssql', + isBeta: false, + }, + { + type: 'ui_link', + id: 'NATS metrics', + title: 'NATS metrics', + categories: ['message_queue'], + uiInternalPath: '/app/home#/tutorial/natsMetrics', + description: 'Fetch monitoring metrics from the Nats server.', + icons: [ + { + type: 'svg', + src: '/dqo/plugins/home/assets/logos/nats.svg', + }, + ], + shipper: 'beats', + eprOverlap: 'nats', + isBeta: false, + }, + { + type: 'ui_link', + id: 'NATS logs', + title: 'NATS logs', + categories: ['message_queue'], + uiInternalPath: '/app/home#/tutorial/natsLogs', + description: 'Collect and parse logs created by Nats.', + icons: [ + { + type: 'svg', + src: '/dqo/plugins/home/assets/logos/nats.svg', + }, + ], + shipper: 'beats', + eprOverlap: 'nats', + isBeta: false, + }, + { + type: 'ui_link', + id: 'Zeek logs', + title: 'Zeek logs', + categories: ['network', 'monitoring', 'security'], + uiInternalPath: '/app/home#/tutorial/zeekLogs', + description: 'Collect Zeek network security monitoring logs.', + icons: [ + { + type: 'svg', + src: '/dqo/plugins/home/assets/logos/zeek.svg', + }, + ], + shipper: 'beats', + eprOverlap: 'zeek', + isBeta: false, + }, + { + type: 'ui_link', + id: 'CoreDNS metrics', + title: 'CoreDNS metrics', + categories: ['security', 'network', 'web'], + uiInternalPath: '/app/home#/tutorial/corednsMetrics', + description: 'Fetch monitoring metrics from the CoreDNS server.', + icons: [ + { + type: 'svg', + src: '/dqo/plugins/home/assets/logos/coredns.svg', + }, + ], + shipper: 'beats', + eprOverlap: 'coredns', + isBeta: false, + }, + { + type: 'ui_link', + id: 'CoreDNS logs', + title: 'CoreDNS logs', + categories: ['security', 'network', 'web'], + uiInternalPath: '/app/home#/tutorial/corednsLogs', + description: 'Collect CoreDNS logs.', + icons: [ + { + type: 'svg', + src: '/dqo/plugins/home/assets/logos/coredns.svg', + }, + ], + shipper: 'beats', + eprOverlap: 'coredns', + isBeta: false, + }, + { + type: 'ui_link', + id: 'Auditbeat', + title: 'Auditbeat', + categories: ['web'], + uiInternalPath: '/app/home#/tutorial/auditbeat', + description: 'Collect audit data from your hosts.', + icons: [ + { + type: 'eui', + src: 'securityAnalyticsApp', + }, + ], + shipper: 'beats', + eprOverlap: 'auditbeat', + isBeta: false, + }, + { + type: 'ui_link', + id: 'Iptables logs', + title: 'Iptables logs', + categories: ['network', 'security'], + uiInternalPath: '/app/home#/tutorial/iptablesLogs', + description: 'Collect iptables and ip6tables logs.', + icons: [ + { + type: 'svg', + src: '/dqo/plugins/home/assets/logos/linux.svg', + }, + ], + shipper: 'beats', + eprOverlap: 'iptables', + isBeta: false, + }, + { + type: 'ui_link', + id: 'Cisco logs', + title: 'Cisco logs', + categories: ['network', 'security'], + uiInternalPath: '/app/home#/tutorial/ciscoLogs', + description: 'Collect Cisco network device logs over syslog or from a file.', + icons: [ + { + type: 'svg', + src: '/dqo/plugins/home/assets/logos/cisco.svg', + }, + ], + shipper: 'beats', + eprOverlap: 'cisco', + isBeta: false, + }, + { + type: 'ui_link', + id: 'Envoy Proxy logs', + title: 'Envoy Proxy logs', + categories: ['elastic_stack', 'datastore'], + uiInternalPath: '/app/home#/tutorial/envoyproxyLogs', + description: 'Collect Envoy Proxy logs.', + icons: [ + { + type: 'svg', + src: '/dqo/plugins/home/assets/logos/envoyproxy.svg', + }, + ], + shipper: 'beats', + eprOverlap: 'envoyproxy', + isBeta: false, + }, + { + type: 'ui_link', + id: 'CouchDB metrics', + title: 'CouchDB metrics', + categories: ['security', 'network', 'web'], + uiInternalPath: '/app/home#/tutorial/couchdbMetrics', + description: 'Fetch monitoring metrics from the CouchdB server.', + icons: [ + { + type: 'svg', + src: '/dqo/plugins/home/assets/logos/couchdb.svg', + }, + ], + shipper: 'beats', + eprOverlap: 'couchdb', + isBeta: false, + }, + { + type: 'ui_link', + id: 'Consul metrics', + title: 'Consul metrics', + categories: ['security', 'network', 'web'], + uiInternalPath: '/app/home#/tutorial/consulMetrics', + description: 'Fetch monitoring metrics from the Consul server.', + icons: [ + { + type: 'svg', + src: '/dqo/plugins/home/assets/logos/consul.svg', + }, + ], + shipper: 'beats', + eprOverlap: 'consul', + isBeta: false, + }, + { + type: 'ui_link', + id: 'CockroachDB metrics', + title: 'CockroachDB metrics', + categories: ['security', 'network', 'web'], + uiInternalPath: '/app/home#/tutorial/cockroachdbMetrics', + description: 'Fetch monitoring metrics from the CockroachDB server.', + icons: [ + { + type: 'svg', + src: '/dqo/plugins/home/assets/logos/cockroachdb.svg', + }, + ], + shipper: 'beats', + eprOverlap: 'cockroachdb', + isBeta: false, + }, + { + type: 'ui_link', + id: 'Traefik metrics', + title: 'Traefik metrics', + categories: ['web', 'security'], + uiInternalPath: '/app/home#/tutorial/traefikMetrics', + description: 'Fetch monitoring metrics from Traefik.', + icons: [ + { + type: 'svg', + src: '/dqo/plugins/home/assets/logos/traefik.svg', + }, + ], + shipper: 'beats', + eprOverlap: 'traefik', + isBeta: false, + }, + { + type: 'ui_link', + id: 'AWS S3 based logs', + title: 'AWS S3 based logs', + categories: ['aws', 'cloud', 'datastore', 'security', 'network'], + uiInternalPath: '/app/home#/tutorial/awsLogs', + description: 'Collect AWS logs from S3 bucket with Filebeat.', + icons: [ + { + type: 'eui', + src: 'logoAWS', + }, + ], + shipper: 'beats', + eprOverlap: 'aws', + isBeta: false, + }, + { + type: 'ui_link', + id: 'ActiveMQ logs', + title: 'ActiveMQ logs', + categories: ['web'], + uiInternalPath: '/app/home#/tutorial/activemqLogs', + description: 'Collect ActiveMQ logs with Filebeat.', + icons: [ + { + type: 'svg', + src: '/dqo/plugins/home/assets/logos/activemq.svg', + }, + ], + shipper: 'beats', + eprOverlap: 'activemq', + isBeta: false, + }, + { + type: 'ui_link', + id: 'ActiveMQ metrics', + title: 'ActiveMQ metrics', + categories: ['web'], + uiInternalPath: '/app/home#/tutorial/activemqMetrics', + description: 'Fetch monitoring metrics from ActiveMQ instances.', + icons: [ + { + type: 'svg', + src: '/dqo/plugins/home/assets/logos/activemq.svg', + }, + ], + shipper: 'beats', + eprOverlap: 'activemq', + isBeta: false, + }, + { + type: 'ui_link', + id: 'Azure metrics', + title: 'Azure metrics', + categories: ['azure', 'cloud', 'network', 'security'], + uiInternalPath: '/app/home#/tutorial/azureMetrics', + description: 'Fetch Azure Monitor metrics.', + icons: [ + { + type: 'eui', + src: 'logoAzure', + }, + ], + shipper: 'beats', + eprOverlap: 'azure', + isBeta: false, + }, + { + type: 'ui_link', + id: 'IBM MQ logs', + title: 'IBM MQ logs', + categories: ['security'], + uiInternalPath: '/app/home#/tutorial/ibmmqLogs', + description: 'Collect IBM MQ logs with Filebeat.', + icons: [ + { + type: 'svg', + src: '/dqo/plugins/home/assets/logos/ibmmq.svg', + }, + ], + shipper: 'beats', + eprOverlap: 'ibmmq', + isBeta: false, + }, + { + type: 'ui_link', + id: 'IBM MQ metrics', + title: 'IBM MQ metrics', + categories: ['security'], + uiInternalPath: '/app/home#/tutorial/ibmmqMetrics', + description: 'Fetch monitoring metrics from IBM MQ instances.', + icons: [ + { + type: 'svg', + src: '/dqo/plugins/home/assets/logos/ibmmq.svg', + }, + ], + shipper: 'beats', + eprOverlap: 'ibmmq', + isBeta: false, + }, + { + type: 'ui_link', + id: 'STAN metrics', + title: 'STAN metrics', + categories: ['message_queue', 'kubernetes'], + uiInternalPath: '/app/home#/tutorial/stanMetrics', + description: 'Fetch monitoring metrics from the STAN server.', + icons: [ + { + type: 'svg', + src: '/dqo/plugins/home/assets/logos/stan.svg', + }, + ], + shipper: 'beats', + eprOverlap: 'stan', + isBeta: false, + }, + { + type: 'ui_link', + id: 'Envoy Proxy metrics', + title: 'Envoy Proxy metrics', + categories: ['elastic_stack', 'datastore'], + uiInternalPath: '/app/home#/tutorial/envoyproxyMetrics', + description: 'Fetch monitoring metrics from Envoy Proxy.', + icons: [ + { + type: 'svg', + src: '/dqo/plugins/home/assets/logos/envoyproxy.svg', + }, + ], + shipper: 'beats', + eprOverlap: 'envoyproxy', + isBeta: false, + }, + { + type: 'ui_link', + id: 'Statsd metrics', + title: 'Statsd metrics', + categories: ['message_queue', 'kubernetes'], + uiInternalPath: '/app/home#/tutorial/statsdMetrics', + description: 'Fetch monitoring metrics from statsd.', + icons: [ + { + type: 'svg', + src: '/dqo/plugins/home/assets/logos/statsd.svg', + }, + ], + shipper: 'beats', + eprOverlap: 'statsd', + isBeta: false, + }, + { + type: 'ui_link', + id: 'Redis Enterprise metrics', + title: 'Redis Enterprise metrics', + categories: ['datastore', 'message_queue'], + uiInternalPath: '/app/home#/tutorial/redisenterpriseMetrics', + description: 'Fetch monitoring metrics from Redis Enterprise Server.', + icons: [ + { + type: 'eui', + src: 'logoRedis', + }, + ], + shipper: 'beats', + eprOverlap: 'redisenterprise', + isBeta: false, + }, + { + type: 'ui_link', + id: 'OpenMetrics metrics', + title: 'OpenMetrics metrics', + categories: ['security'], + uiInternalPath: '/app/home#/tutorial/openmetricsMetrics', + description: 'Fetch metrics from an endpoint that serves metrics in OpenMetrics format.', + icons: [ + { + type: 'svg', + src: '/dqo/plugins/home/assets/logos/openmetrics.svg', + }, + ], + shipper: 'beats', + eprOverlap: 'openmetrics', + isBeta: false, + }, + { + type: 'ui_link', + id: 'oracle metrics', + title: 'oracle metrics', + categories: ['security'], + uiInternalPath: '/app/home#/tutorial/oracleMetrics', + description: 'Fetch internal metrics from a Oracle server.', + icons: [ + { + type: 'svg', + src: '/dqo/plugins/home/assets/logos/oracle.svg', + }, + ], + shipper: 'beats', + eprOverlap: 'oracle', + isBeta: false, + }, + { + type: 'ui_link', + id: 'IIS Metrics', + title: 'IIS Metrics', + categories: ['web'], + uiInternalPath: '/app/home#/tutorial/iisMetrics', + description: 'Collect IIS server related metrics.', + icons: [ + { + type: 'svg', + src: '/dqo/plugins/home/assets/logos/iis.svg', + }, + ], + shipper: 'beats', + eprOverlap: 'iis', + isBeta: false, + }, + { + type: 'ui_link', + id: 'Azure logs', + title: 'Azure logs', + categories: ['azure', 'cloud', 'network', 'security'], + uiInternalPath: '/app/home#/tutorial/azureLogs', + description: 'Collects Azure activity and audit related logs.', + icons: [ + { + type: 'eui', + src: 'logoAzure', + }, + ], + shipper: 'beats', + eprOverlap: 'azure', + isBeta: false, + }, + { + type: 'ui_link', + id: 'Google Cloud metrics', + title: 'Google Cloud metrics', + categories: ['google_cloud', 'cloud', 'network', 'security'], + uiInternalPath: '/app/home#/tutorial/gcpMetrics', + description: + 'Fetch monitoring metrics from Google Cloud Platform using Stackdriver Monitoring API.', + icons: [ + { + type: 'eui', + src: 'logoGCP', + }, + ], + shipper: 'beats', + eprOverlap: 'gcp', + isBeta: false, + }, + { + type: 'ui_link', + id: 'Auditd logs', + title: 'Auditd logs', + categories: ['os_system'], + uiInternalPath: '/app/home#/tutorial/auditdLogs', + description: 'Collect logs from the Linux auditd daemon.', + icons: [ + { + type: 'svg', + src: '/dqo/plugins/home/assets/logos/linux.svg', + }, + ], + shipper: 'beats', + eprOverlap: 'auditd', + isBeta: false, + }, + { + type: 'ui_link', + id: 'Barracuda logs', + title: 'Barracuda logs', + categories: ['network', 'security'], + uiInternalPath: '/app/home#/tutorial/barracudaLogs', + description: 'Collect Barracuda Web Application Firewall logs over syslog or from a file.', + icons: [ + { + type: 'svg', + src: '/dqo/plugins/home/assets/logos/barracuda.svg', + }, + ], + shipper: 'beats', + eprOverlap: 'barracuda', + isBeta: false, + }, + { + type: 'ui_link', + id: 'Bluecoat logs', + title: 'Bluecoat logs', + categories: ['network', 'security'], + uiInternalPath: '/app/home#/tutorial/bluecoatLogs', + description: 'Collect Blue Coat Director logs over syslog or from a file.', + icons: [ + { + type: 'eui', + src: 'logoLogging', + }, + ], + shipper: 'beats', + eprOverlap: 'bluecoat', + isBeta: false, + }, + { + type: 'ui_link', + id: 'CEF logs', + title: 'CEF logs', + categories: ['network', 'security'], + uiInternalPath: '/app/home#/tutorial/cefLogs', + description: 'Collect Common Event Format (CEF) log data over syslog.', + icons: [ + { + type: 'eui', + src: 'logoLogging', + }, + ], + shipper: 'beats', + eprOverlap: 'cef', + isBeta: false, + }, + { + type: 'ui_link', + id: 'Check Point logs', + title: 'Check Point logs', + categories: ['security'], + uiInternalPath: '/app/home#/tutorial/checkpointLogs', + description: 'Collect Check Point firewall logs.', + icons: [ + { + type: 'svg', + src: '/dqo/plugins/home/assets/logos/checkpoint.svg', + }, + ], + shipper: 'beats', + eprOverlap: 'checkpoint', + isBeta: false, + }, + { + type: 'ui_link', + id: 'CrowdStrike logs', + title: 'CrowdStrike logs', + categories: ['security'], + uiInternalPath: '/app/home#/tutorial/crowdstrikeLogs', + description: 'Collect CrowdStrike Falcon logs using the Falcon SIEM Connector.', + icons: [ + { + type: 'svg', + src: '/dqo/plugins/home/assets/logos/crowdstrike.svg', + }, + ], + shipper: 'beats', + eprOverlap: 'crowdstrike', + isBeta: false, + }, + { + type: 'ui_link', + id: 'CylancePROTECT logs', + title: 'CylancePROTECT logs', + categories: ['security'], + uiInternalPath: '/app/home#/tutorial/cylanceLogs', + description: 'Collect CylancePROTECT logs over syslog or from a file.', + icons: [ + { + type: 'svg', + src: '/dqo/plugins/home/assets/logos/cylance.svg', + }, + ], + shipper: 'beats', + eprOverlap: 'cylance', + isBeta: false, + }, + { + type: 'ui_link', + id: 'F5 logs', + title: 'F5 logs', + categories: ['network', 'security'], + uiInternalPath: '/app/home#/tutorial/f5Logs', + description: 'Collect F5 Big-IP Access Policy Manager logs over syslog or from a file.', + icons: [ + { + type: 'svg', + src: '/dqo/plugins/home/assets/logos/f5.svg', + }, + ], + shipper: 'beats', + eprOverlap: 'f5', + isBeta: false, + }, + { + type: 'ui_link', + id: 'Fortinet logs', + title: 'Fortinet logs', + categories: ['security'], + uiInternalPath: '/app/home#/tutorial/fortinetLogs', + description: 'Collect Fortinet FortiOS logs over syslog.', + icons: [ + { + type: 'svg', + src: '/dqo/plugins/home/assets/logos/fortinet.svg', + }, + ], + shipper: 'beats', + eprOverlap: 'fortinet', + isBeta: false, + }, + { + type: 'ui_link', + id: 'Google Cloud logs', + title: 'Google Cloud logs', + categories: ['google_cloud', 'cloud', 'network', 'security'], + uiInternalPath: '/app/home#/tutorial/gcpLogs', + description: 'Collect Google Cloud audit, firewall, and VPC flow logs.', + icons: [ + { + type: 'eui', + src: 'logoGoogleG', + }, + ], + shipper: 'beats', + eprOverlap: 'gcp', + isBeta: false, + }, + { + type: 'ui_link', + id: 'GSuite logs', + title: 'GSuite logs', + categories: ['security'], + uiInternalPath: '/app/home#/tutorial/gsuiteLogs', + description: 'Collect GSuite activity reports.', + icons: [ + { + type: 'eui', + src: 'logoGoogleG', + }, + ], + shipper: 'beats', + eprOverlap: 'gsuite', + isBeta: false, + }, + { + type: 'ui_link', + id: 'HAProxy logs', + title: 'HAProxy logs', + categories: ['network', 'web'], + uiInternalPath: '/app/home#/tutorial/haproxyLogs', + description: 'Collect HAProxy logs.', + icons: [ + { + type: 'eui', + src: 'logoHAproxy', + }, + ], + shipper: 'beats', + eprOverlap: 'haproxy', + isBeta: false, + }, + { + type: 'ui_link', + id: 'Icinga logs', + title: 'Icinga logs', + categories: ['security'], + uiInternalPath: '/app/home#/tutorial/icingaLogs', + description: 'Collect Icinga main, debug, and startup logs.', + icons: [ + { + type: 'svg', + src: '/dqo/plugins/home/assets/logos/icinga.svg', + }, + ], + shipper: 'beats', + eprOverlap: 'icinga', + isBeta: false, + }, + { + type: 'ui_link', + id: 'Imperva logs', + title: 'Imperva logs', + categories: ['network', 'security'], + uiInternalPath: '/app/home#/tutorial/impervaLogs', + description: 'Collect Imperva SecureSphere logs over syslog or from a file.', + icons: [ + { + type: 'eui', + src: 'logoLogging', + }, + ], + shipper: 'beats', + eprOverlap: 'imperva', + isBeta: false, + }, + { + type: 'ui_link', + id: 'Infoblox logs', + title: 'Infoblox logs', + categories: ['network'], + uiInternalPath: '/app/home#/tutorial/infobloxLogs', + description: 'Collect Infoblox NIOS logs over syslog or from a file.', + icons: [ + { + type: 'svg', + src: '/dqo/plugins/home/assets/logos/infoblox.svg', + }, + ], + shipper: 'beats', + eprOverlap: 'infoblox', + isBeta: false, + }, + { + type: 'ui_link', + id: 'Juniper Logs', + title: 'Juniper Logs', + categories: ['network', 'security'], + uiInternalPath: '/app/home#/tutorial/juniperLogs', + description: 'Collect Juniper JUNOS logs over syslog or from a file.', + icons: [ + { + type: 'svg', + src: '/dqo/plugins/home/assets/logos/juniper.svg', + }, + ], + shipper: 'beats', + eprOverlap: 'juniper', + isBeta: false, + }, + { + type: 'ui_link', + id: 'Kibana Logs', + title: 'Kibana Logs', + categories: ['message_queue'], + uiInternalPath: '/app/home#/tutorial/kibanaLogs', + description: 'Collect Kibana logs.', + icons: [ + { + type: 'eui', + src: 'logoKibana', + }, + ], + shipper: 'beats', + eprOverlap: 'kibana', + isBeta: false, + }, + { + type: 'ui_link', + id: 'Microsoft Defender ATP logs', + title: 'Microsoft Defender ATP logs', + categories: ['network', 'security', 'azure'], + uiInternalPath: '/app/home#/tutorial/microsoftLogs', + description: 'Collect Microsoft Defender ATP alerts.', + icons: [ + { + type: 'svg', + src: '/dqo/plugins/home/assets/logos/microsoft.svg', + }, + ], + shipper: 'beats', + eprOverlap: 'microsoft', + isBeta: false, + }, + { + type: 'ui_link', + id: 'MISP threat intel logs', + title: 'MISP threat intel logs', + categories: ['network', 'security', 'azure'], + uiInternalPath: '/app/home#/tutorial/mispLogs', + description: 'Collect MISP threat intelligence data with Filebeat.', + icons: [ + { + type: 'svg', + src: '/dqo/plugins/home/assets/logos/misp.svg', + }, + ], + shipper: 'beats', + eprOverlap: 'misp', + isBeta: false, + }, + { + type: 'ui_link', + id: 'MongoDB logs', + title: 'MongoDB logs', + categories: ['datastore'], + uiInternalPath: '/app/home#/tutorial/mongodbLogs', + description: 'Collect MongoDB logs.', + icons: [ + { + type: 'eui', + src: 'logoMongodb', + }, + ], + shipper: 'beats', + eprOverlap: 'mongodb', + isBeta: false, + }, + { + type: 'ui_link', + id: 'MSSQL logs', + title: 'MSSQL logs', + categories: ['datastore'], + uiInternalPath: '/app/home#/tutorial/mssqlLogs', + description: 'Collect MSSQL logs.', + icons: [ + { + type: 'svg', + src: '/dqo/plugins/home/assets/logos/microsoft.svg', + }, + ], + shipper: 'beats', + eprOverlap: 'mssql', + isBeta: false, + }, + { + type: 'ui_link', + id: 'Arbor Peakflow logs', + title: 'Arbor Peakflow logs', + categories: ['security'], + uiInternalPath: '/app/home#/tutorial/netscoutLogs', + description: 'Collect Netscout Arbor Peakflow SP logs over syslog or from a file.', + icons: [ + { + type: 'svg', + src: '/dqo/plugins/home/assets/logos/netscout.svg', + }, + ], + shipper: 'beats', + eprOverlap: 'netscout', + isBeta: false, + }, + { + type: 'ui_link', + id: 'Office 365 logs', + title: 'Office 365 logs', + categories: ['security'], + uiInternalPath: '/app/home#/tutorial/o365Logs', + description: 'Collect Office 365 activity logs via the Office 365 API.', + icons: [ + { + type: 'svg', + src: '/dqo/plugins/home/assets/logos/o365.svg', + }, + ], + shipper: 'beats', + eprOverlap: 'o365', + isBeta: false, + }, + { + type: 'ui_link', + id: 'Okta logs', + title: 'Okta logs', + categories: ['security'], + uiInternalPath: '/app/home#/tutorial/oktaLogs', + description: 'Collect the Okta system log via the Okta API.', + icons: [ + { + type: 'svg', + src: '/dqo/plugins/home/assets/logos/okta.svg', + }, + ], + shipper: 'beats', + eprOverlap: 'okta', + isBeta: false, + }, + { + type: 'ui_link', + id: 'Palo Alto Networks PAN-OS logs', + title: 'Palo Alto Networks PAN-OS logs', + categories: ['security'], + uiInternalPath: '/app/home#/tutorial/panwLogs', + description: + 'Collect Palo Alto Networks PAN-OS threat and traffic logs over syslog or from a log file.', + icons: [ + { + type: 'svg', + src: '/dqo/plugins/home/assets/logos/paloalto.svg', + }, + ], + shipper: 'beats', + eprOverlap: 'panw', + isBeta: false, + }, + { + type: 'ui_link', + id: 'RabbitMQ logs', + title: 'RabbitMQ logs', + categories: ['message_queue'], + uiInternalPath: '/app/home#/tutorial/rabbitmqLogs', + description: 'Collect RabbitMQ logs.', + icons: [ + { + type: 'svg', + src: '/dqo/plugins/home/assets/logos/rabbitmq.svg', + }, + ], + shipper: 'beats', + eprOverlap: 'rabbitmq', + isBeta: false, + }, + { + type: 'ui_link', + id: 'Radware DefensePro logs', + title: 'Radware DefensePro logs', + categories: ['security'], + uiInternalPath: '/app/home#/tutorial/radwareLogs', + description: 'Collect Radware DefensePro logs over syslog or from a file.', + icons: [ + { + type: 'svg', + src: '/dqo/plugins/home/assets/logos/radware.svg', + }, + ], + shipper: 'beats', + eprOverlap: 'radware', + isBeta: false, + }, + { + type: 'ui_link', + id: 'Google Santa logs', + title: 'Google Santa logs', + categories: ['security', 'os_system'], + uiInternalPath: '/app/home#/tutorial/santaLogs', + description: 'Collect Google Santa logs about process executions on MacOS.', + icons: [ + { + type: 'eui', + src: 'logoLogging', + }, + ], + shipper: 'beats', + eprOverlap: 'santa', + isBeta: false, + }, + { + type: 'ui_link', + id: 'Sonicwall FW logs', + title: 'Sonicwall FW logs', + categories: ['network', 'security'], + uiInternalPath: '/app/home#/tutorial/sonicwallLogs', + description: 'Collect Sonicwall-FW logs over syslog or from a file.', + icons: [ + { + type: 'svg', + src: '/dqo/plugins/home/assets/logos/sonicwall.svg', + }, + ], + shipper: 'beats', + eprOverlap: 'sonicwall', + isBeta: false, + }, + { + type: 'ui_link', + id: 'Sophos logs', + title: 'Sophos logs', + categories: ['security'], + uiInternalPath: '/app/home#/tutorial/sophosLogs', + description: 'Collect Sophos XG SFOS logs over syslog.', + icons: [ + { + type: 'svg', + src: '/dqo/plugins/home/assets/logos/sophos.svg', + }, + ], + shipper: 'beats', + eprOverlap: 'sophos', + isBeta: false, + }, + { + type: 'ui_link', + id: 'Squid logs', + title: 'Squid logs', + categories: ['security'], + uiInternalPath: '/app/home#/tutorial/squidLogs', + description: 'Collect Squid logs over syslog or from a file.', + icons: [ + { + type: 'eui', + src: 'logoLogging', + }, + ], + shipper: 'beats', + eprOverlap: 'squid', + isBeta: false, + }, + { + type: 'ui_link', + id: 'Tomcat logs', + title: 'Tomcat logs', + categories: ['web', 'security'], + uiInternalPath: '/app/home#/tutorial/tomcatLogs', + description: 'Collect Apache Tomcat logs over syslog or from a file.', + icons: [ + { + type: 'svg', + src: '/dqo/plugins/home/assets/logos/tomcat.svg', + }, + ], + shipper: 'beats', + eprOverlap: 'tomcat', + isBeta: false, + }, + { + type: 'ui_link', + id: 'Zscaler Logs', + title: 'Zscaler Logs', + categories: ['network', 'security'], + uiInternalPath: '/app/home#/tutorial/zscalerLogs', + description: 'This is a module for receiving Zscaler NSS logs over Syslog or a file.', + icons: [ + { + type: 'svg', + src: '/dqo/plugins/home/assets/logos/zscaler.svg', + }, + ], + shipper: 'beats', + eprOverlap: 'zscaler', + isBeta: false, + }, + { + type: 'ui_link', + id: 'apm', + title: 'APM', + categories: ['web'], + uiInternalPath: '/app/home#/tutorial/apm', + description: 'Collect in-depth performance metrics and errors from inside your applications.', + icons: [ + { + type: 'eui', + src: 'apmApp', + }, + ], + shipper: 'tutorial', + isBeta: false, + eprOverlap: 'apm', + }, +]; diff --git a/src/plugins/custom_integrations/public/services/stub/index.ts b/src/plugins/custom_integrations/public/services/stub/index.ts new file mode 100644 index 0000000000000..fe7465949d565 --- /dev/null +++ b/src/plugins/custom_integrations/public/services/stub/index.ts @@ -0,0 +1,27 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { + PluginServiceProviders, + PluginServiceProvider, + PluginServiceRegistry, +} from '../../../../presentation_util/public'; + +import { CustomIntegrationsServices } from '..'; +import { findServiceFactory } from './find'; +import { platformServiceFactory } from './platform'; + +export { findServiceFactory } from './find'; +export { platformServiceFactory } from './platform'; + +export const providers: PluginServiceProviders = { + find: new PluginServiceProvider(findServiceFactory), + platform: new PluginServiceProvider(platformServiceFactory), +}; + +export const registry = new PluginServiceRegistry(providers); diff --git a/src/plugins/custom_integrations/public/services/stub/platform.ts b/src/plugins/custom_integrations/public/services/stub/platform.ts new file mode 100644 index 0000000000000..60480f9905cb9 --- /dev/null +++ b/src/plugins/custom_integrations/public/services/stub/platform.ts @@ -0,0 +1,26 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { PluginServiceFactory } from '../../../../presentation_util/public'; + +import type { CustomIntegrationsPlatformService } from '../platform'; + +/** + * A type definition for a factory to produce the `CustomIntegrationsPlatformService` with stubbed output. + * @see /src/plugins/presentation_util/public/services/create/factory.ts + */ +export type CustomIntegrationsPlatformServiceFactory = + PluginServiceFactory; + +/** + * A factory to produce the `CustomIntegrationsPlatformService` with stubbed output. + */ +export const platformServiceFactory: CustomIntegrationsPlatformServiceFactory = () => ({ + getBasePath: () => '/basePath', + getAbsolutePath: (path: string): string => `/basePath${path}`, +}); diff --git a/src/plugins/custom_integrations/public/types.ts b/src/plugins/custom_integrations/public/types.ts index 9a12af767ecbc..946115329e2b5 100755 --- a/src/plugins/custom_integrations/public/types.ts +++ b/src/plugins/custom_integrations/public/types.ts @@ -6,14 +6,19 @@ * Side Public License, v 1. */ +import type { PresentationUtilPluginStart } from '../../presentation_util/public'; + import { CustomIntegration } from '../common'; export interface CustomIntegrationsSetup { getAppendCustomIntegrations: () => Promise; getReplacementCustomIntegrations: () => Promise; } -// eslint-disable-next-line @typescript-eslint/no-empty-interface -export interface CustomIntegrationsStart {} -// eslint-disable-next-line @typescript-eslint/no-empty-interface -export interface AppPluginStartDependencies {} +export interface CustomIntegrationsStart { + ContextProvider: React.FC; +} + +export interface CustomIntegrationsStartDependencies { + presentationUtil: PresentationUtilPluginStart; +} diff --git a/src/plugins/custom_integrations/storybook/decorator.tsx b/src/plugins/custom_integrations/storybook/decorator.tsx new file mode 100644 index 0000000000000..c5fea9615ee47 --- /dev/null +++ b/src/plugins/custom_integrations/storybook/decorator.tsx @@ -0,0 +1,48 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React from 'react'; + +import { DecoratorFn } from '@storybook/react'; +import { I18nProvider } from '@kbn/i18n/react'; + +import { PluginServiceRegistry } from '../../presentation_util/public'; + +import { pluginServices } from '../public/services'; +import { CustomIntegrationsServices } from '../public/services'; +import { providers } from '../public/services/storybook'; +import { EuiThemeProvider } from '../../kibana_react/common/eui_styled_components'; + +/** + * Returns a Storybook Decorator that provides both the `I18nProvider` and access to `PluginServices` + * for components rendered in Storybook. + */ +export const getCustomIntegrationsContextDecorator = + (): DecoratorFn => + (story, { globals }) => { + const ContextProvider = getCustomIntegrationsContextProvider(); + const darkMode = globals.euiTheme === 'v8.dark' || globals.euiTheme === 'v7.dark'; + + return ( + + + {story()} + + + ); + }; + +/** + * Prepares `PluginServices` for use in Storybook and returns a React `Context.Provider` element + * so components that access `PluginServices` can be rendered. + */ +export const getCustomIntegrationsContextProvider = () => { + const registry = new PluginServiceRegistry(providers); + pluginServices.setRegistry(registry.start({})); + return pluginServices.getContextProvider(); +}; diff --git a/src/plugins/custom_integrations/storybook/index.ts b/src/plugins/custom_integrations/storybook/index.ts new file mode 100644 index 0000000000000..a9e34e1aeeb7e --- /dev/null +++ b/src/plugins/custom_integrations/storybook/index.ts @@ -0,0 +1,12 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export { + getCustomIntegrationsContextDecorator as getStorybookContextDecorator, + getCustomIntegrationsContextProvider as getStorybookContextProvider, +} from '../storybook/decorator'; diff --git a/src/plugins/custom_integrations/storybook/main.ts b/src/plugins/custom_integrations/storybook/main.ts new file mode 100644 index 0000000000000..1261fe5a06f69 --- /dev/null +++ b/src/plugins/custom_integrations/storybook/main.ts @@ -0,0 +1,11 @@ +/* + * Copyright 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 { defaultConfig } from '@kbn/storybook'; + +module.exports = defaultConfig; diff --git a/src/plugins/custom_integrations/storybook/manager.ts b/src/plugins/custom_integrations/storybook/manager.ts new file mode 100644 index 0000000000000..99c01efdddfdc --- /dev/null +++ b/src/plugins/custom_integrations/storybook/manager.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 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 { addons } from '@storybook/addons'; +import { create } from '@storybook/theming'; +import { PANEL_ID } from '@storybook/addon-actions'; + +addons.setConfig({ + theme: create({ + base: 'light', + brandTitle: 'Kibana Custom Integrations Storybook', + brandUrl: 'https://github.com/elastic/kibana/tree/master/src/plugins/custom_integrations', + }), + showPanel: true.valueOf, + selectedPanel: PANEL_ID, +}); diff --git a/src/plugins/custom_integrations/storybook/preview.tsx b/src/plugins/custom_integrations/storybook/preview.tsx new file mode 100644 index 0000000000000..c27390261c920 --- /dev/null +++ b/src/plugins/custom_integrations/storybook/preview.tsx @@ -0,0 +1,28 @@ +/* + * Copyright 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 React from 'react'; +import { Title, Subtitle, Description, Primary, Stories } from '@storybook/addon-docs/blocks'; + +import { getCustomIntegrationsContextDecorator } from './decorator'; + +export const decorators = [getCustomIntegrationsContextDecorator()]; + +export const parameters = { + docs: { + page: () => ( + <> + + <Subtitle /> + <Description /> + <Primary /> + <Stories /> + </> + ), + }, +}; diff --git a/src/plugins/custom_integrations/tsconfig.json b/src/plugins/custom_integrations/tsconfig.json index 2ce7bf9c8112c..ccb75c358611b 100644 --- a/src/plugins/custom_integrations/tsconfig.json +++ b/src/plugins/custom_integrations/tsconfig.json @@ -6,8 +6,15 @@ "declaration": true, "declarationMap": true }, - "include": ["common/**/*", "public/**/*", "server/**/*"], + "include": [ + "../../../typings/**/*", + "common/**/*", + "public/**/*", + "server/**/*", + "storybook/**/*" + ], "references": [ - { "path": "../../core/tsconfig.json" } + { "path": "../../core/tsconfig.json" }, + { "path": "../presentation_util/tsconfig.json" } ] } diff --git a/src/plugins/home/server/index.ts b/src/plugins/home/server/index.ts index 9523766596fed..c75ce4e83921c 100644 --- a/src/plugins/home/server/index.ts +++ b/src/plugins/home/server/index.ts @@ -18,9 +18,6 @@ export const config: PluginConfigDescriptor<ConfigSchema> = { disableWelcomeScreen: true, }, schema: configSchema, - deprecations: ({ renameFromRoot }) => [ - renameFromRoot('kibana.disableWelcomeScreen', 'home.disableWelcomeScreen'), - ], }; export const plugin = (initContext: PluginInitializerContext) => new HomeServerPlugin(initContext); diff --git a/src/plugins/newsfeed/server/config.ts b/src/plugins/newsfeed/server/config.ts index f8924706b751c..f14f3452761e1 100644 --- a/src/plugins/newsfeed/server/config.ts +++ b/src/plugins/newsfeed/server/config.ts @@ -11,7 +11,6 @@ import { NEWSFEED_DEFAULT_SERVICE_PATH, NEWSFEED_DEFAULT_SERVICE_BASE_URL, NEWSFEED_DEV_SERVICE_BASE_URL, - NEWSFEED_FALLBACK_LANGUAGE, } from '../common/constants'; export const configSchema = schema.object({ @@ -25,7 +24,6 @@ export const configSchema = schema.object({ schema.string({ defaultValue: NEWSFEED_DEV_SERVICE_BASE_URL }) ), }), - defaultLanguage: schema.string({ defaultValue: NEWSFEED_FALLBACK_LANGUAGE }), // TODO: Deprecate since no longer used mainInterval: schema.duration({ defaultValue: '2m' }), // (2min) How often to retry failed fetches, and/or check if newsfeed items need to be refreshed from remote fetchInterval: schema.duration({ defaultValue: '1d' }), // (1day) How often to fetch remote and reset the last fetched time }); diff --git a/src/plugins/newsfeed/server/index.ts b/src/plugins/newsfeed/server/index.ts index 460d48622af69..fefb725e2804e 100644 --- a/src/plugins/newsfeed/server/index.ts +++ b/src/plugins/newsfeed/server/index.ts @@ -17,7 +17,6 @@ export const config: PluginConfigDescriptor<NewsfeedConfigType> = { mainInterval: true, fetchInterval: true, }, - deprecations: ({ unused }) => [unused('defaultLanguage')], }; export function plugin() { diff --git a/src/plugins/usage_collection/server/config.ts b/src/plugins/usage_collection/server/config.ts index faf8ce7535e8a..f09dc7d431b33 100644 --- a/src/plugins/usage_collection/server/config.ts +++ b/src/plugins/usage_collection/server/config.ts @@ -29,12 +29,6 @@ export type ConfigType = TypeOf<typeof configSchema>; export const config: PluginConfigDescriptor<ConfigType> = { schema: configSchema, - deprecations: ({ renameFromRoot }) => [ - renameFromRoot('ui_metric.enabled', 'usageCollection.uiCounters.enabled'), - renameFromRoot('ui_metric.debug', 'usageCollection.uiCounters.debug'), - renameFromRoot('usageCollection.uiMetric.enabled', 'usageCollection.uiCounters.enabled'), - renameFromRoot('usageCollection.uiMetric.debug', 'usageCollection.uiCounters.debug'), - ], exposeToBrowser: { uiCounters: true, }, diff --git a/test/scripts/jenkins_storybook.sh b/test/scripts/jenkins_storybook.sh index 17ca46b0097b1..bf8b881a91ecd 100755 --- a/test/scripts/jenkins_storybook.sh +++ b/test/scripts/jenkins_storybook.sh @@ -8,6 +8,7 @@ yarn storybook --site apm yarn storybook --site canvas yarn storybook --site codeeditor yarn storybook --site ci_composite +yarn storybook --site custom_integrations yarn storybook --site url_template_editor yarn storybook --site dashboard yarn storybook --site dashboard_enhanced diff --git a/x-pack/plugins/actions/server/actions_client.test.ts b/x-pack/plugins/actions/server/actions_client.test.ts index 7549d2ecaab77..ca51b1cdfea1b 100644 --- a/x-pack/plugins/actions/server/actions_client.test.ts +++ b/x-pack/plugins/actions/server/actions_client.test.ts @@ -439,7 +439,6 @@ describe('create()', () => { test('throws error creating action with disabled actionType', async () => { const localConfigUtils = getActionsConfigurationUtilities({ - enabled: true, enabledActionTypes: ['some-not-ignored-action-type'], allowedHosts: ['*'], preconfiguredAlertHistoryEsIndex: false, diff --git a/x-pack/plugins/actions/server/actions_config.test.ts b/x-pack/plugins/actions/server/actions_config.test.ts index 51cd9e5599472..217f9593ee6d8 100644 --- a/x-pack/plugins/actions/server/actions_config.test.ts +++ b/x-pack/plugins/actions/server/actions_config.test.ts @@ -22,7 +22,6 @@ import moment from 'moment'; const mockLogger = loggingSystemMock.create().get() as jest.Mocked<Logger>; const defaultActionsConfig: ActionsConfig = { - enabled: false, allowedHosts: [], enabledActionTypes: [], preconfiguredAlertHistoryEsIndex: false, @@ -47,7 +46,6 @@ describe('ensureUriAllowed', () => { test('returns true when "any" hostnames are allowed', () => { const config: ActionsConfig = { ...defaultActionsConfig, - enabled: false, allowedHosts: [AllowedHosts.Any], enabledActionTypes: [], }; @@ -77,7 +75,6 @@ describe('ensureUriAllowed', () => { test('returns true when the hostname in the requested uri is in the allowedHosts', () => { const config: ActionsConfig = { ...defaultActionsConfig, - enabled: false, allowedHosts: ['github.com'], enabledActionTypes: [], }; @@ -91,7 +88,6 @@ describe('ensureHostnameAllowed', () => { test('returns true when "any" hostnames are allowed', () => { const config: ActionsConfig = { ...defaultActionsConfig, - enabled: false, allowedHosts: [AllowedHosts.Any], enabledActionTypes: [], }; @@ -112,7 +108,6 @@ describe('ensureHostnameAllowed', () => { test('returns true when the hostname in the requested uri is in the allowedHosts', () => { const config: ActionsConfig = { ...defaultActionsConfig, - enabled: false, allowedHosts: ['github.com'], enabledActionTypes: [], }; @@ -126,7 +121,6 @@ describe('isUriAllowed', () => { test('returns true when "any" hostnames are allowed', () => { const config: ActionsConfig = { ...defaultActionsConfig, - enabled: false, allowedHosts: [AllowedHosts.Any], enabledActionTypes: [], }; @@ -152,7 +146,6 @@ describe('isUriAllowed', () => { test('returns true when the hostname in the requested uri is in the allowedHosts', () => { const config: ActionsConfig = { ...defaultActionsConfig, - enabled: false, allowedHosts: ['github.com'], enabledActionTypes: [], }; @@ -166,7 +159,6 @@ describe('isHostnameAllowed', () => { test('returns true when "any" hostnames are allowed', () => { const config: ActionsConfig = { ...defaultActionsConfig, - enabled: false, allowedHosts: [AllowedHosts.Any], enabledActionTypes: [], }; @@ -181,7 +173,6 @@ describe('isHostnameAllowed', () => { test('returns true when the hostname in the requested uri is in the allowedHosts', () => { const config: ActionsConfig = { ...defaultActionsConfig, - enabled: false, allowedHosts: ['github.com'], enabledActionTypes: [], }; @@ -193,7 +184,6 @@ describe('isActionTypeEnabled', () => { test('returns true when "any" actionTypes are allowed', () => { const config: ActionsConfig = { ...defaultActionsConfig, - enabled: false, allowedHosts: [], enabledActionTypes: ['ignore', EnabledActionTypes.Any], }; @@ -203,7 +193,6 @@ describe('isActionTypeEnabled', () => { test('returns false when no actionType is allowed', () => { const config: ActionsConfig = { ...defaultActionsConfig, - enabled: false, allowedHosts: [], enabledActionTypes: [], }; @@ -213,7 +202,6 @@ describe('isActionTypeEnabled', () => { test('returns false when the actionType is not in the enabled list', () => { const config: ActionsConfig = { ...defaultActionsConfig, - enabled: false, allowedHosts: [], enabledActionTypes: ['foo'], }; @@ -223,7 +211,6 @@ describe('isActionTypeEnabled', () => { test('returns true when the actionType is in the enabled list', () => { const config: ActionsConfig = { ...defaultActionsConfig, - enabled: false, allowedHosts: [], enabledActionTypes: ['ignore', 'foo'], }; @@ -235,7 +222,6 @@ describe('ensureActionTypeEnabled', () => { test('does not throw when any actionType is allowed', () => { const config: ActionsConfig = { ...defaultActionsConfig, - enabled: false, allowedHosts: [], enabledActionTypes: ['ignore', EnabledActionTypes.Any], }; @@ -254,7 +240,6 @@ describe('ensureActionTypeEnabled', () => { test('throws when actionType is not enabled', () => { const config: ActionsConfig = { ...defaultActionsConfig, - enabled: false, allowedHosts: [], enabledActionTypes: ['ignore'], }; @@ -268,7 +253,6 @@ describe('ensureActionTypeEnabled', () => { test('does not throw when actionType is enabled', () => { const config: ActionsConfig = { ...defaultActionsConfig, - enabled: false, allowedHosts: [], enabledActionTypes: ['ignore', 'foo'], }; diff --git a/x-pack/plugins/actions/server/builtin_action_types/lib/axios_utils_connection.test.ts b/x-pack/plugins/actions/server/builtin_action_types/lib/axios_utils_connection.test.ts index 4ed9485e923a7..149ac79522f73 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/lib/axios_utils_connection.test.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/lib/axios_utils_connection.test.ts @@ -247,7 +247,6 @@ async function createServer(useHttps: boolean = false): Promise<CreateServerResu } const BaseActionsConfig: ActionsConfig = { - enabled: true, allowedHosts: ['*'], enabledActionTypes: ['*'], preconfiguredAlertHistoryEsIndex: false, diff --git a/x-pack/plugins/actions/server/config.test.ts b/x-pack/plugins/actions/server/config.test.ts index d99b9349e977b..7f3ce5bee6e02 100644 --- a/x-pack/plugins/actions/server/config.test.ts +++ b/x-pack/plugins/actions/server/config.test.ts @@ -29,7 +29,6 @@ describe('config validation', () => { "idleInterval": "PT1H", "pageSize": 100, }, - "enabled": true, "enabledActionTypes": Array [ "*", ], @@ -70,7 +69,6 @@ describe('config validation', () => { "idleInterval": "PT1H", "pageSize": 100, }, - "enabled": true, "enabledActionTypes": Array [ "*", ], @@ -196,7 +194,6 @@ describe('config validation', () => { "idleInterval": "PT1H", "pageSize": 100, }, - "enabled": true, "enabledActionTypes": Array [ "*", ], diff --git a/x-pack/plugins/actions/server/config.ts b/x-pack/plugins/actions/server/config.ts index 54fd0d72bccee..cf05ee9a24eec 100644 --- a/x-pack/plugins/actions/server/config.ts +++ b/x-pack/plugins/actions/server/config.ts @@ -57,7 +57,6 @@ const customHostSettingsSchema = schema.object({ export type CustomHostSettings = TypeOf<typeof customHostSettingsSchema>; export const configSchema = schema.object({ - enabled: schema.boolean({ defaultValue: true }), allowedHosts: schema.arrayOf( schema.oneOf([schema.string({ hostname: true }), schema.literal(AllowedHosts.Any)]), { diff --git a/x-pack/plugins/actions/server/index.test.ts b/x-pack/plugins/actions/server/index.test.ts deleted file mode 100644 index dbe8fca806f17..0000000000000 --- a/x-pack/plugins/actions/server/index.test.ts +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ -import { config } from './index'; -import { applyDeprecations, configDeprecationFactory } from '@kbn/config'; - -const CONFIG_PATH = 'xpack.actions'; -const applyStackAlertDeprecations = (settings: Record<string, unknown> = {}) => { - const deprecations = config.deprecations!(configDeprecationFactory); - const deprecationMessages: string[] = []; - const _config = { - [CONFIG_PATH]: settings, - }; - const { config: migrated } = applyDeprecations( - _config, - deprecations.map((deprecation) => ({ - deprecation, - path: CONFIG_PATH, - })), - () => - ({ message }) => - deprecationMessages.push(message) - ); - return { - messages: deprecationMessages, - migrated, - }; -}; - -describe('index', () => { - describe('deprecations', () => { - it('should deprecate .enabled flag', () => { - const { messages } = applyStackAlertDeprecations({ enabled: false }); - expect(messages).toMatchInlineSnapshot(` - Array [ - "\\"xpack.actions.enabled\\" is deprecated. The ability to disable this plugin will be removed in 8.0.0.", - ] - `); - }); - }); -}); diff --git a/x-pack/plugins/actions/server/index.ts b/x-pack/plugins/actions/server/index.ts index bf59a1a11687d..bfddd22003978 100644 --- a/x-pack/plugins/actions/server/index.ts +++ b/x-pack/plugins/actions/server/index.ts @@ -122,16 +122,5 @@ export const config: PluginConfigDescriptor<ActionsConfig> = { }); } }, - (settings, fromPath, addDeprecation) => { - const actions = get(settings, fromPath); - if (actions?.enabled === false || actions?.enabled === true) { - addDeprecation({ - message: `"xpack.actions.enabled" is deprecated. The ability to disable this plugin will be removed in 8.0.0.`, - correctiveActions: { - manualSteps: [`Remove "xpack.actions.enabled" from your kibana configs.`], - }, - }); - } - }, ], }; diff --git a/x-pack/plugins/actions/server/lib/custom_host_settings.test.ts b/x-pack/plugins/actions/server/lib/custom_host_settings.test.ts index ec7b46e545112..48c9352566118 100644 --- a/x-pack/plugins/actions/server/lib/custom_host_settings.test.ts +++ b/x-pack/plugins/actions/server/lib/custom_host_settings.test.ts @@ -65,7 +65,6 @@ describe('custom_host_settings', () => { describe('resolveCustomHosts()', () => { const defaultActionsConfig: ActionsConfig = { - enabled: true, allowedHosts: [], enabledActionTypes: [], preconfiguredAlertHistoryEsIndex: false, diff --git a/x-pack/plugins/actions/server/plugin.test.ts b/x-pack/plugins/actions/server/plugin.test.ts index 86d2de783ebe5..08ea99df67c8e 100644 --- a/x-pack/plugins/actions/server/plugin.test.ts +++ b/x-pack/plugins/actions/server/plugin.test.ts @@ -34,7 +34,6 @@ describe('Actions Plugin', () => { beforeEach(() => { context = coreMock.createPluginInitializerContext<ActionsConfig>({ - enabled: true, enabledActionTypes: ['*'], allowedHosts: ['*'], preconfiguredAlertHistoryEsIndex: false, @@ -253,7 +252,6 @@ describe('Actions Plugin', () => { beforeEach(() => { context = coreMock.createPluginInitializerContext<ActionsConfig>({ - enabled: true, enabledActionTypes: ['*'], allowedHosts: ['*'], preconfiguredAlertHistoryEsIndex: false, diff --git a/x-pack/plugins/alerting/server/index.test.ts b/x-pack/plugins/alerting/server/index.test.ts deleted file mode 100644 index b1e64935d7cd9..0000000000000 --- a/x-pack/plugins/alerting/server/index.test.ts +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ -import { config } from './index'; -import { applyDeprecations, configDeprecationFactory } from '@kbn/config'; - -const CONFIG_PATH = 'xpack.alerting'; -const applyStackAlertDeprecations = (settings: Record<string, unknown> = {}) => { - const deprecations = config.deprecations!(configDeprecationFactory); - const deprecationMessages: string[] = []; - const _config = { - [CONFIG_PATH]: settings, - }; - const { config: migrated } = applyDeprecations( - _config, - deprecations.map((deprecation) => ({ - deprecation, - path: CONFIG_PATH, - })), - () => - ({ message }) => - deprecationMessages.push(message) - ); - return { - messages: deprecationMessages, - migrated, - }; -}; - -describe('index', () => { - describe('deprecations', () => { - it('should deprecate .enabled flag', () => { - const { messages } = applyStackAlertDeprecations({ enabled: false }); - expect(messages).toMatchInlineSnapshot(` - Array [ - "\\"xpack.alerting.enabled\\" is deprecated. The ability to disable this plugin will be removed in 8.0.0.", - ] - `); - }); - }); -}); diff --git a/x-pack/plugins/alerting/server/index.ts b/x-pack/plugins/alerting/server/index.ts index 3b4688173e9b5..162ee06216304 100644 --- a/x-pack/plugins/alerting/server/index.ts +++ b/x-pack/plugins/alerting/server/index.ts @@ -4,7 +4,6 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { get } from 'lodash'; import type { PublicMethodsOf } from '@kbn/utility-types'; import { RulesClient as RulesClientClass } from './rules_client'; import { PluginConfigDescriptor, PluginInitializerContext } from '../../../../src/core/server'; @@ -58,16 +57,5 @@ export const config: PluginConfigDescriptor<AlertsConfigType> = { 'xpack.alerts.invalidateApiKeysTask.removalDelay', 'xpack.alerting.invalidateApiKeysTask.removalDelay' ), - (settings, fromPath, addDeprecation) => { - const alerting = get(settings, fromPath); - if (alerting?.enabled === false || alerting?.enabled === true) { - addDeprecation({ - message: `"xpack.alerting.enabled" is deprecated. The ability to disable this plugin will be removed in 8.0.0.`, - correctiveActions: { - manualSteps: [`Remove "xpack.alerting.enabled" from your kibana configs.`], - }, - }); - } - }, ], }; diff --git a/x-pack/plugins/apm/public/tutorial/tutorial_fleet_instructions/index.tsx b/x-pack/plugins/apm/public/tutorial/tutorial_fleet_instructions/index.tsx index c69623f92987a..175d6797bb29f 100644 --- a/x-pack/plugins/apm/public/tutorial/tutorial_fleet_instructions/index.tsx +++ b/x-pack/plugins/apm/public/tutorial/tutorial_fleet_instructions/index.tsx @@ -86,7 +86,7 @@ function TutorialFleetInstructions({ http, basePath, isDarkTheme }: Props) { 'xpack.apm.tutorial.apmServer.fleet.message', { defaultMessage: - 'The APM integration installs Elasticsearch templates and Ingest Node pipelines for APM data.', + 'The APM integration installs Elasticsearch templates and ingest pipelines for APM data.', } )} footer={ diff --git a/x-pack/plugins/banners/server/config.test.ts b/x-pack/plugins/banners/server/config.test.ts deleted file mode 100644 index f080281cf730d..0000000000000 --- a/x-pack/plugins/banners/server/config.test.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 { config } from './config'; -import { getDeprecationsFor } from '../../../../src/core/server/test_utils'; - -function applyDeprecations(settings?: Record<string, any>) { - return getDeprecationsFor({ provider: config.deprecations!, settings, path: 'xpack.banners' }); -} - -describe('deprecations', () => { - it('replaces xpack.banners.placement from "header" to "top"', () => { - const { migrated } = applyDeprecations({ - placement: 'header', - }); - expect(migrated.xpack.banners.placement).toBe('top'); - }); - it('logs a warning message about xpack.banners.placement renaming', () => { - const { messages } = applyDeprecations({ - placement: 'header', - }); - expect(messages).toMatchInlineSnapshot(` - Array [ - "The \`header\` value for xpack.banners.placement has been replaced by \`top\`", - ] - `); - }); - it('do not rename other placement values', () => { - const { migrated, messages } = applyDeprecations({ - placement: 'disabled', - }); - expect(migrated.xpack.banners.placement).toBe('disabled'); - expect(messages.length).toBe(0); - }); -}); diff --git a/x-pack/plugins/banners/server/config.ts b/x-pack/plugins/banners/server/config.ts index 37b4c57fc2ce1..cc0e18c32e310 100644 --- a/x-pack/plugins/banners/server/config.ts +++ b/x-pack/plugins/banners/server/config.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { get } from 'lodash'; +// import { get } from 'lodash'; import { schema, TypeOf } from '@kbn/config-schema'; import { PluginConfigDescriptor } from 'kibana/server'; import { isHexColor } from './utils'; @@ -39,23 +39,4 @@ export type BannersConfigType = TypeOf<typeof configSchema>; export const config: PluginConfigDescriptor<BannersConfigType> = { schema: configSchema, exposeToBrowser: {}, - deprecations: () => [ - (rootConfig, fromPath, addDeprecation) => { - const pluginConfig = get(rootConfig, fromPath); - if (pluginConfig?.placement === 'header') { - addDeprecation({ - message: 'The `header` value for xpack.banners.placement has been replaced by `top`', - correctiveActions: { - manualSteps: [ - `Remove "xpack.banners.placement: header" from your kibana configs.`, - `Add "xpack.banners.placement: to" to your kibana configs instead.`, - ], - }, - }); - return { - set: [{ path: `${fromPath}.placement`, value: 'top' }], - }; - } - }, - ], }; diff --git a/x-pack/plugins/canvas/public/components/positionable/positionable.scss b/x-pack/plugins/canvas/public/components/positionable/positionable.scss index d1d927672e052..6dd0d713a0308 100644 --- a/x-pack/plugins/canvas/public/components/positionable/positionable.scss +++ b/x-pack/plugins/canvas/public/components/positionable/positionable.scss @@ -1,4 +1,3 @@ .canvasPositionable { transform-origin: center center; /* the default, only for clarity */ - transform-style: preserve-3d; } diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_settings.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_settings.test.tsx index 883c8631365eb..83cf21ce86233 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_settings.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_settings.test.tsx @@ -105,84 +105,6 @@ describe('SourceSettings', () => { ); }); - it('handles disabling synchronization', () => { - const wrapper = shallow(<SourceSettings />); - - const synchronizeSwitch = wrapper.find('[data-test-subj="SynchronizeToggle"]').first(); - const event = { target: { checked: false } }; - synchronizeSwitch.prop('onChange')?.(event as any); - - wrapper.find('[data-test-subj="SaveSyncControlsButton"]').simulate('click'); - - expect(updateContentSource).toHaveBeenCalledWith(fullContentSources[0].id, { - indexing: { - enabled: false, - features: { - content_extraction: { enabled: true }, - thumbnails: { enabled: true }, - }, - }, - }); - }); - - it('handles disabling thumbnails', () => { - const wrapper = shallow(<SourceSettings />); - - const thumbnailsSwitch = wrapper.find('[data-test-subj="ThumbnailsToggle"]').first(); - const event = { target: { checked: false } }; - thumbnailsSwitch.prop('onChange')?.(event as any); - - wrapper.find('[data-test-subj="SaveSyncControlsButton"]').simulate('click'); - - expect(updateContentSource).toHaveBeenCalledWith(fullContentSources[0].id, { - indexing: { - enabled: true, - features: { - content_extraction: { enabled: true }, - thumbnails: { enabled: false }, - }, - }, - }); - }); - - it('handles disabling content extraction', () => { - const wrapper = shallow(<SourceSettings />); - - const contentExtractionSwitch = wrapper - .find('[data-test-subj="ContentExtractionToggle"]') - .first(); - const event = { target: { checked: false } }; - contentExtractionSwitch.prop('onChange')?.(event as any); - - wrapper.find('[data-test-subj="SaveSyncControlsButton"]').simulate('click'); - - expect(updateContentSource).toHaveBeenCalledWith(fullContentSources[0].id, { - indexing: { - enabled: true, - features: { - content_extraction: { enabled: false }, - thumbnails: { enabled: true }, - }, - }, - }); - }); - - it('disables the thumbnails switch when globally disabled', () => { - setMockValues({ - ...mockValues, - contentSource: { - ...fullContentSources[0], - areThumbnailsConfigEnabled: false, - }, - }); - - const wrapper = shallow(<SourceSettings />); - - const synchronizeSwitch = wrapper.find('[data-test-subj="ThumbnailsToggle"]'); - - expect(synchronizeSwitch.prop('disabled')).toEqual(true); - }); - describe('DownloadDiagnosticsButton', () => { it('renders for org with correct href', () => { const wrapper = shallow(<SourceSettings />); 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 585477fed058e..dd8625ebd7a7c 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,8 +17,6 @@ import { EuiFlexGroup, EuiFlexItem, EuiFormRow, - EuiSpacer, - EuiSwitch, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; @@ -51,12 +49,6 @@ import { SYNC_DIAGNOSTICS_TITLE, SYNC_DIAGNOSTICS_DESCRIPTION, SYNC_DIAGNOSTICS_BUTTON, - SYNC_MANAGEMENT_TITLE, - SYNC_MANAGEMENT_DESCRIPTION, - SYNC_MANAGEMENT_SYNCHRONIZE_LABEL, - SYNC_MANAGEMENT_THUMBNAILS_LABEL, - SYNC_MANAGEMENT_THUMBNAILS_GLOBAL_CONFIG_LABEL, - SYNC_MANAGEMENT_CONTENT_EXTRACTION_LABEL, } from '../constants'; import { staticSourceData } from '../source_data'; import { SourceLogic } from '../source_logic'; @@ -70,22 +62,7 @@ export const SourceSettings: React.FC = () => { const { getSourceConfigData } = useActions(AddSourceLogic); const { - contentSource: { - name, - id, - serviceType, - custom: isCustom, - isIndexedSource, - areThumbnailsConfigEnabled, - isOauth1, - indexing: { - enabled, - features: { - contentExtraction: { enabled: contentExtractionEnabled }, - thumbnails: { enabled: thumbnailsEnabled }, - }, - }, - }, + contentSource: { name, id, serviceType, isOauth1 }, buttonLoading, } = useValues(SourceLogic); @@ -109,11 +86,6 @@ export const SourceSettings: React.FC = () => { const hideConfirm = () => setModalVisibility(false); const showConfig = isOrganization && !isEmpty(configuredFields); - const showSyncControls = isOrganization && isIndexedSource && !isCustom; - - const [synchronizeChecked, setSynchronize] = useState(enabled); - const [thumbnailsChecked, setThumbnails] = useState(thumbnailsEnabled); - const [contentExtractionChecked, setContentExtraction] = useState(contentExtractionEnabled); const { clientId, clientSecret, publicKey, consumerKey, baseUrl } = configuredFields || {}; @@ -130,18 +102,6 @@ export const SourceSettings: React.FC = () => { updateContentSource(id, { name: inputValue }); }; - const submitSyncControls = () => { - updateContentSource(id, { - indexing: { - enabled: synchronizeChecked, - features: { - content_extraction: { enabled: contentExtractionChecked }, - thumbnails: { enabled: thumbnailsChecked }, - }, - }, - }); - }; - const handleSourceRemoval = () => { /** * The modal was just hanging while the UI waited for the server to respond. @@ -221,58 +181,6 @@ export const SourceSettings: React.FC = () => { </EuiFormRow> </ContentSection> )} - {showSyncControls && ( - <ContentSection title={SYNC_MANAGEMENT_TITLE} description={SYNC_MANAGEMENT_DESCRIPTION}> - <EuiFlexGroup> - <EuiFlexItem grow={false}> - <EuiSwitch - checked={synchronizeChecked} - onChange={(e) => setSynchronize(e.target.checked)} - label={SYNC_MANAGEMENT_SYNCHRONIZE_LABEL} - data-test-subj="SynchronizeToggle" - /> - </EuiFlexItem> - </EuiFlexGroup> - <EuiSpacer /> - <EuiFlexGroup> - <EuiFlexItem grow={false}> - <EuiSwitch - checked={thumbnailsChecked} - onChange={(e) => setThumbnails(e.target.checked)} - label={ - areThumbnailsConfigEnabled - ? SYNC_MANAGEMENT_THUMBNAILS_LABEL - : SYNC_MANAGEMENT_THUMBNAILS_GLOBAL_CONFIG_LABEL - } - disabled={!areThumbnailsConfigEnabled} - data-test-subj="ThumbnailsToggle" - /> - </EuiFlexItem> - </EuiFlexGroup> - <EuiFlexGroup> - <EuiFlexItem grow={false}> - <EuiSwitch - checked={contentExtractionChecked} - onChange={(e) => setContentExtraction(e.target.checked)} - label={SYNC_MANAGEMENT_CONTENT_EXTRACTION_LABEL} - data-test-subj="ContentExtractionToggle" - /> - </EuiFlexItem> - </EuiFlexGroup> - <EuiSpacer /> - <EuiFlexGroup> - <EuiFlexItem grow={false}> - <EuiButton - color="primary" - onClick={submitSyncControls} - data-test-subj="SaveSyncControlsButton" - > - {SAVE_CHANGES_BUTTON} - </EuiButton> - </EuiFlexItem> - </EuiFlexGroup> - </ContentSection> - )} <ContentSection title={SYNC_DIAGNOSTICS_TITLE} description={SYNC_DIAGNOSTICS_DESCRIPTION}> <EuiButton target="_blank" diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/synchronization/blocked_window_item.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/synchronization/blocked_window_item.tsx index 80f4244101fd8..bcc2aa72668f9 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/synchronization/blocked_window_item.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/synchronization/blocked_window_item.tsx @@ -10,7 +10,6 @@ import React from 'react'; import { EuiButton, EuiComboBox, - EuiComboBoxOptionOption, EuiDatePicker, EuiFlexGroup, EuiFlexItem, @@ -81,13 +80,10 @@ const syncOptions = [ }, ]; -const dayPickerOptions = DAYS_OF_WEEK_VALUES.reduce((options, day) => { - options.push({ - label: DAYS_OF_WEEK_LABELS[day.toUpperCase() as keyof typeof DAYS_OF_WEEK_LABELS], - value: day, - }); - return options; -}, [] as Array<EuiComboBoxOptionOption<string>>); +const dayPickerOptions = DAYS_OF_WEEK_VALUES.map((day) => ({ + label: DAYS_OF_WEEK_LABELS[day.toUpperCase() as keyof typeof DAYS_OF_WEEK_LABELS], + value: day, +})); export const BlockedWindowItem: React.FC<Props> = ({ blockedWindow }) => { const handleSyncTypeChange = () => '#TODO'; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/synchronization/blocked_window_tab.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/synchronization/blocked_window_tab.test.tsx index 0d5183b5e95e1..7cada1d39fb6e 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/synchronization/blocked_window_tab.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/synchronization/blocked_window_tab.test.tsx @@ -7,6 +7,7 @@ import '../../../../../__mocks__/shallow_useeffect.mock'; import { setMockActions, setMockValues } from '../../../../../__mocks__/kea_logic'; +import { fullContentSources } from '../../../../__mocks__/content_sources.mock'; import { blockedWindow } from './__mocks__/syncronization.mock'; import React from 'react'; @@ -25,6 +26,7 @@ describe('BlockedWindows', () => { }; const mockValues = { blockedWindows: [blockedWindow], + contentSource: fullContentSources[0], }; beforeEach(() => { diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/synchronization/blocked_window_tab.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/synchronization/blocked_window_tab.tsx index 474bf4cab2a8e..f0227f76d4aa5 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/synchronization/blocked_window_tab.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/synchronization/blocked_window_tab.tsx @@ -13,13 +13,15 @@ import { EuiButton, EuiEmptyPrompt, EuiSpacer } from '@elastic/eui'; import { ADD_LABEL } from '../../../../constants'; import { BLOCKED_EMPTY_STATE_TITLE, BLOCKED_EMPTY_STATE_DESCRIPTION } from '../../constants'; +import { SourceLogic } from '../../source_logic'; import { BlockedWindowItem } from './blocked_window_item'; import { SynchronizationLogic } from './synchronization_logic'; export const BlockedWindows: React.FC = () => { - const { blockedWindows } = useValues(SynchronizationLogic); - const { addBlockedWindow } = useActions(SynchronizationLogic); + const { contentSource } = useValues(SourceLogic); + const { blockedWindows } = useValues(SynchronizationLogic({ contentSource })); + const { addBlockedWindow } = useActions(SynchronizationLogic({ contentSource })); const hasBlockedWindows = blockedWindows.length > 0; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/synchronization/frequency.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/synchronization/frequency.test.tsx index 08de4b41758a2..283c9a9cebbbd 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/synchronization/frequency.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/synchronization/frequency.test.tsx @@ -7,6 +7,7 @@ import '../../../../../__mocks__/shallow_useeffect.mock'; import { setMockActions, setMockValues } from '../../../../../__mocks__/kea_logic'; +import { fullContentSources } from '../../../../__mocks__/content_sources.mock'; import React from 'react'; @@ -23,7 +24,9 @@ describe('Frequency', () => { const mockActions = { handleSelectedTabChanged, }; - const mockValues = {}; + const mockValues = { + contentSource: fullContentSources[0], + }; beforeEach(() => { setMockActions(mockActions); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/synchronization/frequency.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/synchronization/frequency.tsx index fb19c84ecfdd1..3ca34f4960474 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/synchronization/frequency.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/synchronization/frequency.tsx @@ -7,7 +7,7 @@ import React from 'react'; -import { useActions } from 'kea'; +import { useActions, useValues } from 'kea'; import { EuiButton, @@ -31,6 +31,7 @@ import { DIFFERENT_SYNC_TYPES_LINK_LABEL, SYNC_BEST_PRACTICES_LINK_LABEL, } from '../../constants'; +import { SourceLogic } from '../../source_logic'; import { SourceLayout } from '../source_layout'; import { BlockedWindows } from './blocked_window_tab'; @@ -42,7 +43,8 @@ interface FrequencyProps { } export const Frequency: React.FC<FrequencyProps> = ({ tabId }) => { - const { handleSelectedTabChanged } = useActions(SynchronizationLogic); + const { contentSource } = useValues(SourceLogic); + const { handleSelectedTabChanged } = useActions(SynchronizationLogic({ contentSource })); const tabs = [ { diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/synchronization/frequency_item.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/synchronization/frequency_item.test.tsx index fb346ad96117e..ce295b467a09d 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/synchronization/frequency_item.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/synchronization/frequency_item.test.tsx @@ -8,21 +8,24 @@ import React from 'react'; import { shallow } from 'enzyme'; +import moment from 'moment'; import { EuiFieldNumber, EuiSuperSelect } from '@elastic/eui'; import { FrequencyItem } from './frequency_item'; describe('FrequencyItem', () => { + const estimate = { + duration: 'PT3D', + nextStart: '2021-09-27T21:39:24+00:00', + lastRun: '2021-09-25T21:39:24+00:00', + }; + const props = { label: 'Item', description: 'My item', duration: 'PT2D', - estimate: { - duration: 'PT3D', - nextStart: '2021-09-27T21:39:24+00:00', - lastRun: '2021-09-25T21:39:24+00:00', - }, + estimate, }; it('renders', () => { @@ -60,5 +63,25 @@ describe('FrequencyItem', () => { expect(wrapper.find(EuiFieldNumber).prop('value')).toEqual(1); expect(wrapper.find(EuiSuperSelect).prop('valueOfSelected')).toEqual('minutes'); }); + + it('handles "nextStart" that is in past', () => { + const wrapper = shallow(<FrequencyItem {...props} />); + + expect( + (wrapper.find('[data-test-subj="nextStartSummary"]').prop('values') as any)!.nextStartTime + ).toEqual('as soon as the currently running job finishes'); + }); + + it('handles "nextStart" that is in future', () => { + const estimateWithPastNextStart = { + ...estimate, + nextStart: moment().add(2, 'days').format(), + }; + const wrapper = shallow(<FrequencyItem {...props} estimate={estimateWithPastNextStart} />); + + expect( + (wrapper.find('[data-test-subj="nextStartSummary"]').prop('values') as any)!.nextStartTime + ).toEqual('in 2 days'); + }); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/synchronization/frequency_item.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/synchronization/frequency_item.tsx index 4e9eec28dc1eb..38f85ff2accaf 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/synchronization/frequency_item.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/synchronization/frequency_item.tsx @@ -27,6 +27,8 @@ import { } from '../../../../../shared/constants'; import { SyncEstimate } from '../../../../types'; +import { NEXT_SYNC_RUNNING_MESSAGE } from '../../constants'; + interface Props { label: string; description: string; @@ -53,6 +55,8 @@ export const FrequencyItem: React.FC<Props> = ({ label, description, duration, e const [interval, unit] = formatDuration(duration); const { lastRun, nextStart, duration: durationEstimate } = estimate; const estimateDisplay = durationEstimate && moment.duration(durationEstimate).humanize(); + const nextStartIsPast = moment().isAfter(nextStart); + const nextStartTime = nextStartIsPast ? NEXT_SYNC_RUNNING_MESSAGE : moment(nextStart).fromNow(); const onChange = () => '#TODO'; @@ -86,6 +90,7 @@ export const FrequencyItem: React.FC<Props> = ({ label, description, duration, e const nextStartSummary = ( <FormattedMessage + data-test-subj="nextStartSummary" id="xpack.enterpriseSearch.workplaceSearch.contentSources.synchronization.nextStartSummary" defaultMessage="{nextStartStrong} will begin {nextStartTime}." values={{ @@ -97,7 +102,7 @@ export const FrequencyItem: React.FC<Props> = ({ label, description, duration, e /> </strong> ), - nextStartTime: moment(nextStart).fromNow(), + nextStartTime, }} /> ); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/synchronization/objects_and_assets.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/synchronization/objects_and_assets.test.tsx new file mode 100644 index 0000000000000..42a08084db418 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/synchronization/objects_and_assets.test.tsx @@ -0,0 +1,86 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import '../../../../../__mocks__/shallow_useeffect.mock'; +import { setMockActions, setMockValues } from '../../../../../__mocks__/kea_logic'; +import { fullContentSources } from '../../../../__mocks__/content_sources.mock'; +import { blockedWindow } from './__mocks__/syncronization.mock'; + +import React from 'react'; + +import { shallow } from 'enzyme'; + +import { EuiSwitch } from '@elastic/eui'; + +import { ObjectsAndAssets } from './objects_and_assets'; + +describe('ObjectsAndAssets', () => { + const setThumbnailsChecked = jest.fn(); + const setContentExtractionChecked = jest.fn(); + const updateSyncSettings = jest.fn(); + const resetSyncSettings = jest.fn(); + const contentSource = fullContentSources[0]; + + const mockActions = { + setThumbnailsChecked, + setContentExtractionChecked, + updateSyncSettings, + resetSyncSettings, + }; + const mockValues = { + dataLoading: false, + blockedWindows: [blockedWindow], + contentSource, + thumbnailsChecked: true, + contentExtractionChecked: true, + hasUnsavedObjectsAndAssetsChanges: false, + }; + + beforeEach(() => { + setMockActions(mockActions); + setMockValues(mockValues); + }); + + it('renders', () => { + const wrapper = shallow(<ObjectsAndAssets />); + + expect(wrapper.find(EuiSwitch)).toHaveLength(2); + }); + + it('handles thumbnails switch change', () => { + const wrapper = shallow(<ObjectsAndAssets />); + wrapper + .find('[data-test-subj="ThumbnailsToggle"]') + .simulate('change', { target: { checked: false } }); + + expect(setThumbnailsChecked).toHaveBeenCalledWith(false); + }); + + it('handles content extraction switch change', () => { + const wrapper = shallow(<ObjectsAndAssets />); + wrapper + .find('[data-test-subj="ContentExtractionToggle"]') + .simulate('change', { target: { checked: false } }); + + expect(setContentExtractionChecked).toHaveBeenCalledWith(false); + }); + + it('renders correct text when areThumbnailsConfigEnabled is false', () => { + setMockValues({ + ...mockValues, + contentSource: { + ...contentSource, + areThumbnailsConfigEnabled: false, + }, + }); + const wrapper = shallow(<ObjectsAndAssets />); + + expect(wrapper.find('[data-test-subj="ThumbnailsToggle"]').prop('label')).toEqual( + 'Sync thumbnails - disabled at global configuration level' + ); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/synchronization/objects_and_assets.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/synchronization/objects_and_assets.tsx index 4c2804459f1ba..98abdb8bf67ea 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/synchronization/objects_and_assets.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/synchronization/objects_and_assets.tsx @@ -7,33 +7,113 @@ import React from 'react'; -import { EuiHorizontalRule, EuiLink } from '@elastic/eui'; +import { useActions, useValues } from 'kea'; +import { + EuiButton, + EuiButtonEmpty, + EuiFlexGroup, + EuiFlexItem, + EuiHorizontalRule, + EuiLink, + EuiSpacer, + EuiSwitch, + EuiText, +} from '@elastic/eui'; + +import { SAVE_BUTTON_LABEL } from '../../../../../shared/constants'; +import { UnsavedChangesPrompt } from '../../../../../shared/unsaved_changes_prompt'; import { ViewContentHeader } from '../../../../components/shared/view_content_header'; -import { NAV } from '../../../../constants'; +import { NAV, RESET_BUTTON } from '../../../../constants'; import { OBJECTS_AND_ASSETS_DOCS_URL } from '../../../../routes'; import { + SYNC_MANAGEMENT_CONTENT_EXTRACTION_LABEL, + SYNC_MANAGEMENT_THUMBNAILS_LABEL, + SYNC_MANAGEMENT_THUMBNAILS_GLOBAL_CONFIG_LABEL, SOURCE_OBJECTS_AND_ASSETS_DESCRIPTION, SYNC_OBJECTS_TYPES_LINK_LABEL, + SOURCE_OBJECTS_AND_ASSETS_LABEL, + SYNC_UNSAVED_CHANGES_MESSAGE, } from '../../constants'; +import { SourceLogic } from '../../source_logic'; import { SourceLayout } from '../source_layout'; +import { SynchronizationLogic } from './synchronization_logic'; + export const ObjectsAndAssets: React.FC = () => { + const { contentSource, dataLoading } = useValues(SourceLogic); + const { thumbnailsChecked, contentExtractionChecked, hasUnsavedObjectsAndAssetsChanges } = + useValues(SynchronizationLogic({ contentSource })); + const { + setThumbnailsChecked, + setContentExtractionChecked, + updateSyncSettings, + resetSyncSettings, + } = useActions(SynchronizationLogic({ contentSource })); + + const { areThumbnailsConfigEnabled } = contentSource; + + const actions = ( + <EuiFlexGroup> + <EuiFlexItem> + <EuiButtonEmpty onClick={resetSyncSettings} disabled={!hasUnsavedObjectsAndAssetsChanges}> + {RESET_BUTTON} + </EuiButtonEmpty> + </EuiFlexItem> + <EuiFlexItem> + <EuiButton fill onClick={updateSyncSettings} disabled={!hasUnsavedObjectsAndAssetsChanges}> + {SAVE_BUTTON_LABEL} + </EuiButton> + </EuiFlexItem> + </EuiFlexGroup> + ); + return ( <SourceLayout pageChrome={[NAV.SYNCHRONIZATION_OBJECTS_AND_ASSETS]} pageViewTelemetry="source_synchronization" - isLoading={false} + isLoading={dataLoading} > + <UnsavedChangesPrompt + hasUnsavedChanges={hasUnsavedObjectsAndAssetsChanges} + messageText={SYNC_UNSAVED_CHANGES_MESSAGE} + /> <ViewContentHeader title={NAV.SYNCHRONIZATION_OBJECTS_AND_ASSETS} description={SOURCE_OBJECTS_AND_ASSETS_DESCRIPTION} + action={actions} /> <EuiLink href={OBJECTS_AND_ASSETS_DOCS_URL} external> {SYNC_OBJECTS_TYPES_LINK_LABEL} </EuiLink> <EuiHorizontalRule /> - <div>TODO</div> + <EuiText size="m">{SOURCE_OBJECTS_AND_ASSETS_LABEL}</EuiText> + <EuiSpacer /> + <EuiFlexGroup> + <EuiFlexItem grow={false}> + <EuiSwitch + checked={thumbnailsChecked} + onChange={(e) => setThumbnailsChecked(e.target.checked)} + label={ + areThumbnailsConfigEnabled + ? SYNC_MANAGEMENT_THUMBNAILS_LABEL + : SYNC_MANAGEMENT_THUMBNAILS_GLOBAL_CONFIG_LABEL + } + disabled={!areThumbnailsConfigEnabled} + data-test-subj="ThumbnailsToggle" + /> + </EuiFlexItem> + </EuiFlexGroup> + <EuiFlexGroup> + <EuiFlexItem grow={false}> + <EuiSwitch + checked={contentExtractionChecked} + onChange={(e) => setContentExtractionChecked(e.target.checked)} + label={SYNC_MANAGEMENT_CONTENT_EXTRACTION_LABEL} + data-test-subj="ContentExtractionToggle" + /> + </EuiFlexItem> + </EuiFlexGroup> </SourceLayout> ); }; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/synchronization/synchronization.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/synchronization/synchronization.test.tsx index 632af08611ca9..fb9cdc6916fa9 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/synchronization/synchronization.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/synchronization/synchronization.test.tsx @@ -5,7 +5,8 @@ * 2.0. */ -import { setMockValues } from '../../../../../__mocks__/kea_logic'; +import { setMockValues, setMockActions } from '../../../../../__mocks__/kea_logic'; +import { fullContentSources } from '../../../../__mocks__/content_sources.mock'; import React from 'react'; @@ -16,8 +17,15 @@ import { EuiLink, EuiCallOut, EuiSwitch } from '@elastic/eui'; import { Synchronization } from './synchronization'; describe('Synchronization', () => { + const updateSyncEnabled = jest.fn(); + const mockvalues = { contentSource: fullContentSources[0] }; + + beforeEach(() => { + setMockActions({ updateSyncEnabled }); + setMockValues(mockvalues); + }); + it('renders when config enabled', () => { - setMockValues({ contentSource: { isSyncConfigEnabled: true } }); const wrapper = shallow(<Synchronization />); expect(wrapper.find(EuiLink)).toHaveLength(1); @@ -25,9 +33,16 @@ describe('Synchronization', () => { }); it('renders when config disabled', () => { - setMockValues({ contentSource: { isSyncConfigEnabled: false } }); + setMockValues({ contentSource: { isSyncConfigEnabled: false, indexing: { enabled: true } } }); const wrapper = shallow(<Synchronization />); expect(wrapper.find(EuiCallOut)).toHaveLength(1); }); + + it('handles EuiSwitch change event', () => { + const wrapper = shallow(<Synchronization />); + wrapper.find(EuiSwitch).simulate('change', { target: { checked: true } }); + + expect(updateSyncEnabled).toHaveBeenCalled(); + }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/synchronization/synchronization.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/synchronization/synchronization.tsx index 21daee8f26d40..21c44225615ea 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/synchronization/synchronization.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/synchronization/synchronization.tsx @@ -7,7 +7,7 @@ import React from 'react'; -import { useValues } from 'kea'; +import { useActions, useValues } from 'kea'; import { EuiCallOut, EuiLink, EuiPanel, EuiSwitch, EuiSpacer, EuiText } from '@elastic/eui'; @@ -25,17 +25,23 @@ import { import { SourceLogic } from '../../source_logic'; import { SourceLayout } from '../source_layout'; +import { SynchronizationLogic } from './synchronization_logic'; + export const Synchronization: React.FC = () => { + const { contentSource } = useValues(SourceLogic); + const { updateSyncEnabled } = useActions(SynchronizationLogic({ contentSource })); + const { - contentSource: { isSyncConfigEnabled }, - } = useValues(SourceLogic); + isSyncConfigEnabled, + indexing: { enabled }, + } = contentSource; - const onChange = (checked: boolean) => `#TODO: ${checked}`; + const onChange = (checked: boolean) => updateSyncEnabled(checked); const syncToggle = ( <EuiPanel hasBorder> <EuiSwitch label={SOURCE_SYNCRONIZATION_TOGGLE_LABEL} - checked + checked={enabled} onChange={(e) => onChange(e.target.checked)} /> <EuiSpacer size="m" /> diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/synchronization/synchronization_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/synchronization/synchronization_logic.test.ts index 50553d1493417..c51ef6cf2bf34 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/synchronization/synchronization_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/synchronization/synchronization_logic.test.ts @@ -5,14 +5,22 @@ * 2.0. */ -import { LogicMounter, mockKibanaValues } from '../../../../../__mocks__/kea_logic'; +import { + LogicMounter, + mockFlashMessageHelpers, + mockHttpValues, + mockKibanaValues, +} from '../../../../../__mocks__/kea_logic'; +import { fullContentSources } from '../../../../__mocks__/content_sources.mock'; import { nextTick } from '@kbn/test/jest'; -const contentSource = { id: 'source123' }; +import { expectedAsyncError } from '../../../../../test_helpers'; + jest.mock('../../source_logic', () => ({ - SourceLogic: { values: { contentSource } }, + SourceLogic: { actions: { setContentSource: jest.fn() } }, })); +import { SourceLogic } from '../../source_logic'; jest.mock('../../../../app_logic', () => ({ AppLogic: { values: { isOrganization: true } }, @@ -21,17 +29,23 @@ jest.mock('../../../../app_logic', () => ({ import { SynchronizationLogic, emptyBlockedWindow } from './synchronization_logic'; describe('SynchronizationLogic', () => { + const { http } = mockHttpValues; + const { flashAPIErrors, flashSuccessToast } = mockFlashMessageHelpers; const { navigateToUrl } = mockKibanaValues; const { mount } = new LogicMounter(SynchronizationLogic); + const contentSource = fullContentSources[0]; const defaultValues = { navigatingBetweenTabs: false, + hasUnsavedObjectsAndAssetsChanges: false, + contentExtractionChecked: true, + thumbnailsChecked: true, blockedWindows: [], }; beforeEach(() => { jest.clearAllMocks(); - mount(); + mount({}, { contentSource }); }); it('has expected default values', () => { @@ -50,6 +64,18 @@ describe('SynchronizationLogic', () => { expect(SynchronizationLogic.values.blockedWindows).toEqual([emptyBlockedWindow]); }); + + it('setThumbnailsChecked', () => { + SynchronizationLogic.actions.setThumbnailsChecked(false); + + expect(SynchronizationLogic.values.thumbnailsChecked).toEqual(false); + }); + + it('setContentExtractionChecked', () => { + SynchronizationLogic.actions.setContentExtractionChecked(false); + + expect(SynchronizationLogic.values.contentExtractionChecked).toEqual(false); + }); }); describe('listeners', () => { @@ -63,7 +89,7 @@ describe('SynchronizationLogic', () => { await nextTick(); expect(setNavigatingBetweenTabsSpy).toHaveBeenCalledWith(true); - expect(navigateToUrl).toHaveBeenCalledWith('/sources/source123/synchronization/frequency'); + expect(navigateToUrl).toHaveBeenCalledWith('/sources/123/synchronization/frequency'); }); it('calls calls correct route for "blocked_time_windows"', async () => { @@ -71,8 +97,125 @@ describe('SynchronizationLogic', () => { await nextTick(); expect(navigateToUrl).toHaveBeenCalledWith( - '/sources/source123/synchronization/frequency/blocked_windows' + '/sources/123/synchronization/frequency/blocked_windows' + ); + }); + }); + + describe('updateSyncEnabled', () => { + it('calls API and sets values for false value', async () => { + const setContentSourceSpy = jest.spyOn(SourceLogic.actions, 'setContentSource'); + const promise = Promise.resolve(contentSource); + http.patch.mockReturnValue(promise); + SynchronizationLogic.actions.updateSyncEnabled(false); + + expect(http.patch).toHaveBeenCalledWith( + '/internal/workplace_search/org/sources/123/settings', + { + body: JSON.stringify({ + content_source: { + indexing: { enabled: false }, + }, + }), + } + ); + await promise; + expect(setContentSourceSpy).toHaveBeenCalledWith(contentSource); + expect(flashSuccessToast).toHaveBeenCalledWith('Source synchronization disabled.'); + }); + + it('calls API and sets values for true value', async () => { + const promise = Promise.resolve(contentSource); + http.patch.mockReturnValue(promise); + SynchronizationLogic.actions.updateSyncEnabled(true); + + expect(http.patch).toHaveBeenCalledWith( + '/internal/workplace_search/org/sources/123/settings', + { + body: JSON.stringify({ + content_source: { + indexing: { enabled: true }, + }, + }), + } + ); + await promise; + expect(flashSuccessToast).toHaveBeenCalledWith('Source synchronization enabled.'); + }); + + it('handles error', async () => { + const error = { + response: { + error: 'this is an error', + status: 400, + }, + }; + const promise = Promise.reject(error); + http.patch.mockReturnValue(promise); + SynchronizationLogic.actions.updateSyncEnabled(false); + await expectedAsyncError(promise); + + expect(flashAPIErrors).toHaveBeenCalledWith(error); + }); + }); + + describe('resetSyncSettings', () => { + it('calls methods', async () => { + const setThumbnailsCheckedSpy = jest.spyOn( + SynchronizationLogic.actions, + 'setThumbnailsChecked' + ); + const setContentExtractionCheckedSpy = jest.spyOn( + SynchronizationLogic.actions, + 'setContentExtractionChecked' ); + SynchronizationLogic.actions.resetSyncSettings(); + + expect(setThumbnailsCheckedSpy).toHaveBeenCalledWith(true); + expect(setContentExtractionCheckedSpy).toHaveBeenCalledWith(true); + }); + }); + + describe('updateSyncSettings', () => { + it('calls API and sets values', async () => { + const setContentSourceSpy = jest.spyOn(SourceLogic.actions, 'setContentSource'); + const promise = Promise.resolve(contentSource); + http.patch.mockReturnValue(promise); + SynchronizationLogic.actions.updateSyncSettings(); + + expect(http.patch).toHaveBeenCalledWith( + '/internal/workplace_search/org/sources/123/settings', + { + body: JSON.stringify({ + content_source: { + indexing: { + features: { + content_extraction: { enabled: true }, + thumbnails: { enabled: true }, + }, + }, + }, + }), + } + ); + await promise; + expect(setContentSourceSpy).toHaveBeenCalledWith(contentSource); + expect(flashSuccessToast).toHaveBeenCalledWith('Source synchronization settings updated.'); + }); + + it('handles error', async () => { + const error = { + response: { + error: 'this is an error', + status: 400, + }, + }; + const promise = Promise.reject(error); + http.patch.mockReturnValue(promise); + SynchronizationLogic.actions.updateSyncSettings(); + await expectedAsyncError(promise); + + expect(flashAPIErrors).toHaveBeenCalledWith(error); }); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/synchronization/synchronization_logic.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/synchronization/synchronization_logic.ts index 4f67f6471e6e1..4106ab70cf201 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/synchronization/synchronization_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/synchronization/synchronization_logic.ts @@ -10,6 +10,8 @@ import moment from 'moment'; export type TabId = 'source_sync_frequency' | 'blocked_time_windows'; +import { flashAPIErrors, flashSuccessToast } from '../../../../../shared/flash_messages'; +import { HttpLogic } from '../../../../../shared/http'; import { KibanaLogic } from '../../../../../shared/kibana'; import { AppLogic } from '../../../../app_logic'; import { @@ -19,17 +21,30 @@ import { } from '../../../../routes'; import { BlockedWindow } from '../../../../types'; +import { + SYNC_ENABLED_MESSAGE, + SYNC_DISABLED_MESSAGE, + SYNC_SETTINGS_UPDATED_MESSAGE, +} from '../../constants'; import { SourceLogic } from '../../source_logic'; interface SynchronizationActions { setNavigatingBetweenTabs(navigatingBetweenTabs: boolean): boolean; handleSelectedTabChanged(tabId: TabId): TabId; addBlockedWindow(): void; + updateSyncSettings(): void; + resetSyncSettings(): void; + updateSyncEnabled(enabled: boolean): boolean; + setThumbnailsChecked(checked: boolean): boolean; + setContentExtractionChecked(checked: boolean): boolean; } interface SynchronizationValues { - hasUnsavedChanges: boolean; navigatingBetweenTabs: boolean; + hasUnsavedFrequencyChanges: boolean; + hasUnsavedObjectsAndAssetsChanges: boolean; + thumbnailsChecked: boolean; + contentExtractionChecked: boolean; blockedWindows: BlockedWindow[]; } @@ -43,12 +58,18 @@ export const emptyBlockedWindow: BlockedWindow = { export const SynchronizationLogic = kea< MakeLogicType<SynchronizationValues, SynchronizationActions> >({ + path: ['enterprise_search', 'workplace_search', 'synchronization_logic'], actions: { setNavigatingBetweenTabs: (navigatingBetweenTabs: boolean) => navigatingBetweenTabs, handleSelectedTabChanged: (tabId: TabId) => tabId, + updateSyncEnabled: (enabled: boolean) => enabled, + setThumbnailsChecked: (checked: boolean) => checked, + setContentExtractionChecked: (checked: boolean) => checked, + updateSyncSettings: true, + resetSyncSettings: true, addBlockedWindow: true, }, - reducers: { + reducers: ({ props }) => ({ navigatingBetweenTabs: [ false, { @@ -61,11 +82,47 @@ export const SynchronizationLogic = kea< addBlockedWindow: (state, _) => [...state, emptyBlockedWindow], }, ], - }, - listeners: ({ actions }) => ({ + thumbnailsChecked: [ + props.contentSource.indexing.features.thumbnails.enabled, + { + setThumbnailsChecked: (_, thumbnailsChecked) => thumbnailsChecked, + }, + ], + contentExtractionChecked: [ + props.contentSource.indexing.features.contentExtraction.enabled, + { + setContentExtractionChecked: (_, contentExtractionChecked) => contentExtractionChecked, + }, + ], + }), + selectors: ({ selectors }) => ({ + hasUnsavedObjectsAndAssetsChanges: [ + () => [ + selectors.thumbnailsChecked, + selectors.contentExtractionChecked, + (_, props) => props.contentSource, + ], + (thumbnailsChecked, contentExtractionChecked, contentSource) => { + const { + indexing: { + features: { + thumbnails: { enabled: thumbnailsEnabled }, + contentExtraction: { enabled: contentExtractionEnabled }, + }, + }, + } = contentSource; + + return ( + thumbnailsChecked !== thumbnailsEnabled || + contentExtractionChecked !== contentExtractionEnabled + ); + }, + ], + }), + listeners: ({ actions, values, props }) => ({ handleSelectedTabChanged: async (tabId, breakpoint) => { const { isOrganization } = AppLogic.values; - const { id: sourceId } = SourceLogic.values.contentSource; + const { id: sourceId } = props.contentSource; const path = tabId === 'source_sync_frequency' ? getContentSourcePath(SYNC_FREQUENCY_PATH, sourceId, isOrganization) @@ -82,5 +139,51 @@ export const SynchronizationLogic = kea< KibanaLogic.values.navigateToUrl(path); actions.setNavigatingBetweenTabs(false); }, + updateSyncEnabled: async (enabled) => { + const { id: sourceId } = props.contentSource; + const route = `/internal/workplace_search/org/sources/${sourceId}/settings`; + const successMessage = enabled ? SYNC_ENABLED_MESSAGE : SYNC_DISABLED_MESSAGE; + + try { + const response = await HttpLogic.values.http.patch(route, { + body: JSON.stringify({ content_source: { indexing: { enabled } } }), + }); + + SourceLogic.actions.setContentSource(response); + flashSuccessToast(successMessage); + } catch (e) { + flashAPIErrors(e); + } + }, + resetSyncSettings: () => { + actions.setThumbnailsChecked(props.contentSource.indexing.features.thumbnails.enabled); + actions.setContentExtractionChecked( + props.contentSource.indexing.features.contentExtraction.enabled + ); + }, + updateSyncSettings: async () => { + const { id: sourceId } = props.contentSource; + const route = `/internal/workplace_search/org/sources/${sourceId}/settings`; + + try { + const response = await HttpLogic.values.http.patch(route, { + body: JSON.stringify({ + content_source: { + indexing: { + features: { + content_extraction: { enabled: values.contentExtractionChecked }, + thumbnails: { enabled: values.thumbnailsChecked }, + }, + }, + }, + }), + }); + + SourceLogic.actions.setContentSource(response); + flashSuccessToast(SYNC_SETTINGS_UPDATED_MESSAGE); + } catch (e) { + flashAPIErrors(e); + } + }, }), }); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/constants.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/constants.ts index ae55a970a4f9f..4e46100b591b9 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/constants.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/constants.ts @@ -306,20 +306,6 @@ export const SOURCE_CONFIG_TITLE = i18n.translate( } ); -export const SYNC_MANAGEMENT_TITLE = i18n.translate( - 'xpack.enterpriseSearch.workplaceSearch.contentSources.syncManagementTitle', - { - defaultMessage: 'Sync management', - } -); - -export const SYNC_MANAGEMENT_DESCRIPTION = i18n.translate( - 'xpack.enterpriseSearch.workplaceSearch.contentSources.syncManagementDescription', - { - defaultMessage: 'Enable and disable extraction of specific content for this source.', - } -); - export const SYNC_MANAGEMENT_SYNCHRONIZE_LABEL = i18n.translate( 'xpack.enterpriseSearch.workplaceSearch.contentSources.syncManagementSynchronizeLabel', { @@ -344,7 +330,7 @@ export const SYNC_MANAGEMENT_THUMBNAILS_GLOBAL_CONFIG_LABEL = i18n.translate( export const SYNC_MANAGEMENT_CONTENT_EXTRACTION_LABEL = i18n.translate( 'xpack.enterpriseSearch.workplaceSearch.contentSources.syncManagementContentExtractionLabel', { - defaultMessage: 'Sync all text and content', + defaultMessage: 'Sync full-text from files', } ); @@ -565,6 +551,13 @@ export const SOURCE_OBJECTS_AND_ASSETS_DESCRIPTION = i18n.translate( } ); +export const SOURCE_OBJECTS_AND_ASSETS_LABEL = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.sources.sourceObjectsAndAssetsLabel', + { + defaultMessage: 'Object and details to include in search results', + } +); + export const SOURCE_SYNCRONIZATION_TOGGLE_LABEL = i18n.translate( 'xpack.enterpriseSearch.workplaceSearch.sources.sourceSyncronizationToggleLabel', { @@ -711,3 +704,38 @@ export const BLOCKED_EMPTY_STATE_DESCRIPTION = i18n.translate( defaultMessage: 'Add a blocked time window to only perform syncs at the right time.', } ); + +export const SYNC_ENABLED_MESSAGE = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.sources.syncEnabledMessage', + { + defaultMessage: 'Source synchronization enabled.', + } +); + +export const SYNC_DISABLED_MESSAGE = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.sources.syncDisabledMessage', + { + defaultMessage: 'Source synchronization disabled.', + } +); + +export const SYNC_SETTINGS_UPDATED_MESSAGE = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.sources.syncSettingsUpdatedMessage', + { + defaultMessage: 'Source synchronization settings updated.', + } +); + +export const SYNC_UNSAVED_CHANGES_MESSAGE = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.sources.syncUnsavedChangesMessage', + { + defaultMessage: 'Your changes have not been saved. Are you sure you want to leave?', + } +); + +export const NEXT_SYNC_RUNNING_MESSAGE = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.sources.nextSyncRunningMessage', + { + defaultMessage: 'as soon as the currently running job finishes', + } +); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_logic.test.ts index 0d8d5684c4a4c..1fb4477cea5c0 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_logic.test.ts @@ -58,8 +58,8 @@ describe('SourceLogic', () => { }); describe('actions', () => { - it('onInitializeSource', () => { - SourceLogic.actions.onInitializeSource(contentSource); + it('setContentSource', () => { + SourceLogic.actions.setContentSource(contentSource); expect(SourceLogic.values.contentSource).toEqual(contentSource); expect(SourceLogic.values.dataLoading).toEqual(false); @@ -67,7 +67,7 @@ describe('SourceLogic', () => { it('onUpdateSourceName', () => { const NAME = 'foo'; - SourceLogic.actions.onInitializeSource(contentSource); + SourceLogic.actions.setContentSource(contentSource); SourceLogic.actions.onUpdateSourceName(NAME); expect(SourceLogic.values.contentSource).toEqual({ @@ -88,7 +88,7 @@ describe('SourceLogic', () => { it('setContentFilterValue', () => { const VALUE = 'bar'; SourceLogic.actions.setSearchResults(searchServerResponse); - SourceLogic.actions.onInitializeSource(contentSource); + SourceLogic.actions.setContentSource(contentSource); SourceLogic.actions.setContentFilterValue(VALUE); expect(SourceLogic.values.contentMeta).toEqual({ @@ -127,7 +127,7 @@ describe('SourceLogic', () => { describe('listeners', () => { describe('initializeSource', () => { it('calls API and sets values (org)', async () => { - const onInitializeSourceSpy = jest.spyOn(SourceLogic.actions, 'onInitializeSource'); + const onInitializeSourceSpy = jest.spyOn(SourceLogic.actions, 'setContentSource'); const promise = Promise.resolve(contentSource); http.get.mockReturnValue(promise); SourceLogic.actions.initializeSource(contentSource.id); @@ -140,7 +140,7 @@ describe('SourceLogic', () => { it('calls API and sets values (account)', async () => { AppLogic.values.isOrganization = false; - const onInitializeSourceSpy = jest.spyOn(SourceLogic.actions, 'onInitializeSource'); + const onInitializeSourceSpy = jest.spyOn(SourceLogic.actions, 'setContentSource'); const promise = Promise.resolve(contentSource); http.get.mockReturnValue(promise); SourceLogic.actions.initializeSource(contentSource.id); 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 c31eacda69515..d10400bc5ba2d 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 @@ -23,7 +23,7 @@ import { PRIVATE_SOURCES_PATH, SOURCES_PATH, getSourcesPath } from '../../routes import { ContentSourceFullData, Meta, DocumentSummaryItem, SourceContentItem } from '../../types'; export interface SourceActions { - onInitializeSource(contentSource: ContentSourceFullData): ContentSourceFullData; + setContentSource(contentSource: ContentSourceFullData): ContentSourceFullData; onUpdateSourceName(name: string): string; setSearchResults(searchResultsResponse: SearchResultsResponse): SearchResultsResponse; initializeFederatedSummary(sourceId: string): { sourceId: string }; @@ -73,7 +73,7 @@ interface SourceUpdatePayload { export const SourceLogic = kea<MakeLogicType<SourceValues, SourceActions>>({ path: ['enterprise_search', 'workplace_search', 'source_logic'], actions: { - onInitializeSource: (contentSource: ContentSourceFullData) => contentSource, + setContentSource: (contentSource: ContentSourceFullData) => contentSource, onUpdateSourceName: (name: string) => name, onUpdateSummary: (summary: object[]) => summary, setSearchResults: (searchResultsResponse: SearchResultsResponse) => searchResultsResponse, @@ -93,7 +93,7 @@ export const SourceLogic = kea<MakeLogicType<SourceValues, SourceActions>>({ contentSource: [ {} as ContentSourceFullData, { - onInitializeSource: (_, contentSource) => contentSource, + setContentSource: (_, contentSource) => contentSource, onUpdateSourceName: (contentSource, name) => ({ ...contentSource, name, @@ -108,7 +108,7 @@ export const SourceLogic = kea<MakeLogicType<SourceValues, SourceActions>>({ dataLoading: [ true, { - onInitializeSource: () => false, + setContentSource: () => false, resetSourceState: () => true, }, ], @@ -158,7 +158,7 @@ export const SourceLogic = kea<MakeLogicType<SourceValues, SourceActions>>({ try { const response = await HttpLogic.values.http.get(route); - actions.onInitializeSource(response); + actions.setContentSource(response); if (response.isFederatedSource) { actions.initializeFederatedSummary(sourceId); } diff --git a/x-pack/plugins/event_log/server/event_log_service.mock.ts b/x-pack/plugins/event_log/server/event_log_service.mock.ts index a6e43a5b488c7..a3ad81eb0e5a6 100644 --- a/x-pack/plugins/event_log/server/event_log_service.mock.ts +++ b/x-pack/plugins/event_log/server/event_log_service.mock.ts @@ -10,7 +10,6 @@ import { eventLoggerMock } from './event_logger.mock'; const createEventLogServiceMock = () => { const mock: jest.Mocked<IEventLogService> = { - isEnabled: jest.fn(), isLoggingEntries: jest.fn(), isIndexingEntries: jest.fn(), registerProviderActions: jest.fn(), diff --git a/x-pack/plugins/event_log/server/event_log_service.test.ts b/x-pack/plugins/event_log/server/event_log_service.test.ts index ad8b8c06b47f3..aad51a03b2f06 100644 --- a/x-pack/plugins/event_log/server/event_log_service.test.ts +++ b/x-pack/plugins/event_log/server/event_log_service.test.ts @@ -19,14 +19,13 @@ describe('EventLogService', () => { const esContext = contextMock.create(); function getService(config: IEventLogConfig) { - const { enabled, logEntries, indexEntries } = config; + const { logEntries, indexEntries } = config; return new EventLogService({ esContext, systemLogger, kibanaUUID: '42', savedObjectProviderRegistry, config: { - enabled, logEntries, indexEntries, }, @@ -37,30 +36,19 @@ describe('EventLogService', () => { test('returns config values from service methods', () => { let service; - service = getService({ enabled: true, logEntries: true, indexEntries: true }); - expect(service.isEnabled()).toEqual(true); + service = getService({ logEntries: true, indexEntries: true }); expect(service.isLoggingEntries()).toEqual(true); expect(service.isIndexingEntries()).toEqual(true); - service = getService({ enabled: true, logEntries: false, indexEntries: true }); - expect(service.isEnabled()).toEqual(true); + service = getService({ logEntries: false, indexEntries: true }); expect(service.isLoggingEntries()).toEqual(false); expect(service.isIndexingEntries()).toEqual(true); - service = getService({ enabled: true, logEntries: true, indexEntries: false }); - expect(service.isEnabled()).toEqual(true); + service = getService({ logEntries: true, indexEntries: false }); expect(service.isLoggingEntries()).toEqual(true); expect(service.isIndexingEntries()).toEqual(false); - service = getService({ enabled: true, logEntries: false, indexEntries: false }); - expect(service.isEnabled()).toEqual(true); - expect(service.isLoggingEntries()).toEqual(false); - expect(service.isIndexingEntries()).toEqual(false); - - // this is the only non-obvious one; when enabled is false, - // logging/indexing will be false as well. - service = getService({ enabled: false, logEntries: true, indexEntries: true }); - expect(service.isEnabled()).toEqual(false); + service = getService({ logEntries: false, indexEntries: false }); expect(service.isLoggingEntries()).toEqual(false); expect(service.isIndexingEntries()).toEqual(false); }); diff --git a/x-pack/plugins/event_log/server/event_log_service.ts b/x-pack/plugins/event_log/server/event_log_service.ts index f6e1533aa1155..993631ed3ca8a 100644 --- a/x-pack/plugins/event_log/server/event_log_service.ts +++ b/x-pack/plugins/event_log/server/event_log_service.ts @@ -55,16 +55,12 @@ export class EventLogService implements IEventLogService { this.kibanaVersion = kibanaVersion; } - public isEnabled(): boolean { - return this.config.enabled; - } - public isLoggingEntries(): boolean { - return this.isEnabled() && this.config.logEntries; + return this.config.logEntries; } public isIndexingEntries(): boolean { - return this.isEnabled() && this.config.indexEntries; + return this.config.indexEntries; } registerProviderActions(provider: string, actions: string[]): void { diff --git a/x-pack/plugins/event_log/server/event_logger.test.ts b/x-pack/plugins/event_log/server/event_logger.test.ts index d90fd93c60043..43d791a18b5fc 100644 --- a/x-pack/plugins/event_log/server/event_logger.test.ts +++ b/x-pack/plugins/event_log/server/event_logger.test.ts @@ -31,7 +31,7 @@ describe('EventLogger', () => { service = new EventLogService({ esContext, systemLogger, - config: { enabled: true, logEntries: true, indexEntries: true }, + config: { logEntries: true, indexEntries: true }, kibanaUUID: KIBANA_SERVER_UUID, savedObjectProviderRegistry: savedObjectProviderRegistryMock.create(), kibanaVersion: '1.0.1', diff --git a/x-pack/plugins/event_log/server/event_logger.ts b/x-pack/plugins/event_log/server/event_logger.ts index bcda73da215ae..97335f9cda8d1 100644 --- a/x-pack/plugins/event_log/server/event_logger.ts +++ b/x-pack/plugins/event_log/server/event_logger.ts @@ -66,8 +66,6 @@ export class EventLogger implements IEventLogger { // non-blocking, but spawns an async task to do the work logEvent(eventProperties: IEvent): void { - if (!this.eventLogService.isEnabled()) return; - const event: IEvent = {}; const fixedProperties = { ecs: { diff --git a/x-pack/plugins/event_log/server/index.ts b/x-pack/plugins/event_log/server/index.ts index deeee970ce68a..14c121664d4a8 100644 --- a/x-pack/plugins/event_log/server/index.ts +++ b/x-pack/plugins/event_log/server/index.ts @@ -26,20 +26,5 @@ export { createReadySignal } from './lib/ready_signal'; export const config: PluginConfigDescriptor<IEventLogConfig> = { schema: ConfigSchema, - deprecations: () => [ - (settings, fromPath, addDeprecation) => { - if ( - settings?.xpack?.eventLog?.enabled === false || - settings?.xpack?.eventLog?.enabled === true - ) { - addDeprecation({ - message: `"xpack.eventLog.enabled" is deprecated. The ability to disable this plugin will be removed in 8.0.0.`, - correctiveActions: { - manualSteps: [`Remove "xpack.eventLog.enabled" from your kibana configs.`], - }, - }); - } - }, - ], }; export const plugin = (context: PluginInitializerContext) => new Plugin(context); diff --git a/x-pack/plugins/event_log/server/plugin.test.ts b/x-pack/plugins/event_log/server/plugin.test.ts index 166b084deb6bf..fa66d4b0b02b3 100644 --- a/x-pack/plugins/event_log/server/plugin.test.ts +++ b/x-pack/plugins/event_log/server/plugin.test.ts @@ -21,7 +21,6 @@ describe('event_log plugin', () => { const setup = plugin.setup(coreSetup); expect(typeof setup.getLogger).toBe('function'); expect(typeof setup.getProviderActions).toBe('function'); - expect(typeof setup.isEnabled).toBe('function'); expect(typeof setup.isIndexingEntries).toBe('function'); expect(typeof setup.isLoggingEntries).toBe('function'); expect(typeof setup.isProviderActionRegistered).toBe('function'); diff --git a/x-pack/plugins/event_log/server/types.ts b/x-pack/plugins/event_log/server/types.ts index 0750e89473b8e..c50bed7e01dd5 100644 --- a/x-pack/plugins/event_log/server/types.ts +++ b/x-pack/plugins/event_log/server/types.ts @@ -18,7 +18,6 @@ import { SavedObjectProvider } from './saved_object_provider_registry'; export const SAVED_OBJECT_REL_PRIMARY = 'primary'; export const ConfigSchema = schema.object({ - enabled: schema.boolean({ defaultValue: true }), logEntries: schema.boolean({ defaultValue: false }), indexEntries: schema.boolean({ defaultValue: true }), }); @@ -27,7 +26,6 @@ export type IEventLogConfig = TypeOf<typeof ConfigSchema>; // the object exposed by plugin.setup() export interface IEventLogService { - isEnabled(): boolean; isLoggingEntries(): boolean; isIndexingEntries(): boolean; registerProviderActions(provider: string, actions: string[]): void; diff --git a/x-pack/plugins/fleet/public/applications/fleet/hooks/use_breadcrumbs.tsx b/x-pack/plugins/fleet/public/applications/fleet/hooks/use_breadcrumbs.tsx index 3149a454c6c52..0202fc3351fc0 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/hooks/use_breadcrumbs.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/hooks/use_breadcrumbs.tsx @@ -64,24 +64,6 @@ const breadcrumbGetters: { }, { text: policyName }, ], - add_integration_from_policy: ({ policyName, policyId }) => [ - BASE_BREADCRUMB, - { - href: pagePathGetters.policies()[1], - text: i18n.translate('xpack.fleet.breadcrumbs.policiesPageTitle', { - defaultMessage: 'Agent policies', - }), - }, - { - href: pagePathGetters.policy_details({ policyId })[1], - text: policyName, - }, - { - text: i18n.translate('xpack.fleet.breadcrumbs.addPackagePolicyPageTitle', { - defaultMessage: 'Add integration', - }), - }, - ], add_integration_to_policy: ({ pkgTitle, pkgkey, integration }) => [ INTEGRATIONS_BASE_BREADCRUMB, { diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/layout.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/layout.tsx index 9354ae4dbe4f9..59498325bf91f 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/layout.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/layout.tsx @@ -48,7 +48,7 @@ export const CreatePackagePolicyPageLayout: React.FunctionComponent<{ 'data-test-subj': dataTestSubj, tabs = [], }) => { - const isAdd = useMemo(() => ['policy', 'package'].includes(from), [from]); + const isAdd = useMemo(() => ['package'].includes(from), [from]); const isEdit = useMemo(() => ['edit', 'package-edit'].includes(from), [from]); const isUpgrade = useMemo( () => diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/index.tsx index a83f6902ed056..ffc9cba90efea 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/index.tsx @@ -27,12 +27,7 @@ import type { ApplicationStart } from 'kibana/public'; import { safeLoad } from 'js-yaml'; import { toMountPoint } from '../../../../../../../../../src/plugins/kibana_react/public'; -import type { - AgentPolicy, - PackageInfo, - NewPackagePolicy, - CreatePackagePolicyRouteState, -} from '../../../types'; +import type { AgentPolicy, NewPackagePolicy, CreatePackagePolicyRouteState } from '../../../types'; import { useLink, useBreadcrumbs, @@ -40,8 +35,9 @@ import { useStartServices, useConfig, sendGetAgentStatus, + useGetPackageInfoByKey, } from '../../../hooks'; -import { Loading } from '../../../components'; +import { Loading, Error } from '../../../components'; import { ConfirmDeployAgentPolicyModal } from '../components'; import { useIntraAppState, useUIExtension } from '../../../hooks'; import { ExtensionWrapper } from '../../../components'; @@ -74,16 +70,12 @@ interface AddToPolicyParams { policyId?: string; } -interface AddFromPolicyParams { - policyId: string; -} - export const CreatePackagePolicyPage: React.FunctionComponent = () => { const { notifications } = useStartServices(); const { agents: { enabled: isFleetEnabled }, } = useConfig(); - const { params } = useRouteMatch<AddToPolicyParams | AddFromPolicyParams>(); + const { params } = useRouteMatch<AddToPolicyParams>(); const { getHref, getPath } = useLink(); const history = useHistory(); const handleNavigateTo = useNavigateToCallback(); @@ -110,10 +102,8 @@ export const CreatePackagePolicyPage: React.FunctionComponent = () => { const from: EditPackagePolicyFrom = 'policyId' in params || queryParamsPolicyId ? 'policy' : 'package'; - // Agent policy and package info states + // Agent policy state const [agentPolicy, setAgentPolicy] = useState<AgentPolicy | undefined>(); - const [packageInfo, setPackageInfo] = useState<PackageInfo>(); - const [isLoadingAgentPolicyStep, setIsLoadingAgentPolicyStep] = useState<boolean>(false); // Retrieve agent count const agentPolicyId = agentPolicy?.id; @@ -142,30 +132,24 @@ export const CreatePackagePolicyPage: React.FunctionComponent = () => { inputs: [], }); - // Package policy validation state + // Validation state const [validationResults, setValidationResults] = useState<PackagePolicyValidationResults>(); + const [hasAgentPolicyError, setHasAgentPolicyError] = useState<boolean>(false); // Form state - const [formState, setFormState] = useState<PackagePolicyFormState>('INVALID'); - - // Update package info method - const updatePackageInfo = useCallback( - (updatedPackageInfo: PackageInfo | undefined) => { - if (updatedPackageInfo) { - setPackageInfo(updatedPackageInfo); - if (agentPolicy) { - setFormState('VALID'); - } - } else { - setFormState('INVALID'); - setPackageInfo(undefined); - } + const [formState, setFormState] = useState<PackagePolicyFormState>('VALID'); - // eslint-disable-next-line no-console - console.debug('Package info updated', updatedPackageInfo); - }, - [agentPolicy, setPackageInfo, setFormState] - ); + // Fetch package info + const { + data: packageInfoData, + error: packageInfoError, + isLoading: isPackageInfoLoading, + } = useGetPackageInfoByKey(params.pkgkey); + const packageInfo = useMemo(() => { + if (packageInfoData && packageInfoData.response) { + return packageInfoData.response; + } + }, [packageInfoData]); // Update agent policy method const updateAgentPolicy = useCallback( @@ -251,12 +235,12 @@ export const CreatePackagePolicyPage: React.FunctionComponent = () => { if (routeState && routeState.onCancelUrl) { return routeState.onCancelUrl; } - return from === 'policy' + return from === 'policy' && agentPolicyId ? getHref('policy_details', { - policyId: agentPolicyId || (params as AddFromPolicyParams).policyId, + policyId: agentPolicyId, }) - : getHref('integration_details_overview', { pkgkey: (params as AddToPolicyParams).pkgkey }); - }, [agentPolicyId, params, from, getHref, routeState]); + : getHref('integration_details_overview', { pkgkey: params.pkgkey }); + }, [routeState, from, agentPolicyId, getHref, params.pkgkey]); const cancelClickHandler: ReactEventHandler = useCallback( (ev) => { @@ -305,7 +289,7 @@ export const CreatePackagePolicyPage: React.FunctionComponent = () => { } else { history.push( getPath('policy_details', { - policyId: agentPolicy?.id || (params as AddFromPolicyParams).policyId, + policyId: agentPolicy!.id, }) ); } @@ -313,8 +297,7 @@ export const CreatePackagePolicyPage: React.FunctionComponent = () => { const fromPolicyWithoutAgentsAssigned = from === 'policy' && agentPolicy && agentCount === 0; - const fromPackageWithoutAgentsAssigned = - from === 'package' && packageInfo && agentPolicy && agentCount === 0; + const fromPackageWithoutAgentsAssigned = packageInfo && agentPolicy && agentCount === 0; const hasAgentsAssigned = agentCount && agentPolicy; @@ -379,7 +362,6 @@ export const CreatePackagePolicyPage: React.FunctionComponent = () => { hasErrors, agentCount, savePackagePolicy, - doOnSaveNavigation, from, agentPolicy, packageInfo, @@ -390,7 +372,6 @@ export const CreatePackagePolicyPage: React.FunctionComponent = () => { handleNavigateTo, history, getPath, - params, ]); const integrationInfo = useMemo( @@ -418,24 +399,23 @@ export const CreatePackagePolicyPage: React.FunctionComponent = () => { const stepSelectAgentPolicy = useMemo( () => ( <StepSelectAgentPolicy - pkgkey={(params as AddToPolicyParams).pkgkey} - updatePackageInfo={updatePackageInfo} + packageInfo={packageInfo} defaultAgentPolicyId={queryParamsPolicyId} agentPolicy={agentPolicy} updateAgentPolicy={updateAgentPolicy} - setIsLoadingSecondStep={setIsLoadingAgentPolicyStep} + setHasAgentPolicyError={setHasAgentPolicyError} /> ), - [params, updatePackageInfo, agentPolicy, updateAgentPolicy, queryParamsPolicyId] + [packageInfo, queryParamsPolicyId, agentPolicy, updateAgentPolicy] ); const extensionView = useUIExtension(packagePolicy.package?.name ?? '', 'package-policy-create'); const stepConfigurePackagePolicy = useMemo( () => - isLoadingAgentPolicyStep ? ( + isPackageInfoLoading ? ( <Loading /> - ) : agentPolicy && packageInfo ? ( + ) : packageInfo ? ( <> <StepDefinePackagePolicy agentPolicy={agentPolicy} @@ -459,8 +439,8 @@ export const CreatePackagePolicyPage: React.FunctionComponent = () => { /> )} - {/* If an Agent Policy and a package has been selected, then show UI extension (if any) */} - {extensionView && packagePolicy.policy_id && packagePolicy.package?.name && ( + {/* If a package has been loaded, then show UI extension (if any) */} + {extensionView && packagePolicy.package?.name && ( <ExtensionWrapper> <extensionView.Component newPolicy={packagePolicy} @@ -473,7 +453,7 @@ export const CreatePackagePolicyPage: React.FunctionComponent = () => { <div /> ), [ - isLoadingAgentPolicyStep, + isPackageInfoLoading, agentPolicy, packageInfo, packagePolicy, @@ -491,7 +471,6 @@ export const CreatePackagePolicyPage: React.FunctionComponent = () => { title: i18n.translate('xpack.fleet.createPackagePolicy.stepConfigurePackagePolicyTitle', { defaultMessage: 'Configure integration', }), - status: !packageInfo || !agentPolicy || isLoadingAgentPolicyStep ? 'disabled' : undefined, 'data-test-subj': 'dataCollectionSetupStep', children: stepConfigurePackagePolicy, }, @@ -503,6 +482,21 @@ export const CreatePackagePolicyPage: React.FunctionComponent = () => { }, ]; + // Display package error if there is one + if (packageInfoError) { + return ( + <Error + title={ + <FormattedMessage + id="xpack.fleet.createPackagePolicy.StepSelectPolicy.errorLoadingPackageTitle" + defaultMessage="Error loading package information" + /> + } + error={packageInfoError} + /> + ); + } + return ( <CreatePackagePolicyPageLayout {...layoutProps} data-test-subj="createPackagePolicy"> <EuiErrorBoundary> @@ -527,10 +521,7 @@ export const CreatePackagePolicyPage: React.FunctionComponent = () => { <CustomEuiBottomBar data-test-subj="integrationsBottomBar"> <EuiFlexGroup justifyContent="spaceBetween" alignItems="center"> <EuiFlexItem grow={false}> - {!isLoadingAgentPolicyStep && - agentPolicy && - packageInfo && - formState === 'INVALID' ? ( + {agentPolicy && packageInfo && formState === 'INVALID' ? ( <FormattedMessage id="xpack.fleet.createPackagePolicy.errorOnSaveText" defaultMessage="Your integration policy has errors. Please fix them before saving." @@ -557,7 +548,7 @@ export const CreatePackagePolicyPage: React.FunctionComponent = () => { <EuiButton onClick={onSubmit} isLoading={formState === 'LOADING'} - disabled={formState !== 'VALID'} + disabled={formState !== 'VALID' || hasAgentPolicyError} iconType="save" color="primary" fill diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/step_define_package_policy.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/step_define_package_policy.tsx index e84831a3006f4..7e4896837013c 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/step_define_package_policy.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/step_define_package_policy.tsx @@ -47,7 +47,7 @@ const FormGroupResponsiveFields = styled(EuiDescribedFormGroup)` `; export const StepDefinePackagePolicy: React.FunctionComponent<{ - agentPolicy: AgentPolicy; + agentPolicy?: AgentPolicy; packageInfo: PackageInfo; packagePolicy: NewPackagePolicy; integrationToEnable?: string; @@ -92,15 +92,17 @@ export const StepDefinePackagePolicy: React.FunctionComponent<{ if (currentPkgKey !== pkgKey) { // Existing package policies on the agent policy using the package name, retrieve highest number appended to package policy name const pkgPoliciesNamePattern = new RegExp(`${packageInfo.name}-(\\d+)`); - const pkgPoliciesWithMatchingNames = (agentPolicy.package_policies as PackagePolicy[]) - .filter((ds) => Boolean(ds.name.match(pkgPoliciesNamePattern))) - .map((ds) => parseInt(ds.name.match(pkgPoliciesNamePattern)![1], 10)) - .sort((a, b) => a - b); + const pkgPoliciesWithMatchingNames = agentPolicy + ? (agentPolicy.package_policies as PackagePolicy[]) + .filter((ds) => Boolean(ds.name.match(pkgPoliciesNamePattern))) + .map((ds) => parseInt(ds.name.match(pkgPoliciesNamePattern)![1], 10)) + .sort((a, b) => a - b) + : []; updatePackagePolicy( packageToPackagePolicy( packageInfo, - agentPolicy.id, + agentPolicy?.id || '', packagePolicy.output_id, packagePolicy.namespace, `${packageInfo.name}-${ @@ -115,7 +117,7 @@ export const StepDefinePackagePolicy: React.FunctionComponent<{ } // If agent policy has changed, update package policy's agent policy ID and namespace - if (packagePolicy.policy_id !== agentPolicy.id) { + if (agentPolicy && packagePolicy.policy_id !== agentPolicy.id) { updatePackagePolicy({ policy_id: agentPolicy.id, namespace: agentPolicy.namespace, diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/step_select_agent_policy.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/step_select_agent_policy.tsx index 63cf1a0c87b29..72bd829dcf61a 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/step_select_agent_policy.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/step_select_agent_policy.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { useEffect, useState, useMemo } from 'react'; +import React, { useEffect, useState, useMemo, useCallback } from 'react'; import styled from 'styled-components'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; @@ -26,7 +26,6 @@ import { Error } from '../../../components'; import type { AgentPolicy, PackageInfo, GetAgentPoliciesResponseItem } from '../../../types'; import { isPackageLimited, doesAgentPolicyAlreadyIncludePackage } from '../../../services'; import { - useGetPackageInfoByKey, useGetAgentPolicies, sendGetOneAgentPolicy, useCapabilities, @@ -41,19 +40,17 @@ const AgentPolicyFormRow = styled(EuiFormRow)` `; export const StepSelectAgentPolicy: React.FunctionComponent<{ - pkgkey: string; - updatePackageInfo: (packageInfo: PackageInfo | undefined) => void; + packageInfo?: PackageInfo; defaultAgentPolicyId?: string; agentPolicy: AgentPolicy | undefined; updateAgentPolicy: (agentPolicy: AgentPolicy | undefined) => void; - setIsLoadingSecondStep: (isLoading: boolean) => void; + setHasAgentPolicyError: (hasError: boolean) => void; }> = ({ - pkgkey, - updatePackageInfo, + packageInfo, agentPolicy, updateAgentPolicy, - setIsLoadingSecondStep, defaultAgentPolicyId, + setHasAgentPolicyError, }) => { const { isReady: isFleetReady } = useFleetStatus(); @@ -68,14 +65,6 @@ export const StepSelectAgentPolicy: React.FunctionComponent<{ const [isCreateAgentPolicyFlyoutOpen, setIsCreateAgentPolicyFlyoutOpen] = useState<boolean>(false); - // Fetch package info - const { - data: packageInfoData, - error: packageInfoError, - isLoading: isPackageInfoLoading, - } = useGetPackageInfoByKey(pkgkey); - const isLimitedPackage = (packageInfoData && isPackageLimited(packageInfoData.response)) || false; - // Fetch agent policies info const { data: agentPoliciesData, @@ -101,18 +90,19 @@ export const StepSelectAgentPolicy: React.FunctionComponent<{ }, {}); }, [agentPolicies]); - // Update parent package state - useEffect(() => { - if (packageInfoData && packageInfoData.response) { - updatePackageInfo(packageInfoData.response); - } - }, [packageInfoData, updatePackageInfo]); + const doesAgentPolicyHaveLimitedPackage = useCallback( + (policy: AgentPolicy, pkgInfo: PackageInfo) => { + return policy + ? isPackageLimited(pkgInfo) && doesAgentPolicyAlreadyIncludePackage(policy, pkgInfo.name) + : false; + }, + [] + ); // Update parent selected agent policy state useEffect(() => { const fetchAgentPolicyInfo = async () => { if (selectedPolicyId) { - setIsLoadingSecondStep(true); const { data, error } = await sendGetOneAgentPolicy(selectedPolicyId); if (error) { setSelectedAgentPolicyError(error); @@ -125,39 +115,36 @@ export const StepSelectAgentPolicy: React.FunctionComponent<{ setSelectedAgentPolicyError(undefined); updateAgentPolicy(undefined); } - setIsLoadingSecondStep(false); }; if (!agentPolicy || selectedPolicyId !== agentPolicy.id) { fetchAgentPolicyInfo(); } - }, [selectedPolicyId, agentPolicy, updateAgentPolicy, setIsLoadingSecondStep]); + }, [selectedPolicyId, agentPolicy, updateAgentPolicy]); const agentPolicyOptions: Array<EuiComboBoxOptionOption<string>> = useMemo( () => - packageInfoData + packageInfo ? agentPolicies.map((agentConf) => { - const alreadyHasLimitedPackage = - (isLimitedPackage && - doesAgentPolicyAlreadyIncludePackage(agentConf, packageInfoData.response.name)) || - false; return { label: agentConf.name, value: agentConf.id, - disabled: alreadyHasLimitedPackage, + disabled: doesAgentPolicyHaveLimitedPackage(agentConf, packageInfo), 'data-test-subj': 'agentPolicyItem', }; }) : [], - [agentPolicies, isLimitedPackage, packageInfoData] + [agentPolicies, doesAgentPolicyHaveLimitedPackage, packageInfo] ); - const selectedAgentPolicyOption = agentPolicyOptions.find( - (option) => option.value === selectedPolicyId + const selectedAgentPolicyOption = useMemo( + () => agentPolicyOptions.find((option) => option.value === selectedPolicyId), + [agentPolicyOptions, selectedPolicyId] ); // Try to select default agent policy useEffect(() => { if (!selectedPolicyId && agentPolicies.length && agentPolicyOptions.length) { + const firstEnabledOption = agentPolicyOptions.find((option) => !option.disabled); const defaultAgentPolicy = agentPolicies.find((policy) => policy.is_default); if (defaultAgentPolicy) { const defaultAgentPolicyOption = agentPolicyOptions.find( @@ -165,25 +152,33 @@ export const StepSelectAgentPolicy: React.FunctionComponent<{ ); if (defaultAgentPolicyOption && !defaultAgentPolicyOption.disabled) { setSelectedPolicyId(defaultAgentPolicy.id); + } else { + if (firstEnabledOption) { + setSelectedPolicyId(firstEnabledOption.value); + } } + } else if (firstEnabledOption) { + setSelectedPolicyId(firstEnabledOption.value); } } }, [agentPolicies, agentPolicyOptions, selectedPolicyId]); - // Display package error if there is one - if (packageInfoError) { - return ( - <Error - title={ - <FormattedMessage - id="xpack.fleet.createPackagePolicy.StepSelectPolicy.errorLoadingPackageTitle" - defaultMessage="Error loading package information" - /> - } - error={packageInfoError} - /> - ); - } + // Bubble up any issues with agent policy selection + useEffect(() => { + if ( + selectedPolicyId && + !selectedAgentPolicyError && + selectedAgentPolicyOption && + !selectedAgentPolicyOption.disabled + ) { + setHasAgentPolicyError(false); + } else setHasAgentPolicyError(true); + }, [ + selectedAgentPolicyError, + selectedAgentPolicyOption, + selectedPolicyId, + setHasAgentPolicyError, + ]); // Display agent policies list error if there is one if (agentPoliciesError) { @@ -276,6 +271,27 @@ export const StepSelectAgentPolicy: React.FunctionComponent<{ /> ) : null } + isInvalid={Boolean( + !selectedPolicyId || + !packageInfo || + doesAgentPolicyHaveLimitedPackage( + agentPoliciesById[selectedPolicyId], + packageInfo + ) + )} + error={ + !selectedPolicyId ? ( + <FormattedMessage + id="xpack.fleet.createPackagePolicy.StepSelectPolicy.noPolicySelectedError" + defaultMessage="An agent policy is required." + /> + ) : ( + <FormattedMessage + id="xpack.fleet.createPackagePolicy.StepSelectPolicy.cannotAddLimitedIntegrationError" + defaultMessage="This integration can only be added once per agent policy." + /> + ) + } > <EuiComboBox placeholder={i18n.translate( @@ -287,7 +303,7 @@ export const StepSelectAgentPolicy: React.FunctionComponent<{ singleSelection={{ asPlainText: true }} isClearable={false} fullWidth={true} - isLoading={isAgentPoliciesLoading || isPackageInfoLoading} + isLoading={isAgentPoliciesLoading || !packageInfo} options={agentPolicyOptions} selectedOptions={selectedAgentPolicyOption ? [selectedAgentPolicyOption] : []} onChange={(options) => { diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/index.tsx index 9d9077a9abdbd..15196fadf7529 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/index.tsx @@ -15,7 +15,6 @@ import { DefaultLayout } from '../../layouts'; import { AgentPolicyListPage } from './list_page'; import { AgentPolicyDetailsPage } from './details_page'; -import { CreatePackagePolicyPage } from './create_package_policy_page'; import { EditPackagePolicyPage } from './edit_package_policy_page'; import { UpgradePackagePolicyPage } from './upgrade_package_policy_page'; @@ -32,9 +31,6 @@ export const AgentPolicyApp: React.FunctionComponent = () => { <Route path={FLEET_ROUTING_PATHS.upgrade_package_policy}> <UpgradePackagePolicyPage /> </Route> - <Route path={FLEET_ROUTING_PATHS.add_integration_from_policy}> - <CreatePackagePolicyPage /> - </Route> <Route path={FLEET_ROUTING_PATHS.policy_details}> <AgentPolicyDetailsPage /> </Route> diff --git a/x-pack/plugins/fleet/public/applications/integrations/app.tsx b/x-pack/plugins/fleet/public/applications/integrations/app.tsx index d37cbe4c166df..c5cc1e1892eda 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/app.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/app.tsx @@ -201,13 +201,15 @@ export const IntegrationsAppContext: React.FC<{ <EuiThemeProvider darkMode={isDarkMode}> <UIExtensionsContext.Provider value={extensions}> <FleetStatusProvider> - <Router history={history}> - <AgentPolicyContextProvider> - <PackageInstallProvider notifications={startServices.notifications}> - {children} - </PackageInstallProvider> - </AgentPolicyContextProvider> - </Router> + <startServices.customIntegrations.ContextProvider> + <Router history={history}> + <AgentPolicyContextProvider> + <PackageInstallProvider notifications={startServices.notifications}> + {children} + </PackageInstallProvider> + </AgentPolicyContextProvider> + </Router> + </startServices.customIntegrations.ContextProvider> </FleetStatusProvider> </UIExtensionsContext.Provider> </EuiThemeProvider> diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/overview/details.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/overview/details.tsx index 467dae12fa583..5ef06e734f580 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/overview/details.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/overview/details.tsx @@ -18,6 +18,10 @@ import { } from '@elastic/eui'; import type { EuiDescriptionListProps } from '@elastic/eui/src/components/description_list/description_list'; +import styled, { useTheme } from 'styled-components'; + +import type { EuiTheme } from '../../../../../../../../../../../src/plugins/kibana_react/common'; + import type { PackageInfo, PackageSpecCategory, @@ -28,13 +32,21 @@ import { entries } from '../../../../../types'; import { useGetCategories } from '../../../../../hooks'; import { AssetTitleMap, DisplayedAssets, ServiceTitleMap } from '../../../constants'; +import { + withSuspense, + LazyReplacementCard, +} from '../../../../../../../../../../../src/plugins/custom_integrations/public'; + import { NoticeModal } from './notice_modal'; +const ReplacementCard = withSuspense(LazyReplacementCard); + interface Props { packageInfo: PackageInfo; } export const Details: React.FC<Props> = memo(({ packageInfo }) => { + const theme = useTheme() as EuiTheme; const { data: categoriesData, isLoading: isLoadingCategories } = useGetCategories(); const packageCategories: string[] = useMemo(() => { if (!isLoadingCategories && categoriesData && categoriesData.response) { @@ -163,6 +175,23 @@ export const Details: React.FC<Props> = memo(({ packageInfo }) => { toggleNoticeModal, ]); + const Replacements = styled(EuiFlexItem)` + margin: 0; + + & .euiAccordion { + padding-top: ${parseInt(theme.eui.euiSizeL, 10) * 2}px; + + &::before { + content: ''; + display: block; + border-top: 1px solid ${theme.eui.euiColorLightShade}; + position: relative; + top: -${theme.eui.euiSizeL}; + margin: 0 ${theme.eui.euiSizeXS}; + } + } + `; + return ( <> <EuiPortal> @@ -181,6 +210,9 @@ export const Details: React.FC<Props> = memo(({ packageInfo }) => { <EuiFlexItem> <EuiDescriptionList type="column" compressed listItems={listItems} /> </EuiFlexItem> + <Replacements> + <ReplacementCard eprPackageName={packageInfo.name} /> + </Replacements> </EuiFlexGroup> </> ); diff --git a/x-pack/plugins/fleet/public/constants/page_paths.ts b/x-pack/plugins/fleet/public/constants/page_paths.ts index e430e58d297f9..0673d50ec9485 100644 --- a/x-pack/plugins/fleet/public/constants/page_paths.ts +++ b/x-pack/plugins/fleet/public/constants/page_paths.ts @@ -26,7 +26,6 @@ export type DynamicPage = | 'integration_details_custom' | 'integration_policy_edit' | 'policy_details' - | 'add_integration_from_policy' | 'add_integration_to_policy' | 'edit_integration' | 'upgrade_package_policy' @@ -56,8 +55,6 @@ export const FLEET_ROUTING_PATHS = { policy_details_settings: '/policies/:policyId/settings', edit_integration: '/policies/:policyId/edit-integration/:packagePolicyId', upgrade_package_policy: '/policies/:policyId/upgrade-package-policy/:packagePolicyId', - // TODO: Review uses and remove if it is no longer used or linked to in any UX flows - add_integration_from_policy: '/policies/:policyId/add-integration', enrollment_tokens: '/enrollment-tokens', data_streams: '/data-streams', @@ -127,11 +124,6 @@ export const pagePathGetters: { FLEET_BASE_PATH, `/policies/${policyId}${tabId ? `/${tabId}` : ''}`, ], - // TODO: This might need to be removed because we do not have a way to pick an integration in line anymore - add_integration_from_policy: ({ policyId }) => [ - FLEET_BASE_PATH, - `/policies/${policyId}/add-integration`, - ], add_integration_to_policy: ({ pkgkey, integration, agentPolicyId }) => [ FLEET_BASE_PATH, // prettier-ignore diff --git a/x-pack/plugins/fleet/public/hooks/use_merge_epr_with_replacements.ts b/x-pack/plugins/fleet/public/hooks/use_merge_epr_with_replacements.ts index a3c1fea5e744f..ac53badc2446d 100644 --- a/x-pack/plugins/fleet/public/hooks/use_merge_epr_with_replacements.ts +++ b/x-pack/plugins/fleet/public/hooks/use_merge_epr_with_replacements.ts @@ -10,6 +10,7 @@ import type { CustomIntegration, IntegrationCategory, } from '../../../../../src/plugins/custom_integrations/common'; +import { filterCustomIntegrations } from '../../../../../src/plugins/custom_integrations/public'; // Export this as a utility to find replacements for a package (e.g. in the overview-page for an EPR package) function findReplacementsForEprPackage( @@ -20,9 +21,7 @@ function findReplacementsForEprPackage( if (release === 'ga') { return []; } - return replacements.filter((customIntegration: CustomIntegration) => { - return customIntegration.eprOverlap === packageName; - }); + return filterCustomIntegrations(replacements, { eprPackageName: packageName }); } export function useMergeEprPackagesWithReplacements( diff --git a/x-pack/plugins/fleet/public/mock/plugin_dependencies.ts b/x-pack/plugins/fleet/public/mock/plugin_dependencies.ts index 5f3ee5c188b45..f78fe58a6ad88 100644 --- a/x-pack/plugins/fleet/public/mock/plugin_dependencies.ts +++ b/x-pack/plugins/fleet/public/mock/plugin_dependencies.ts @@ -26,5 +26,6 @@ export const createStartDepsMock = (): MockedFleetStartDeps => { return { data: dataPluginMock.createStartContract(), navigation: navigationPluginMock.createStartContract(), + customIntegrations: customIntegrationsMock.createStart(), }; }; diff --git a/x-pack/plugins/fleet/public/plugin.ts b/x-pack/plugins/fleet/public/plugin.ts index d7cc332910dc2..d23bfcfe7b888 100644 --- a/x-pack/plugins/fleet/public/plugin.ts +++ b/x-pack/plugins/fleet/public/plugin.ts @@ -16,8 +16,12 @@ import { i18n } from '@kbn/i18n'; import type { NavigationPublicPluginStart } from 'src/plugins/navigation/public'; +import type { + CustomIntegrationsStart, + CustomIntegrationsSetup, +} from 'src/plugins/custom_integrations/public'; + import { DEFAULT_APP_CATEGORIES, AppNavLinkStatus } from '../../../../src/core/public'; -import type { CustomIntegrationsSetup } from '../../../../src/plugins/custom_integrations/public'; import type { DataPublicPluginSetup, @@ -76,6 +80,7 @@ export interface FleetSetupDeps { export interface FleetStartDeps { data: DataPublicPluginStart; navigation: NavigationPublicPluginStart; + customIntegrations: CustomIntegrationsStart; } export interface FleetStartServices extends CoreStart, FleetStartDeps { diff --git a/x-pack/plugins/fleet/storybook/context/index.tsx b/x-pack/plugins/fleet/storybook/context/index.tsx index 6c6b2e479d234..e5a360c28385b 100644 --- a/x-pack/plugins/fleet/storybook/context/index.tsx +++ b/x-pack/plugins/fleet/storybook/context/index.tsx @@ -13,12 +13,12 @@ import { createBrowserHistory } from 'history'; import { I18nProvider } from '@kbn/i18n/react'; import { ScopedHistory } from '../../../../../src/core/public'; +import { getStorybookContextProvider } from '../../../../../src/plugins/custom_integrations/storybook'; import { IntegrationsAppContext } from '../../public/applications/integrations/app'; import type { FleetConfigType, FleetStartServices } from '../../public/plugin'; -// TODO: This is a contract leak, and should be on the context, rather than a setter. +// TODO: These are contract leaks, and should be on the context, rather than a setter. import { setHttpClient } from '../../public/hooks/use_request'; - import { setCustomIntegrations } from '../../public/services/custom_integrations'; import { getApplication } from './application'; @@ -36,7 +36,6 @@ import { stubbedStartServices } from './stubs'; // Expect this to grow as components that are given Stories need access to mocked services. export const StorybookContext: React.FC<{ storyContext?: StoryContext }> = ({ children: storyChildren, - storyContext, }) => { const basepath = ''; const browserHistory = createBrowserHistory(); @@ -56,6 +55,9 @@ export const StorybookContext: React.FC<{ storyContext?: StoryContext }> = ({ injectedMetadata: { getInjectedVar: () => null, }, + customIntegrations: { + ContextProvider: getStorybookContextProvider(), + }, ...stubbedStartServices, }; diff --git a/x-pack/plugins/fleet/storybook/decorator.tsx b/x-pack/plugins/fleet/storybook/decorator.tsx index 91d6cc41e6b9a..8e68249809574 100644 --- a/x-pack/plugins/fleet/storybook/decorator.tsx +++ b/x-pack/plugins/fleet/storybook/decorator.tsx @@ -10,6 +10,6 @@ import type { DecoratorFn } from '@storybook/react'; import { StorybookContext } from './context'; -export const decorator: DecoratorFn = (story: Function) => { +export const decorator: DecoratorFn = (story, storybook) => { return <StorybookContext>{story()}</StorybookContext>; }; diff --git a/x-pack/plugins/index_management/__jest__/client_integration/helpers/test_subjects.ts b/x-pack/plugins/index_management/__jest__/client_integration/helpers/test_subjects.ts index 1c75ef314b728..b24defcdcd79c 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/helpers/test_subjects.ts +++ b/x-pack/plugins/index_management/__jest__/client_integration/helpers/test_subjects.ts @@ -25,6 +25,7 @@ export type TestSubjects = | 'ilmPolicyLink' | 'includeStatsSwitch' | 'includeManagedSwitch' + | 'indexManagementHeaderContent' | 'indexTable' | 'indexTableIncludeHiddenIndicesToggle' | 'indexTableIndexNameLink' diff --git a/x-pack/plugins/index_management/__jest__/client_integration/home/home.helpers.ts b/x-pack/plugins/index_management/__jest__/client_integration/home/home.helpers.ts index 921812943a3e6..a15e4f2a613d3 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/home/home.helpers.ts +++ b/x-pack/plugins/index_management/__jest__/client_integration/home/home.helpers.ts @@ -24,24 +24,31 @@ const initTestBed = registerTestBed(WithAppDependencies(IndexManagementHome), te export interface HomeTestBed extends TestBed<TestSubjects> { actions: { selectHomeTab: (tab: 'indicesTab' | 'templatesTab') => void; + toggleHiddenIndices: () => void; }; } export const setup = async (): Promise<HomeTestBed> => { const testBed = await initTestBed(); + const { find } = testBed; /** * User Actions */ const selectHomeTab = (tab: 'indicesTab' | 'templatesTab') => { - testBed.find(tab).simulate('click'); + find(tab).simulate('click'); + }; + + const toggleHiddenIndices = async function () { + find('indexTableIncludeHiddenIndicesToggle').simulate('click'); }; return { ...testBed, actions: { selectHomeTab, + toggleHiddenIndices, }, }; }; diff --git a/x-pack/plugins/index_management/__jest__/client_integration/home/home.test.ts b/x-pack/plugins/index_management/__jest__/client_integration/home/home.test.ts index 42863d36050d7..426bb11f3c733 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/home/home.test.ts +++ b/x-pack/plugins/index_management/__jest__/client_integration/home/home.test.ts @@ -54,15 +54,18 @@ describe('<IndexManagementHome />', () => { }); describe('tabs', () => { - test('should have 2 tabs', () => { + test('should have 4 tabs', () => { const { find } = testBed; - const templatesTab = find('templatesTab'); - const indicesTab = find('indicesTab'); - expect(indicesTab.length).toBe(1); - expect(indicesTab.text()).toEqual('Indices'); - expect(templatesTab.length).toBe(1); - expect(templatesTab.text()).toEqual('Index Templates'); + const indexManagementContainer = find('indexManagementHeaderContent'); + const tabListContainer = indexManagementContainer.find('.euiTabs'); + const allTabs = tabListContainer.children(); + const allTabsLabels = ['Indices', 'Data Streams', 'Index Templates', 'Component Templates']; + + expect(allTabs.length).toBe(4); + for (let i = 0; i < allTabs.length; i++) { + expect(tabListContainer.childAt(i).text()).toEqual(allTabsLabels[i]); + } }); test('should navigate to Index Templates tab', async () => { diff --git a/x-pack/plugins/index_management/public/application/components/no_match/no_match.tsx b/x-pack/plugins/index_management/public/application/components/no_match/no_match.tsx index 34cbc10a32d09..8546a179fafdf 100644 --- a/x-pack/plugins/index_management/public/application/components/no_match/no_match.tsx +++ b/x-pack/plugins/index_management/public/application/components/no_match/no_match.tsx @@ -9,7 +9,7 @@ import React from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; export const NoMatch = () => ( - <div> + <div data-test-subj="noIndicesMessage"> <FormattedMessage id="xpack.idxMgmt.noMatch.noIndicesDescription" defaultMessage="No indices to show" diff --git a/x-pack/plugins/index_management/public/application/sections/home/home.tsx b/x-pack/plugins/index_management/public/application/sections/home/home.tsx index 003aa045f9591..8fce2d354f87b 100644 --- a/x-pack/plugins/index_management/public/application/sections/home/home.tsx +++ b/x-pack/plugins/index_management/public/application/sections/home/home.tsx @@ -85,6 +85,7 @@ export const IndexManagementHome: React.FunctionComponent<RouteComponentProps<Ma return ( <> <EuiPageHeader + data-test-subj="indexManagementHeaderContent" pageTitle={ <span data-test-subj="appTitle"> <FormattedMessage id="xpack.idxMgmt.home.appTitle" defaultMessage="Index Management" /> diff --git a/x-pack/plugins/ingest_pipelines/README.md b/x-pack/plugins/ingest_pipelines/README.md index 00d4f5a91863d..dd7c130c7a72d 100644 --- a/x-pack/plugins/ingest_pipelines/README.md +++ b/x-pack/plugins/ingest_pipelines/README.md @@ -1,9 +1,9 @@ -# Ingest Node Pipelines UI +# Ingest Pipelines UI ## Summary -The `ingest_pipelines` plugin provides Kibana support for [Elasticsearch's ingest nodes](https://www.elastic.co/guide/en/elasticsearch/reference/master/ingest.html). Please refer to the Elasticsearch documentation for more details. +The `ingest_pipelines` plugin provides Kibana support for [Elasticsearch's ingest pipelines](https://www.elastic.co/guide/en/elasticsearch/reference/master/ingest.html). -This plugin allows Kibana to create, edit, clone and delete ingest node pipelines. It also provides support to simulate a pipeline. +This plugin allows Kibana to create, edit, clone and delete ingest pipelines. It also provides support to simulate a pipeline. It requires a Basic license and the following cluster privileges: `manage_pipeline` and `cluster:monitor/nodes/info`. @@ -11,7 +11,7 @@ It requires a Basic license and the following cluster privileges: `manage_pipeli ## Development -A new app called Ingest Node Pipelines is registered in the Management section and follows a typical CRUD UI pattern. The client-side portion of this app lives in [public/application](public/application) and uses endpoints registered in [server/routes/api](server/routes/api). For more information on the pipeline processors editor component, check out the [component readme](public/application/components/pipeline_processors_editor/README.md). +A new app called Ingest Pipelines is registered in the Management section and follows a typical CRUD UI pattern. The client-side portion of this app lives in [public/application](public/application) and uses endpoints registered in [server/routes/api](server/routes/api). For more information on the pipeline processors editor component, check out the [component readme](public/application/components/pipeline_processors_editor/README.md). See the [kibana contributing guide](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md) for instructions on setting up your development environment. @@ -25,7 +25,7 @@ The app has the following test coverage: ### Quick steps for manual testing -You can run the following request in Console to create an ingest node pipeline: +You can run the following request in Console to create an ingest pipeline: ``` PUT _ingest/pipeline/test_pipeline @@ -73,7 +73,7 @@ PUT _ingest/pipeline/test_pipeline } ``` -Then, go to the Ingest Node Pipelines UI to edit, delete, clone, or view details of the pipeline. +Then, go to the Ingest Pipelines UI to edit, delete, clone, or view details of the pipeline. To simulate a pipeline, go to the "Edit" page of your pipeline. Click the "Add documents" link under the "Processors" section. You may add the following sample documents to test the pipeline: diff --git a/x-pack/plugins/ingest_pipelines/__jest__/client_integration/ingest_pipelines_list.test.ts b/x-pack/plugins/ingest_pipelines/__jest__/client_integration/ingest_pipelines_list.test.ts index 78e3f2dab0d1d..19a2abb5a5a52 100644 --- a/x-pack/plugins/ingest_pipelines/__jest__/client_integration/ingest_pipelines_list.test.ts +++ b/x-pack/plugins/ingest_pipelines/__jest__/client_integration/ingest_pipelines_list.test.ts @@ -52,11 +52,11 @@ describe('<PipelinesList />', () => { // Verify app title expect(exists('appTitle')).toBe(true); - expect(find('appTitle').text()).toEqual('Ingest Node Pipelines'); + expect(find('appTitle').text()).toEqual('Ingest Pipelines'); // Verify documentation link expect(exists('documentationLink')).toBe(true); - expect(find('documentationLink').text()).toBe('Ingest Node Pipelines docs'); + expect(find('documentationLink').text()).toBe('Ingest Pipelines docs'); // Verify create button exists expect(exists('createPipelineButton')).toBe(true); diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/shared/map_processor_type_to_form.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/shared/map_processor_type_to_form.tsx index 0d61d839156e2..eae48a6b46dd1 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/shared/map_processor_type_to_form.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/shared/map_processor_type_to_form.tsx @@ -557,7 +557,7 @@ export const mapProcessorTypeToDescriptor: MapProcessorTypeToDescriptor = { defaultMessage: 'Pipeline', }), typeDescription: i18n.translate('xpack.ingestPipelines.processors.description.pipeline', { - defaultMessage: 'Runs another ingest node pipeline.', + defaultMessage: 'Runs another ingest pipeline.', }), getDefaultDescription: ({ name }) => i18n.translate('xpack.ingestPipelines.processors.defaultDescription.pipeline', { diff --git a/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_list/main.tsx b/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_list/main.tsx index ae68cfcb399f0..95621601011f9 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_list/main.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_list/main.tsx @@ -153,7 +153,7 @@ export const PipelinesList: React.FunctionComponent<RouteComponentProps> = ({ <span data-test-subj="appTitle"> <FormattedMessage id="xpack.ingestPipelines.list.listTitle" - defaultMessage="Ingest Node Pipelines" + defaultMessage="Ingest Pipelines" /> </span> } @@ -172,7 +172,7 @@ export const PipelinesList: React.FunctionComponent<RouteComponentProps> = ({ > <FormattedMessage id="xpack.ingestPipelines.list.pipelinesDocsLinkText" - defaultMessage="Ingest Node Pipelines docs" + defaultMessage="Ingest Pipelines docs" /> </EuiButtonEmpty>, ]} diff --git a/x-pack/plugins/ingest_pipelines/public/application/services/breadcrumbs.ts b/x-pack/plugins/ingest_pipelines/public/application/services/breadcrumbs.ts index 138fdf4e8ead6..f1b2d22e776b9 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/services/breadcrumbs.ts +++ b/x-pack/plugins/ingest_pipelines/public/application/services/breadcrumbs.ts @@ -11,7 +11,7 @@ import { ManagementAppMountParams } from '../../../../../../src/plugins/manageme type SetBreadcrumbs = ManagementAppMountParams['setBreadcrumbs']; const homeBreadcrumbText = i18n.translate('xpack.ingestPipelines.breadcrumb.pipelinesLabel', { - defaultMessage: 'Ingest Node Pipelines', + defaultMessage: 'Ingest Pipelines', }); export class BreadcrumbService { diff --git a/x-pack/plugins/ingest_pipelines/public/plugin.ts b/x-pack/plugins/ingest_pipelines/public/plugin.ts index 4e85490de6209..68dc2c1801e0c 100644 --- a/x-pack/plugins/ingest_pipelines/public/plugin.ts +++ b/x-pack/plugins/ingest_pipelines/public/plugin.ts @@ -25,7 +25,7 @@ export class IngestPipelinesPlugin apiService.setup(http, uiMetricService); const pluginName = i18n.translate('xpack.ingestPipelines.appTitle', { - defaultMessage: 'Ingest Node Pipelines', + defaultMessage: 'Ingest Pipelines', }); management.sections.section.ingest.registerApp({ diff --git a/x-pack/plugins/lens/public/embeddable/embeddable.tsx b/x-pack/plugins/lens/public/embeddable/embeddable.tsx index 263198871f07a..2e0ab2401c70f 100644 --- a/x-pack/plugins/lens/public/embeddable/embeddable.tsx +++ b/x-pack/plugins/lens/public/embeddable/embeddable.tsx @@ -7,6 +7,7 @@ import { isEqual, uniqBy } from 'lodash'; import React from 'react'; +import { i18n } from '@kbn/i18n'; import { render, unmountComponentAtNode } from 'react-dom'; import type { ExecutionContextSearch, @@ -41,11 +42,7 @@ import { ReferenceOrValueEmbeddable, } from '../../../../../src/plugins/embeddable/public'; import { Document, injectFilterReferences } from '../persistence'; -import { - ExpressionWrapper, - ExpressionWrapperProps, - savedObjectConflictError, -} from './expression_wrapper'; +import { ExpressionWrapper, ExpressionWrapperProps } from './expression_wrapper'; import { UiActionsStart } from '../../../../../src/plugins/ui_actions/public'; import { isLensBrushEvent, @@ -63,6 +60,7 @@ import { LensAttributeService } from '../lens_attribute_service'; import type { ErrorMessage } from '../editor_frame_service/types'; import { getLensInspectorService, LensInspector } from '../lens_inspector_service'; import { SharingSavedObjectProps } from '../types'; +import type { SpacesPluginStart } from '../../../spaces/public'; export type LensSavedObjectAttributes = Omit<Document, 'savedObjectId' | 'type'>; export interface ResolvedLensSavedObjectAttributes extends LensSavedObjectAttributes { @@ -108,6 +106,7 @@ export interface LensEmbeddableDeps { getTriggerCompatibleActions?: UiActionsStart['getTriggerCompatibleActions']; capabilities: { canSaveVisualizations: boolean; canSaveDashboards: boolean }; usageCollection?: UsageCollectionSetup; + spaces?: SpacesPluginStart; } export class Embeddable @@ -281,8 +280,17 @@ export class Embeddable }; const { ast, errors } = await this.deps.documentToExpression(this.savedVis); this.errors = errors; - if (sharingSavedObjectProps?.outcome === 'conflict') { - const conflictError = savedObjectConflictError(sharingSavedObjectProps.errorJSON!); + if (sharingSavedObjectProps?.outcome === 'conflict' && this.deps.spaces) { + const conflictError = { + shortMessage: i18n.translate('xpack.lens.embeddable.legacyURLConflict.shortMessage', { + defaultMessage: `You've encountered a URL conflict`, + }), + longMessage: ( + <this.deps.spaces.ui.components.getSavedObjectConflictMessage + json={sharingSavedObjectProps.errorJSON!} + /> + ), + }; this.errors = this.errors ? [...this.errors, conflictError] : [conflictError]; } this.expression = ast ? toExpression(ast) : null; diff --git a/x-pack/plugins/lens/public/embeddable/embeddable_factory.ts b/x-pack/plugins/lens/public/embeddable/embeddable_factory.ts index 954905c51a4b7..e51ec4c3e5588 100644 --- a/x-pack/plugins/lens/public/embeddable/embeddable_factory.ts +++ b/x-pack/plugins/lens/public/embeddable/embeddable_factory.ts @@ -24,6 +24,7 @@ import { LensAttributeService } from '../lens_attribute_service'; import { DOC_TYPE } from '../../common/constants'; import { ErrorMessage } from '../editor_frame_service/types'; import { extract, inject } from '../../common/embeddable_factory'; +import type { SpacesPluginStart } from '../../../spaces/public'; export interface LensEmbeddableStartServices { timefilter: TimefilterContract; @@ -38,6 +39,7 @@ export interface LensEmbeddableStartServices { documentToExpression: ( doc: Document ) => Promise<{ ast: Ast | null; errors: ErrorMessage[] | undefined }>; + spaces?: SpacesPluginStart; } export class EmbeddableFactory implements EmbeddableFactoryDefinition { @@ -90,6 +92,7 @@ export class EmbeddableFactory implements EmbeddableFactoryDefinition { capabilities, usageCollection, inspector, + spaces, } = await this.getStartServices(); const { Embeddable } = await import('../async_services'); @@ -110,6 +113,7 @@ export class EmbeddableFactory implements EmbeddableFactoryDefinition { canSaveVisualizations: Boolean(capabilities.visualize.save), }, usageCollection, + spaces, }, input, parent diff --git a/x-pack/plugins/lens/public/embeddable/expression_wrapper.tsx b/x-pack/plugins/lens/public/embeddable/expression_wrapper.tsx index c827fe74cc52b..3de914d13d69d 100644 --- a/x-pack/plugins/lens/public/embeddable/expression_wrapper.tsx +++ b/x-pack/plugins/lens/public/embeddable/expression_wrapper.tsx @@ -5,20 +5,10 @@ * 2.0. */ -import React, { useState } from 'react'; +import React from 'react'; import { I18nProvider } from '@kbn/i18n/react'; import { FormattedMessage } from '@kbn/i18n/react'; -import { - EuiFlexGroup, - EuiFlexItem, - EuiText, - EuiIcon, - EuiEmptyPrompt, - EuiButtonEmpty, - EuiCallOut, - EuiSpacer, - EuiLink, -} from '@elastic/eui'; +import { EuiFlexGroup, EuiFlexItem, EuiText, EuiIcon, EuiEmptyPrompt } from '@elastic/eui'; import { ExpressionRendererEvent, ReactExpressionRendererType, @@ -28,7 +18,6 @@ import type { KibanaExecutionContext } from 'src/core/public'; import { ExecutionContextSearch } from 'src/plugins/data/public'; import { DefaultInspectorAdapters, RenderMode } from 'src/plugins/expressions'; import classNames from 'classnames'; -import { i18n } from '@kbn/i18n'; import { getOriginalRequestErrorMessages } from '../editor_frame_service/error_helper'; import { ErrorMessage } from '../editor_frame_service/types'; import { LensInspector } from '../lens_inspector_service'; @@ -172,52 +161,3 @@ export function ExpressionWrapper({ </I18nProvider> ); } - -const SavedObjectConflictMessage = ({ json }: { json: string }) => { - const [expandError, setExpandError] = useState(false); - return ( - <> - <FormattedMessage - id="xpack.lens.embeddable.legacyURLConflict.longMessage" - defaultMessage="Disable the {documentationLink} associated with this object." - values={{ - documentationLink: ( - <EuiLink - external - href="https://www.elastic.co/guide/en/kibana/master/legacy-url-aliases.html" - target="_blank" - > - {i18n.translate('xpack.lens.embeddable.legacyURLConflict.documentationLinkText', { - defaultMessage: 'legacy URL alias', - })} - </EuiLink> - ), - }} - /> - <EuiSpacer /> - {expandError ? ( - <EuiCallOut - title={i18n.translate('xpack.lens.embeddable.legacyURLConflict.expandErrorText', { - defaultMessage: `This object has the same URL as a legacy alias. Disable the alias to resolve this error : {json}`, - values: { json }, - })} - color="danger" - iconType="alert" - /> - ) : ( - <EuiButtonEmpty onClick={() => setExpandError(true)}> - {i18n.translate('xpack.lens.embeddable.legacyURLConflict.expandError', { - defaultMessage: `Show more`, - })} - </EuiButtonEmpty> - )} - </> - ); -}; - -export const savedObjectConflictError = (json: string): ErrorMessage => ({ - shortMessage: i18n.translate('xpack.lens.embeddable.legacyURLConflict.shortMessage', { - defaultMessage: `You've encountered a URL conflict`, - }), - longMessage: <SavedObjectConflictMessage json={json} />, -}); diff --git a/x-pack/plugins/lens/public/plugin.ts b/x-pack/plugins/lens/public/plugin.ts index 7891b5990989c..1532b2b099104 100644 --- a/x-pack/plugins/lens/public/plugin.ts +++ b/x-pack/plugins/lens/public/plugin.ts @@ -212,6 +212,7 @@ export class LensPlugin { uiActions: plugins.uiActions, usageCollection, inspector: plugins.inspector, + spaces: plugins.spaces, }; }; diff --git a/x-pack/plugins/licensing/README.md b/x-pack/plugins/licensing/README.md index 3de1fe9cae425..52204c66dd2bf 100644 --- a/x-pack/plugins/licensing/README.md +++ b/x-pack/plugins/licensing/README.md @@ -126,6 +126,7 @@ This change makes NP & LP licensing service not compatible. We have to keep both **LP**: `xpack.xpack_main.xpack_api_polling_frequency_millis` **NP**: `xpack.licensing.api_polling_frequency` +Support for deprecated `xpack.xpack_main.xpack_api_polling_frequency_millis` is removed in v8.0.0. See https://github.com/elastic/kibana/issues/103915 for more details. #### License **NP**: `mode` field is provided, but deprecated. diff --git a/x-pack/plugins/licensing/server/licensing_config.ts b/x-pack/plugins/licensing/server/licensing_config.ts index a27eaba56df50..85de9f84a703f 100644 --- a/x-pack/plugins/licensing/server/licensing_config.ts +++ b/x-pack/plugins/licensing/server/licensing_config.ts @@ -18,10 +18,4 @@ export const config: PluginConfigDescriptor<LicenseConfigType> = { schema: schema.object({ api_polling_frequency: schema.duration({ defaultValue: '30s' }), }), - deprecations: ({ renameFromRoot }) => [ - renameFromRoot( - 'xpack.xpack_main.xpack_api_polling_frequency_millis', - 'xpack.licensing.api_polling_frequency' - ), - ], }; diff --git a/x-pack/plugins/maps/kibana.json b/x-pack/plugins/maps/kibana.json index bfd501dbcb295..2aa2e4a756490 100644 --- a/x-pack/plugins/maps/kibana.json +++ b/x-pack/plugins/maps/kibana.json @@ -29,6 +29,7 @@ "savedObjectsTagging", "charts", "security", + "spaces", "usageCollection" ], "ui": true, diff --git a/x-pack/plugins/maps/public/embeddable/_index.scss b/x-pack/plugins/maps/public/embeddable/_index.scss index 966236f54d259..07c874d966fec 100644 --- a/x-pack/plugins/maps/public/embeddable/_index.scss +++ b/x-pack/plugins/maps/public/embeddable/_index.scss @@ -5,4 +5,12 @@ flex: 1 1 100%; z-index: 1; min-height: 0; // Absolute must for Firefox to scroll contents +} + +.mapEmbeddedError { + flex-grow: 1; + display: flex; + align-items: center; + justify-content: center; + overflow: auto; } \ No newline at end of file diff --git a/x-pack/plugins/maps/public/embeddable/map_embeddable.tsx b/x-pack/plugins/maps/public/embeddable/map_embeddable.tsx index c15138f6c5b15..b0daace7afa9e 100644 --- a/x-pack/plugins/maps/public/embeddable/map_embeddable.tsx +++ b/x-pack/plugins/maps/public/embeddable/map_embeddable.tsx @@ -12,6 +12,7 @@ import { Provider } from 'react-redux'; import { render, unmountComponentAtNode } from 'react-dom'; import { Subscription } from 'rxjs'; import { Unsubscribe } from 'redux'; +import { EuiEmptyPrompt } from '@elastic/eui'; import { Embeddable, IContainer, @@ -66,6 +67,7 @@ import { getCoreI18n, getHttp, getChartsPaletteServiceGetColor, + getSpacesApi, getSearchService, } from '../kibana_services'; import { LayerDescriptor, MapExtent } from '../../common/descriptor_types'; @@ -353,23 +355,38 @@ export class MapEmbeddable return; } - const I18nContext = getCoreI18n().Context; + const sharingSavedObjectProps = this._savedMap.getSharingSavedObjectProps(); + const spaces = getSpacesApi(); + const content = + sharingSavedObjectProps && spaces && sharingSavedObjectProps?.outcome === 'conflict' ? ( + <div className="mapEmbeddedError"> + <EuiEmptyPrompt + iconType="alert" + iconColor="danger" + data-test-subj="embeddable-maps-failure" + body={spaces.ui.components.getSavedObjectConflictMessage({ + json: sharingSavedObjectProps.errorJSON!, + })} + /> + </div> + ) : ( + <MapContainer + onSingleValueTrigger={this.onSingleValueTrigger} + addFilters={this.input.hideFilterActions ? null : this.addFilters} + getFilterActions={this.getFilterActions} + getActionContext={this.getActionContext} + renderTooltipContent={this._renderTooltipContent} + title={this.getTitle()} + description={this.getDescription()} + waitUntilTimeLayersLoad$={waitUntilTimeLayersLoad$(this._savedMap.getStore())} + isSharable={this._isSharable} + /> + ); + const I18nContext = getCoreI18n().Context; render( <Provider store={this._savedMap.getStore()}> - <I18nContext> - <MapContainer - onSingleValueTrigger={this.onSingleValueTrigger} - addFilters={this.input.hideFilterActions ? null : this.addFilters} - getFilterActions={this.getFilterActions} - getActionContext={this.getActionContext} - renderTooltipContent={this._renderTooltipContent} - title={this.getTitle()} - description={this.getDescription()} - waitUntilTimeLayersLoad$={waitUntilTimeLayersLoad$(this._savedMap.getStore())} - isSharable={this._isSharable} - /> - </I18nContext> + <I18nContext>{content}</I18nContext> </Provider>, this._domNode ); diff --git a/x-pack/plugins/maps/public/inspector/adapters/map_adapter.js b/x-pack/plugins/maps/public/inspector/map_adapter.ts similarity index 78% rename from x-pack/plugins/maps/public/inspector/adapters/map_adapter.js rename to x-pack/plugins/maps/public/inspector/map_adapter.ts index 44561ff6d66ff..3097f686382ac 100644 --- a/x-pack/plugins/maps/public/inspector/adapters/map_adapter.js +++ b/x-pack/plugins/maps/public/inspector/map_adapter.ts @@ -6,9 +6,13 @@ */ import { EventEmitter } from 'events'; +import { Stats } from './types'; class MapAdapter extends EventEmitter { - setMapState({ stats, style }) { + private stats?: Stats; + private style?: string; + + setMapState({ stats, style }: { stats: Stats; style: string }) { this.stats = stats; this.style = style; this._onChange(); diff --git a/x-pack/plugins/maps/public/inspector/views/map_details.js b/x-pack/plugins/maps/public/inspector/map_details.tsx similarity index 74% rename from x-pack/plugins/maps/public/inspector/views/map_details.js rename to x-pack/plugins/maps/public/inspector/map_details.tsx index d477e55270eb5..6689cb0d6ccb4 100644 --- a/x-pack/plugins/maps/public/inspector/views/map_details.js +++ b/x-pack/plugins/maps/public/inspector/map_details.tsx @@ -6,7 +6,6 @@ */ import React, { Component } from 'react'; -import PropTypes from 'prop-types'; import { EuiTab, EuiTabs, @@ -22,31 +21,42 @@ import { FormattedMessage } from '@kbn/i18n/react'; const DETAILS_TAB_ID = 'details'; const STYLE_TAB_ID = 'mapStyle'; -class MapDetails extends Component { - tabs = [ - { - id: DETAILS_TAB_ID, - name: i18n.translate('xpack.maps.inspector.mapDetailsTitle', { - defaultMessage: 'Map details', - }), - dataTestSubj: 'mapDetailsTab', - }, - { - id: STYLE_TAB_ID, - name: i18n.translate('xpack.maps.inspector.mapboxStyleTitle', { - defaultMessage: 'Mapbox style', - }), - dataTestSubj: 'mapboxStyleTab', - }, - ]; +const TABS = [ + { + id: DETAILS_TAB_ID, + name: i18n.translate('xpack.maps.inspector.mapDetailsTitle', { + defaultMessage: 'Map details', + }), + dataTestSubj: 'mapDetailsTab', + }, + { + id: STYLE_TAB_ID, + name: i18n.translate('xpack.maps.inspector.mapboxStyleTitle', { + defaultMessage: 'Mapbox style', + }), + dataTestSubj: 'mapboxStyleTab', + }, +]; - state = { +interface Props { + centerLon: number; + centerLat: number; + zoom: number; + style: string; +} + +interface State { + selectedTabId: typeof DETAILS_TAB_ID | typeof STYLE_TAB_ID; +} + +export class MapDetails extends Component<Props, State> { + state: State = { selectedTabId: DETAILS_TAB_ID, }; - onSelectedTabChanged = (id) => { + onSelectedTabChanged = (id: string) => { this.setState({ - selectedTabId: id, + selectedTabId: id as typeof DETAILS_TAB_ID | typeof STYLE_TAB_ID, }); }; @@ -55,7 +65,7 @@ class MapDetails extends Component { return ( <div data-test-subj="mapboxStyleContainer"> <EuiCodeBlock language="json" paddingSize="s"> - {JSON.stringify(this.props.mapStyle, null, 2)} + {JSON.stringify(this.props.style, null, 2)} </EuiCodeBlock> </div> ); @@ -96,7 +106,7 @@ class MapDetails extends Component { }; renderTabs() { - return this.tabs.map((tab, index) => ( + return TABS.map((tab, index) => ( <EuiTab onClick={() => this.onSelectedTabChanged(tab.id)} isSelected={tab.id === this.state.selectedTabId} @@ -118,12 +128,3 @@ class MapDetails extends Component { ); } } - -MapDetails.propTypes = { - centerLon: PropTypes.number.isRequired, - centerLat: PropTypes.number.isRequired, - zoom: PropTypes.number.isRequired, - mapStyle: PropTypes.object.isRequired, -}; - -export { MapDetails }; diff --git a/x-pack/plugins/maps/public/inspector/map_inspector_view.tsx b/x-pack/plugins/maps/public/inspector/map_inspector_view.tsx new file mode 100644 index 0000000000000..77809b81b9077 --- /dev/null +++ b/x-pack/plugins/maps/public/inspector/map_inspector_view.tsx @@ -0,0 +1,31 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { lazy } from 'react'; +import type { Adapters } from 'src/plugins/inspector/public'; +import { i18n } from '@kbn/i18n'; +import { LazyWrapper } from '../lazy_wrapper'; + +const getLazyComponent = () => { + return lazy(() => import('./map_view_component')); +}; + +export const MapInspectorView = { + title: i18n.translate('xpack.maps.inspector.mapDetailsViewTitle', { + defaultMessage: 'Map details', + }), + order: 30, + help: i18n.translate('xpack.maps.inspector.mapDetailsViewHelpText', { + defaultMessage: 'View the map state', + }), + shouldShow(adapters: Adapters) { + return Boolean(adapters.map); + }, + component: (props: { adapters: Adapters }) => { + return <LazyWrapper getLazyComponent={getLazyComponent} lazyComponentProps={props} />; + }, +}; diff --git a/x-pack/plugins/maps/public/inspector/map_view_component.tsx b/x-pack/plugins/maps/public/inspector/map_view_component.tsx new file mode 100644 index 0000000000000..364f4d4d2d435 --- /dev/null +++ b/x-pack/plugins/maps/public/inspector/map_view_component.tsx @@ -0,0 +1,51 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { Component } from 'react'; +import type { Adapters } from 'src/plugins/inspector/public'; +import { MapDetails } from './map_details'; +import { Stats } from './types'; + +interface Props { + adapters: Adapters; +} + +interface State { + stats: Stats; + style: string; +} + +class MapViewComponent extends Component<Props, State> { + state: State = this.props.adapters.map.getMapState(); + + _onMapChange = () => { + this.setState(this.props.adapters.map.getMapState()); + }; + + componentDidMount() { + this.props.adapters.map.on('change', this._onMapChange); + } + + componentWillUnmount() { + this.props.adapters.map.removeListener('change', this._onMapChange); + } + + render() { + return ( + <MapDetails + centerLon={this.state.stats.center[0]} + centerLat={this.state.stats.center[1]} + zoom={this.state.stats.zoom} + style={this.state.style} + /> + ); + } +} + +// default export required for React.Lazy +// eslint-disable-next-line import/no-default-export +export default MapViewComponent; diff --git a/x-pack/plugins/maps/public/inspector/types.ts b/x-pack/plugins/maps/public/inspector/types.ts new file mode 100644 index 0000000000000..e8bbd126cdd08 --- /dev/null +++ b/x-pack/plugins/maps/public/inspector/types.ts @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor 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 interface Stats { + center: [number, number]; + zoom: number; +} diff --git a/x-pack/plugins/maps/public/inspector/views/map_view.js b/x-pack/plugins/maps/public/inspector/views/map_view.js deleted file mode 100644 index e2aac26dc7d17..0000000000000 --- a/x-pack/plugins/maps/public/inspector/views/map_view.js +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React, { Component } from 'react'; -import PropTypes from 'prop-types'; -import { MapDetails } from './map_details'; -import { i18n } from '@kbn/i18n'; - -class MapViewComponent extends Component { - constructor(props) { - super(props); - props.adapters.map.on('change', this._onMapChange); - - const { stats, style } = props.adapters.map.getMapState(); - this.state = { - stats, - mapStyle: style, - }; - } - - _onMapChange = () => { - const { stats, style } = this.props.adapters.map.getMapState(); - this.setState({ - stats, - mapStyle: style, - }); - }; - - componentWillUnmount() { - this.props.adapters.map.removeListener('change', this._onMapChange); - } - - render() { - return ( - <MapDetails - centerLon={this.state.stats.center[0]} - centerLat={this.state.stats.center[1]} - zoom={this.state.stats.zoom} - mapStyle={this.state.mapStyle} - /> - ); - } -} - -MapViewComponent.propTypes = { - adapters: PropTypes.object.isRequired, -}; - -const MapView = { - title: i18n.translate('xpack.maps.inspector.mapDetailsViewTitle', { - defaultMessage: 'Map details', - }), - order: 30, - help: i18n.translate('xpack.maps.inspector.mapDetailsViewHelpText', { - defaultMessage: 'View the map state', - }), - shouldShow(adapters) { - return Boolean(adapters.map); - }, - component: MapViewComponent, -}; - -export { MapView }; diff --git a/x-pack/plugins/maps/public/kibana_services.ts b/x-pack/plugins/maps/public/kibana_services.ts index 300fe07a841e9..5ad3a1d3fd23d 100644 --- a/x-pack/plugins/maps/public/kibana_services.ts +++ b/x-pack/plugins/maps/public/kibana_services.ts @@ -53,6 +53,7 @@ export const getNavigateToApp = () => coreStart.application.navigateToApp; export const getSavedObjectsTagging = () => pluginsStart.savedObjectsTagging; export const getPresentationUtilContext = () => pluginsStart.presentationUtil.ContextProvider; export const getSecurityService = () => pluginsStart.security; +export const getSpacesApi = () => pluginsStart.spaces; // xpack.maps.* kibana.yml settings from this plugin let mapAppConfig: MapsConfigType; diff --git a/x-pack/plugins/maps/public/lazy_wrapper.tsx b/x-pack/plugins/maps/public/lazy_wrapper.tsx new file mode 100644 index 0000000000000..1a808799bf4c4 --- /dev/null +++ b/x-pack/plugins/maps/public/lazy_wrapper.tsx @@ -0,0 +1,31 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { FC, Suspense } from 'react'; +import { EuiDelayRender, EuiErrorBoundary, EuiLoadingContent } from '@elastic/eui'; + +const Fallback = () => ( + <EuiDelayRender> + <EuiLoadingContent lines={3} /> + </EuiDelayRender> +); + +interface Props<T> { + getLazyComponent: () => FC<T>; + lazyComponentProps: T; +} + +export function LazyWrapper<T>({ getLazyComponent, lazyComponentProps }: Props<T>) { + const LazyComponent = getLazyComponent(); + return ( + <EuiErrorBoundary> + <Suspense fallback={<Fallback />}> + <LazyComponent {...lazyComponentProps} /> + </Suspense> + </EuiErrorBoundary> + ); +} diff --git a/x-pack/plugins/maps/public/map_attribute_service.ts b/x-pack/plugins/maps/public/map_attribute_service.ts index 5f7c45b1b42d7..ab380ca5a6b66 100644 --- a/x-pack/plugins/maps/public/map_attribute_service.ts +++ b/x-pack/plugins/maps/public/map_attribute_service.ts @@ -14,8 +14,18 @@ import { checkForDuplicateTitle, OnSaveProps } from '../../../../src/plugins/sav import { getCoreOverlays, getEmbeddableService, getSavedObjectsClient } from './kibana_services'; import { extractReferences, injectReferences } from '../common/migrations/references'; import { MapByValueInput, MapByReferenceInput } from './embeddable/types'; +import { getSpacesApi } from './kibana_services'; -type MapDoc = MapSavedObjectAttributes & { references?: SavedObjectReference[] }; +export interface SharingSavedObjectProps { + outcome?: 'aliasMatch' | 'exactMatch' | 'conflict'; + aliasTargetId?: string; + errorJSON?: string; +} + +type MapDoc = MapSavedObjectAttributes & { + sharingSavedObjectProps?: SharingSavedObjectProps; + references?: SavedObjectReference[]; +}; export type MapAttributeService = AttributeService<MapDoc, MapByValueInput, MapByReferenceInput>; @@ -58,7 +68,11 @@ export function getMapAttributeService(): MapAttributeService { return { id: savedObject.id }; }, unwrapMethod: async (savedObjectId: string): Promise<MapDoc> => { - const savedObject = await getSavedObjectsClient().get<MapSavedObjectAttributes>( + const { + saved_object: savedObject, + outcome, + alias_target_id: aliasTargetId, + } = await getSavedObjectsClient().resolve<MapSavedObjectAttributes>( MAP_SAVED_OBJECT_TYPE, savedObjectId ); @@ -68,7 +82,22 @@ export function getMapAttributeService(): MapAttributeService { } const { attributes } = injectReferences(savedObject); - return { ...attributes, references: savedObject.references }; + return { + ...attributes, + references: savedObject.references, + sharingSavedObjectProps: { + aliasTargetId, + outcome, + errorJSON: + outcome === 'conflict' && getSpacesApi() + ? JSON.stringify({ + targetType: MAP_SAVED_OBJECT_TYPE, + sourceId: savedObjectId, + targetSpace: (await getSpacesApi()!.getActiveSpace()).id, + }) + : undefined, + }, + }; }, checkForDuplicateTitle: (props: OnSaveProps) => { return checkForDuplicateTitle( diff --git a/x-pack/plugins/maps/public/plugin.ts b/x-pack/plugins/maps/public/plugin.ts index 8f6e74adfc2fd..ee3202ba022c9 100644 --- a/x-pack/plugins/maps/public/plugin.ts +++ b/x-pack/plugins/maps/public/plugin.ts @@ -20,8 +20,7 @@ import type { PluginInitializerContext, } from '../../../../src/core/public'; import { DEFAULT_APP_CATEGORIES } from '../../../../src/core/public'; -// @ts-ignore -import { MapView } from './inspector/views/map_view'; +import { MapInspectorView } from './inspector/map_inspector_view'; import { setEMSSettings, setKibanaCommonConfig, @@ -83,7 +82,8 @@ import { tileMapRenderer, tileMapVisType, } from './legacy_visualizations'; -import { SecurityPluginStart } from '../../security/public'; +import type { SecurityPluginStart } from '../../security/public'; +import type { SpacesPluginStart } from '../../spaces/public'; export interface MapsPluginSetupDependencies { expressions: ReturnType<ExpressionsPublicPlugin['setup']>; @@ -113,6 +113,7 @@ export interface MapsPluginStartDependencies { savedObjectsTagging?: SavedObjectTaggingPluginStart; presentationUtil: PresentationUtilPluginStart; security: SecurityPluginStart; + spaces?: SpacesPluginStart; } /** @@ -166,7 +167,7 @@ export class MapsPlugin }) ); - plugins.inspector.registerView(MapView); + plugins.inspector.registerView(MapInspectorView); if (plugins.home) { plugins.home.featureCatalogue.register(featureCatalogueEntry); } diff --git a/x-pack/plugins/maps/public/reducers/non_serializable_instances.js b/x-pack/plugins/maps/public/reducers/non_serializable_instances.js index 402d7727cd6fe..9524d25a9a476 100644 --- a/x-pack/plugins/maps/public/reducers/non_serializable_instances.js +++ b/x-pack/plugins/maps/public/reducers/non_serializable_instances.js @@ -6,7 +6,7 @@ */ import { RequestAdapter } from '../../../../../src/plugins/inspector/common/adapters/request'; -import { MapAdapter } from '../inspector/adapters/map_adapter'; +import { MapAdapter } from '../inspector/map_adapter'; import { getShowMapsInspectorAdapter } from '../kibana_services'; const REGISTER_CANCEL_CALLBACK = 'REGISTER_CANCEL_CALLBACK'; diff --git a/x-pack/plugins/maps/public/render_app.tsx b/x-pack/plugins/maps/public/render_app.tsx index de67939b1a42a..3eefaeb6f7a9b 100644 --- a/x-pack/plugins/maps/public/render_app.tsx +++ b/x-pack/plugins/maps/public/render_app.tsx @@ -9,7 +9,7 @@ import React from 'react'; import { render, unmountComponentAtNode } from 'react-dom'; import { Router, Switch, Route, Redirect, RouteComponentProps } from 'react-router-dom'; import { i18n } from '@kbn/i18n'; -import { AppMountParameters } from 'kibana/public'; +import type { AppMountParameters } from 'kibana/public'; import { getCoreChrome, getCoreI18n, @@ -98,6 +98,7 @@ export async function renderApp( setHeaderActionMenu={setHeaderActionMenu} stateTransfer={stateTransfer} originatingApp={originatingApp} + history={history} key={routeProps.match.params.savedMapId ? routeProps.match.params.savedMapId : 'new'} /> ); diff --git a/x-pack/plugins/maps/public/routes/map_page/map_app/map_app.tsx b/x-pack/plugins/maps/public/routes/map_page/map_app/map_app.tsx index 212fa89e2ad65..8fc2d97c4862a 100644 --- a/x-pack/plugins/maps/public/routes/map_page/map_app/map_app.tsx +++ b/x-pack/plugins/maps/public/routes/map_page/map_app/map_app.tsx @@ -18,6 +18,7 @@ import { getCoreChrome, getMapsCapabilities, getNavigation, + getSpacesApi, getTimeFilter, getToasts, } from '../../../kibana_services'; @@ -40,7 +41,8 @@ import { getIndexPatternsFromIds } from '../../../index_pattern_util'; import { getTopNavConfig } from '../top_nav_config'; import { goToSpecifiedPath } from '../../../render_app'; import { MapSavedObjectAttributes } from '../../../../common/map_saved_object_type'; -import { getFullPath, APP_ID } from '../../../../common/constants'; +import { getEditPath, getFullPath, APP_ID } from '../../../../common/constants'; +import { getMapEmbeddableDisplayName } from '../../../../common/i18n_getters'; import { getInitialQuery, getInitialRefreshConfig, @@ -85,6 +87,7 @@ export interface Props { isSaveDisabled: boolean; query: Query | undefined; setHeaderActionMenu: AppMountParameters['setHeaderActionMenu']; + history: AppMountParameters['history']; } export interface State { @@ -347,6 +350,16 @@ export class MapApp extends React.Component<Props, State> { return; } + const sharingSavedObjectProps = this.props.savedMap.getSharingSavedObjectProps(); + const spaces = getSpacesApi(); + if (spaces && sharingSavedObjectProps?.outcome === 'aliasMatch') { + // We found this object by a legacy URL alias from its old ID; redirect the user to the page with its new ID, preserving any URL hash + const newObjectId = sharingSavedObjectProps?.aliasTargetId; // This is always defined if outcome === 'aliasMatch' + const newPath = `${getEditPath(newObjectId)}${this.props.history.location.hash}`; + await spaces.ui.redirectLegacyUrl(newPath, getMapEmbeddableDisplayName()); + return; + } + this.props.savedMap.setBreadcrumbs(); getCoreChrome().docTitle.change(this.props.savedMap.getTitle()); const savedObjectId = this.props.savedMap.getSavedObjectId(); @@ -437,6 +450,21 @@ export class MapApp extends React.Component<Props, State> { this._onFiltersChange([...this.props.filters, ...newFilters]); }; + _renderLegacyUrlConflict() { + const sharingSavedObjectProps = this.props.savedMap.getSharingSavedObjectProps(); + const spaces = getSpacesApi(); + return spaces && sharingSavedObjectProps?.outcome === 'conflict' + ? spaces.ui.components.getLegacyUrlConflict({ + objectNoun: getMapEmbeddableDisplayName(), + currentObjectId: this.props.savedMap.getSavedObjectId()!, + otherObjectId: sharingSavedObjectProps.aliasTargetId!, + otherObjectPath: `${getEditPath(sharingSavedObjectProps.aliasTargetId!)}${ + this.props.history.location.hash + }`, + }) + : null; + } + render() { if (!this.state.initialized) { return null; @@ -447,6 +475,7 @@ export class MapApp extends React.Component<Props, State> { {this._renderTopNav()} <h1 className="euiScreenReaderOnly">{`screenTitle placeholder`}</h1> <div id="react-maps-root"> + {this._renderLegacyUrlConflict()} <MapContainer addFilters={this._addFilter} title={this.props.savedMap.getAttributes().title} diff --git a/x-pack/plugins/maps/public/routes/map_page/map_page.tsx b/x-pack/plugins/maps/public/routes/map_page/map_page.tsx index 73ea62ef59d7c..7e927115a5d06 100644 --- a/x-pack/plugins/maps/public/routes/map_page/map_page.tsx +++ b/x-pack/plugins/maps/public/routes/map_page/map_page.tsx @@ -7,8 +7,8 @@ import React, { Component } from 'react'; import { Provider } from 'react-redux'; -import { AppMountParameters } from 'kibana/public'; -import { EmbeddableStateTransfer } from 'src/plugins/embeddable/public'; +import type { AppMountParameters } from 'kibana/public'; +import type { EmbeddableStateTransfer } from 'src/plugins/embeddable/public'; import { MapApp } from './map_app'; import { SavedMap, getInitialLayersFromUrlParam } from './saved_map'; import { MapEmbeddableInput } from '../../embeddable/types'; @@ -20,6 +20,7 @@ interface Props { setHeaderActionMenu: AppMountParameters['setHeaderActionMenu']; stateTransfer: EmbeddableStateTransfer; originatingApp?: string; + history: AppMountParameters['history']; } interface State { @@ -69,6 +70,7 @@ export class MapPage extends Component<Props, State> { return ( <Provider store={this.state.savedMap.getStore()}> <MapApp + history={this.props.history} savedMap={this.state.savedMap} onAppLeave={this.props.onAppLeave} setHeaderActionMenu={this.props.setHeaderActionMenu} diff --git a/x-pack/plugins/maps/public/routes/map_page/saved_map/saved_map.ts b/x-pack/plugins/maps/public/routes/map_page/saved_map/saved_map.ts index fab88af308f8d..004b88a242623 100644 --- a/x-pack/plugins/maps/public/routes/map_page/saved_map/saved_map.ts +++ b/x-pack/plugins/maps/public/routes/map_page/saved_map/saved_map.ts @@ -30,7 +30,7 @@ import { setHiddenLayers, } from '../../../actions'; import { getIsLayerTOCOpen, getOpenTOCDetails } from '../../../selectors/ui_selectors'; -import { getMapAttributeService } from '../../../map_attribute_service'; +import { getMapAttributeService, SharingSavedObjectProps } from '../../../map_attribute_service'; import { OnSaveProps } from '../../../../../../../src/plugins/saved_objects/public'; import { MapByReferenceInput, MapEmbeddableInput } from '../../../embeddable/types'; import { @@ -50,6 +50,7 @@ import { whenLicenseInitialized } from '../../../licensed_features'; export class SavedMap { private _attributes: MapSavedObjectAttributes | null = null; + private _sharingSavedObjectProps: SharingSavedObjectProps | null = null; private readonly _defaultLayers: LayerDescriptor[]; private readonly _embeddableId?: string; private _initialLayerListConfig: LayerDescriptor[] = []; @@ -98,8 +99,11 @@ export class SavedMap { }; } else { const doc = await getMapAttributeService().unwrapAttributes(this._mapEmbeddableInput); - const { references, ...savedObjectAttributes } = doc; + const { references, sharingSavedObjectProps, ...savedObjectAttributes } = doc; this._attributes = savedObjectAttributes; + if (sharingSavedObjectProps) { + this._sharingSavedObjectProps = sharingSavedObjectProps; + } const savedObjectsTagging = getSavedObjectsTagging(); if (savedObjectsTagging && references && references.length) { this._tags = savedObjectsTagging.ui.getTagIdsFromReferences(references); @@ -274,6 +278,10 @@ export class SavedMap { return this._attributes; } + public getSharingSavedObjectProps(): SharingSavedObjectProps | null { + return this._sharingSavedObjectProps; + } + public isByValue(): boolean { const hasSavedObjectId = !!this.getSavedObjectId(); return getIsAllowByValueEmbeddables() && !!this._originatingApp && !hasSavedObjectId; diff --git a/x-pack/plugins/maps/tsconfig.json b/x-pack/plugins/maps/tsconfig.json index 3f6e1fdbe8475..e2be2f3d66561 100644 --- a/x-pack/plugins/maps/tsconfig.json +++ b/x-pack/plugins/maps/tsconfig.json @@ -36,6 +36,7 @@ { "path": "../licensing/tsconfig.json" }, { "path": "../file_upload/tsconfig.json" }, { "path": "../saved_objects_tagging/tsconfig.json" }, - { "path": "../security/tsconfig.json" } + { "path": "../security/tsconfig.json" }, + { "path": "../spaces/tsconfig.json" } ] } diff --git a/x-pack/plugins/monitoring/public/components/page_loading/page_loading.test.js b/x-pack/plugins/monitoring/public/components/page_loading/page_loading.test.js index f7973231f41b0..533446b1d0193 100644 --- a/x-pack/plugins/monitoring/public/components/page_loading/page_loading.test.js +++ b/x-pack/plugins/monitoring/public/components/page_loading/page_loading.test.js @@ -9,7 +9,8 @@ import React from 'react'; import { renderWithIntl } from '@kbn/test/jest'; import { PageLoading } from '.'; -describe('PageLoading', () => { +// https://github.com/elastic/kibana/issues/113991 +describe.skip('PageLoading', () => { test('should show a simple page loading component', () => { expect(renderWithIntl(<PageLoading />)).toMatchSnapshot(); }); diff --git a/x-pack/plugins/reporting/common/constants.ts b/x-pack/plugins/reporting/common/constants.ts index 9224a23fcb33f..3fb02677dd981 100644 --- a/x-pack/plugins/reporting/common/constants.ts +++ b/x-pack/plugins/reporting/common/constants.ts @@ -7,6 +7,8 @@ export const PLUGIN_ID = 'reporting'; +export const REPORTING_SYSTEM_INDEX = '.reporting'; + export const JOB_COMPLETION_NOTIFICATIONS_SESSION_KEY = 'xpack.reporting.jobCompletionNotifications'; diff --git a/x-pack/plugins/reporting/server/config/schema.test.ts b/x-pack/plugins/reporting/server/config/schema.test.ts index 0998a80103131..0b2e2cac6ff7c 100644 --- a/x-pack/plugins/reporting/server/config/schema.test.ts +++ b/x-pack/plugins/reporting/server/config/schema.test.ts @@ -84,7 +84,6 @@ describe('Reporting Config Schema', () => { }, "enabled": true, "encryptionKey": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", - "index": ".reporting", "kibanaServer": Object {}, "poll": Object { "jobCompletionNotifier": Object { @@ -189,7 +188,6 @@ describe('Reporting Config Schema', () => { "useByteOrderMarkEncoding": false, }, "enabled": true, - "index": ".reporting", "kibanaServer": Object {}, "poll": Object { "jobCompletionNotifier": Object { diff --git a/x-pack/plugins/reporting/server/config/schema.ts b/x-pack/plugins/reporting/server/config/schema.ts index d616a18289df0..affd8b7bee7ff 100644 --- a/x-pack/plugins/reporting/server/config/schema.ts +++ b/x-pack/plugins/reporting/server/config/schema.ts @@ -155,8 +155,6 @@ const RolesSchema = schema.object({ allow: schema.arrayOf(schema.string(), { defaultValue: ['reporting_user'] }), }); -const IndexSchema = schema.string({ defaultValue: '.reporting' }); - // Browser side polling: job completion notifier, management table auto-refresh // NOTE: can not use schema.duration, a bug prevents it being passed to the browser correctly const PollSchema = schema.object({ @@ -178,7 +176,6 @@ export const ConfigSchema = schema.object({ csv: CsvSchema, encryptionKey: EncryptionKeySchema, roles: RolesSchema, - index: IndexSchema, poll: PollSchema, }); diff --git a/x-pack/plugins/reporting/server/export_types/csv/execute_job.test.ts b/x-pack/plugins/reporting/server/export_types/csv/execute_job.test.ts index 5032eaab46e84..e5d0ed2613719 100644 --- a/x-pack/plugins/reporting/server/export_types/csv/execute_job.test.ts +++ b/x-pack/plugins/reporting/server/export_types/csv/execute_job.test.ts @@ -77,7 +77,6 @@ describe('CSV Execute Job', function () { stream = { write: jest.fn((chunk) => (content += chunk)) } as unknown as typeof stream; configGetStub = sinon.stub(); configGetStub.withArgs('queue', 'timeout').returns(moment.duration('2m')); - configGetStub.withArgs('index').returns('.reporting-foo-test'); configGetStub.withArgs('encryptionKey').returns(encryptionKey); configGetStub.withArgs('csv', 'maxSizeBytes').returns(1024 * 1000); // 1mB configGetStub.withArgs('csv', 'scroll').returns({}); diff --git a/x-pack/plugins/reporting/server/lib/store/store.ts b/x-pack/plugins/reporting/server/lib/store/store.ts index d49337391ca40..01a6f7a3cd06d 100644 --- a/x-pack/plugins/reporting/server/lib/store/store.ts +++ b/x-pack/plugins/reporting/server/lib/store/store.ts @@ -9,7 +9,7 @@ import { IndexResponse, UpdateResponse } from '@elastic/elasticsearch/api/types' import { ElasticsearchClient } from 'src/core/server'; import { LevelLogger, statuses } from '../'; import { ReportingCore } from '../../'; -import { ILM_POLICY_NAME } from '../../../common/constants'; +import { ILM_POLICY_NAME, REPORTING_SYSTEM_INDEX } from '../../../common/constants'; import { JobStatus, ReportOutput, ReportSource } from '../../../common/types'; import { ReportTaskParams } from '../tasks'; import { Report, ReportDocument, SavedReport } from './'; @@ -87,7 +87,7 @@ export class ReportingStore { constructor(private reportingCore: ReportingCore, private logger: LevelLogger) { const config = reportingCore.getConfig(); - this.indexPrefix = config.get('index'); + this.indexPrefix = REPORTING_SYSTEM_INDEX; this.indexInterval = config.get('queue', 'indexInterval'); this.logger = logger.clone(['store']); } diff --git a/x-pack/plugins/reporting/server/routes/lib/jobs_query.ts b/x-pack/plugins/reporting/server/routes/lib/jobs_query.ts index afa83ed331672..54efe0636536a 100644 --- a/x-pack/plugins/reporting/server/routes/lib/jobs_query.ts +++ b/x-pack/plugins/reporting/server/routes/lib/jobs_query.ts @@ -13,6 +13,7 @@ import { UnwrapPromise } from '@kbn/utility-types'; import { ElasticsearchClient } from 'src/core/server'; import { PromiseType } from 'utility-types'; import { ReportingCore } from '../../'; +import { REPORTING_SYSTEM_INDEX } from '../../../common/constants'; import { ReportApiJSON, ReportSource } from '../../../common/types'; import { statuses } from '../../lib/statuses'; import { Report } from '../../lib/store'; @@ -54,9 +55,7 @@ interface JobsQueryFactory { export function jobsQueryFactory(reportingCore: ReportingCore): JobsQueryFactory { function getIndex() { - const config = reportingCore.getConfig(); - - return `${config.get('index')}-*`; + return `${REPORTING_SYSTEM_INDEX}-*`; } async function execQuery< 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 9a452943ff699..69213d8f8cacc 100644 --- a/x-pack/plugins/reporting/server/usage/get_reporting_usage.ts +++ b/x-pack/plugins/reporting/server/usage/get_reporting_usage.ts @@ -8,6 +8,7 @@ import type { estypes } from '@elastic/elasticsearch'; import type { ElasticsearchClient } from 'kibana/server'; import { get } from 'lodash'; import type { ReportingConfig } from '../'; +import { REPORTING_SYSTEM_INDEX } from '../../common/constants'; import type { ExportTypesRegistry } from '../lib/export_types_registry'; import type { GetLicense } from './'; import { getExportStats } from './get_export_stats'; @@ -144,7 +145,7 @@ export async function getReportingUsage( esClient: ElasticsearchClient, exportTypesRegistry: ExportTypesRegistry ): Promise<ReportingUsageType> { - const reportingIndex = config.get('index'); + const reportingIndex = REPORTING_SYSTEM_INDEX; const params = { index: `${reportingIndex}-*`, filterPath: 'aggregations.*.buckets', diff --git a/x-pack/plugins/security_solution/cypress/objects/rule.ts b/x-pack/plugins/security_solution/cypress/objects/rule.ts index db76bfc3cf4df..788e177fec721 100644 --- a/x-pack/plugins/security_solution/cypress/objects/rule.ts +++ b/x-pack/plugins/security_solution/cypress/objects/rule.ts @@ -72,10 +72,6 @@ export interface OverrideRule extends CustomRule { timestampOverride: string; } -export interface EventCorrelationRule extends CustomRule { - language: string; -} - export interface ThreatIndicatorRule extends CustomRule { indicatorIndexPattern: string[]; indicatorMappingField: string; @@ -330,7 +326,7 @@ export const getEqlRule = (): CustomRule => ({ maxSignals: 100, }); -export const getCCSEqlRule = (): EventCorrelationRule => ({ +export const getCCSEqlRule = (): CustomRule => ({ customQuery: 'any where process.name == "run-parts"', name: 'New EQL Rule', index: [`${ccsRemoteName}:run-parts`], @@ -346,7 +342,6 @@ export const getCCSEqlRule = (): EventCorrelationRule => ({ lookBack: getLookBack(), timeline: getTimeline(), maxSignals: 100, - language: 'eql', }); export const getEqlSequenceRule = (): CustomRule => ({ diff --git a/x-pack/plugins/security_solution/cypress/tasks/api_calls/rules.ts b/x-pack/plugins/security_solution/cypress/tasks/api_calls/rules.ts index 130467cde053d..04ff0fcabc081 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/api_calls/rules.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/api_calls/rules.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { CustomRule, EventCorrelationRule, ThreatIndicatorRule } from '../../objects/rule'; +import { CustomRule, ThreatIndicatorRule } from '../../objects/rule'; export const createCustomRule = (rule: CustomRule, ruleId = 'rule_testing', interval = '100m') => cy.request({ @@ -29,7 +29,7 @@ export const createCustomRule = (rule: CustomRule, ruleId = 'rule_testing', inte failOnStatusCode: false, }); -export const createEventCorrelationRule = (rule: EventCorrelationRule, ruleId = 'rule_testing') => +export const createEventCorrelationRule = (rule: CustomRule, ruleId = 'rule_testing') => cy.request({ method: 'POST', url: 'api/detection_engine/rules', diff --git a/x-pack/plugins/spaces/public/mocks.ts b/x-pack/plugins/spaces/public/mocks.ts index 897f58e1d649c..76cafd4c7f5ae 100644 --- a/x-pack/plugins/spaces/public/mocks.ts +++ b/x-pack/plugins/spaces/public/mocks.ts @@ -41,6 +41,7 @@ const createApiUiComponentsMock = () => { getSpaceList: jest.fn(), getLegacyUrlConflict: jest.fn(), getSpaceAvatar: jest.fn(), + getSavedObjectConflictMessage: jest.fn(), }; return mock; diff --git a/x-pack/plugins/spaces/public/share_saved_objects_to_space/components/get_saved_object_conflict_message.tsx b/x-pack/plugins/spaces/public/share_saved_objects_to_space/components/get_saved_object_conflict_message.tsx new file mode 100644 index 0000000000000..66b2a5652057a --- /dev/null +++ b/x-pack/plugins/spaces/public/share_saved_objects_to_space/components/get_saved_object_conflict_message.tsx @@ -0,0 +1,19 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; + +import type { SavedObjectConflictMessageProps } from '../types'; + +export const getSavedObjectConflictMessage = async (): Promise< + React.FC<SavedObjectConflictMessageProps> +> => { + const { SavedObjectConflictMessage } = await import('./saved_object_conflict_message'); + return (props: SavedObjectConflictMessageProps) => { + return <SavedObjectConflictMessage {...props} />; + }; +}; diff --git a/x-pack/plugins/spaces/public/share_saved_objects_to_space/components/index.ts b/x-pack/plugins/spaces/public/share_saved_objects_to_space/components/index.ts index c0828e3b5331d..fa641d03fd715 100644 --- a/x-pack/plugins/spaces/public/share_saved_objects_to_space/components/index.ts +++ b/x-pack/plugins/spaces/public/share_saved_objects_to_space/components/index.ts @@ -6,4 +6,5 @@ */ export { getShareToSpaceFlyoutComponent } from './share_to_space_flyout'; +export { getSavedObjectConflictMessage } from './get_saved_object_conflict_message'; export { getLegacyUrlConflict } from './legacy_url_conflict'; diff --git a/x-pack/plugins/spaces/public/share_saved_objects_to_space/components/saved_object_conflict_message.tsx b/x-pack/plugins/spaces/public/share_saved_objects_to_space/components/saved_object_conflict_message.tsx new file mode 100644 index 0000000000000..22a1ad7cd20aa --- /dev/null +++ b/x-pack/plugins/spaces/public/share_saved_objects_to_space/components/saved_object_conflict_message.tsx @@ -0,0 +1,56 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { EuiButtonEmpty, EuiCallOut, EuiLink, EuiSpacer } from '@elastic/eui'; +import React, { useState } from 'react'; + +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; + +import type { SavedObjectConflictMessageProps } from '../types'; + +export const SavedObjectConflictMessage = ({ json }: SavedObjectConflictMessageProps) => { + const [expandError, setExpandError] = useState(false); + return ( + <> + <FormattedMessage + id="xpack.spaces.legacyURLConflict.longMessage" + defaultMessage="Disable the {documentationLink} associated with this object." + values={{ + documentationLink: ( + <EuiLink + external + href="https://www.elastic.co/guide/en/kibana/master/legacy-url-aliases.html" + target="_blank" + > + {i18n.translate('xpack.spaces.legacyURLConflict.documentationLinkText', { + defaultMessage: 'legacy URL alias', + })} + </EuiLink> + ), + }} + /> + <EuiSpacer /> + {expandError ? ( + <EuiCallOut + title={i18n.translate('xpack.spaces.legacyURLConflict.expandErrorText', { + defaultMessage: `This object has the same URL as a legacy alias. Disable the alias to resolve this error : {json}`, + values: { json }, + })} + color="danger" + iconType="alert" + /> + ) : ( + <EuiButtonEmpty onClick={() => setExpandError(true)}> + {i18n.translate('xpack.spaces.legacyURLConflict.expandError', { + defaultMessage: `Show more`, + })} + </EuiButtonEmpty> + )} + </> + ); +}; diff --git a/x-pack/plugins/spaces/public/share_saved_objects_to_space/index.ts b/x-pack/plugins/spaces/public/share_saved_objects_to_space/index.ts index fe90ee8d6a8a9..465fd179c8441 100644 --- a/x-pack/plugins/spaces/public/share_saved_objects_to_space/index.ts +++ b/x-pack/plugins/spaces/public/share_saved_objects_to_space/index.ts @@ -5,10 +5,15 @@ * 2.0. */ -export { getShareToSpaceFlyoutComponent, getLegacyUrlConflict } from './components'; +export { + getShareToSpaceFlyoutComponent, + getLegacyUrlConflict, + getSavedObjectConflictMessage, +} from './components'; export { createRedirectLegacyUrl } from './utils'; export type { LegacyUrlConflictProps, ShareToSpaceFlyoutProps, ShareToSpaceSavedObjectTarget, + SavedObjectConflictMessageProps, } from './types'; diff --git a/x-pack/plugins/spaces/public/share_saved_objects_to_space/types.ts b/x-pack/plugins/spaces/public/share_saved_objects_to_space/types.ts index 1beccaa546282..21290f2b90de5 100644 --- a/x-pack/plugins/spaces/public/share_saved_objects_to_space/types.ts +++ b/x-pack/plugins/spaces/public/share_saved_objects_to_space/types.ts @@ -140,3 +140,10 @@ export interface ShareToSpaceSavedObjectTarget { */ noun?: string; } + +/** + * Properties for the SavedObjectConflictMessage component. + */ +export interface SavedObjectConflictMessageProps { + json: string; +} diff --git a/x-pack/plugins/spaces/public/ui_api/components.tsx b/x-pack/plugins/spaces/public/ui_api/components.tsx index a33480712ffae..71dc2e34a0d70 100644 --- a/x-pack/plugins/spaces/public/ui_api/components.tsx +++ b/x-pack/plugins/spaces/public/ui_api/components.tsx @@ -14,6 +14,7 @@ import { getCopyToSpaceFlyoutComponent } from '../copy_saved_objects_to_space'; import type { PluginsStart } from '../plugin'; import { getLegacyUrlConflict, + getSavedObjectConflictMessage, getShareToSpaceFlyoutComponent, } from '../share_saved_objects_to_space'; import { getSpaceAvatarComponent } from '../space_avatar'; @@ -56,5 +57,6 @@ export const getComponents = ({ getSpaceList: wrapLazy(getSpaceListComponent), getLegacyUrlConflict: wrapLazy(() => getLegacyUrlConflict({ getStartServices })), getSpaceAvatar: wrapLazy(getSpaceAvatarComponent), + getSavedObjectConflictMessage: wrapLazy(() => getSavedObjectConflictMessage()), }; }; diff --git a/x-pack/plugins/spaces/public/ui_api/types.ts b/x-pack/plugins/spaces/public/ui_api/types.ts index 5048e5a9b9652..67e43f0cd31a6 100644 --- a/x-pack/plugins/spaces/public/ui_api/types.ts +++ b/x-pack/plugins/spaces/public/ui_api/types.ts @@ -12,6 +12,7 @@ import type { CoreStart } from 'src/core/public'; import type { CopyToSpaceFlyoutProps } from '../copy_saved_objects_to_space'; import type { LegacyUrlConflictProps, + SavedObjectConflictMessageProps, ShareToSpaceFlyoutProps, } from '../share_saved_objects_to_space'; import type { SpaceAvatarProps } from '../space_avatar'; @@ -109,4 +110,8 @@ export interface SpacesApiUiComponent { * Displays an avatar for the given space. */ getSpaceAvatar: LazyComponentFn<SpaceAvatarProps>; + /** + * Displays a saved object conflict message that directs user to disable legacy URL alias + */ + getSavedObjectConflictMessage: LazyComponentFn<SavedObjectConflictMessageProps>; } diff --git a/x-pack/plugins/stack_alerts/common/config.ts b/x-pack/plugins/stack_alerts/common/config.ts index ebc12ee563350..e1eb28f092408 100644 --- a/x-pack/plugins/stack_alerts/common/config.ts +++ b/x-pack/plugins/stack_alerts/common/config.ts @@ -7,8 +7,6 @@ import { schema, TypeOf } from '@kbn/config-schema'; -export const configSchema = schema.object({ - enabled: schema.boolean({ defaultValue: true }), -}); +export const configSchema = schema.object({}); export type Config = TypeOf<typeof configSchema>; diff --git a/x-pack/plugins/task_manager/server/config.test.ts b/x-pack/plugins/task_manager/server/config.test.ts index e9701fc3e7c05..8d7a6c7872e7e 100644 --- a/x-pack/plugins/task_manager/server/config.test.ts +++ b/x-pack/plugins/task_manager/server/config.test.ts @@ -12,7 +12,6 @@ describe('config validation', () => { const config: Record<string, unknown> = {}; expect(configSchema.validate(config)).toMatchInlineSnapshot(` Object { - "enabled": true, "ephemeral_tasks": Object { "enabled": false, "request_capacity": 10, @@ -71,7 +70,6 @@ describe('config validation', () => { const config: Record<string, unknown> = {}; expect(configSchema.validate(config)).toMatchInlineSnapshot(` Object { - "enabled": true, "ephemeral_tasks": Object { "enabled": false, "request_capacity": 10, @@ -117,7 +115,6 @@ describe('config validation', () => { }; expect(configSchema.validate(config)).toMatchInlineSnapshot(` Object { - "enabled": true, "ephemeral_tasks": Object { "enabled": false, "request_capacity": 10, diff --git a/x-pack/plugins/task_manager/server/config.ts b/x-pack/plugins/task_manager/server/config.ts index 286a9feaa1b5e..f2026ecac3adc 100644 --- a/x-pack/plugins/task_manager/server/config.ts +++ b/x-pack/plugins/task_manager/server/config.ts @@ -43,7 +43,6 @@ export const taskExecutionFailureThresholdSchema = schema.object( export const configSchema = schema.object( { - enabled: schema.boolean({ defaultValue: true }), /* The maximum number of times a task will be attempted before being abandoned as failed */ max_attempts: schema.number({ defaultValue: 3, diff --git a/x-pack/plugins/task_manager/server/ephemeral_task_lifecycle.test.ts b/x-pack/plugins/task_manager/server/ephemeral_task_lifecycle.test.ts index 558aa108c2462..ec6f25b7f1b61 100644 --- a/x-pack/plugins/task_manager/server/ephemeral_task_lifecycle.test.ts +++ b/x-pack/plugins/task_manager/server/ephemeral_task_lifecycle.test.ts @@ -42,7 +42,6 @@ describe('EphemeralTaskLifecycle', () => { definitions: new TaskTypeDictionary(taskManagerLogger), executionContext, config: { - enabled: true, max_workers: 10, index: 'foo', max_attempts: 9, diff --git a/x-pack/plugins/task_manager/server/index.test.ts b/x-pack/plugins/task_manager/server/index.test.ts index 8419e826dfd31..8d095ce8131cd 100644 --- a/x-pack/plugins/task_manager/server/index.test.ts +++ b/x-pack/plugins/task_manager/server/index.test.ts @@ -52,13 +52,4 @@ describe('deprecations', () => { ] `); }); - - it('logs a deprecation warning for the enabled config', () => { - const { messages } = applyTaskManagerDeprecations({ enabled: true }); - expect(messages).toMatchInlineSnapshot(` - Array [ - "\\"xpack.task_manager.enabled\\" is deprecated. The ability to disable this plugin will be removed in 8.0.0.", - ] - `); - }); }); diff --git a/x-pack/plugins/task_manager/server/index.ts b/x-pack/plugins/task_manager/server/index.ts index 368b5a3441778..611fa40591c4d 100644 --- a/x-pack/plugins/task_manager/server/index.ts +++ b/x-pack/plugins/task_manager/server/index.ts @@ -68,16 +68,5 @@ export const config: PluginConfigDescriptor<TaskManagerConfig> = { }); } }, - (settings, fromPath, addDeprecation) => { - const taskManager = get(settings, fromPath); - if (taskManager?.enabled === false || taskManager?.enabled === true) { - addDeprecation({ - message: `"xpack.task_manager.enabled" is deprecated. The ability to disable this plugin will be removed in 8.0.0.`, - correctiveActions: { - manualSteps: [`Remove "xpack.task_manager.enabled" from your kibana configs.`], - }, - }); - } - }, ], }; diff --git a/x-pack/plugins/task_manager/server/integration_tests/managed_configuration.test.ts b/x-pack/plugins/task_manager/server/integration_tests/managed_configuration.test.ts index f714fd36c2658..c9cc5be2d5cd6 100644 --- a/x-pack/plugins/task_manager/server/integration_tests/managed_configuration.test.ts +++ b/x-pack/plugins/task_manager/server/integration_tests/managed_configuration.test.ts @@ -29,7 +29,6 @@ describe('managed configuration', () => { clock = sinon.useFakeTimers(); const context = coreMock.createPluginInitializerContext<TaskManagerConfig>({ - enabled: true, max_workers: 10, index: 'foo', max_attempts: 9, diff --git a/x-pack/plugins/task_manager/server/monitoring/configuration_statistics.test.ts b/x-pack/plugins/task_manager/server/monitoring/configuration_statistics.test.ts index 6e88e9803add2..bbd5bc217ae3b 100644 --- a/x-pack/plugins/task_manager/server/monitoring/configuration_statistics.test.ts +++ b/x-pack/plugins/task_manager/server/monitoring/configuration_statistics.test.ts @@ -13,7 +13,6 @@ import { TaskManagerConfig } from '../config'; describe('Configuration Statistics Aggregator', () => { test('merges the static config with the merged configs', async () => { const configuration: TaskManagerConfig = { - enabled: true, max_workers: 10, index: 'foo', max_attempts: 9, diff --git a/x-pack/plugins/task_manager/server/monitoring/monitoring_stats_stream.test.ts b/x-pack/plugins/task_manager/server/monitoring/monitoring_stats_stream.test.ts index ec94d9df1a4dc..e29dbc978c64a 100644 --- a/x-pack/plugins/task_manager/server/monitoring/monitoring_stats_stream.test.ts +++ b/x-pack/plugins/task_manager/server/monitoring/monitoring_stats_stream.test.ts @@ -17,7 +17,6 @@ beforeEach(() => { describe('createMonitoringStatsStream', () => { const configuration: TaskManagerConfig = { - enabled: true, max_workers: 10, index: 'foo', max_attempts: 9, diff --git a/x-pack/plugins/task_manager/server/plugin.test.ts b/x-pack/plugins/task_manager/server/plugin.test.ts index c47f006eca415..c2345d7bf8193 100644 --- a/x-pack/plugins/task_manager/server/plugin.test.ts +++ b/x-pack/plugins/task_manager/server/plugin.test.ts @@ -16,7 +16,6 @@ describe('TaskManagerPlugin', () => { describe('setup', () => { test('throws if no valid UUID is available', async () => { const pluginInitializerContext = coreMock.createPluginInitializerContext<TaskManagerConfig>({ - enabled: true, max_workers: 10, index: 'foo', max_attempts: 9, @@ -59,7 +58,6 @@ describe('TaskManagerPlugin', () => { test('throws if setup methods are called after start', async () => { const pluginInitializerContext = coreMock.createPluginInitializerContext<TaskManagerConfig>({ - enabled: true, max_workers: 10, index: 'foo', max_attempts: 9, @@ -131,7 +129,6 @@ describe('TaskManagerPlugin', () => { test('it logs a warning when the unsafe `exclude_task_types` config is used', async () => { const pluginInitializerContext = coreMock.createPluginInitializerContext<TaskManagerConfig>({ - enabled: true, max_workers: 10, index: 'foo', max_attempts: 9, diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 26f021379a2a9..7d6a668733992 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -1653,12 +1653,6 @@ "data.functions.esaggs.help": "AggConfig 集約を実行します", "data.functions.esaggs.inspector.dataRequest.description": "このリクエストはElasticsearchにクエリし、ビジュアライゼーション用のデータを取得します。", "data.functions.esaggs.inspector.dataRequest.title": "データ", - "dataViews.indexPatternLoad.help": "インデックスパターンを読み込みます", - "dataViews.functions.indexPatternLoad.id.help": "読み込むインデックスパターンID", - "dataViews.ensureDefaultIndexPattern.bannerLabel": "Kibanaでデータの可視化と閲覧を行うには、Elasticsearchからデータを取得するためのインデックスパターンの作成が必要です。", - "dataViews.fetchFieldErrorTitle": "インデックスパターンのフィールド取得中にエラーが発生 {title}(ID:{id})", - "dataViews.indexPatternLoad.error.kibanaRequest": "サーバーでこの検索を実行するには、KibanaRequest が必要です。式実行パラメーターに要求オブジェクトを渡してください。", - "dataViews.unableWriteLabel": "インデックスパターンを書き込めません。このインデックスパターンへの最新の変更を取得するには、ページを更新してください。", "data.inspector.table..dataDescriptionTooltip": "ビジュアライゼーションの元のデータを表示", "data.inspector.table.dataTitle": "データ", "data.inspector.table.downloadCSVToggleButtonLabel": "CSV をダウンロード", @@ -2297,6 +2291,12 @@ "data.searchSessions.sessionService.sessionObjectFetchError": "検索セッション情報を取得できませんでした", "data.triggers.applyFilterDescription": "Kibanaフィルターが適用されるとき。単一の値または範囲フィルターにすることができます。", "data.triggers.applyFilterTitle": "フィルターを適用", + "dataViews.indexPatternLoad.help": "インデックスパターンを読み込みます", + "dataViews.functions.indexPatternLoad.id.help": "読み込むインデックスパターンID", + "dataViews.ensureDefaultIndexPattern.bannerLabel": "Kibanaでデータの可視化と閲覧を行うには、Elasticsearchからデータを取得するためのインデックスパターンの作成が必要です。", + "dataViews.fetchFieldErrorTitle": "インデックスパターンのフィールド取得中にエラーが発生 {title}(ID:{id})", + "dataViews.indexPatternLoad.error.kibanaRequest": "サーバーでこの検索を実行するには、KibanaRequest が必要です。式実行パラメーターに要求オブジェクトを渡してください。", + "dataViews.unableWriteLabel": "インデックスパターンを書き込めません。このインデックスパターンへの最新の変更を取得するには、ページを更新してください。", "devTools.badge.readOnly.text": "読み取り専用", "devTools.badge.readOnly.tooltip": "を保存できませんでした", "devTools.devToolsTitle": "開発ツール", @@ -9951,11 +9951,9 @@ "xpack.enterpriseSearch.workplaceSearch.contentSources.displaySettings.unsaved.message": "表示設定は保存されていません。終了してよろしいですか?", "xpack.enterpriseSearch.workplaceSearch.contentSources.displaySettings.visibleFields.title": "表示フィールド", "xpack.enterpriseSearch.workplaceSearch.contentSources.syncManagementContentExtractionLabel": "すべてのテキストとコンテンツを同期", - "xpack.enterpriseSearch.workplaceSearch.contentSources.syncManagementDescription": "このソースの特定のコンテンツの抽出を有効および無効にします。", "xpack.enterpriseSearch.workplaceSearch.contentSources.syncManagementGlobalConfigLabel": "サムネイルを同期 - グローバル構成レベルでは無効", "xpack.enterpriseSearch.workplaceSearch.contentSources.syncManagementSynchronizeLabel": "このソースを同期", "xpack.enterpriseSearch.workplaceSearch.contentSources.syncManagementThumbnailsLabel": "サムネイルを同期", - "xpack.enterpriseSearch.workplaceSearch.contentSources.syncManagementTitle": "同期管理", "xpack.enterpriseSearch.workplaceSearch.copyText": "コピー", "xpack.enterpriseSearch.workplaceSearch.credentials.description": "クライアントで次の資格情報を使用して、認証サーバーからアクセストークンを要求します。", "xpack.enterpriseSearch.workplaceSearch.credentials.title": "資格情報", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index f2e5308bd6c5f..cd5fb213c0696 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -1669,12 +1669,6 @@ "data.functions.esaggs.help": "运行 AggConfig 聚合", "data.functions.esaggs.inspector.dataRequest.description": "此请求查询 Elasticsearch,以获取可视化的数据。", "data.functions.esaggs.inspector.dataRequest.title": "数据", - "dataViews.indexPatternLoad.help": "加载索引模式", - "dataViews.functions.indexPatternLoad.id.help": "要加载的索引模式 id", - "dataViews.ensureDefaultIndexPattern.bannerLabel": "要在 Kibana 中可视化和浏览数据,必须创建索引模式,以从 Elasticsearch 中检索数据。", - "dataViews.fetchFieldErrorTitle": "提取索引模式 {title} (ID: {id}) 的字段时出错", - "dataViews.indexPatternLoad.error.kibanaRequest": "在服务器上执行此搜索时需要 Kibana 请求。请向表达式执行模式参数提供请求对象。", - "dataViews.unableWriteLabel": "无法写入索引模式!请刷新页面以获取此索引模式的最新更改。", "data.inspector.table..dataDescriptionTooltip": "查看可视化后面的数据", "data.inspector.table.dataTitle": "数据", "data.inspector.table.downloadCSVToggleButtonLabel": "下载 CSV", @@ -2319,6 +2313,12 @@ "data.searchSessions.sessionService.sessionObjectFetchError": "无法提取搜索会话信息", "data.triggers.applyFilterDescription": "应用 kibana 筛选时。可能是单个值或范围筛选。", "data.triggers.applyFilterTitle": "应用筛选", + "dataViews.indexPatternLoad.help": "加载索引模式", + "dataViews.functions.indexPatternLoad.id.help": "要加载的索引模式 id", + "dataViews.ensureDefaultIndexPattern.bannerLabel": "要在 Kibana 中可视化和浏览数据,必须创建索引模式,以从 Elasticsearch 中检索数据。", + "dataViews.fetchFieldErrorTitle": "提取索引模式 {title} (ID: {id}) 的字段时出错", + "dataViews.indexPatternLoad.error.kibanaRequest": "在服务器上执行此搜索时需要 Kibana 请求。请向表达式执行模式参数提供请求对象。", + "dataViews.unableWriteLabel": "无法写入索引模式!请刷新页面以获取此索引模式的最新更改。", "devTools.badge.readOnly.text": "只读", "devTools.badge.readOnly.tooltip": "无法保存", "devTools.devToolsTitle": "开发工具", @@ -10050,11 +10050,9 @@ "xpack.enterpriseSearch.workplaceSearch.contentSources.displaySettings.unsaved.message": "您的显示设置尚未保存。是否确定要离开?", "xpack.enterpriseSearch.workplaceSearch.contentSources.displaySettings.visibleFields.title": "可见的字段", "xpack.enterpriseSearch.workplaceSearch.contentSources.syncManagementContentExtractionLabel": "同步所有文本和内容", - "xpack.enterpriseSearch.workplaceSearch.contentSources.syncManagementDescription": "为此源启用和禁用特定内容的提取。", "xpack.enterpriseSearch.workplaceSearch.contentSources.syncManagementGlobalConfigLabel": "同步缩略图 - 已在全局配置级别禁用", "xpack.enterpriseSearch.workplaceSearch.contentSources.syncManagementSynchronizeLabel": "同步此源", "xpack.enterpriseSearch.workplaceSearch.contentSources.syncManagementThumbnailsLabel": "同步缩略图", - "xpack.enterpriseSearch.workplaceSearch.contentSources.syncManagementTitle": "同步管理", "xpack.enterpriseSearch.workplaceSearch.copyText": "复制", "xpack.enterpriseSearch.workplaceSearch.credentials.description": "在您的客户端中使用以下凭据从我们的身份验证服务器请求访问令牌。", "xpack.enterpriseSearch.workplaceSearch.credentials.title": "凭据", diff --git a/x-pack/plugins/triggers_actions_ui/server/index.test.ts b/x-pack/plugins/triggers_actions_ui/server/index.test.ts deleted file mode 100644 index 1149843d85a50..0000000000000 --- a/x-pack/plugins/triggers_actions_ui/server/index.test.ts +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ -import { config } from './index'; -import { applyDeprecations, configDeprecationFactory } from '@kbn/config'; - -const CONFIG_PATH = 'xpack.trigger_actions_ui'; -const applyStackAlertDeprecations = (settings: Record<string, unknown> = {}) => { - const deprecations = config.deprecations!(configDeprecationFactory); - const deprecationMessages: string[] = []; - const _config = { - [CONFIG_PATH]: settings, - }; - const { config: migrated } = applyDeprecations( - _config, - deprecations.map((deprecation) => ({ - deprecation, - path: CONFIG_PATH, - })), - () => - ({ message }) => - deprecationMessages.push(message) - ); - return { - messages: deprecationMessages, - migrated, - }; -}; - -describe('index', () => { - describe('deprecations', () => { - it('should deprecate .enabled flag', () => { - const { messages } = applyStackAlertDeprecations({ enabled: false }); - expect(messages).toMatchInlineSnapshot(` - Array [ - "\\"xpack.trigger_actions_ui.enabled\\" is deprecated. The ability to disable this plugin will be removed in 8.0.0.", - ] - `); - }); - }); -}); diff --git a/x-pack/plugins/triggers_actions_ui/server/index.ts b/x-pack/plugins/triggers_actions_ui/server/index.ts index c7d363af45247..89c17ea0d4189 100644 --- a/x-pack/plugins/triggers_actions_ui/server/index.ts +++ b/x-pack/plugins/triggers_actions_ui/server/index.ts @@ -4,7 +4,6 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { get } from 'lodash'; import { PluginConfigDescriptor, PluginInitializerContext } from 'kibana/server'; import { configSchema, ConfigSchema } from '../config'; import { TriggersActionsPlugin } from './plugin'; @@ -26,19 +25,6 @@ export const config: PluginConfigDescriptor<ConfigSchema> = { enableGeoTrackingThresholdAlert: true, }, schema: configSchema, - deprecations: () => [ - (settings, fromPath, addDeprecation) => { - const triggersActionsUi = get(settings, fromPath); - if (triggersActionsUi?.enabled === false || triggersActionsUi?.enabled === true) { - addDeprecation({ - message: `"xpack.trigger_actions_ui.enabled" is deprecated. The ability to disable this plugin will be removed in 8.0.0.`, - correctiveActions: { - manualSteps: [`Remove "xpack.trigger_actions_ui.enabled" from your kibana configs.`], - }, - }); - } - }, - ], }; export const plugin = (ctx: PluginInitializerContext) => new TriggersActionsPlugin(ctx); diff --git a/x-pack/test/accessibility/apps/ingest_node_pipelines.ts b/x-pack/test/accessibility/apps/ingest_node_pipelines.ts index b9bc216db60b5..dab9c86bf018e 100644 --- a/x-pack/test/accessibility/apps/ingest_node_pipelines.ts +++ b/x-pack/test/accessibility/apps/ingest_node_pipelines.ts @@ -14,14 +14,14 @@ export default function ({ getService, getPageObjects }: any) { const log = getService('log'); const a11y = getService('a11y'); /* this is the wrapping service around axe */ - describe('Ingest Node Pipelines', async () => { + describe('Ingest Pipelines', async () => { before(async () => { await putSamplePipeline(esClient); await common.navigateToApp('ingestPipelines'); }); it('List View', async () => { - await retry.waitFor('Ingest Node Pipelines page to be visible', async () => { + await retry.waitFor('Ingest Pipelines page to be visible', async () => { await common.navigateToApp('ingestPipelines'); return testSubjects.exists('pipelineDetailsLink') ? true : false; }); diff --git a/x-pack/test/accessibility/apps/login_page.ts b/x-pack/test/accessibility/apps/login_page.ts index 580df3e4ccc88..8de4a47e10b1e 100644 --- a/x-pack/test/accessibility/apps/login_page.ts +++ b/x-pack/test/accessibility/apps/login_page.ts @@ -14,7 +14,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const retry = getService('retry'); const PageObjects = getPageObjects(['common', 'security']); - describe('Security', () => { + // Failing: See https://github.com/elastic/kibana/issues/96372 + describe.skip('Security', () => { describe('Login Page', () => { before(async () => { await esArchiver.load('x-pack/test/functional/es_archives/empty_kibana'); diff --git a/x-pack/test/api_integration/apis/management/ingest_pipelines/index.ts b/x-pack/test/api_integration/apis/management/ingest_pipelines/index.ts index 54bd29ede5865..d36c573e14e1f 100644 --- a/x-pack/test/api_integration/apis/management/ingest_pipelines/index.ts +++ b/x-pack/test/api_integration/apis/management/ingest_pipelines/index.ts @@ -8,7 +8,7 @@ import { FtrProviderContext } from '../../../ftr_provider_context'; export default function ({ loadTestFile }: FtrProviderContext) { - describe('Ingest Node Pipelines', () => { + describe('Ingest Pipelines', () => { loadTestFile(require.resolve('./ingest_pipelines')); }); } diff --git a/x-pack/test/api_integration/apis/management/ingest_pipelines/ingest_pipelines.ts b/x-pack/test/api_integration/apis/management/ingest_pipelines/ingest_pipelines.ts index 1ad2a05d4f783..80790a6df400f 100644 --- a/x-pack/test/api_integration/apis/management/ingest_pipelines/ingest_pipelines.ts +++ b/x-pack/test/api_integration/apis/management/ingest_pipelines/ingest_pipelines.ts @@ -145,7 +145,7 @@ export default function ({ getService }: FtrProviderContext) { await createPipeline({ body: PIPELINE, id: PIPELINE_ID }, true); } catch (err) { // eslint-disable-next-line no-console - console.log('[Setup error] Error creating ingest node pipeline'); + console.log('[Setup error] Error creating ingest pipeline'); throw err; } }); @@ -225,7 +225,7 @@ export default function ({ getService }: FtrProviderContext) { await createPipeline({ body: PIPELINE, id: PIPELINE_ID }, true); } catch (err) { // eslint-disable-next-line no-console - console.log('[Setup error] Error creating ingest node pipeline'); + console.log('[Setup error] Error creating ingest pipeline'); throw err; } }); diff --git a/x-pack/test/banners_functional/config.ts b/x-pack/test/banners_functional/config.ts index c9acff91aecd1..03f91dfbc34e2 100644 --- a/x-pack/test/banners_functional/config.ts +++ b/x-pack/test/banners_functional/config.ts @@ -32,7 +32,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { ...kibanaFunctionalConfig.get('kbnTestServer'), serverArgs: [ ...kibanaFunctionalConfig.get('kbnTestServer.serverArgs'), - '--xpack.banners.placement=header', + '--xpack.banners.placement=top', '--xpack.banners.textContent="global banner text"', ], }, diff --git a/x-pack/test/case_api_integration/common/config.ts b/x-pack/test/case_api_integration/common/config.ts index 514b54982ee42..e6fda129eaa16 100644 --- a/x-pack/test/case_api_integration/common/config.ts +++ b/x-pack/test/case_api_integration/common/config.ts @@ -140,7 +140,7 @@ export function createTestConfig(name: string, options: CreateTestConfigOptions) (pluginDir) => `--plugin-path=${path.resolve(__dirname, 'fixtures', 'plugins', pluginDir)}` ), - `--server.xsrf.whitelist=${JSON.stringify(getAllExternalServiceSimulatorPaths())}`, + `--server.xsrf.allowlist=${JSON.stringify(getAllExternalServiceSimulatorPaths())}`, ...(ssl ? [ `--elasticsearch.hosts=${servers.elasticsearch.protocol}://${servers.elasticsearch.hostname}:${servers.elasticsearch.port}`, diff --git a/x-pack/test/functional/apps/ingest_pipelines/feature_controls/ingest_pipelines_security.ts b/x-pack/test/functional/apps/ingest_pipelines/feature_controls/ingest_pipelines_security.ts index aed73d6c9858d..1f89ea8c635a6 100644 --- a/x-pack/test/functional/apps/ingest_pipelines/feature_controls/ingest_pipelines_security.ts +++ b/x-pack/test/functional/apps/ingest_pipelines/feature_controls/ingest_pipelines_security.ts @@ -60,7 +60,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { it('should render the "Ingest" section with ingest pipelines', async () => { await PageObjects.common.navigateToApp('management'); const sections = await managementMenu.getSections(); - // We gave the ingest node pipelines user access to advanced settings to allow them to use ingest node pipelines. + // We gave the ingest pipelines user access to advanced settings to allow them to use ingest pipelines. // See https://github.com/elastic/kibana/pull/102409/ expect(sections).to.have.length(2); expect(sections[0]).to.eql({ diff --git a/x-pack/test/functional/apps/ingest_pipelines/ingest_pipelines.ts b/x-pack/test/functional/apps/ingest_pipelines/ingest_pipelines.ts index 17b4fef06f5ce..026cea52e8102 100644 --- a/x-pack/test/functional/apps/ingest_pipelines/ingest_pipelines.ts +++ b/x-pack/test/functional/apps/ingest_pipelines/ingest_pipelines.ts @@ -29,10 +29,10 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { }); it('Loads the app', async () => { - log.debug('Checking for section heading to say Ingest Node Pipelines.'); + log.debug('Checking for section heading to say Ingest Pipelines.'); const headingText = await pageObjects.ingestPipelines.sectionHeadingText(); - expect(headingText).to.be('Ingest Node Pipelines'); + expect(headingText).to.be('Ingest Pipelines'); }); it('Creates a pipeline', async () => { diff --git a/x-pack/test/plugin_api_integration/config.ts b/x-pack/test/plugin_api_integration/config.ts index cd13186a69cc6..0a9535df5a9f3 100644 --- a/x-pack/test/plugin_api_integration/config.ts +++ b/x-pack/test/plugin_api_integration/config.ts @@ -38,7 +38,6 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { ...integrationConfig.get('kbnTestServer'), serverArgs: [ ...integrationConfig.get('kbnTestServer.serverArgs'), - '--xpack.eventLog.enabled=true', '--xpack.eventLog.logEntries=true', '--xpack.eventLog.indexEntries=true', '--xpack.task_manager.monitored_aggregated_stats_refresh_rate=5000', diff --git a/x-pack/test/plugin_api_integration/plugins/event_log/server/init_routes.ts b/x-pack/test/plugin_api_integration/plugins/event_log/server/init_routes.ts index 5c27ffe62a48d..805feee159f27 100644 --- a/x-pack/test/plugin_api_integration/plugins/event_log/server/init_routes.ts +++ b/x-pack/test/plugin_api_integration/plugins/event_log/server/init_routes.ts @@ -179,7 +179,7 @@ export const isEventLogServiceEnabledRoute = ( res: KibanaResponseFactory ): Promise<IKibanaResponse<any>> { logger.info(`test if event logger is enabled`); - return res.ok({ body: { isEnabled: eventLogService.isEnabled() } }); + return res.ok({ body: { isEnabled: true } }); } ); }; diff --git a/x-pack/test/plugin_api_integration/test_suites/event_log/service_api_integration.ts b/x-pack/test/plugin_api_integration/test_suites/event_log/service_api_integration.ts index 4c624cdbdda63..2c8564df02e0b 100644 --- a/x-pack/test/plugin_api_integration/test_suites/event_log/service_api_integration.ts +++ b/x-pack/test/plugin_api_integration/test_suites/event_log/service_api_integration.ts @@ -19,14 +19,6 @@ export default function ({ getService }: FtrProviderContext) { const retry = getService('retry'); describe('Event Log service API', () => { - it('should check if it is enabled', async () => { - const configValue = config - .get('kbnTestServer.serverArgs') - .find((val: string) => val === '--xpack.eventLog.enabled=true'); - const result = await isEventLogServiceEnabled(); - expect(configValue).to.be.eql(`--xpack.eventLog.enabled=${result.body.isEnabled}`); - }); - it('should check if logging entries is enabled', async () => { const configValue = config .get('kbnTestServer.serverArgs') @@ -216,14 +208,6 @@ export default function ({ getService }: FtrProviderContext) { .expect(200); } - async function isEventLogServiceEnabled() { - log.debug(`isEventLogServiceEnabled`); - return await supertest - .get(`/api/log_event_fixture/isEventLogServiceEnabled`) - .set('kbn-xsrf', 'foo') - .expect(200); - } - async function isEventLogServiceLoggingEntries() { log.debug(`isEventLogServiceLoggingEntries`); return await supertest diff --git a/x-pack/test/rule_registry/common/config.ts b/x-pack/test/rule_registry/common/config.ts index 487af84141d20..9cce58c30f6e9 100644 --- a/x-pack/test/rule_registry/common/config.ts +++ b/x-pack/test/rule_registry/common/config.ts @@ -83,7 +83,7 @@ export function createTestConfig(name: string, options: CreateTestConfigOptions) // TO DO: Remove feature flags once we're good to go '--xpack.securitySolution.enableExperimental=["ruleRegistryEnabled"]', '--xpack.ruleRegistry.write.enabled=true', - `--server.xsrf.whitelist=${JSON.stringify(getAllExternalServiceSimulatorPaths())}`, + `--server.xsrf.allowlist=${JSON.stringify(getAllExternalServiceSimulatorPaths())}`, ...(ssl ? [ `--elasticsearch.hosts=${servers.elasticsearch.protocol}://${servers.elasticsearch.hostname}:${servers.elasticsearch.port}`, diff --git a/x-pack/test/timeline/common/config.ts b/x-pack/test/timeline/common/config.ts index ba1c8528527e4..fa8ddb2ad10a7 100644 --- a/x-pack/test/timeline/common/config.ts +++ b/x-pack/test/timeline/common/config.ts @@ -83,7 +83,7 @@ export function createTestConfig(name: string, options: CreateTestConfigOptions) // TO DO: Remove feature flags once we're good to go '--xpack.securitySolution.enableExperimental=["ruleRegistryEnabled"]', '--xpack.ruleRegistry.write.enabled=true', - `--server.xsrf.whitelist=${JSON.stringify(getAllExternalServiceSimulatorPaths())}`, + `--server.xsrf.allowlist=${JSON.stringify(getAllExternalServiceSimulatorPaths())}`, ...(ssl ? [ `--elasticsearch.hosts=${servers.elasticsearch.protocol}://${servers.elasticsearch.hostname}:${servers.elasticsearch.port}`,