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 3a383ee72b86a..1830e8f140e60 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 @@ -134,6 +134,9 @@ readonly links: { readonly visualize: Record; readonly apis: Readonly<{ bulkIndexAlias: string; + byteSizeUnits: string; + createAutoFollowPattern: string; + createFollower: string; createIndex: string; createSnapshotLifecyclePolicy: string; createRoleMapping: string; @@ -153,6 +156,7 @@ readonly links: { putIndexTemplateV1: string; putWatch: string; simulatePipeline: string; + timeUnits: string; updateTransform: string; }>; readonly observability: Record; 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 c5bf4babd9da9..4242159ff3c20 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 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 auditbeat: {
readonly base: 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 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 indexPatterns: {
readonly introduction: string;
readonly fieldFormattersNumber: string;
readonly fieldFormattersString: string;
};
readonly addData: string;
readonly kibana: string;
readonly upgradeAssistant: string;
readonly elasticsearch: Record<string, string>;
readonly siem: {
readonly guide: string;
readonly gettingStarted: string;
};
readonly query: {
readonly eql: string;
readonly kueryQuerySyntax: string;
readonly luceneQuerySyntax: string;
readonly percolate: string;
readonly queryDsl: string;
};
readonly date: {
readonly dateMath: string;
readonly dateMathIndexNames: string;
};
readonly management: Record<string, string>;
readonly ml: Record<string, string>;
readonly transforms: Record<string, string>;
readonly visualize: Record<string, string>;
readonly apis: Readonly<{
bulkIndexAlias: string;
createIndex: string;
createSnapshotLifecyclePolicy: string;
createRoleMapping: string;
createRoleMappingTemplates: 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;
updateTransform: string;
}>;
readonly observability: Record<string, 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>;
} | | +| [links](./kibana-plugin-core-public.doclinksstart.links.md) | {
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 auditbeat: {
readonly base: 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 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 indexPatterns: {
readonly introduction: string;
readonly fieldFormattersNumber: string;
readonly fieldFormattersString: string;
};
readonly addData: string;
readonly kibana: string;
readonly upgradeAssistant: string;
readonly elasticsearch: Record<string, string>;
readonly siem: {
readonly guide: string;
readonly gettingStarted: string;
};
readonly query: {
readonly eql: string;
readonly kueryQuerySyntax: string;
readonly luceneQuerySyntax: string;
readonly percolate: string;
readonly queryDsl: string;
};
readonly date: {
readonly dateMath: string;
readonly dateMathIndexNames: string;
};
readonly management: Record<string, string>;
readonly ml: Record<string, string>;
readonly transforms: Record<string, string>;
readonly visualize: Record<string, string>;
readonly apis: Readonly<{
bulkIndexAlias: string;
byteSizeUnits: string;
createAutoFollowPattern: string;
createFollower: string;
createIndex: string;
createSnapshotLifecyclePolicy: string;
createRoleMapping: string;
createRoleMappingTemplates: string;
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: Record<string, 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>;
} | | diff --git a/src/core/public/doc_links/doc_links_service.ts b/src/core/public/doc_links/doc_links_service.ts index e3fc839ef70b5..80a6667148df8 100644 --- a/src/core/public/doc_links/doc_links_service.ts +++ b/src/core/public/doc_links/doc_links_service.ts @@ -311,6 +311,9 @@ export class DocLinksService { }, apis: { bulkIndexAlias: `${ELASTICSEARCH_DOCS}indices-aliases.html`, + byteSizeUnits: `${ELASTICSEARCH_DOCS}common-options.html#byte-units`, + createAutoFollowPattern: `${ELASTICSEARCH_DOCS}ccr-put-auto-follow-pattern.html`, + createFollower: `${ELASTICSEARCH_DOCS}ccr-put-follow.html`, createIndex: `${ELASTICSEARCH_DOCS}indices-create-index.html`, createSnapshotLifecyclePolicy: `${ELASTICSEARCH_DOCS}slm-api-put-policy.html`, createRoleMapping: `${ELASTICSEARCH_DOCS}security-api-put-role-mapping.html`, @@ -331,6 +334,7 @@ export class DocLinksService { putSnapshotLifecyclePolicy: `${ELASTICSEARCH_DOCS}slm-api-put-policy.html`, putWatch: `${ELASTICSEARCH_DOCS}watcher-api-put-watch.html`, simulatePipeline: `${ELASTICSEARCH_DOCS}simulate-pipeline-api.html`, + timeUnits: `${ELASTICSEARCH_DOCS}common-options.html#time-units`, updateTransform: `${ELASTICSEARCH_DOCS}update-transform.html`, }, plugins: { @@ -529,6 +533,9 @@ export interface DocLinksStart { readonly visualize: Record; readonly apis: Readonly<{ bulkIndexAlias: string; + byteSizeUnits: string; + createAutoFollowPattern: string; + createFollower: string; createIndex: string; createSnapshotLifecyclePolicy: string; createRoleMapping: string; @@ -548,6 +555,7 @@ export interface DocLinksStart { putIndexTemplateV1: string; putWatch: string; simulatePipeline: string; + timeUnits: string; updateTransform: string; }>; readonly observability: Record; diff --git a/src/core/public/public.api.md b/src/core/public/public.api.md index 574f37cb592e7..13660da598ea0 100644 --- a/src/core/public/public.api.md +++ b/src/core/public/public.api.md @@ -619,6 +619,9 @@ export interface DocLinksStart { readonly visualize: Record; readonly apis: Readonly<{ bulkIndexAlias: string; + byteSizeUnits: string; + createAutoFollowPattern: string; + createFollower: string; createIndex: string; createSnapshotLifecyclePolicy: string; createRoleMapping: string; @@ -638,6 +641,7 @@ export interface DocLinksStart { putIndexTemplateV1: string; putWatch: string; simulatePipeline: string; + timeUnits: string; updateTransform: string; }>; readonly observability: Record; diff --git a/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/helpers/setup_environment.js b/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/helpers/setup_environment.js index 5b6b54135722c..41efd474e43dc 100644 --- a/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/helpers/setup_environment.js +++ b/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/helpers/setup_environment.js @@ -8,7 +8,9 @@ import axios from 'axios'; import axiosXhrAdapter from 'axios/lib/adapters/xhr'; +import { docLinksServiceMock } from '../../../../../../../src/core/public/mocks'; import { setHttpClient } from '../../../app/services/api'; +import { init as initDocumentation } from '../../../app/services/documentation_links'; import { init as initHttpRequests } from './http_requests'; export const setupEnvironment = () => { @@ -17,6 +19,7 @@ export const setupEnvironment = () => { const client = axios.create({ adapter: axiosXhrAdapter }); client.interceptors.response.use(({ data }) => data); setHttpClient(client); + initDocumentation(docLinksServiceMock.createStartContract()); const { server, httpRequestsMockHelpers } = initHttpRequests(); diff --git a/x-pack/plugins/cross_cluster_replication/public/app/components/auto_follow_pattern_page_title.js b/x-pack/plugins/cross_cluster_replication/public/app/components/auto_follow_pattern_page_title.js index 45023811fd619..ada998ef37be8 100644 --- a/x-pack/plugins/cross_cluster_replication/public/app/components/auto_follow_pattern_page_title.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/components/auto_follow_pattern_page_title.js @@ -18,7 +18,7 @@ import { EuiTitle, } from '@elastic/eui'; -import { getAutoFollowPatternUrl } from '../services/documentation_links'; +import { documentationLinks } from '../services/documentation_links'; export const AutoFollowPatternPageTitle = ({ title }) => ( @@ -36,7 +36,7 @@ export const AutoFollowPatternPageTitle = ({ title }) => ( - - - ), - }} - /> -); +export const getAdvancedSettingsFields = (documentationLinks) => { + const byteUnitsHelpText = ( + + + + ), + }} + /> + ); -const timeUnitsHelpText = ( - - - - ), - }} - /> -); + const timeUnitsHelpText = ( + + + + ), + }} + /> + ); -export const advancedSettingsFields = [ - { - field: 'maxReadRequestOperationCount', - testSubject: 'maxReadRequestOperationCountInput', - title: i18n.translate( - 'xpack.crossClusterReplication.followerIndexForm.advancedSettings.maxReadRequestOperationCountTitle', - { - defaultMessage: 'Max read request operation count', - } - ), - description: i18n.translate( - 'xpack.crossClusterReplication.followerIndexForm.advancedSettings.maxReadRequestOperationCountDescription', - { - defaultMessage: - 'The maximum number of operations to pull per read from the remote cluster.', - } - ), - label: i18n.translate( - 'xpack.crossClusterReplication.followerIndexForm.advancedSettings.maxReadRequestOperationCountLabel', - { - defaultMessage: 'Max read request operation count', - } - ), - defaultValue: getSettingDefault('maxReadRequestOperationCount'), - type: 'number', - }, - { - field: 'maxOutstandingReadRequests', - testSubject: 'maxOutstandingReadRequestsInput', - title: i18n.translate( - 'xpack.crossClusterReplication.followerIndexForm.advancedSettings.maxOutstandingReadRequestsTitle', - { - defaultMessage: 'Max outstanding read requests', - } - ), - description: i18n.translate( - 'xpack.crossClusterReplication.followerIndexForm.advancedSettings.maxOutstandingReadRequestsDescription', - { - defaultMessage: 'The maximum number of outstanding read requests from the remote cluster.', - } - ), - label: i18n.translate( - 'xpack.crossClusterReplication.followerIndexForm.advancedSettings.maxOutstandingReadRequestsLabel', - { - defaultMessage: 'Max outstanding read requests', - } - ), - defaultValue: getSettingDefault('maxOutstandingReadRequests'), - type: 'number', - }, - { - field: 'maxReadRequestSize', - testSubject: 'maxReadRequestSizeInput', - title: i18n.translate( - 'xpack.crossClusterReplication.followerIndexForm.advancedSettings.maxReadRequestSizeTitle', - { - defaultMessage: 'Max read request size', - } - ), - description: i18n.translate( - 'xpack.crossClusterReplication.followerIndexForm.advancedSettings.maxReadRequestSizeDescription', - { - defaultMessage: - 'The maximum size in bytes of per read of a batch of operations pulled from the remote cluster.', - } - ), - label: i18n.translate( - 'xpack.crossClusterReplication.followerIndexForm.advancedSettings.maxReadRequestSizeLabel', - { - defaultMessage: 'Max read request size', - } - ), - defaultValue: getSettingDefault('maxReadRequestSize'), - helpText: byteUnitsHelpText, - }, - { - field: 'maxWriteRequestOperationCount', - testSubject: 'maxWriteRequestOperationCountInput', - title: i18n.translate( - 'xpack.crossClusterReplication.followerIndexForm.advancedSettings.maxWriteRequestOperationCountTitle', - { - defaultMessage: 'Max write request operation count', - } - ), - description: i18n.translate( - 'xpack.crossClusterReplication.followerIndexForm.advancedSettings.maxWriteRequestOperationCountDescription', - { - defaultMessage: - 'The maximum number of operations per bulk write request executed on the follower.', - } - ), - label: i18n.translate( - 'xpack.crossClusterReplication.followerIndexForm.advancedSettings.maxWriteRequestOperationCountLabel', - { - defaultMessage: 'Max write request operation count', - } - ), - defaultValue: getSettingDefault('maxWriteRequestOperationCount'), - type: 'number', - }, - { - field: 'maxWriteRequestSize', - testSubject: 'maxWriteRequestSizeInput', - title: i18n.translate( - 'xpack.crossClusterReplication.followerIndexForm.advancedSettings.maxWriteRequestSizeTitle', - { - defaultMessage: 'Max write request size', - } - ), - description: i18n.translate( - 'xpack.crossClusterReplication.followerIndexForm.advancedSettings.maxWriteRequestSizeDescription', - { - defaultMessage: - 'The maximum total bytes of operations per bulk write request executed on the follower.', - } - ), - label: i18n.translate( - 'xpack.crossClusterReplication.followerIndexForm.advancedSettings.maxWriteRequestSizeLabel', - { - defaultMessage: 'Max write request size', - } - ), - defaultValue: getSettingDefault('maxWriteRequestSize'), - helpText: byteUnitsHelpText, - }, - { - field: 'maxOutstandingWriteRequests', - testSubject: 'maxOutstandingWriteRequestsInput', - title: i18n.translate( - 'xpack.crossClusterReplication.followerIndexForm.advancedSettings.maxOutstandingWriteRequestsTitle', - { - defaultMessage: 'Max outstanding write requests', - } - ), - description: i18n.translate( - 'xpack.crossClusterReplication.followerIndexForm.advancedSettings.maxOutstandingWriteRequestsDescription', - { - defaultMessage: 'The maximum number of outstanding write requests on the follower.', - } - ), - label: i18n.translate( - 'xpack.crossClusterReplication.followerIndexForm.advancedSettings.maxOutstandingWriteRequestsLabel', - { - defaultMessage: 'Max outstanding write requests', - } - ), - defaultValue: getSettingDefault('maxOutstandingWriteRequests'), - type: 'number', - }, - { - field: 'maxWriteBufferCount', - testSubject: 'maxWriteBufferCountInput', - title: i18n.translate( - 'xpack.crossClusterReplication.followerIndexForm.advancedSettings.maxWriteBufferCountTitle', - { - defaultMessage: 'Max write buffer count', - } - ), - description: i18n.translate( - 'xpack.crossClusterReplication.followerIndexForm.advancedSettings.maxWriteBufferCountDescription', - { - defaultMessage: - 'The maximum number of operations that can be queued for writing; when this ' + - 'limit is reached, reads from the remote cluster will be deferred until the number of queued ' + - 'operations goes below the limit.', - } - ), - label: i18n.translate( - 'xpack.crossClusterReplication.followerIndexForm.advancedSettings.maxWriteBufferCountLabel', - { - defaultMessage: 'Max write buffer count', - } - ), - defaultValue: getSettingDefault('maxWriteBufferCount'), - type: 'number', - }, - { - field: 'maxWriteBufferSize', - testSubject: 'maxWriteBufferSizeInput', - title: i18n.translate( - 'xpack.crossClusterReplication.followerIndexForm.advancedSettings.maxWriteBufferSizeTitle', - { - defaultMessage: 'Max write buffer size', - } - ), - description: i18n.translate( - 'xpack.crossClusterReplication.followerIndexForm.advancedSettings.maxWriteBufferSizeDescription', - { - defaultMessage: - 'The maximum total bytes of operations that can be queued for writing; when ' + - 'this limit is reached, reads from the remote cluster will be deferred until the total bytes ' + - 'of queued operations goes below the limit.', - } - ), - label: i18n.translate( - 'xpack.crossClusterReplication.followerIndexForm.advancedSettings.maxWriteBufferSizeLabel', - { - defaultMessage: 'Max write buffer size', - } - ), - defaultValue: getSettingDefault('maxWriteBufferSize'), - helpText: byteUnitsHelpText, - }, - { - field: 'maxRetryDelay', - testSubject: 'maxRetryDelayInput', - title: i18n.translate( - 'xpack.crossClusterReplication.followerIndexForm.advancedSettings.maxRetryDelayTitle', - { - defaultMessage: 'Max retry delay', - } - ), - description: i18n.translate( - 'xpack.crossClusterReplication.followerIndexForm.advancedSettings.maxRetryDelayDescription', - { - defaultMessage: - 'The maximum time to wait before retrying an operation that failed exceptionally; ' + - 'an exponential backoff strategy is employed when retrying.', - } - ), - label: i18n.translate( - 'xpack.crossClusterReplication.followerIndexForm.advancedSettings.maxRetryDelayLabel', - { - defaultMessage: 'Max retry delay', - } - ), - defaultValue: getSettingDefault('maxRetryDelay'), - helpText: timeUnitsHelpText, - }, - { - field: 'readPollTimeout', - testSubject: 'readPollTimeoutInput', - title: i18n.translate( - 'xpack.crossClusterReplication.followerIndexForm.advancedSettings.readPollTimeoutTitle', - { - defaultMessage: 'Read poll timeout', - } - ), - description: i18n.translate( - 'xpack.crossClusterReplication.followerIndexForm.advancedSettings.readPollTimeoutDescription', - { - defaultMessage: - 'The maximum time to wait for new operations on the remote cluster when the ' + - 'follower index is synchronized with the leader index; when the timeout has elapsed, the ' + - 'poll for operations will return to the follower so that it can update some statistics, and ' + - 'then the follower will immediately attempt to read from the leader again.', - } - ), - label: i18n.translate( - 'xpack.crossClusterReplication.followerIndexForm.advancedSettings.readPollTimeoutLabel', - { - defaultMessage: 'Read poll timeout', - } - ), - defaultValue: getSettingDefault('readPollTimeout'), - helpText: timeUnitsHelpText, - }, -]; + return [ + { + field: 'maxReadRequestOperationCount', + testSubject: 'maxReadRequestOperationCountInput', + title: i18n.translate( + 'xpack.crossClusterReplication.followerIndexForm.advancedSettings.maxReadRequestOperationCountTitle', + { + defaultMessage: 'Max read request operation count', + } + ), + description: i18n.translate( + 'xpack.crossClusterReplication.followerIndexForm.advancedSettings.maxReadRequestOperationCountDescription', + { + defaultMessage: + 'The maximum number of operations to pull per read from the remote cluster.', + } + ), + label: i18n.translate( + 'xpack.crossClusterReplication.followerIndexForm.advancedSettings.maxReadRequestOperationCountLabel', + { + defaultMessage: 'Max read request operation count', + } + ), + defaultValue: getSettingDefault('maxReadRequestOperationCount'), + type: 'number', + }, + { + field: 'maxOutstandingReadRequests', + testSubject: 'maxOutstandingReadRequestsInput', + title: i18n.translate( + 'xpack.crossClusterReplication.followerIndexForm.advancedSettings.maxOutstandingReadRequestsTitle', + { + defaultMessage: 'Max outstanding read requests', + } + ), + description: i18n.translate( + 'xpack.crossClusterReplication.followerIndexForm.advancedSettings.maxOutstandingReadRequestsDescription', + { + defaultMessage: + 'The maximum number of outstanding read requests from the remote cluster.', + } + ), + label: i18n.translate( + 'xpack.crossClusterReplication.followerIndexForm.advancedSettings.maxOutstandingReadRequestsLabel', + { + defaultMessage: 'Max outstanding read requests', + } + ), + defaultValue: getSettingDefault('maxOutstandingReadRequests'), + type: 'number', + }, + { + field: 'maxReadRequestSize', + testSubject: 'maxReadRequestSizeInput', + title: i18n.translate( + 'xpack.crossClusterReplication.followerIndexForm.advancedSettings.maxReadRequestSizeTitle', + { + defaultMessage: 'Max read request size', + } + ), + description: i18n.translate( + 'xpack.crossClusterReplication.followerIndexForm.advancedSettings.maxReadRequestSizeDescription', + { + defaultMessage: + 'The maximum size in bytes of per read of a batch of operations pulled from the remote cluster.', + } + ), + label: i18n.translate( + 'xpack.crossClusterReplication.followerIndexForm.advancedSettings.maxReadRequestSizeLabel', + { + defaultMessage: 'Max read request size', + } + ), + defaultValue: getSettingDefault('maxReadRequestSize'), + helpText: byteUnitsHelpText, + }, + { + field: 'maxWriteRequestOperationCount', + testSubject: 'maxWriteRequestOperationCountInput', + title: i18n.translate( + 'xpack.crossClusterReplication.followerIndexForm.advancedSettings.maxWriteRequestOperationCountTitle', + { + defaultMessage: 'Max write request operation count', + } + ), + description: i18n.translate( + 'xpack.crossClusterReplication.followerIndexForm.advancedSettings.maxWriteRequestOperationCountDescription', + { + defaultMessage: + 'The maximum number of operations per bulk write request executed on the follower.', + } + ), + label: i18n.translate( + 'xpack.crossClusterReplication.followerIndexForm.advancedSettings.maxWriteRequestOperationCountLabel', + { + defaultMessage: 'Max write request operation count', + } + ), + defaultValue: getSettingDefault('maxWriteRequestOperationCount'), + type: 'number', + }, + { + field: 'maxWriteRequestSize', + testSubject: 'maxWriteRequestSizeInput', + title: i18n.translate( + 'xpack.crossClusterReplication.followerIndexForm.advancedSettings.maxWriteRequestSizeTitle', + { + defaultMessage: 'Max write request size', + } + ), + description: i18n.translate( + 'xpack.crossClusterReplication.followerIndexForm.advancedSettings.maxWriteRequestSizeDescription', + { + defaultMessage: + 'The maximum total bytes of operations per bulk write request executed on the follower.', + } + ), + label: i18n.translate( + 'xpack.crossClusterReplication.followerIndexForm.advancedSettings.maxWriteRequestSizeLabel', + { + defaultMessage: 'Max write request size', + } + ), + defaultValue: getSettingDefault('maxWriteRequestSize'), + helpText: byteUnitsHelpText, + }, + { + field: 'maxOutstandingWriteRequests', + testSubject: 'maxOutstandingWriteRequestsInput', + title: i18n.translate( + 'xpack.crossClusterReplication.followerIndexForm.advancedSettings.maxOutstandingWriteRequestsTitle', + { + defaultMessage: 'Max outstanding write requests', + } + ), + description: i18n.translate( + 'xpack.crossClusterReplication.followerIndexForm.advancedSettings.maxOutstandingWriteRequestsDescription', + { + defaultMessage: 'The maximum number of outstanding write requests on the follower.', + } + ), + label: i18n.translate( + 'xpack.crossClusterReplication.followerIndexForm.advancedSettings.maxOutstandingWriteRequestsLabel', + { + defaultMessage: 'Max outstanding write requests', + } + ), + defaultValue: getSettingDefault('maxOutstandingWriteRequests'), + type: 'number', + }, + { + field: 'maxWriteBufferCount', + testSubject: 'maxWriteBufferCountInput', + title: i18n.translate( + 'xpack.crossClusterReplication.followerIndexForm.advancedSettings.maxWriteBufferCountTitle', + { + defaultMessage: 'Max write buffer count', + } + ), + description: i18n.translate( + 'xpack.crossClusterReplication.followerIndexForm.advancedSettings.maxWriteBufferCountDescription', + { + defaultMessage: + 'The maximum number of operations that can be queued for writing; when this ' + + 'limit is reached, reads from the remote cluster will be deferred until the number of queued ' + + 'operations goes below the limit.', + } + ), + label: i18n.translate( + 'xpack.crossClusterReplication.followerIndexForm.advancedSettings.maxWriteBufferCountLabel', + { + defaultMessage: 'Max write buffer count', + } + ), + defaultValue: getSettingDefault('maxWriteBufferCount'), + type: 'number', + }, + { + field: 'maxWriteBufferSize', + testSubject: 'maxWriteBufferSizeInput', + title: i18n.translate( + 'xpack.crossClusterReplication.followerIndexForm.advancedSettings.maxWriteBufferSizeTitle', + { + defaultMessage: 'Max write buffer size', + } + ), + description: i18n.translate( + 'xpack.crossClusterReplication.followerIndexForm.advancedSettings.maxWriteBufferSizeDescription', + { + defaultMessage: + 'The maximum total bytes of operations that can be queued for writing; when ' + + 'this limit is reached, reads from the remote cluster will be deferred until the total bytes ' + + 'of queued operations goes below the limit.', + } + ), + label: i18n.translate( + 'xpack.crossClusterReplication.followerIndexForm.advancedSettings.maxWriteBufferSizeLabel', + { + defaultMessage: 'Max write buffer size', + } + ), + defaultValue: getSettingDefault('maxWriteBufferSize'), + helpText: byteUnitsHelpText, + }, + { + field: 'maxRetryDelay', + testSubject: 'maxRetryDelayInput', + title: i18n.translate( + 'xpack.crossClusterReplication.followerIndexForm.advancedSettings.maxRetryDelayTitle', + { + defaultMessage: 'Max retry delay', + } + ), + description: i18n.translate( + 'xpack.crossClusterReplication.followerIndexForm.advancedSettings.maxRetryDelayDescription', + { + defaultMessage: + 'The maximum time to wait before retrying an operation that failed exceptionally; ' + + 'an exponential backoff strategy is employed when retrying.', + } + ), + label: i18n.translate( + 'xpack.crossClusterReplication.followerIndexForm.advancedSettings.maxRetryDelayLabel', + { + defaultMessage: 'Max retry delay', + } + ), + defaultValue: getSettingDefault('maxRetryDelay'), + helpText: timeUnitsHelpText, + }, + { + field: 'readPollTimeout', + testSubject: 'readPollTimeoutInput', + title: i18n.translate( + 'xpack.crossClusterReplication.followerIndexForm.advancedSettings.readPollTimeoutTitle', + { + defaultMessage: 'Read poll timeout', + } + ), + description: i18n.translate( + 'xpack.crossClusterReplication.followerIndexForm.advancedSettings.readPollTimeoutDescription', + { + defaultMessage: + 'The maximum time to wait for new operations on the remote cluster when the ' + + 'follower index is synchronized with the leader index; when the timeout has elapsed, the ' + + 'poll for operations will return to the follower so that it can update some statistics, and ' + + 'then the follower will immediately attempt to read from the leader again.', + } + ), + label: i18n.translate( + 'xpack.crossClusterReplication.followerIndexForm.advancedSettings.readPollTimeoutLabel', + { + defaultMessage: 'Read poll timeout', + } + ), + defaultValue: getSettingDefault('readPollTimeout'), + helpText: timeUnitsHelpText, + }, + ]; +}; -export const emptyAdvancedSettings = advancedSettingsFields.reduce((obj, advancedSetting) => { - const { field, defaultValue } = advancedSetting; - return { ...obj, [field]: defaultValue }; -}, {}); +export const getEmptyAdvancedSettings = (documentationLinks) => + getAdvancedSettingsFields(documentationLinks).reduce((obj, advancedSetting) => { + const { field, defaultValue } = advancedSetting; + return { ...obj, [field]: defaultValue }; + }, {}); -export function areAdvancedSettingsEdited(followerIndex) { - return advancedSettingsFields.some((advancedSetting) => { +export function areAdvancedSettingsEdited(followerIndex, documentationLinks) { + return getAdvancedSettingsFields(documentationLinks).some((advancedSetting) => { const { field } = advancedSetting; - return followerIndex[field] !== emptyAdvancedSettings[field]; + return followerIndex[field] !== getEmptyAdvancedSettings(documentationLinks)[field]; }); } diff --git a/x-pack/plugins/cross_cluster_replication/public/app/components/follower_index_form/follower_index_form.js b/x-pack/plugins/cross_cluster_replication/public/app/components/follower_index_form/follower_index_form.js index bca4ec702a5b5..dc117a9cd4581 100644 --- a/x-pack/plugins/cross_cluster_replication/public/app/components/follower_index_form/follower_index_form.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/components/follower_index_form/follower_index_form.js @@ -34,14 +34,15 @@ import { indexNameValidator, leaderIndexValidator } from '../../services/input_v import { routing } from '../../services/routing'; import { getFatalErrors } from '../../services/notifications'; import { loadIndices } from '../../services/api'; +import { documentationLinks } from '../../services/documentation_links'; import { API_STATUS } from '../../constants'; import { getRemoteClusterName } from '../../services/get_remote_cluster_name'; import { RemoteClustersFormField } from '../remote_clusters_form_field'; import { SectionError } from '../section_error'; import { FormEntryRow } from '../form_entry_row'; import { - advancedSettingsFields, - emptyAdvancedSettings, + getAdvancedSettingsFields, + getEmptyAdvancedSettings, areAdvancedSettingsEdited, } from './advanced_settings_fields'; @@ -49,23 +50,24 @@ import { FollowerIndexRequestFlyout } from './follower_index_request_flyout'; const indexNameIllegalCharacters = indices.INDEX_ILLEGAL_CHARACTERS_VISIBLE.join(' '); -const fieldToValidatorMap = advancedSettingsFields.reduce( - (map, advancedSetting) => { - const { field, validator } = advancedSetting; - map[field] = validator; - return map; - }, - { - name: indexNameValidator, - leaderIndex: leaderIndexValidator, - } -); +const getFieldToValidatorMap = (advancedSettingsFields) => + advancedSettingsFields.reduce( + (map, advancedSetting) => { + const { field, validator } = advancedSetting; + map[field] = validator; + return map; + }, + { + name: indexNameValidator, + leaderIndex: leaderIndexValidator, + } + ); const getEmptyFollowerIndex = (remoteClusterName = '') => ({ name: '', remoteCluster: remoteClusterName, leaderIndex: '', - ...emptyAdvancedSettings, + ...getEmptyAdvancedSettings(documentationLinks), }); /** @@ -121,7 +123,7 @@ export class FollowerIndexForm extends PureComponent { // eslint-disable-next-line no-nested-ternary const areAdvancedSettingsVisible = isNew ? false - : areAdvancedSettingsEdited(followerIndex) + : areAdvancedSettingsEdited(followerIndex, documentationLinks) ? true : false; @@ -164,7 +166,8 @@ export class FollowerIndexForm extends PureComponent { getFieldsErrors = (newFields) => { return Object.keys(newFields).reduce((errors, field) => { - const validator = fieldToValidatorMap[field]; + const advancedSettings = getAdvancedSettingsFields(documentationLinks); + const validator = getFieldToValidatorMap(advancedSettings)[field]; const value = newFields[field]; if (validator) { @@ -278,17 +281,20 @@ export class FollowerIndexForm extends PureComponent { } // Clear the advanced settings form. - this.onFieldsChange(emptyAdvancedSettings); + this.onFieldsChange(getEmptyAdvancedSettings(documentationLinks)); // Save a cache of the advanced settings. const fields = this.getFields(); - this.cachedAdvancedSettings = advancedSettingsFields.reduce((cache, { field }) => { - const value = fields[field]; - if (value !== '') { - cache[field] = value; - } - return cache; - }, {}); + this.cachedAdvancedSettings = getAdvancedSettingsFields(documentationLinks).reduce( + (cache, { field }) => { + const value = fields[field]; + if (value !== '') { + cache[field] = value; + } + return cache; + }, + {} + ); // Hide the advanced settings. this.setState({ @@ -614,7 +620,7 @@ export class FollowerIndexForm extends PureComponent { {areAdvancedSettingsVisible && ( - {advancedSettingsFields.map((advancedSetting) => { + {getAdvancedSettingsFields(documentationLinks).map((advancedSetting) => { const { field, testSubject, diff --git a/x-pack/plugins/cross_cluster_replication/public/app/components/follower_index_page_title.js b/x-pack/plugins/cross_cluster_replication/public/app/components/follower_index_page_title.js index afc8892352132..b5652d3f2b6e6 100644 --- a/x-pack/plugins/cross_cluster_replication/public/app/components/follower_index_page_title.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/components/follower_index_page_title.js @@ -18,7 +18,7 @@ import { EuiTitle, } from '@elastic/eui'; -import { getFollowerIndexUrl } from '../services/documentation_links'; +import { documentationLinks } from '../services/documentation_links'; export const FollowerIndexPageTitle = ({ title }) => ( @@ -36,7 +36,7 @@ export const FollowerIndexPageTitle = ({ title }) => ( { // Import and initialize additional services here instead of in plugin.ts to reduce the size of the // initial bundle as much as possible. initBreadcrumbs(setBreadcrumbs); - initDocumentation(`${ELASTIC_WEBSITE_URL}guide/en/elasticsearch/reference/${DOC_LINK_VERSION}/`); + initDocumentation(docLinks); return renderApp(element, I18nContext, history, getUrlForApp); } diff --git a/x-pack/plugins/cross_cluster_replication/public/app/services/documentation_links.ts b/x-pack/plugins/cross_cluster_replication/public/app/services/documentation_links.ts index 25c92bbcdcad6..65bbfd919f94d 100644 --- a/x-pack/plugins/cross_cluster_replication/public/app/services/documentation_links.ts +++ b/x-pack/plugins/cross_cluster_replication/public/app/services/documentation_links.ts @@ -5,13 +5,10 @@ * 2.0. */ -let _esBase: string; +import type { DocLinksStart } from 'src/core/public'; -export const init = (esBase: string) => { - _esBase = esBase; -}; +export let documentationLinks: DocLinksStart['links']; -export const getAutoFollowPatternUrl = (): string => `${_esBase}/ccr-put-auto-follow-pattern.html`; -export const getFollowerIndexUrl = (): string => `${_esBase}/ccr-put-follow.html`; -export const getByteUnitsUrl = (): string => `${_esBase}/common-options.html#byte-units`; -export const getTimeUnitsUrl = (): string => `${_esBase}/common-options.html#time-units`; +export const init = (docLinks: DocLinksStart) => { + documentationLinks = docLinks.links; +}; diff --git a/x-pack/plugins/cross_cluster_replication/public/plugin.ts b/x-pack/plugins/cross_cluster_replication/public/plugin.ts index 7998cdbdf750b..a45862d46beeb 100644 --- a/x-pack/plugins/cross_cluster_replication/public/plugin.ts +++ b/x-pack/plugins/cross_cluster_replication/public/plugin.ts @@ -48,7 +48,7 @@ export class CrossClusterReplicationPlugin implements Plugin { const { chrome: { docTitle }, i18n: { Context: I18nContext }, - docLinks: { ELASTIC_WEBSITE_URL, DOC_LINK_VERSION }, + docLinks, application: { getUrlForApp }, } = coreStart; @@ -58,8 +58,7 @@ export class CrossClusterReplicationPlugin implements Plugin { element, setBreadcrumbs, I18nContext, - ELASTIC_WEBSITE_URL, - DOC_LINK_VERSION, + docLinks, history, getUrlForApp, }); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_nav.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_nav.tsx index 7d8c1b420378f..530accb501c41 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_nav.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_nav.tsx @@ -124,8 +124,8 @@ export const EngineNav: React.FC = () => { )} {canViewEngineSchema && ( diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_router.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_router.test.tsx index 0b5d778e987c7..3d9c61599cc25 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_router.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_router.test.tsx @@ -23,6 +23,7 @@ import { Documents, DocumentDetail } from '../documents'; import { EngineOverview } from '../engine_overview'; import { RelevanceTuning } from '../relevance_tuning'; import { ResultSettings } from '../result_settings'; +import { SchemaRouter } from '../schema'; import { SearchUI } from '../search_ui'; import { SourceEngines } from '../source_engines'; import { Synonyms } from '../synonyms'; @@ -112,6 +113,13 @@ describe('EngineRouter', () => { expect(wrapper.find(DocumentDetail)).toHaveLength(1); }); + it('renders a schema view', () => { + setMockValues({ ...values, myRole: { canViewEngineSchema: true } }); + const wrapper = shallow(); + + expect(wrapper.find(SchemaRouter)).toHaveLength(1); + }); + it('renders a synonyms view', () => { setMockValues({ ...values, myRole: { canManageEngineSynonyms: true } }); const wrapper = shallow(); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_router.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_router.tsx index 65769446b10db..387f8cf1b9837 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_router.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_router.tsx @@ -22,7 +22,7 @@ import { ENGINE_ANALYTICS_PATH, ENGINE_DOCUMENTS_PATH, ENGINE_DOCUMENT_DETAIL_PATH, - // ENGINE_SCHEMA_PATH, + ENGINE_SCHEMA_PATH, // ENGINE_CRAWLER_PATH, META_ENGINE_SOURCE_ENGINES_PATH, ENGINE_RELEVANCE_TUNING_PATH, @@ -39,6 +39,7 @@ import { DocumentDetail, Documents } from '../documents'; import { EngineOverview } from '../engine_overview'; import { RelevanceTuning } from '../relevance_tuning'; import { ResultSettings } from '../result_settings'; +import { SchemaRouter } from '../schema'; import { SearchUI } from '../search_ui'; import { SourceEngines } from '../source_engines'; import { Synonyms } from '../synonyms'; @@ -50,7 +51,7 @@ export const EngineRouter: React.FC = () => { myRole: { canViewEngineAnalytics, canViewEngineDocuments, - // canViewEngineSchema, + canViewEngineSchema, // canViewEngineCrawler, canViewMetaEngineSourceEngines, canManageEngineRelevanceTuning, @@ -102,6 +103,11 @@ export const EngineRouter: React.FC = () => { )} + {canViewEngineSchema && ( + + + + )} {canManageEngineCurations && ( diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/index.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/index.ts index 0c286914d5c2b..c0e9ae19e075b 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/index.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/index.ts @@ -6,3 +6,4 @@ */ export { SCHEMA_TITLE } from './constants'; +export { SchemaRouter } from './schema_router'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/reindex_job/index.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/reindex_job/index.ts new file mode 100644 index 0000000000000..5ed22298c4862 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/reindex_job/index.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { ReindexJob } from './reindex_job'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/reindex_job/reindex_job.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/reindex_job/reindex_job.test.tsx new file mode 100644 index 0000000000000..9e8386e2e8337 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/reindex_job/reindex_job.test.tsx @@ -0,0 +1,30 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import '../../../../__mocks__/react_router_history.mock'; + +import React from 'react'; +import { useParams } from 'react-router-dom'; + +import { shallow } from 'enzyme'; + +import { ReindexJob } from './'; + +describe('ReindexJob', () => { + const props = { + schemaBreadcrumb: ['Engines', 'some-engine', 'Schema'], + }; + + beforeEach(() => { + (useParams as jest.Mock).mockReturnValueOnce({ reindexJobId: 'abc1234567890' }); + }); + + it('renders', () => { + shallow(); + // TODO: Check child components + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/reindex_job/reindex_job.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/reindex_job/reindex_job.tsx new file mode 100644 index 0000000000000..19da08d446300 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/reindex_job/reindex_job.tsx @@ -0,0 +1,45 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { useParams } from 'react-router-dom'; + +import { EuiPageHeader, EuiPageContentBody } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; + +import { FlashMessages } from '../../../../shared/flash_messages'; +import { SetAppSearchChrome as SetPageChrome } from '../../../../shared/kibana_chrome'; +import { BreadcrumbTrail } from '../../../../shared/kibana_chrome/generate_breadcrumbs'; + +interface Props { + schemaBreadcrumb: BreadcrumbTrail; +} + +export const ReindexJob: React.FC = ({ schemaBreadcrumb }) => { + const { reindexJobId } = useParams() as { reindexJobId: string }; + + return ( + <> + + + + {reindexJobId} + + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/schema_router.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/schema_router.test.tsx new file mode 100644 index 0000000000000..13a94c666509b --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/schema_router.test.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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { setMockValues, rerender } from '../../../__mocks__'; +import '../../__mocks__/engine_logic.mock'; + +import React from 'react'; +import { Route, Switch } from 'react-router-dom'; + +import { shallow } from 'enzyme'; + +import { ReindexJob } from './reindex_job'; +import { Schema, MetaEngineSchema } from './views'; + +import { SchemaRouter } from './'; + +describe('SchemaRouter', () => { + const wrapper = shallow(); + + it('renders', () => { + expect(wrapper.find(Switch)).toHaveLength(1); + expect(wrapper.find(Route)).toHaveLength(2); + }); + + it('renders the ReindexJob route', () => { + expect(wrapper.find(ReindexJob)).toHaveLength(1); + }); + + it('renders the MetaEngineSchema view if the current engine is a meta engine', () => { + setMockValues({ isMetaEngine: true }); + rerender(wrapper); + + expect(wrapper.find(MetaEngineSchema)).toHaveLength(1); + expect(wrapper.find(Schema)).toHaveLength(0); + }); + + it('renders the default Schema view if the current engine is not a meta engine', () => { + setMockValues({ isMetaEngine: false }); + rerender(wrapper); + + expect(wrapper.find(Schema)).toHaveLength(1); + expect(wrapper.find(MetaEngineSchema)).toHaveLength(0); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/schema_router.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/schema_router.tsx new file mode 100644 index 0000000000000..bfa346fee468b --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/schema_router.tsx @@ -0,0 +1,36 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { Route, Switch } from 'react-router-dom'; + +import { useValues } from 'kea'; + +import { SetAppSearchChrome as SetPageChrome } from '../../../shared/kibana_chrome'; +import { ENGINE_REINDEX_JOB_PATH } from '../../routes'; +import { EngineLogic, getEngineBreadcrumbs } from '../engine'; + +import { SCHEMA_TITLE } from './constants'; +import { ReindexJob } from './reindex_job'; +import { Schema, MetaEngineSchema } from './views'; + +export const SchemaRouter: React.FC = () => { + const { isMetaEngine } = useValues(EngineLogic); + const schemaBreadcrumb = getEngineBreadcrumbs([SCHEMA_TITLE]); + + return ( + + + + + + + {isMetaEngine ? : } + + + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/views/index.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/views/index.ts new file mode 100644 index 0000000000000..24f8edd856e48 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/views/index.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { Schema } from './schema'; +export { MetaEngineSchema } from './meta_engine_schema'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/views/meta_engine_schema.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/views/meta_engine_schema.test.tsx new file mode 100644 index 0000000000000..8412af6455285 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/views/meta_engine_schema.test.tsx @@ -0,0 +1,25 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; + +import { shallow } from 'enzyme'; + +import { MetaEngineSchema } from './'; + +describe('MetaEngineSchema', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('renders', () => { + const wrapper = shallow(); + + expect(wrapper.isEmptyRender()).toBe(false); + // TODO: Check for schema components + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/views/meta_engine_schema.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/views/meta_engine_schema.tsx new file mode 100644 index 0000000000000..d79ddae3d9b78 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/views/meta_engine_schema.tsx @@ -0,0 +1,32 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; + +import { EuiPageHeader, EuiPageContentBody } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; + +import { FlashMessages } from '../../../../shared/flash_messages'; + +export const MetaEngineSchema: React.FC = () => { + return ( + <> + + + TODO + + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/views/schema.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/views/schema.test.tsx new file mode 100644 index 0000000000000..5b6367d9ce668 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/views/schema.test.tsx @@ -0,0 +1,38 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; + +import { shallow } from 'enzyme'; + +import { EuiPageHeader, EuiButton } from '@elastic/eui'; + +import { Schema } from './'; + +describe('Schema', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('renders', () => { + const wrapper = shallow(); + + expect(wrapper.isEmptyRender()).toBe(false); + // TODO: Check for schema components + }); + + it('renders page action buttons', () => { + const wrapper = shallow() + .find(EuiPageHeader) + .dive() + .children() + .dive(); + + expect(wrapper.find(EuiButton)).toHaveLength(2); + // TODO: Expect click actions + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/views/schema.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/views/schema.tsx new file mode 100644 index 0000000000000..ad53fd2c718b2 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/views/schema.tsx @@ -0,0 +1,45 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; + +import { EuiPageHeader, EuiButton, EuiPageContentBody } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; + +import { FlashMessages } from '../../../../shared/flash_messages'; + +export const Schema: React.FC = () => { + return ( + <> + + {i18n.translate( + 'xpack.enterpriseSearch.appSearch.engine.schema.updateSchemaButtonLabel', + { defaultMessage: 'Update types' } + )} + , + + {i18n.translate( + 'xpack.enterpriseSearch.appSearch.engine.schema.createSchemaFieldButtonLabel', + { defaultMessage: 'Create a schema field' } + )} + , + ]} + /> + + TODO + + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/routes.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/routes.ts index 727312801c610..872db3e149b60 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/routes.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/routes.ts @@ -35,8 +35,8 @@ export const ENGINE_ANALYTICS_QUERY_DETAIL_PATH = `${ENGINE_ANALYTICS_QUERY_DETA export const ENGINE_DOCUMENTS_PATH = `${ENGINE_PATH}/documents`; export const ENGINE_DOCUMENT_DETAIL_PATH = `${ENGINE_DOCUMENTS_PATH}/:documentId`; -export const ENGINE_SCHEMA_PATH = `${ENGINE_PATH}/schema/edit`; -export const ENGINE_REINDEX_JOB_PATH = `${ENGINE_PATH}/reindex-job/:activeReindexJobId`; +export const ENGINE_SCHEMA_PATH = `${ENGINE_PATH}/schema`; +export const ENGINE_REINDEX_JOB_PATH = `${ENGINE_SCHEMA_PATH}/reindex_job/:reindexJobId`; export const ENGINE_CRAWLER_PATH = `${ENGINE_PATH}/crawler`; // TODO: Crawler sub-pages diff --git a/x-pack/plugins/security/common/constants.ts b/x-pack/plugins/security/common/constants.ts index ef83230fc2aba..0ff04e4f731d0 100644 --- a/x-pack/plugins/security/common/constants.ts +++ b/x-pack/plugins/security/common/constants.ts @@ -17,6 +17,12 @@ export const UNKNOWN_SPACE = '?'; export const GLOBAL_RESOURCE = '*'; export const APPLICATION_PREFIX = 'kibana-'; + +/** + * Reserved application privileges are always assigned to this "wildcard" application. + * This allows them to be applied to any Kibana "tenant" (`kibana.index`). Since reserved privileges are always assigned to reserved (built-in) roles, + * it's not possible to know the tenant ahead of time. + */ export const RESERVED_PRIVILEGES_APPLICATION_WILDCARD = 'kibana-*'; /** diff --git a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/privilege_space_table.test.tsx b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/privilege_space_table.test.tsx index 4c657294c965c..6f00df3a4ee7b 100644 --- a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/privilege_space_table.test.tsx +++ b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/privilege_space_table.test.tsx @@ -742,6 +742,21 @@ describe('global base read', () => { }); }); +describe('global and reserved', () => { + it('base all, reserved_foo', () => { + const props = buildProps([ + { spaces: ['*'], base: ['all'], feature: {} }, + { spaces: ['*'], base: [], feature: {}, _reserved: ['foo'] }, + ]); + const component = mountWithIntl(); + const actualTable = getTableFromComponent(component); + expect(actualTable).toEqual([ + { spaces: ['*'], privileges: { summary: 'Foo', overridden: false } }, + { spaces: ['*'], privileges: { summary: 'All', overridden: false } }, + ]); + }); +}); + describe('global normal feature privilege all', () => { describe('default and marketing space', () => { it('base all', () => { diff --git a/x-pack/plugins/security/server/routes/authorization/roles/get.test.ts b/x-pack/plugins/security/server/routes/authorization/roles/get.test.ts index 24366a250cf11..d2385adc99162 100644 --- a/x-pack/plugins/security/server/routes/authorization/roles/get.test.ts +++ b/x-pack/plugins/security/server/routes/authorization/roles/get.test.ts @@ -285,7 +285,7 @@ describe('GET role', () => { indices: [], applications: [ { - application, + application: reservedPrivilegesApplicationWildcard, privileges: ['reserved_customApplication1', 'reserved_customApplication2'], resources: ['*'], }, diff --git a/x-pack/plugins/security/server/routes/authorization/roles/get_all.test.ts b/x-pack/plugins/security/server/routes/authorization/roles/get_all.test.ts index d490153b30394..09262d7cbbadd 100644 --- a/x-pack/plugins/security/server/routes/authorization/roles/get_all.test.ts +++ b/x-pack/plugins/security/server/routes/authorization/roles/get_all.test.ts @@ -283,7 +283,7 @@ describe('GET all roles', () => { indices: [], applications: [ { - application, + application: reservedPrivilegesApplicationWildcard, privileges: ['reserved_customApplication1', 'reserved_customApplication2'], resources: ['*'], }, @@ -1030,7 +1030,7 @@ describe('GET all roles', () => { ); getRolesTest( - `reserved privilege assigned with a feature privilege returns empty kibana section with _transform_error set to ['kibana']`, + `reserved privilege assigned with a feature privilege returns populated kibana section`, { apiResponse: async () => ({ first_role: { @@ -1039,7 +1039,12 @@ describe('GET all roles', () => { applications: [ { application, - privileges: ['reserved_foo', 'feature_foo.foo-privilege-1'], + privileges: ['feature_foo.foo-privilege-1'], + resources: ['*'], + }, + { + application: reservedPrivilegesApplicationWildcard, + privileges: ['reserved_foo'], resources: ['*'], }, ], @@ -1068,8 +1073,22 @@ describe('GET all roles', () => { indices: [], run_as: [], }, - kibana: [], - _transform_error: ['kibana'], + kibana: [ + { + base: [], + feature: { + foo: ['foo-privilege-1'], + }, + spaces: ['*'], + }, + { + base: [], + feature: {}, + _reserved: ['foo'], + spaces: ['*'], + }, + ], + _transform_error: [], _unrecognized_applications: [], }, ], diff --git a/x-pack/plugins/security/server/routes/authorization/roles/model/elasticsearch_role.ts b/x-pack/plugins/security/server/routes/authorization/roles/model/elasticsearch_role.ts index 74a035cdd0cb6..fa119ca704753 100644 --- a/x-pack/plugins/security/server/routes/authorization/roles/model/elasticsearch_role.ts +++ b/x-pack/plugins/security/server/routes/authorization/roles/model/elasticsearch_role.ts @@ -83,13 +83,13 @@ function transformRoleApplicationsToKibanaPrivileges( }; } - // if space privilege assigned globally, we can't transform these + // if there is a reserved privilege assigned to an application other than the reserved privileges application wildcard, we won't transform these. if ( roleKibanaApplications.some( (entry) => - entry.resources.includes(GLOBAL_RESOURCE) && + entry.application !== RESERVED_PRIVILEGES_APPLICATION_WILDCARD && entry.privileges.some((privilege) => - PrivilegeSerializer.isSerializedSpaceBasePrivilege(privilege) + PrivilegeSerializer.isSerializedReservedPrivilege(privilege) ) ) ) { @@ -98,15 +98,13 @@ function transformRoleApplicationsToKibanaPrivileges( }; } - // if global base or reserved privilege assigned at a space, we can't transform these + // if space privilege assigned globally, we can't transform these if ( roleKibanaApplications.some( (entry) => - !entry.resources.includes(GLOBAL_RESOURCE) && - entry.privileges.some( - (privilege) => - PrivilegeSerializer.isSerializedGlobalBasePrivilege(privilege) || - PrivilegeSerializer.isSerializedReservedPrivilege(privilege) + entry.resources.includes(GLOBAL_RESOURCE) && + entry.privileges.some((privilege) => + PrivilegeSerializer.isSerializedSpaceBasePrivilege(privilege) ) ) ) { @@ -115,15 +113,15 @@ function transformRoleApplicationsToKibanaPrivileges( }; } - // if reserved privilege assigned with feature or base privileges, we won't transform these + // if global base or reserved privilege assigned at a space, we can't transform these if ( roleKibanaApplications.some( (entry) => - entry.privileges.some((privilege) => - PrivilegeSerializer.isSerializedReservedPrivilege(privilege) - ) && + !entry.resources.includes(GLOBAL_RESOURCE) && entry.privileges.some( - (privilege) => !PrivilegeSerializer.isSerializedReservedPrivilege(privilege) + (privilege) => + PrivilegeSerializer.isSerializedGlobalBasePrivilege(privilege) || + PrivilegeSerializer.isSerializedReservedPrivilege(privilege) ) ) ) { @@ -163,7 +161,10 @@ function transformRoleApplicationsToKibanaPrivileges( }; } - const allResources = roleKibanaApplications.map((entry) => entry.resources).flat(); + const allResources = roleKibanaApplications + .filter((entry) => entry.application !== RESERVED_PRIVILEGES_APPLICATION_WILDCARD) + .flatMap((entry) => entry.resources); + // if we have improperly formatted resource entries, we can't transform these if ( allResources.some( diff --git a/x-pack/plugins/snapshot_restore/public/application/sections/home/policy_list/policy_details/tabs/tab_summary.tsx b/x-pack/plugins/snapshot_restore/public/application/sections/home/policy_list/policy_details/tabs/tab_summary.tsx index 50cb13ea74e92..10dd430295319 100644 --- a/x-pack/plugins/snapshot_restore/public/application/sections/home/policy_list/policy_details/tabs/tab_summary.tsx +++ b/x-pack/plugins/snapshot_restore/public/application/sections/home/policy_list/policy_details/tabs/tab_summary.tsx @@ -77,7 +77,7 @@ export const TabSummary: React.FunctionComponent = ({ policy }) => { {/** Stats panel */} {stats && ( - + = ({ const renderRetentionPanel = (cronSchedule: string) => ( <> - + diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index a6187937cbf6d..8dc3227e74e1c 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -22163,8 +22163,6 @@ "xpack.upgradeAssistant.checkupTab.reindexing.reindexButton.loadingLabel": "読み込み中…", "xpack.upgradeAssistant.checkupTab.reindexing.reindexButton.pausedLabel": "一時停止中", "xpack.upgradeAssistant.checkupTab.reindexing.reindexButton.reindexLabel": "再インデックス", - "xpack.upgradeAssistant.overviewTab.steps.deprecationLogsStep.enableDeprecationLoggingToggleSwitch.enabledLabel": "オン", - "xpack.upgradeAssistant.overviewTab.steps.deprecationLogsStep.enableDeprecationLoggingToggleSwitch.errorLabel": "ログステータスを読み込めませんでした", "xpack.upgradeAssistant.reindex.reindexPrivilegesErrorBatch": "「{indexName}」に再インデックスするための権限が不十分です。", "xpack.upgradeAssistant.tabs.incompleteCallout.calloutBody.breackingChangesDocButtonLabel": "廃止と互換性を破る変更", "xpack.upgradeAssistant.tabs.incompleteCallout.calloutBody.calloutDetail": "Elasticsearch {nextEsVersion} の {breakingChangesDocButton} の完全なリストは、最終の {currentEsVersion} マイナーリリースで確認できます。この警告は、リストがすべて解決されると消えます。", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 6a452f25f0283..7ea3ed2e1bf2c 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -22515,8 +22515,6 @@ "xpack.upgradeAssistant.checkupTab.reindexing.reindexButton.loadingLabel": "正在加载……", "xpack.upgradeAssistant.checkupTab.reindexing.reindexButton.pausedLabel": "已暂停", "xpack.upgradeAssistant.checkupTab.reindexing.reindexButton.reindexLabel": "重新索引", - "xpack.upgradeAssistant.overviewTab.steps.deprecationLogsStep.enableDeprecationLoggingToggleSwitch.enabledLabel": "开启", - "xpack.upgradeAssistant.overviewTab.steps.deprecationLogsStep.enableDeprecationLoggingToggleSwitch.errorLabel": "无法加载日志状态", "xpack.upgradeAssistant.reindex.reindexPrivilegesErrorBatch": "您没有足够的权限重新索引“{indexName}”。", "xpack.upgradeAssistant.tabs.incompleteCallout.calloutBody.breackingChangesDocButtonLabel": "弃用内容和重大更改", "xpack.upgradeAssistant.tabs.incompleteCallout.calloutBody.calloutDetail": "Elasticsearch {nextEsVersion} 中的 {breakingChangesDocButton} 完整列表将在最终的 {currentEsVersion} 次要版本中提供。完成列表后,此警告将消失。", diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/overview/deprecation_logging_toggle.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/overview/deprecation_logging_toggle.tsx index 6be7793f0bd4a..ab2f94ee58240 100644 --- a/x-pack/plugins/upgrade_assistant/public/application/components/overview/deprecation_logging_toggle.tsx +++ b/x-pack/plugins/upgrade_assistant/public/application/components/overview/deprecation_logging_toggle.tsx @@ -7,104 +7,195 @@ import React, { useEffect, useState } from 'react'; -import { EuiLoadingSpinner, EuiSwitch } from '@elastic/eui'; +import { + EuiButton, + EuiFlexItem, + EuiFlexGroup, + EuiText, + EuiTextColor, + EuiButtonEmpty, +} from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { useAppContext } from '../../app_context'; import { ResponseError } from '../../lib/api'; const i18nTexts = { - toggleErrorLabel: i18n.translate( - 'xpack.upgradeAssistant.overviewTab.steps.deprecationLogsStep.enableDeprecationLoggingToggleSwitch.errorLabel', + fetchErrorMessage: i18n.translate( + 'xpack.upgradeAssistant.overview.deprecationLogs.fetchErrorMessage', { - defaultMessage: 'Could not load logging state', + defaultMessage: 'Could not retrieve logging information.', } ), - toggleLabel: i18n.translate( - 'xpack.upgradeAssistant.overviewTab.steps.deprecationLogsStep.enableDeprecationLoggingToggleSwitch.enabledLabel', + reloadButtonLabel: i18n.translate( + 'xpack.upgradeAssistant.overview.deprecationLogs.reloadButtonLabel', { - defaultMessage: 'Enable deprecation logging', + defaultMessage: 'Try again', + } + ), + updateErrorMessage: i18n.translate( + 'xpack.upgradeAssistant.overview.deprecationLogs.updateErrorMessage', + { + defaultMessage: 'Could not update logging state.', } ), enabledMessage: i18n.translate( - 'xpack.upgradeAssistant.overviewTab.steps.deprecationLogsStep.enableDeprecationLoggingToggleSwitch.enabledToastMessage', + 'xpack.upgradeAssistant.overview.deprecationLogs.enabledToastMessage', { defaultMessage: 'Log deprecated actions.', } ), disabledMessage: i18n.translate( - 'xpack.upgradeAssistant.overviewTab.steps.deprecationLogsStep.enableDeprecationLoggingToggleSwitch.disabledToastMessage', + 'xpack.upgradeAssistant.overview.deprecationLogs.disabledToastMessage', { defaultMessage: 'Do not log deprecated actions.', } ), + fetchButtonLabel: i18n.translate( + 'xpack.upgradeAssistant.overview.deprecationLogging.loadingLabel', + { + defaultMessage: 'Retrieving logging state', + } + ), + enablingButtonLabel: i18n.translate( + 'xpack.upgradeAssistant.overview.deprecationLogs.enablingButtonLabel', + { + defaultMessage: 'Enabling deprecation logging', + } + ), + disablingButtonLabel: i18n.translate( + 'xpack.upgradeAssistant.overview.deprecationLogs.disablingButtonLabel', + { + defaultMessage: 'Disabling deprecation logging', + } + ), + enableButtonLabel: i18n.translate( + 'xpack.upgradeAssistant.overview.deprecationLogs.enableButtonLabel', + { + defaultMessage: 'Enable deprecation logging', + } + ), + disableButtonLabel: i18n.translate( + 'xpack.upgradeAssistant.overview.deprecationLogs.disableButtonLabel', + { + defaultMessage: 'Disable deprecation logging', + } + ), + fetchErrorButtonLabel: i18n.translate( + 'xpack.upgradeAssistant.overview.deprecationLogs.fetchErrorButtonLabel', + { + defaultMessage: 'Deprecation logging unavailable', + } + ), }; export const DeprecationLoggingToggle: React.FunctionComponent = () => { const { api, notifications } = useAppContext(); - const [isEnabled, setIsEnabled] = useState(true); - const [isLoading, setIsLoading] = useState(false); - const [error, setError] = useState(undefined); - - useEffect(() => { - async function getDeprecationLoggingStatus() { - setIsLoading(true); - - const { data, error: responseError } = await api.getDeprecationLogging(); + const { data, error: fetchError, isLoading, resendRequest } = api.useLoadDeprecationLogging(); - setIsLoading(false); + const [isEnabled, setIsEnabled] = useState(undefined); + const [isUpdating, setIsUpdating] = useState(false); + const [updateError, setUpdateError] = useState(undefined); - if (responseError) { - setError(responseError); - } else if (data) { - setIsEnabled(data.isEnabled); - } + const getButtonLabel = () => { + if (isLoading) { + return i18nTexts.fetchButtonLabel; } - getDeprecationLoggingStatus(); - }, [api]); + if (isUpdating) { + return isEnabled ? i18nTexts.disablingButtonLabel : i18nTexts.enablingButtonLabel; + } - if (isLoading) { - return ; - } + if (fetchError) { + return i18nTexts.fetchErrorButtonLabel; + } - const renderLoggingState = () => { - if (error) { - return i18nTexts.toggleErrorLabel; + if (isEnabled) { + return i18nTexts.disableButtonLabel; } - return i18nTexts.toggleLabel; + return i18nTexts.enableButtonLabel; }; + useEffect(() => { + if (isLoading === false && data) { + setIsEnabled(data.isEnabled); + } + }, [data, isLoading]); + const toggleLogging = async () => { const newIsEnabledValue = !isEnabled; - setIsLoading(true); + setIsUpdating(true); - const { data, error: updateError } = await api.updateDeprecationLogging({ + const { + data: updatedLoggingState, + error: updateDeprecationError, + } = await api.updateDeprecationLogging({ isEnabled: newIsEnabledValue, }); - setIsLoading(false); + setIsUpdating(false); - if (updateError) { - setError(updateError); - } else if (data) { - setIsEnabled(data.isEnabled); + if (updateDeprecationError) { + setUpdateError(updateDeprecationError); + } else if (updatedLoggingState) { + setIsEnabled(updatedLoggingState.isEnabled); notifications.toasts.addSuccess( - data.isEnabled ? i18nTexts.enabledMessage : i18nTexts.disabledMessage + updatedLoggingState.isEnabled ? i18nTexts.enabledMessage : i18nTexts.disabledMessage ); } }; return ( - + + + + {getButtonLabel()} + + + + {fetchError && ( + + +

+ {i18nTexts.fetchErrorMessage} + {fetchError.statusCode && fetchError.message && ( + <> + {' '} + {`${fetchError.statusCode}: ${fetchError.message}`} + + )}{' '} + + {i18nTexts.reloadButtonLabel} + +

+
+
+ )} + + {updateError && ( + + +

+ {i18nTexts.updateErrorMessage} + {updateError.statusCode && updateError.message && ( + <> + {' '} + {`${updateError.statusCode}: ${updateError.message}`} + + )} +

+
+
+ )} +
); }; diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/overview/overview.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/overview/overview.tsx index b346d918f212a..6b3048b669aa2 100644 --- a/x-pack/plugins/upgrade_assistant/public/application/components/overview/overview.tsx +++ b/x-pack/plugins/upgrade_assistant/public/application/components/overview/overview.tsx @@ -17,8 +17,8 @@ import { EuiFlexItem, EuiFlexGroup, EuiSpacer, + EuiTitle, EuiLink, - EuiFormRow, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; @@ -31,35 +31,41 @@ import { KibanaDeprecationStats } from './kibana_stats'; import { DeprecationLoggingToggle } from './deprecation_logging_toggle'; const i18nTexts = { - pageTitle: i18n.translate('xpack.upgradeAssistant.pageTitle', { + pageTitle: i18n.translate('xpack.upgradeAssistant.overview.pageTitle', { defaultMessage: 'Upgrade Assistant', }), - getPageDescription: (nextMajor: string) => - i18n.translate('xpack.upgradeAssistant.pageDescription', { - defaultMessage: - 'Prepare to upgrade by identifying deprecated settings and updating your configuration. Enable deprecation logging to see if your are using deprecated features that will not be available after you upgrade to Elastic {nextMajor}.', - values: { - nextMajor, - }, - }), - getDeprecationLoggingLabel: (href: string) => ( + pageDescription: i18n.translate('xpack.upgradeAssistant.overview.pageDescription', { + defaultMessage: + 'Prepare to upgrade by identifying deprecated settings and updating your configuration.', + }), + docLink: i18n.translate('xpack.upgradeAssistant.overview.documentationLinkText', { + defaultMessage: 'Documentation', + }), + deprecationLoggingTitle: i18n.translate( + 'xpack.upgradeAssistant.overview.deprecationLoggingTitle', + { + defaultMessage: 'Deprecation logs', + } + ), + getDeprecationLoggingDescription: (nextMajor: string, href: string) => ( - {i18n.translate('xpack.upgradeAssistant.deprecationLoggingDescription.learnMoreLink', { - defaultMessage: 'Learn more.', - })} + {i18n.translate( + 'xpack.upgradeAssistant.deprecationLoggingDescription.deprecationLoggingLink', + { + defaultMessage: 'deprecation logging', + } + )} ), }} /> ), - docLink: i18n.translate('xpack.upgradeAssistant.documentationLinkText', { - defaultMessage: 'Documentation', - }), }; interface Props { @@ -104,7 +110,7 @@ export const DeprecationsOverview: FunctionComponent = ({ history }) => { <> -

{i18nTexts.getPageDescription(`${nextMajor}.x`)}

+

{i18nTexts.pageDescription}

@@ -114,6 +120,7 @@ export const DeprecationsOverview: FunctionComponent = ({ history }) => { + {/* Deprecation stats */} @@ -126,14 +133,27 @@ export const DeprecationsOverview: FunctionComponent = ({ history }) => { - - - + {/* Deprecation logging */} + + + +

{i18nTexts.deprecationLoggingTitle}

+
+ + +

+ {i18nTexts.getDeprecationLoggingDescription( + `${nextMajor}.x`, + docLinks.links.elasticsearch.deprecationLogging + )} +

+
+ + + + +
+
diff --git a/x-pack/plugins/upgrade_assistant/public/application/lib/api.ts b/x-pack/plugins/upgrade_assistant/public/application/lib/api.ts index 1bb8be62f1700..1c42c249e9d54 100644 --- a/x-pack/plugins/upgrade_assistant/public/application/lib/api.ts +++ b/x-pack/plugins/upgrade_assistant/public/application/lib/api.ts @@ -62,12 +62,11 @@ export class ApiService { return result; } - public async getDeprecationLogging() { - const result = await this.sendRequest<{ isEnabled: boolean }>({ + public useLoadDeprecationLogging() { + return this.useRequest<{ isEnabled: boolean }>({ path: `${API_BASE_PATH}/deprecation_logging`, method: 'get', }); - return result; } public async updateDeprecationLogging(loggingData: { isEnabled: boolean }) { diff --git a/x-pack/plugins/upgrade_assistant/tests_client_integration/helpers/overview.helpers.ts b/x-pack/plugins/upgrade_assistant/tests_client_integration/helpers/overview.helpers.ts index 52346c94ef46b..9e4b2eae483ea 100644 --- a/x-pack/plugins/upgrade_assistant/tests_client_integration/helpers/overview.helpers.ts +++ b/x-pack/plugins/upgrade_assistant/tests_client_integration/helpers/overview.helpers.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { act } from 'react-dom/test-utils'; import { registerTestBed, TestBed, TestBedConfig } from '@kbn/test/jest'; import { DeprecationsOverview } from '../../public/application/components/overview'; import { WithAppDependencies } from './setup_environment'; @@ -17,7 +18,29 @@ const testBedConfig: TestBedConfig = { doMountAsync: true, }; -export type OverviewTestBed = TestBed; +export type OverviewTestBed = TestBed & { + actions: ReturnType; +}; + +const createActions = (testBed: TestBed) => { + /** + * User Actions + */ + + const clickDeprecationToggle = async () => { + const { find, component } = testBed; + + await act(async () => { + find('upgradeAssistantDeprecationToggle').simulate('click'); + }); + + component.update(); + }; + + return { + clickDeprecationToggle, + }; +}; export const setup = async (overrides?: Record): Promise => { const initTestBed = registerTestBed( @@ -26,7 +49,10 @@ export const setup = async (overrides?: Record): Promise { describe('Deprecation logging', () => { test('toggles deprecation logging', async () => { - const { form, find, component } = testBed; + const { find, actions } = testBed; httpRequestsMockHelpers.setUpdateDeprecationLoggingResponse({ isEnabled: false }); - expect(find('upgradeAssistantDeprecationToggle').props()['aria-checked']).toBe(true); - expect(find('upgradeAssistantDeprecationToggle').props().disabled).toBe(false); - - await act(async () => { - form.toggleEuiSwitch('upgradeAssistantDeprecationToggle'); - }); + expect(find('upgradeAssistantDeprecationToggle').text()).toEqual( + 'Disable deprecation logging' + ); - component.update(); + await actions.clickDeprecationToggle(); - expect(find('upgradeAssistantDeprecationToggle').props()['aria-checked']).toBe(false); - expect(find('upgradeAssistantDeprecationToggle').props().disabled).toBe(false); + const latestRequest = server.requests[server.requests.length - 1]; + expect(JSON.parse(JSON.parse(latestRequest.requestBody).body)).toEqual({ isEnabled: false }); + expect(find('upgradeAssistantDeprecationToggle').text()).toEqual( + 'Enable deprecation logging' + ); }); - test('handles network error', async () => { + test('handles network error when updating logging state', async () => { const error = { statusCode: 500, error: 'Internal server error', message: 'Internal server error', }; - const { form, find, component } = testBed; + const { actions, find, exists } = testBed; httpRequestsMockHelpers.setUpdateDeprecationLoggingResponse(undefined, error); - expect(find('upgradeAssistantDeprecationToggle').props()['aria-checked']).toBe(true); - expect(find('upgradeAssistantDeprecationToggle').props().disabled).toBe(false); - expect(find('deprecationLoggingFormRow').find('.euiSwitch__label').text()).toContain( - 'Enable deprecation logging' + expect(find('upgradeAssistantDeprecationToggle').text()).toEqual( + 'Disable deprecation logging' + ); + + await actions.clickDeprecationToggle(); + + // Logging state should not change since there was an error + expect(find('upgradeAssistantDeprecationToggle').text()).toEqual( + 'Disable deprecation logging' ); + expect(exists('updateLoggingError')).toBe(true); + }); + + test('handles network error when fetching logging state', async () => { + const error = { + statusCode: 500, + error: 'Internal server error', + message: 'Internal server error', + }; + + httpRequestsMockHelpers.setLoadDeprecationLoggingResponse(undefined, error); await act(async () => { - form.toggleEuiSwitch('upgradeAssistantDeprecationToggle'); + testBed = await setupOverviewPage(); }); + const { component, exists, find } = testBed; + component.update(); - expect(find('upgradeAssistantDeprecationToggle').props()['aria-checked']).toBe(true); - expect(find('upgradeAssistantDeprecationToggle').props().disabled).toBe(true); - expect(find('deprecationLoggingFormRow').find('.euiSwitch__label').text()).toContain( - 'Could not load logging state' + expect(find('upgradeAssistantDeprecationToggle').text()).toEqual( + 'Deprecation logging unavailable' ); + expect(exists('fetchLoggingError')).toBe(true); }); });