diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index bafa023cf3f35..de323128afed1 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -188,6 +188,7 @@ /src/core/ @elastic/kibana-core /src/plugins/saved_objects_tagging_oss @elastic/kibana-core /config/kibana.yml @elastic/kibana-core +/x-pack/plugins/banners/ @elastic/kibana-core /x-pack/plugins/features/ @elastic/kibana-core /x-pack/plugins/licensing/ @elastic/kibana-core /x-pack/plugins/global_search/ @elastic/kibana-core diff --git a/Jenkinsfile b/Jenkinsfile index 4c8f126b4883b..db5ae306e6e2e 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -6,7 +6,7 @@ kibanaLibrary.load() kibanaPipeline(timeoutMinutes: 210, checkPrChanges: true, setCommitStatus: true) { slackNotifications.onFailure(disabled: !params.NOTIFY_ON_FAILURE) { githubPr.withDefaultPrComments { - ciStats.trackBuild(requireSuccess: githubPr.isPr()) { + ciStats.trackBuild(requireSuccess: githubPr.isTrackedBranchPr()) { catchError { retryable.enable() kibanaPipeline.allCiTasks() diff --git a/STYLEGUIDE.mdx b/STYLEGUIDE.mdx index 628a7d157dfcc..afe00476640b3 100644 --- a/STYLEGUIDE.mdx +++ b/STYLEGUIDE.mdx @@ -1,7 +1,7 @@ --- id: kibStyleGuide slug: /kibana-dev-docs/styleguide -title: StyleGuide +title: Style Guide summary: JavaScript/TypeScript styleguide. date: 2021-05-06 tags: ['kibana', 'onboarding', 'dev', 'styleguide', 'typescript', 'javascript'] @@ -680,7 +680,7 @@ Using `react-component` means adding a bunch of components into angular, while ` ### Action function names and prop function names -Name action functions in the form of a strong verb and passed properties in the form of on. E.g: +Name action functions in the form of a strong verb and passed properties in the form of `on`. E.g: ```jsx diff --git a/WORKSPACE.bazel b/WORKSPACE.bazel index d334d7979ed59..60af851ff2dc6 100644 --- a/WORKSPACE.bazel +++ b/WORKSPACE.bazel @@ -10,15 +10,15 @@ load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") # Fetch Node.js rules http_archive( name = "build_bazel_rules_nodejs", - sha256 = "65067dcad93a61deb593be7d3d9a32a4577d09665536d8da536d731da5cd15e2", - urls = ["https://github.com/bazelbuild/rules_nodejs/releases/download/3.4.2/rules_nodejs-3.4.2.tar.gz"], + sha256 = "10f534e1c80f795cffe1f2822becd4897754d18564612510c59b3c73544ae7c6", + urls = ["https://github.com/bazelbuild/rules_nodejs/releases/download/3.5.0/rules_nodejs-3.5.0.tar.gz"], ) # Now that we have the rules let's import from them to complete the work load("@build_bazel_rules_nodejs//:index.bzl", "check_rules_nodejs_version", "node_repositories", "yarn_install") # Assure we have at least a given rules_nodejs version -check_rules_nodejs_version(minimum_version_string = "3.4.2") +check_rules_nodejs_version(minimum_version_string = "3.5.0") # Setup the Node.js toolchain for the architectures we want to support # diff --git a/api_docs/spaces_oss.json b/api_docs/spaces_oss.json index bce557db8e516..a0ed4297ddc39 100644 --- a/api_docs/spaces_oss.json +++ b/api_docs/spaces_oss.json @@ -577,15 +577,11 @@ "deprecated": false, "children": [ { - "parentPluginId": "spacesOss", - "id": "def-public.SpacesApi.activeSpace$", - "type": "Object", - "tags": [], - "label": "activeSpace$", - "description": [ - "\nObservable representing the currently active space.\nThe details of the space can change without a full page reload (such as display name, color, etc.)" - ], + "id": "def-public.SpacesApi.getActiveSpace$", + "type": "Function", + "label": "getActiveSpace$", "signature": [ + "() => ", "Observable", "<", { @@ -597,11 +593,16 @@ }, ">" ], + "description": [ + "\nObservable representing the currently active space.\nThe details of the space can change without a full page reload (such as display name, color, etc.)" + ], + "children": [], + "tags": [], + "returnComment": [], "source": { "path": "src/plugins/spaces_oss/public/api.ts", "lineNumber": 22 - }, - "deprecated": false + } }, { "parentPluginId": "spacesOss", diff --git a/docs/developer/contributing/development-functional-tests.asciidoc b/docs/developer/contributing/development-functional-tests.asciidoc index f149e9de7aaba..110704a8e569a 100644 --- a/docs/developer/contributing/development-functional-tests.asciidoc +++ b/docs/developer/contributing/development-functional-tests.asciidoc @@ -140,7 +140,7 @@ export default function (/* { providerAPI } */) { ----------- **Services**::: -Services are named singleton values produced by a Service Provider. Tests and other services can retrieve service instances by asking for them by name. All functionality except the mocha API is exposed via services.\ +Services are named singleton values produced by a Service Provider. Tests and other services can retrieve service instances by asking for them by name. All functionality except the mocha API is exposed via services. **Page objects**::: Page objects are a special type of service that encapsulate behaviors common to a particular page or plugin. When you write your own plugin, you’ll likely want to add a page object (or several) that describes the common interactions your tests need to execute. diff --git a/docs/developer/getting-started/monorepo-packages.asciidoc b/docs/developer/getting-started/monorepo-packages.asciidoc index cba5b9bfadf98..3d6fd6c98dd95 100644 --- a/docs/developer/getting-started/monorepo-packages.asciidoc +++ b/docs/developer/getting-started/monorepo-packages.asciidoc @@ -84,6 +84,7 @@ yarn kbn watch-bazel - @kbn/securitysolution-utils - @kbn/securitysolution-io-ts-utils - @kbn/std +- @kbn/telemetry-utils - @kbn/tinymath - @kbn/utility-types - @kbn/utils 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 180d376ceaf51..0448ad42c94fa 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 @@ -144,6 +144,7 @@ readonly links: { createSnapshotLifecyclePolicy: string; createRoleMapping: string; createRoleMappingTemplates: string; + createRollupJobsRequest: string; createApiKey: string; createPipeline: string; createTransformRequest: 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 91ef8358b5fd2..ac625095da2a4 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,7 @@ 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 search: {
readonly sessions: 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>;
} | | + +| [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 search: {
readonly sessions: 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;
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: 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/docs/development/core/server/kibana-plugin-core-server.assistanceapiresponse.md b/docs/development/core/server/kibana-plugin-core-server.assistanceapiresponse.md index 4778c98493b5b..1daaf95a73d5d 100644 --- a/docs/development/core/server/kibana-plugin-core-server.assistanceapiresponse.md +++ b/docs/development/core/server/kibana-plugin-core-server.assistanceapiresponse.md @@ -6,6 +6,7 @@ > Warning: This API is now obsolete. > +> 7.16 > Signature: diff --git a/docs/development/core/server/kibana-plugin-core-server.assistantapiclientparams.md b/docs/development/core/server/kibana-plugin-core-server.assistantapiclientparams.md index 6d3f8df2fa518..1031d733fed4a 100644 --- a/docs/development/core/server/kibana-plugin-core-server.assistantapiclientparams.md +++ b/docs/development/core/server/kibana-plugin-core-server.assistantapiclientparams.md @@ -6,6 +6,7 @@ > Warning: This API is now obsolete. > +> 7.16 > Signature: diff --git a/docs/development/core/server/kibana-plugin-core-server.deprecationapiclientparams.md b/docs/development/core/server/kibana-plugin-core-server.deprecationapiclientparams.md index ed64d61e75fab..fc1748d4db907 100644 --- a/docs/development/core/server/kibana-plugin-core-server.deprecationapiclientparams.md +++ b/docs/development/core/server/kibana-plugin-core-server.deprecationapiclientparams.md @@ -6,6 +6,7 @@ > Warning: This API is now obsolete. > +> 7.16 > Signature: diff --git a/docs/development/core/server/kibana-plugin-core-server.deprecationapiresponse.md b/docs/development/core/server/kibana-plugin-core-server.deprecationapiresponse.md index 1d837d9b4705d..ce40bd7c750f0 100644 --- a/docs/development/core/server/kibana-plugin-core-server.deprecationapiresponse.md +++ b/docs/development/core/server/kibana-plugin-core-server.deprecationapiresponse.md @@ -6,6 +6,7 @@ > Warning: This API is now obsolete. > +> 7.16 > Signature: diff --git a/docs/development/core/server/kibana-plugin-core-server.deprecationinfo.md b/docs/development/core/server/kibana-plugin-core-server.deprecationinfo.md index 8eeb5ef638a82..d9d1c6c3edb41 100644 --- a/docs/development/core/server/kibana-plugin-core-server.deprecationinfo.md +++ b/docs/development/core/server/kibana-plugin-core-server.deprecationinfo.md @@ -6,6 +6,7 @@ > Warning: This API is now obsolete. > +> 7.16 > Signature: diff --git a/docs/development/core/server/kibana-plugin-core-server.ilegacyclusterclient.md b/docs/development/core/server/kibana-plugin-core-server.ilegacyclusterclient.md index b5fbb3d54b972..d1e87feba0f03 100644 --- a/docs/development/core/server/kibana-plugin-core-server.ilegacyclusterclient.md +++ b/docs/development/core/server/kibana-plugin-core-server.ilegacyclusterclient.md @@ -6,7 +6,7 @@ > Warning: This API is now obsolete. > -> Use [IClusterClient](./kibana-plugin-core-server.iclusterclient.md). +> Use [IClusterClient](./kibana-plugin-core-server.iclusterclient.md). 7.16 > Represents an Elasticsearch cluster API client created by the platform. It allows to call API on behalf of the internal Kibana user and the actual user that is derived from the request headers (via `asScoped(...)`). diff --git a/docs/development/core/server/kibana-plugin-core-server.ilegacycustomclusterclient.md b/docs/development/core/server/kibana-plugin-core-server.ilegacycustomclusterclient.md index 4da121984d084..c004ad2548802 100644 --- a/docs/development/core/server/kibana-plugin-core-server.ilegacycustomclusterclient.md +++ b/docs/development/core/server/kibana-plugin-core-server.ilegacycustomclusterclient.md @@ -6,7 +6,7 @@ > Warning: This API is now obsolete. > -> Use [ICustomClusterClient](./kibana-plugin-core-server.icustomclusterclient.md). +> Use [ICustomClusterClient](./kibana-plugin-core-server.icustomclusterclient.md). 7.16 > Represents an Elasticsearch cluster API client created by a plugin. It allows to call API on behalf of the internal Kibana user and the actual user that is derived from the request headers (via `asScoped(...)`). diff --git a/docs/development/core/server/kibana-plugin-core-server.ilegacyscopedclusterclient.md b/docs/development/core/server/kibana-plugin-core-server.ilegacyscopedclusterclient.md index 51d0b2e4882cb..8e7ecdb9f7ec2 100644 --- a/docs/development/core/server/kibana-plugin-core-server.ilegacyscopedclusterclient.md +++ b/docs/development/core/server/kibana-plugin-core-server.ilegacyscopedclusterclient.md @@ -6,7 +6,7 @@ > Warning: This API is now obsolete. > -> Use [IScopedClusterClient](./kibana-plugin-core-server.iscopedclusterclient.md). +> Use [IScopedClusterClient](./kibana-plugin-core-server.iscopedclusterclient.md). 7.16 > Serves the same purpose as "normal" `ClusterClient` but exposes additional `callAsCurrentUser` method that doesn't use credentials of the Kibana internal user (as `callAsInternalUser` does) to request Elasticsearch API, but rather passes HTTP headers extracted from the current user request to the API. diff --git a/docs/development/core/server/kibana-plugin-core-server.indexsettingsdeprecationinfo.md b/docs/development/core/server/kibana-plugin-core-server.indexsettingsdeprecationinfo.md index 706898c4ad9aa..9103f9cfc6740 100644 --- a/docs/development/core/server/kibana-plugin-core-server.indexsettingsdeprecationinfo.md +++ b/docs/development/core/server/kibana-plugin-core-server.indexsettingsdeprecationinfo.md @@ -6,6 +6,7 @@ > Warning: This API is now obsolete. > +> 7.16 > Signature: diff --git a/docs/development/core/server/kibana-plugin-core-server.legacyapicaller.md b/docs/development/core/server/kibana-plugin-core-server.legacyapicaller.md index 168209659046e..2378e61484da5 100644 --- a/docs/development/core/server/kibana-plugin-core-server.legacyapicaller.md +++ b/docs/development/core/server/kibana-plugin-core-server.legacyapicaller.md @@ -6,6 +6,7 @@ > Warning: This API is now obsolete. > +> 7.16 > Signature: diff --git a/docs/development/core/server/kibana-plugin-core-server.legacycallapioptions.md b/docs/development/core/server/kibana-plugin-core-server.legacycallapioptions.md index 40def157114ef..219180af26fd8 100644 --- a/docs/development/core/server/kibana-plugin-core-server.legacycallapioptions.md +++ b/docs/development/core/server/kibana-plugin-core-server.legacycallapioptions.md @@ -6,6 +6,7 @@ > Warning: This API is now obsolete. > +> 7.16 > The set of options that defines how API call should be made and result be processed. diff --git a/docs/development/core/server/kibana-plugin-core-server.legacyclusterclient.md b/docs/development/core/server/kibana-plugin-core-server.legacyclusterclient.md index 0872e5ba7c219..05855c31477c3 100644 --- a/docs/development/core/server/kibana-plugin-core-server.legacyclusterclient.md +++ b/docs/development/core/server/kibana-plugin-core-server.legacyclusterclient.md @@ -6,7 +6,7 @@ > Warning: This API is now obsolete. > -> Use [IClusterClient](./kibana-plugin-core-server.iclusterclient.md). +> Use [IClusterClient](./kibana-plugin-core-server.iclusterclient.md). 7.16 > Represents an Elasticsearch cluster API client created by the platform. It allows to call API on behalf of the internal Kibana user and the actual user that is derived from the request headers (via `asScoped(...)`). diff --git a/docs/development/core/server/kibana-plugin-core-server.legacyelasticsearcherror.md b/docs/development/core/server/kibana-plugin-core-server.legacyelasticsearcherror.md index 7c53356615ee9..7cf696ad8d73f 100644 --- a/docs/development/core/server/kibana-plugin-core-server.legacyelasticsearcherror.md +++ b/docs/development/core/server/kibana-plugin-core-server.legacyelasticsearcherror.md @@ -4,7 +4,7 @@ ## LegacyElasticsearchError interface -@deprecated. The new elasticsearch client doesn't wrap errors anymore. +@deprecated. The new elasticsearch client doesn't wrap errors anymore. 7.16 Signature: diff --git a/docs/development/core/server/kibana-plugin-core-server.legacyscopedclusterclient.callascurrentuser.md b/docs/development/core/server/kibana-plugin-core-server.legacyscopedclusterclient.callascurrentuser.md index 7517acc59ac80..0f2d653e41a55 100644 --- a/docs/development/core/server/kibana-plugin-core-server.legacyscopedclusterclient.callascurrentuser.md +++ b/docs/development/core/server/kibana-plugin-core-server.legacyscopedclusterclient.callascurrentuser.md @@ -6,7 +6,7 @@ > Warning: This API is now obsolete. > -> Use [IScopedClusterClient.asCurrentUser](./kibana-plugin-core-server.iscopedclusterclient.ascurrentuser.md). +> Use [IScopedClusterClient.asCurrentUser](./kibana-plugin-core-server.iscopedclusterclient.ascurrentuser.md). 7.16 > Calls specified `endpoint` with provided `clientParams` on behalf of the user initiated request to the Kibana server (via HTTP request headers). See [LegacyAPICaller](./kibana-plugin-core-server.legacyapicaller.md). diff --git a/docs/development/core/server/kibana-plugin-core-server.legacyscopedclusterclient.callasinternaluser.md b/docs/development/core/server/kibana-plugin-core-server.legacyscopedclusterclient.callasinternaluser.md index b683d3945f9ff..2c184b0fde5b3 100644 --- a/docs/development/core/server/kibana-plugin-core-server.legacyscopedclusterclient.callasinternaluser.md +++ b/docs/development/core/server/kibana-plugin-core-server.legacyscopedclusterclient.callasinternaluser.md @@ -6,7 +6,7 @@ > Warning: This API is now obsolete. > -> Use [IScopedClusterClient.asInternalUser](./kibana-plugin-core-server.iscopedclusterclient.asinternaluser.md). +> Use [IScopedClusterClient.asInternalUser](./kibana-plugin-core-server.iscopedclusterclient.asinternaluser.md). 7.16 > Calls specified `endpoint` with provided `clientParams` on behalf of the Kibana internal user. See [LegacyAPICaller](./kibana-plugin-core-server.legacyapicaller.md). diff --git a/docs/development/core/server/kibana-plugin-core-server.legacyscopedclusterclient.md b/docs/development/core/server/kibana-plugin-core-server.legacyscopedclusterclient.md index 6b6649e833a92..6678c3bc16d53 100644 --- a/docs/development/core/server/kibana-plugin-core-server.legacyscopedclusterclient.md +++ b/docs/development/core/server/kibana-plugin-core-server.legacyscopedclusterclient.md @@ -6,7 +6,7 @@ > Warning: This API is now obsolete. > -> Use [scoped cluster client](./kibana-plugin-core-server.iscopedclusterclient.md). +> Use [scoped cluster client](./kibana-plugin-core-server.iscopedclusterclient.md). 7.16 > Serves the same purpose as the normal [cluster client](./kibana-plugin-core-server.iclusterclient.md) but exposes an additional `asCurrentUser` method that doesn't use credentials of the Kibana internal user (as `asInternalUser` does) to request Elasticsearch API, but rather passes HTTP headers extracted from the current user request to the API instead. diff --git a/docs/development/core/server/kibana-plugin-core-server.md b/docs/development/core/server/kibana-plugin-core-server.md index 4df8d074ba9c8..3a9118a9c56bd 100644 --- a/docs/development/core/server/kibana-plugin-core-server.md +++ b/docs/development/core/server/kibana-plugin-core-server.md @@ -108,7 +108,7 @@ The plugin integrates with the core system via lifecycle events: `setup` | [KibanaRequestRoute](./kibana-plugin-core-server.kibanarequestroute.md) | Request specific route information exposed to a handler. | | [LegacyAPICaller](./kibana-plugin-core-server.legacyapicaller.md) | | | [LegacyCallAPIOptions](./kibana-plugin-core-server.legacycallapioptions.md) | The set of options that defines how API call should be made and result be processed. | -| [LegacyElasticsearchError](./kibana-plugin-core-server.legacyelasticsearcherror.md) | @deprecated. The new elasticsearch client doesn't wrap errors anymore. | +| [LegacyElasticsearchError](./kibana-plugin-core-server.legacyelasticsearcherror.md) | @deprecated. The new elasticsearch client doesn't wrap errors anymore. 7.16 | | [LegacyRequest](./kibana-plugin-core-server.legacyrequest.md) | | | [LoggerContextConfigInput](./kibana-plugin-core-server.loggercontextconfiginput.md) | | | [LoggingServiceSetup](./kibana-plugin-core-server.loggingservicesetup.md) | Provides APIs to plugins for customizing the plugin's logger. | diff --git a/docs/development/core/server/kibana-plugin-core-server.migration_assistance_index_action.md b/docs/development/core/server/kibana-plugin-core-server.migration_assistance_index_action.md index a924f0cea6b6b..ea0a277931eaf 100644 --- a/docs/development/core/server/kibana-plugin-core-server.migration_assistance_index_action.md +++ b/docs/development/core/server/kibana-plugin-core-server.migration_assistance_index_action.md @@ -6,6 +6,7 @@ > Warning: This API is now obsolete. > +> 7.16 > Signature: diff --git a/docs/development/core/server/kibana-plugin-core-server.migration_deprecation_level.md b/docs/development/core/server/kibana-plugin-core-server.migration_deprecation_level.md index 0fcae8c847cb4..f71e6e78a4c34 100644 --- a/docs/development/core/server/kibana-plugin-core-server.migration_deprecation_level.md +++ b/docs/development/core/server/kibana-plugin-core-server.migration_deprecation_level.md @@ -6,6 +6,7 @@ > Warning: This API is now obsolete. > +> 7.16 > Signature: diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.search.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.search.md index 259009c1c5668..3c99ae4c86c63 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.search.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.search.md @@ -21,7 +21,7 @@ search: { })[]; InvalidEsCalendarIntervalError: typeof InvalidEsCalendarIntervalError; InvalidEsIntervalFormatError: typeof InvalidEsIntervalFormatError; - Ipv4Address: typeof Ipv4Address; + IpAddress: typeof IpAddress; isDateHistogramBucketAggConfig: typeof isDateHistogramBucketAggConfig; isNumberType: (agg: import("../common").AggConfig) => boolean; isStringType: (agg: import("../common").AggConfig) => boolean; diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.search.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.search.md index 930f7710f9a00..7072f25489db2 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.search.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.search.md @@ -21,7 +21,7 @@ search: { })[]; InvalidEsCalendarIntervalError: typeof InvalidEsCalendarIntervalError; InvalidEsIntervalFormatError: typeof InvalidEsIntervalFormatError; - Ipv4Address: typeof Ipv4Address; + IpAddress: typeof IpAddress; isNumberType: (agg: import("../common").AggConfig) => boolean; isStringType: (agg: import("../common").AggConfig) => boolean; isType: (...types: string[]) => (agg: import("../common").AggConfig) => boolean; diff --git a/docs/user/dashboard/images/lens_advanced_2_1.png b/docs/user/dashboard/images/lens_advanced_2_1.png index 5090f0d3b2841..dab32369d71a1 100644 Binary files a/docs/user/dashboard/images/lens_advanced_2_1.png and b/docs/user/dashboard/images/lens_advanced_2_1.png differ diff --git a/docs/user/dashboard/images/lens_advanced_2_1_1.png b/docs/user/dashboard/images/lens_advanced_2_1_1.png deleted file mode 100644 index f4d9ca488782e..0000000000000 Binary files a/docs/user/dashboard/images/lens_advanced_2_1_1.png and /dev/null differ diff --git a/docs/user/dashboard/images/lens_advanced_3_1.gif b/docs/user/dashboard/images/lens_advanced_2_2.gif similarity index 100% rename from docs/user/dashboard/images/lens_advanced_3_1.gif rename to docs/user/dashboard/images/lens_advanced_2_2.gif diff --git a/docs/user/dashboard/images/lens_advanced_2_2.png b/docs/user/dashboard/images/lens_advanced_2_2.png index 820bc3bd4dfa9..1d88bcd238ca3 100644 Binary files a/docs/user/dashboard/images/lens_advanced_2_2.png and b/docs/user/dashboard/images/lens_advanced_2_2.png differ diff --git a/docs/user/dashboard/images/lens_advanced_2_2_1.png b/docs/user/dashboard/images/lens_advanced_2_2_1.png index 3044f1070367d..c3fb697666b46 100644 Binary files a/docs/user/dashboard/images/lens_advanced_2_2_1.png and b/docs/user/dashboard/images/lens_advanced_2_2_1.png differ diff --git a/docs/user/dashboard/images/lens_advanced_3_3.png b/docs/user/dashboard/images/lens_advanced_2_3.png similarity index 100% rename from docs/user/dashboard/images/lens_advanced_3_3.png rename to docs/user/dashboard/images/lens_advanced_2_3.png diff --git a/docs/user/dashboard/images/lens_advanced_3_1.png b/docs/user/dashboard/images/lens_advanced_3_1.png new file mode 100644 index 0000000000000..1473b203924a3 Binary files /dev/null and b/docs/user/dashboard/images/lens_advanced_3_1.png differ diff --git a/docs/user/dashboard/images/lens_advanced_3_1_1.png b/docs/user/dashboard/images/lens_advanced_3_1_1.png deleted file mode 100644 index c3fb697666b46..0000000000000 Binary files a/docs/user/dashboard/images/lens_advanced_3_1_1.png and /dev/null differ diff --git a/docs/user/dashboard/images/lens_advanced_3_2.png b/docs/user/dashboard/images/lens_advanced_3_2.png index 20da2ed706dfd..15f2f0228a0fc 100644 Binary files a/docs/user/dashboard/images/lens_advanced_3_2.png and b/docs/user/dashboard/images/lens_advanced_3_2.png differ diff --git a/docs/user/dashboard/images/lens_advanced_4_1.png b/docs/user/dashboard/images/lens_advanced_4_1.png index 43c8db213d482..50d1affa268dd 100644 Binary files a/docs/user/dashboard/images/lens_advanced_4_1.png and b/docs/user/dashboard/images/lens_advanced_4_1.png differ diff --git a/docs/user/dashboard/images/lens_advanced_4_2.png b/docs/user/dashboard/images/lens_advanced_4_2.png deleted file mode 100644 index 4b3e98910e7b7..0000000000000 Binary files a/docs/user/dashboard/images/lens_advanced_4_2.png and /dev/null differ diff --git a/docs/user/dashboard/images/lens_advanced_5_1.png b/docs/user/dashboard/images/lens_advanced_5_1.png new file mode 100644 index 0000000000000..5090f0d3b2841 Binary files /dev/null and b/docs/user/dashboard/images/lens_advanced_5_1.png differ diff --git a/docs/user/dashboard/images/lens_advanced_5_2.png b/docs/user/dashboard/images/lens_advanced_5_2.png new file mode 100644 index 0000000000000..820bc3bd4dfa9 Binary files /dev/null and b/docs/user/dashboard/images/lens_advanced_5_2.png differ diff --git a/docs/user/dashboard/images/lens_advanced_5_2_1.png b/docs/user/dashboard/images/lens_advanced_5_2_1.png new file mode 100644 index 0000000000000..3044f1070367d Binary files /dev/null and b/docs/user/dashboard/images/lens_advanced_5_2_1.png differ diff --git a/docs/user/dashboard/images/lens_advanced_6_1.png b/docs/user/dashboard/images/lens_advanced_6_1.png new file mode 100644 index 0000000000000..5d5cefa472a13 Binary files /dev/null and b/docs/user/dashboard/images/lens_advanced_6_1.png differ diff --git a/docs/user/dashboard/images/lens_advanced_7_1.png b/docs/user/dashboard/images/lens_advanced_7_1.png new file mode 100644 index 0000000000000..3d66d5d7e4579 Binary files /dev/null and b/docs/user/dashboard/images/lens_advanced_7_1.png differ diff --git a/docs/user/dashboard/images/lens_advanced_result.png b/docs/user/dashboard/images/lens_advanced_result.png index 19963d87c8e1c..8cf087f936abd 100644 Binary files a/docs/user/dashboard/images/lens_advanced_result.png and b/docs/user/dashboard/images/lens_advanced_result.png differ diff --git a/docs/user/dashboard/lens-advanced.asciidoc b/docs/user/dashboard/lens-advanced.asciidoc index 6b090f6017f5d..e49db0c0d026d 100644 --- a/docs/user/dashboard/lens-advanced.asciidoc +++ b/docs/user/dashboard/lens-advanced.asciidoc @@ -1,11 +1,11 @@ [[create-a-dashboard-of-panels-with-ecommerce-data]] -== Tutorial: Create a dashboard of panels with ecommerce sales data +== Time series analysis with Lens -You collected sales data from your store, and you want to visualize and analyze the data on a dashboard. +The tutorial uses sample data from the perspective of a shop owner looking +at sales trends, but this type of dashboard works on any type of data. To create dashboard panels of the data, open the *Lens* visualization builder, then create the visualization panels that best display the data. - -When you've completed the tutorial, you'll have a dashboard that provides you with a complete overview of your ecommerce sales data. +Before using this tutorial, you should be familiar with the <>. [role="screenshot"] image::images/lens_advanced_result.png[Dashboard view] @@ -14,36 +14,52 @@ image::images/lens_advanced_result.png[Dashboard view] [[add-the-data-and-create-the-dashboard-advanced]] === Add the data and create the dashboard -To create visualizations of the data from your store, add the data set, then create the dashboard. +If you are working with your own data, you should already have an <>. +To install the sample sales data: . From the {kib} *Home* page, click *Try our sample data*. . From *Sample eCommerce orders*, click *Add data*. +Then create a new dashboard: + . Open the main menu, then click *Dashboard*. . On the *Dashboards* page, click *Create dashboard*. +. Set the <> to *Last 30 days*. + [float] [[open-and-set-up-lens-advanced]] === Open and set up Lens -Open the *Lens* editor, then make sure the correct fields appear. +*Lens* is designed to help you quickly build visualizations for your dashboard, as shown in <>, while providing support for advanced usage as well. -. From the dashboard, click *Create panel*. +Open the *Lens* editor, then make sure the correct fields appear. -. On the *New visualization* window, click *Lens*. -+ -[role="screenshot"] -image::images/lens_end_to_end_1_1.png[New visualization popover] +. From the dashboard, click *Create visualization*. -. Make sure the *kibana_sample_data_ecommerce_* index appears. +. Make sure the *kibana_sample_data_ecommerce* index appears. [discrete] -[[view-the-number-of-transactions-per-day]] -=== View the number of transactions per hour +[[custom-time-interval]] +=== View a date histogram with a custom time interval + +It is common to use the automatic date histogram interval, but sometimes you want a larger or smaller +interval. *Lens* only lets you choose the minimum time interval, not the exact time interval, for +performance reasons. The performance limit is controlled by the <> +advanced setting and the overall time range. To see hourly sales over a 30 day time period, choose +one of these options: -To determine the number of orders made every hour, create a bar chart, then add the chart to the dashboard. +* View less than 30 days at a time, then use the time picker to select each day separately. + +* Increase `histogram:maxBars` from 100 to at least 720, which the number of hours in 30 days. +This affects all visualizations and can reduce performance. + +* If approximation is okay, use the *Normalize unit* option. This can convert *Average sales per 12 hours* +into *Average sales per 12 hours (per hour)* by dividing the number of hours. + +For the sample data, approximation is okay. To use the *Normalize unit* option: . Set the <> to *Last 30 days*. @@ -75,183 +91,258 @@ image::images/lens_advanced_1_2.png[Orders per day] . Click *Save and return*. [discrete] -[[view-the-cumulative-number-of-products-sold-over-time]] -=== View the cumulative number of products sold on weekends +[[add-a-data-layer-advanced]] +=== Monitor multiple series within a date histogram -To determine the number of orders made only on Saturday and Sunday, create an area chart, then add it to the dashboard. +It is often required to monitor multiple series within a time interval. These series can be have similar configurations with few changes between one and another. +*Lens* copies a function when you drag and drop it to the *Drop a field or click to add* +field within the same group, or when you drag and drop to the *Duplicate* field on a different group. +You can also drag and drop using your keyboard. For more information, refer to <>. -. Open *Lens*. +To quickly create many copies of a percentile metric that shows distribution of price over time: -. From the *Chart Type* dropdown, select *Area*. +. From the *Chart Type* dropdown, select *Line*. + [role="screenshot"] -image::images/lens_advanced_2_1_1.png[Chart type menu with Area selected] +image::images/lens_advanced_2_1.png[Chart type menu with Line selected] -. Configure the cumulative sum of the store orders. - -.. From the *Available fields* list, drag and drop *Records* to the visualization builder. +. From the *Available fields* list, drag and drop *products.price* to the visualization builder. -.. From the editor, click *Count of Records*. +. Create the 95th percentile. -.. From *Select a function*, click *Cumulative sum*. +.. In the editor, click *Median of products.price*. -.. In the *Display name* field, enter `Cumulative orders during weekend days`, then click *Close*. +.. From *Select a function*, click *Percentile*. -. Filter the results to display the data for only Saturday and Sunday. +.. In the *Display name* field, enter `95th`, then click *Close*. -.. From the editor, click the *Drop a field or click to add* field for *Break down by*. +. To create the 90th percentile, duplicate the `95th` percentile. -.. From *Select a function*, click *Filters*. +.. Drag and drop *95th* to *Drop a field or click to add*. -.. Click *All records*. +.. Click *95th [1]*, then enter `90` in the *Percentile* field. -.. In the *KQL* field, enter `day_of_week : "Saturday" or day_of_week : "Sunday"`, then press Return. -+ -The <> displays all documents where `day_of_week` matches `Saturday` or `Sunday`. +.. In the *Display name* field enter `90th`, then click *Close*. + [role="screenshot"] -image::images/lens_advanced_2_1.png[Filter aggregation to filter weekend days] +image::images/lens_advanced_2_2.gif[Easily duplicate the items with drag and drop] -. To hide the legend, open the *Legend* menu, then click *Hide*. +.. Repeat the duplication steps to create the `50th` and `10th` percentile, naming them accordingly. + +. To change the left axis label, open the *Left Axis* menu, then enter `Percentiles for product prices` in the *Axis name* field. + [role="screenshot"] -image::images/lens_advanced_2_2_1.png[Legend menu] +image::images/lens_advanced_2_2_1.png[Left Axis menu] + -You have an area chart that shows you how many orders your store received during the weekend. +You have a line chart that shows you the price distribution of products sold over time. + [role="screenshot"] -image::images/lens_advanced_2_2.png[Line chart with cumulative sum of orders made on the weekend] +image::images/lens_advanced_2_3.png[Percentiles for product prices chart] -. Click *Save and return*. +. Add the filter for the redirect codes. [discrete] -[[add-a-data-layer-advanced]] -=== Create multiple key percentiles of product prices - -To view the price distribution of products sold over time, create a percentile chart, then add it to the dashboard. - -. Open *Lens*. - -. From the *Chart Type* dropdown, select *Line*. - -. From the *Available fields* list, drag and drop the data fields to the *Drop a field or click to add* fields in the editor. - -* Drag and drop *products.price* to the *Vertical axis* field. +[[add-a-data-layer]] +==== Multiple chart types or index patterns in one visualization -* Drag and drop *order_date* to the *Horizontal axis* field. +You can add multiple metrics to a single chart type, but if you want to overlay +multiple chart types or index patterns, use a second layer. When building layered charts, +it is important to match the data on the horizontal axis so that it uses the same +scale. To add a line chart layer on top of an existing chart: -. Create the 95th percentile. +To compare product prices with customers traffic: -.. In the editor, click *Median of products.price*. +. From the *Available fields* list, drag and drop *products.price* to the visualization builder. -.. From *Select a function*, click *Percentile*. +.. In the *KQL* field, enter `response.keyword>=500 AND response.keyword<600`. -.. In the *Display name* field, enter `95th`, then click *Close*. +.. From *Select a function*, click *Average*. -. To create the 90th percentile, duplicate the `95th` percentile. +.. In the *Display name* field, enter `Average of prices`, then click *Close*. -.. Drag and drop *95th* to *Drop a field or click to add*. +. From the *Chart Type* dropdown, select *Area*. -.. Click *95th [1]*, then enter `90` in the *Percentile* field. +. Create a new layer to overlay with custom traffic. -.. In the *Display name* field enter `90th`, then click *Close*. +. In the editor, click *+*. + [role="screenshot"] -image::images/lens_advanced_3_1.gif[Easily duplicate the items with drag and drop] - -. Create the 50th percentile. - -.. Drag and drop *90th* to *Drop a field or click to add*. +image::images/lens_advanced_3_1.png[Add new layer button] -.. Click *90th [1]*, then enter `50` in the *Percentile* field. +. From the *Available fields* list, drag and drop *customer_id* to the *Vertical Axis* of the newly created layer. -.. In the *Display name* field enter `50th`, then click *Close*. +.. In the editor, click *Unique count of customer_id*. -. Create the 10th percentile. +.. In the *Display name* field, enter `Unique customers`, then click *Close*. -.. Drag and drop *50th* to *Drop a field or click to add*. +. In the *Series color* field, enter *#D36086*, then click *Close*. -.. Click *50th [1]*, then enter `10` in the *Percentile* field. +. For *Axis side*, click *Right*, then click *Close*. -.. In the *Display name* field enter `10th`, then click *Close*. +. From the *Available fields* list, drag and drop *order_date* to the *Horizontal Axis* of the newly created layer. -. To change the left axis label, open the *Left Axis* menu, then enter `Percentiles for product prices` in the *Axis name* field. +. From the new layer editor, click the *Chart type* dropdown, then click the line chart. + [role="screenshot"] -image::images/lens_advanced_3_1_1.png[Left Axis menu] -+ -You have a line chart that shows you the price distribution of products sold over time. -+ -[role="screenshot"] -image::images/lens_advanced_3_3.png[Percentiles for product prices chart] +image::images/lens_advanced_3_2.png[Change layer type] + +The visualization is done, but the legend uses a lot of space. Change the legend position to the top of the chart. + +. From the *Legend* dropdown, select the top position. . Click *Save and return*. [discrete] -[[add-the-response-code-filters-advanced]] -=== View the moving average of inventory prices +[[percentage-stacked-area]] +=== Compare the change in percentage over time -To view and analyze the prices of shoes, accessories, and clothing in the store inventory, create a line chart. +By default, *Lens* shows *date histograms* using a stacked chart visualization, which helps understand how distinct sets of documents perform over time. Sometimes it is useful to understand how the distributions of these sets change over time. +Combine *filters* and *date histogram* functions to see the change over time in specific +sets of documents. To view this as a percentage, use a *stacked percentage* bar or area chart. -. Open *Lens*. +To see sales change of product by type over time: -. From the *Chart Type* dropdown, select *Line*. +. From the *Available fields* list, drag and drop *Records* to the visualization builder. -. From the *Available fields* list, drag and drop *products.price* to the visualization builder. +. Click *Bar vertical stacked*, then select *Area percentage*. -. In the editor, click the *Drop a field or click to add* field for *Break down by*. +For each category type that you want to break down, create a filter. + +. In the editor, click the *Drop a field or click to add* field for *Break down by*. . From *Select a function*, click *Filters*. -. Add a filter for shoes. +. Add the filter for the clothing category. .. Click *All records*. -.. In the *KQL* field, enter `category.keyword : *Shoes*`. +.. In the *KQL* field, enter `category.keyword : *Clothing`. + +.. In the *Label* field, enter `Clothing`, then press Return. + +. Add the filter for the shoes category. + +.. Click *Add a filter*. + +.. In the *KQL* field, enter `category.keyword : *Shoes`. .. In the *Label* field, enter `Shoes`, then press Return. -. Add a filter for accessories. +. Add the filter for the accessories category. .. Click *Add a filter*. -.. In the *KQL* field, enter `category.keyword : *Accessories*`. +.. In the *KQL* field, enter `category.keyword : *Accessories`. .. In the *Label* field, enter `Accessories`, then press Return. -. Add a filter for clothing. +Change the legend position to the top of the chart. -.. Click *Add a filter*. +. From the *Legend* dropdown, select the top position. -.. In the *KQL* field, enter `category.keyword : *Clothing*`. ++ +[role="screenshot"] +image::images/lens_advanced_4_1.png[Prices share by category] -.. In the *Label* field, enter `Clothing`, then press Return. + Click *Save and return*. + +[discrete] +[[view-the-cumulative-number-of-products-sold-on-weekends]] +=== View the cumulative number of products sold on weekends + +To determine the number of orders made only on Saturday and Sunday, create an area chart, then add it to the dashboard. + +. Open *Lens*. + +. From the *Chart Type* dropdown, select *Area*. + +. Configure the cumulative sum of the store orders. + +.. From the *Available fields* list, drag and drop *Records* to the visualization builder. + +.. From the editor, click *Count of Records*. + +.. From *Select a function*, click *Cumulative sum*. + +.. In the *Display name* field, enter `Cumulative orders during weekend days`, then click *Close*. + +. Filter the results to display the data for only Saturday and Sunday. + +.. From the editor, click the *Drop a field or click to add* field for *Break down by*. + +.. From *Select a function*, click *Filters*. + +.. Click *All records*. + +.. In the *KQL* field, enter `day_of_week : "Saturday" or day_of_week : "Sunday"`, then press Return. ++ +The <> displays all documents where `day_of_week` matches `Saturday` or `Sunday`. ++ +[role="screenshot"] +image::images/lens_advanced_5_1.png[Filter aggregation to filter weekend days] + +. To hide the legend, open the *Legend* menu, then click *Hide*. ++ +[role="screenshot"] +image::images/lens_advanced_5_2_1.png[Legend menu] ++ +You have an area chart that shows you how many orders your store received during the weekend. -. Click *Close* +. Click *Bar vertical stacked*, then select *Area*. + [role="screenshot"] -image::images/lens_advanced_4_1.png[Median prices chart for different categories] +image::images/lens_advanced_5_2.png[Line chart with cumulative sum of orders made on the weekend] + +. Click *Save and return*. [discrete] -[[add-the-moving-average]] -==== Add the moving average +[[view-customers-over-time-by-continents]] +=== View table of customers by category over time + +Tables are an alternative type of visualization for time series, useful when you want to read the actual values. +You can build a date histogram table, and group the customer count metric by category, like the continent registered in their profile. + +In *Lens* you can split the metric in a table leveraging the *Columns* field, where each data value from the aggregation is used as column of the table and the relative metric value is shown. + +To build a date histogram table: + +. Open *Lens*. + +. From the *Chart Type* dropdown, select *Table*. -To focus on the general trends rather than on the peaks in the data, add the moving average, then add the visualization to the dashboard. +.. From the *Available fields* list, drag and drop *customer_id* to the *Metrics* field of the editor. -. In the editor, click the *Median of products.price*. +.. From the editor, click *Unique count of customer_id*. -. From *Select a function*, click *Moving average*. +.. In the *Display name* field, enter `Customers`, then click *Close*. -. In the *Window size* field, enter `7`, then click *Close*. +.. From the *Available fields* list, drag and drop *order_date* to the *Rows* field of the editor. + +.. From the editor *Rows*, click the *order_date* field just dropped. + +. Select *Customize time interval*. + +. Change the *Minimum interval* to `1 days`, then click *Close*. + +.. In the *Display name* field, enter `Sale`, then click *Close*. + +To split the customers count by continent: + +. From the *Available fields* list, drag and drop *geoip.continent_name* to the *Columns* field of the editor. + [role="screenshot"] -image::images/lens_advanced_4_2.png[Moving average prices chart for different categories] +image::images/lens_advanced_6_1.png[Table with daily customers by continent configuration] . Click *Save and return*. [discrete] === Save the dashboard +By default the dashboard attempts to match the palette across panels, but in this case there's no need for that, so it can be disabled. + +[role="screenshot"] +image::images/lens_advanced_7_1.png[Disable palette sync in dashboard] + Now that you have a complete overview of your ecommerce sales data, save the dashboard. . In the toolbar, click *Save*. diff --git a/docs/user/dashboard/lens-end-to-end/images/lens_end_to_end_1_1.png b/docs/user/dashboard/lens-end-to-end/images/lens_end_to_end_1_1.png deleted file mode 100644 index 1c752972791af..0000000000000 Binary files a/docs/user/dashboard/lens-end-to-end/images/lens_end_to_end_1_1.png and /dev/null differ diff --git a/docs/user/dashboard/lens-end-to-end/images/lens_end_to_end_1_2.png b/docs/user/dashboard/lens-end-to-end/images/lens_end_to_end_1_2.png index de719c852dfde..ea58057433a19 100644 Binary files a/docs/user/dashboard/lens-end-to-end/images/lens_end_to_end_1_2.png and b/docs/user/dashboard/lens-end-to-end/images/lens_end_to_end_1_2.png differ diff --git a/docs/user/dashboard/lens-end-to-end/images/lens_end_to_end_1_2_1.png b/docs/user/dashboard/lens-end-to-end/images/lens_end_to_end_1_2_1.png index 9408cd736b27e..fa404d50b4dd1 100644 Binary files a/docs/user/dashboard/lens-end-to-end/images/lens_end_to_end_1_2_1.png and b/docs/user/dashboard/lens-end-to-end/images/lens_end_to_end_1_2_1.png differ diff --git a/docs/user/dashboard/lens-end-to-end/images/lens_end_to_end_1_3.png b/docs/user/dashboard/lens-end-to-end/images/lens_end_to_end_1_3.png index 4302c95eb8a25..b069acae43163 100644 Binary files a/docs/user/dashboard/lens-end-to-end/images/lens_end_to_end_1_3.png and b/docs/user/dashboard/lens-end-to-end/images/lens_end_to_end_1_3.png differ diff --git a/docs/user/dashboard/lens-end-to-end/images/lens_end_to_end_2_1.png b/docs/user/dashboard/lens-end-to-end/images/lens_end_to_end_2_1.png deleted file mode 100644 index c08a865e52137..0000000000000 Binary files a/docs/user/dashboard/lens-end-to-end/images/lens_end_to_end_2_1.png and /dev/null differ diff --git a/docs/user/dashboard/lens-end-to-end/images/lens_end_to_end_2_1_1.png b/docs/user/dashboard/lens-end-to-end/images/lens_end_to_end_2_1_1.png index 48712ec5ef224..e996b58520d41 100644 Binary files a/docs/user/dashboard/lens-end-to-end/images/lens_end_to_end_2_1_1.png and b/docs/user/dashboard/lens-end-to-end/images/lens_end_to_end_2_1_1.png differ diff --git a/docs/user/dashboard/lens-end-to-end/images/lens_end_to_end_2_1_2.png b/docs/user/dashboard/lens-end-to-end/images/lens_end_to_end_2_1_2.png new file mode 100644 index 0000000000000..a524dd1e456f6 Binary files /dev/null and b/docs/user/dashboard/lens-end-to-end/images/lens_end_to_end_2_1_2.png differ diff --git a/docs/user/dashboard/lens-end-to-end/images/lens_end_to_end_4_3.png b/docs/user/dashboard/lens-end-to-end/images/lens_end_to_end_4_3.png index b70910fdd2500..2cd75d2797a98 100644 Binary files a/docs/user/dashboard/lens-end-to-end/images/lens_end_to_end_4_3.png and b/docs/user/dashboard/lens-end-to-end/images/lens_end_to_end_4_3.png differ diff --git a/docs/user/dashboard/lens-end-to-end/images/lens_end_to_end_6_2.png b/docs/user/dashboard/lens-end-to-end/images/lens_end_to_end_6_2.png deleted file mode 100644 index e95b3d9a97b8d..0000000000000 Binary files a/docs/user/dashboard/lens-end-to-end/images/lens_end_to_end_6_2.png and /dev/null differ diff --git a/docs/user/dashboard/lens-end-to-end/images/lens_end_to_end_7_1.png b/docs/user/dashboard/lens-end-to-end/images/lens_end_to_end_7_1.png deleted file mode 100644 index e63f7e77f1e9f..0000000000000 Binary files a/docs/user/dashboard/lens-end-to-end/images/lens_end_to_end_7_1.png and /dev/null differ diff --git a/docs/user/dashboard/lens-end-to-end/images/lens_end_to_end_dashboard.png b/docs/user/dashboard/lens-end-to-end/images/lens_end_to_end_dashboard.png index 9bce65401f4c9..9e9a5ed3d758e 100644 Binary files a/docs/user/dashboard/lens-end-to-end/images/lens_end_to_end_dashboard.png and b/docs/user/dashboard/lens-end-to-end/images/lens_end_to_end_dashboard.png differ diff --git a/docs/user/dashboard/tsvb.asciidoc b/docs/user/dashboard/tsvb.asciidoc index ff1c16c14d467..5c4ce8e365e86 100644 --- a/docs/user/dashboard/tsvb.asciidoc +++ b/docs/user/dashboard/tsvb.asciidoc @@ -2,7 +2,8 @@ === TSVB *TSVB* enables you to visualize the data from multiple data series, supports <>, multiple visualization types, custom functions, and some math. To use *TSVB*, your data must have a date field. +most {es} metric aggregations>>, multiple visualization types, custom functions, and some math. +To create *TSVB* visualization panels, your data must have a time field. [role="screenshot"] image::visualize/images/tsvb-screenshot.png[TSVB overview] @@ -17,15 +18,19 @@ Open *TSVB*, then make sure the required settings are configured. . On the *New visualization* window, click *TSVB*. -. In *TSVB*, click *Panel options*, then make sure the following settings are configured: +. In *TSVB*, click *Panel options*, then specify the required *Data* settings. -* *Index pattern* -* *Time field* -* *Interval* +.. From the *Index pattern* dropdown, select the index pattern you want to visualize. ++ +To visualize an {es} index, open the *Index pattern select mode* menu, deselect *Use only {kib} index patterns*, then enter the {es} index. + +.. From the *Time field* dropdown, select the field you want to visualize, then enter the field *Interval*. -. Select a *Drop last bucket* option. It is dropped by default because the time filter intersects the time range of the last bucket, but can be enabled to see the partial data. +.. Select a *Drop last bucket* option. ++ +By default, *TSVB* drops the last bucket because the time filter intersects the time range of the last bucket. To view the partial data, select *No*. -. In the *Panel filter* field, specify any <> to select specific documents. +.. In the *Panel filter* field, enter <> to view specific documents. [float] [[configure-the-data-series]] diff --git a/docs/user/dashboard/tutorial-create-a-dashboard-of-lens-panels.asciidoc b/docs/user/dashboard/tutorial-create-a-dashboard-of-lens-panels.asciidoc index 22483b2801848..1375aa8d5934e 100644 --- a/docs/user/dashboard/tutorial-create-a-dashboard-of-lens-panels.asciidoc +++ b/docs/user/dashboard/tutorial-create-a-dashboard-of-lens-panels.asciidoc @@ -1,10 +1,10 @@ [[create-a-dashboard-of-panels-with-web-server-data]] -== Tutorial: Create a dashboard of panels with web server data +== Build your first dashboard -You collected data from your web server, and you want to visualize and analyze the data on a dashboard. To create dashboard panels of the data, open the *Lens* visualization builder, then -create the visualization panels that best display the data. - -When you've completed the tutorial, you'll have a dashboard that provides you with a complete overview of your web server data. +Learn the most common ways to build a dashboard from your own data. +The tutorial will use sample data from the perspective of an analyst looking +at website logs, but this type of dashboard works on any type of data. +Before using this tutorial, you should be familiar with the <>. [role="screenshot"] image::images/lens_end_to_end_dashboard.png[Final dashboard vis] @@ -13,46 +13,78 @@ image::images/lens_end_to_end_dashboard.png[Final dashboard vis] [[add-the-data-and-create-the-dashboard]] === Add the data and create the dashboard -To create visualizations of the data from the web server, add the data set, then create the dashboard. +Install the sample web logs data that you'll use to create your dashboard. -. From the {kib} *Home* page, click *Try our sample data*. +. On the {kib} *Home* page, click *Try our sample data*. . From *Sample web logs*, click *Add data*. -. Open the main menu, click *Dashboard*. +Then create a new dashboard: + +. Open the main menu, then click *Dashboard*. . Click *Create dashboard*. +. Set the <> to *Last 90 days*. + [float] [[open-and-set-up-lens]] -=== Open and set up Lens +=== Open Lens and get familiar with the data -With *Lens*, you identify the data fields you want to visualize, drag and drop the fields, then watch as -*Lens* uses heuristics to apply the fields and create a visualization for you. +. On the dashboard, click *Create visualization*. -. From the dashboard, click *Create panel*. - -. On the *New visualization* window, click *Lens*. +. Make sure the *kibana_sample_data_logs* index appears. You might need to select +a different index pattern from the dropdown: + [role="screenshot"] -image::images/lens_end_to_end_1_1.png[New visualization popover] +image::images/lens_end_to_end_1_2.png[Lens index pattern selector, width=50%] + +This tutorial uses `Records`, timestamp`, `bytes`, `clientip`, and `referer.keyword`. +To see the most frequent values of a field, click the field name to view a summary. + +The main elements of *Lens* are named: -. Make sure the *kibana_sample_data_logs* index appears. +Workspace panel:: Displays your visualization. You can drag and drop onto this area. +Dimensions:: Each dimension is a function with extra options. Dimensions are grouped +for each visualization type, for example the *Vertical axis* is a group that allows +multiple dimensions. Each dimension starts empty with the label *Drop a field or click to add*. +Functions:: There are two main types of functions: *buckets* and *metrics*, which are +equivalent to what {es} provides. [discrete] [[view-the-number-of-website-visitors]] -=== View the number of website visitors +=== Create your first visualization -To determine how many users have visited your website within the last 90 days, create a metric visualization, then add it to the dashboard. +Every time you build a visualization in *Lens*, you need to: -. Set the <> to *Last 90 days*. +* *Choose your visualization.* Do you know the type of visualization you'd like to use? +If you do, select the type before dragging any fields. If you don't, you can change the +visualization type after configuring your functions. + +* *Choose your field.* Do you know the dimension group you want to use the field in? If you do, +drag and drop the field from the field list to your chosen dimension and Lens will pick a function for you. +If you don't, drag and drop the field onto the workspace panel. Skip this step if you are +using the *Filters* function. + +* *Edit and delete.* To change the function or styling options, click the dimension to open +the configuration panel. To delete a specific dimension, close the configuration panel and click +the delete button. To reset the entire visualization, click *Reset layer*. + +To put this into practice, pick a field you want to analyze, such as `clientip`. If you want +to analyze only this field, you can use *Metric* to show a big number. +The only number function that you can use with `clientip` is *Unique count*. +*Unique count*, also known as cardinality, approximates the number of unique values +of the `clientip` field. -. From the *Chart Type* dropdown, select *Metric*. +. To select the visualization type, click *Bar vertical stacked* to open the chart type dropdown, then select *Metric*. + [role="screenshot"] -image::images/lens_end_to_end_1_2_1.png[Chart Type dropdown with Metric selected] +image::images/lens_end_to_end_1_2_1.png[Chart Type dropdown with Metric selected, width=50%] -. From the *Available fields* list, drag and drop *clientip* to the visualization builder. +. From the *Available fields* list, drag and drop `clientip` to the workspace panel. +Lens selects *Unique count* because it is the only numeric function +that works for IP addresses. You can also drag and drop `clientip` onto +the empty dimension for the same result. + [role="screenshot"] image::images/lens_end_to_end_1_3.png[Changed type and dropped clientip field] @@ -68,294 +100,220 @@ image::images/lens_end_to_end_1_4.png[Flyout config open] . Click *Save and return*. -[discrete] -[[view-the-distribution-of-visitors-by-operating-system]] -=== View the distribution of visitors by operating system - -To determine the operating systems you should continue to support, and the importance of mobile traffic from iOS devices, -create a donut chart that displays the top operating systems that your visitors used to access your website within the last 90 days. +. Customize the newly added panel: -. Open *Lens*, then set the <> to *Last 90 days*. +.. Drag the bottom corner of the panel until the metric takes up one quarter of the screen +width. The row for the metric will have 4 items on it later. -. From the *Chart Type* dropdown, select *Donut*. +.. The metric visualization has its own label, so you do not need to add a panel title. -. From the *Available fields* list, drag and drop the data fields to the *Drop a field or click to add* fields in the editor. +. Click *Save* on the dashboard menu -.. Drag and drop *clientip* to the *Size by* field. +. In the *Title* field, enter `Logs dashboard`. -.. Drag and drop *machine.os.keyword* to the *Slice by* field. -+ -[role="screenshot"] -image::images/lens_end_to_end_2_1_1.png[Donut chart with clientip and machine.os.keyword fields] - -. Change the color palette. - -.. In the editor, click *Top values of machine.os.keyword*. - -.. From the *Color palette* dropdown, select *Compatibility*. - -.. Click *Close*. -+ -[role="screenshot"] -image::images/lens_end_to_end_2_1.png[Donut chart with open config panel] +. Select *Store time with dashboard* box, then click *Save*. -. Click *Save and return*. +. After the dashboard refreshes, click *Edit* again. [discrete] [[mixed-multiaxis]] -=== View the average of bytes transfer per day +=== View a metric over time -To prevent potential server failures, and optimize the cost of website maintenance, create an area chart that displays the average of bytes transfer. To compare -the data to the number of visitors to your website, add a line chart layer. +*Lens* has two shortcuts that simplify viewing metrics over time. +If you drag and drop a numeric field to the workspace panel, *Lens* adds the default +time field from the index pattern. If the *Date histogram* function is being used, +quickly replace the time field by dragging and dropping on the workspace panel. -. Open *Lens*. +To visualize the `bytes` field over time without choosing a visualization type or function: -. From the *Available fields* list, drag and drop *bytes* to the visualization builder. +. From the *Available fields* list, drag and drop `bytes` onto the workspace panel to have *Lens* automatically +create a chart. *Lens* creates a bar chart with two dimensions, *timestamp* and *Median of bytes*. + +. *Lens* automatically chooses a date interval. To zoom in on the data you want to view, +click and drag your cursor across the bars. -. To zoom in on the data you want to view, click and drag your cursor across the bars. -+ [role="screenshot"] image::images/lens_end_to_end_3_1_1.gif[Zoom in on the data] -. Change the *timestamp* interval. - -.. In the editor, click *timestamp*. +To emphasize the change in *Median of bytes* over time, use a line chart. +To change the visualization type, use one of the following ways: -.. Select *Customize time interval*. +* From the *Suggestions*, click the line chart. +* Click *Bar vertical stacked*, then select *Line*. +* Click the chart type icon above *Horizontal axis*, then click the line icon. -.. Change the *Minimum interval* to `1 days`, then click *Close*. -+ -[role="screenshot"] -image::images/lens_end_to_end_3_1.png[Customize time interval] +Most users use the automatic time interval. You can increase and decrease +the minimum interval that *Lens* uses, but you cannot decrease the interval +below the {kib} advanced settings. To set the minimum time interval: -. From the *Chart Type* dropdown, select *Area*. +. In the editor, click *timestamp*. -[discrete] -[[add-a-data-layer]] -==== Add the line chart layer +. Click *How it works* to learn about the *Lens* minimum interval -To compare the average of bytes transfer to the number of users that visit your website, add a line chart layer. +. Select *Customize time interval*. -. In the editor, click *+*. +. Increase the *Minimum interval* to `1 days`, then click *Close*. + [role="screenshot"] -image::images/lens_end_to_end_3_2.png[Add new layer button] +image::images/lens_end_to_end_3_1.png[Customize time interval] -. From the new layer editor, click the *Chart type* dropdown, then click the line chart. +To save space on the dashboard, so to save space, hide the vertical and horizontal +axis labels. + +. Open the *Left axis* menu, then deselect *Show*. + [role="screenshot"] -image::images/lens_end_to_end_3_3.png[Change layer type] -+ -The chart type for the visualization changes to *Mixed XY*. - -. From the *Available fields* list, drag and drop the data fields to the *Drop a field or click to add* fields in the editor. - -.. Drag and drop *timestamp* to the *Horizontal axis* field. - -.. Drag and drop *clientip* to the *Vertical axis* field. - -. Change the *timestamp* interval. +image::images/lens_end_to_end_4_3.png[Turn off axis name] -.. In the editor, click *timestamp* in the line chart layer. +. Open the *Bottom axis* menu, then deselect *Show*. -.. Select *Customize time interval*. +. Click *Save and return* -.. Change the *Minimum interval* to `1 days`, then click *Close*. +. On the dashboard, move the panel so that it is in the same row as the *Metric* visualization panel. The two should +take up half the screen width. -. Change the *Unique count of clientip* label and color. +. Add a panel title to explain the panel, which is necessary because you removed the axis labels. -.. In the editor, click *Unique count of clientip*. +.. Open the panel menu and choose *Edit panel title*. -.. In the *Display name* field, enter `Unique visitors` in the line chart layer. +.. In the *Title* field, enter `Median of bytes`, then click *Save*. -.. In the *Series color* field, enter *#CA8EAE*, then click *Close*. +. In the toolbar, click *Save*. [discrete] -[[configure-the-multiaxis-chart]] -==== Configure the y-axes - -There is a significant difference between the *timestamp per day* and *Unique visitors* data, which makes the *Unique visitors* data difficult to read. To improve the readability, -display the *Unique visitors* data along a second y-axis, then change the formatting. When functions contain multiple formats, separate axes are created by default. +[[view-the-distribution-of-visitors-by-operating-system]] +=== View the top values of a field -. In the editor, click *Unique visitors* in the line chart layer. +The *Top values* function ranks the unique values of a field by another dimension. +The values are the most frequent when ranked by a *Count* dimension. +The values are the largest when ranked by a *Sum* dimension. -. For *Axis side*, click *Right*, then click *Close*. +When you drag and drop a text or IP address field onto the workspace panel, +*Lens* adds a *Top values* function ranked by *Count of records* to show the most frequent values. -[float] -[[change-the-visualization-type]] -==== Change the visualization type +For this tutorial, you have picked a field and function, but not a visualization type. +You want to see the most frequent values of `request.keyword` on your website, ranked by the unique visitors. +This means that you want to use *Top values of request.keyword* ranked by *Unique count of clientip*, instead of +being ranked by *Count of records*. -. In the editor, click *Average of bytes* in the area chart layer. +. From the *Available fields* list, drag and drop `clientip` onto the *Vertical axis*. +*Lens* chooses the function for you when you drop onto a dimension, which is *Unique count* here. +Do not drop the field into the main workspace because `clientip` will be added to the wrong axis. -. From the *Value format* dropdown, select *Bytes (1024)*, then click *Close*. +. Drag and drop `request.keyword` to the main workspace. *Lens* adds *Top values of request.keyword* +to the *Horizontal axis*. + [role="screenshot"] -image::images/lens_end_to_end_3_4.png[Multiaxis chart] - -[discrete] -[[lens-legend-position]] -==== Change the legend position +image::images/lens_end_to_end_2_1_1.png[Vertical bar chart with top values of request.keyword by most unique visitors] -The visualization is done, but the legend uses a lot of space. Change the legend position to the top of the chart. +This chart is hard to read because the `request.keyword` field contains long text. You could try +using one of the *Suggestions*, but the suggestions also have issues with long text. Instead, switch +to the *Table* visualization. -. From the *Legend* dropdown, select the top position. +Click *Bar vertical stacked*, then select *Table*. + [role="screenshot"] -image::images/lens_end_to_end_3_5.png[legend position] - -. Click *Save and return*. - -[discrete] -[[percentage-stacked-area]] -=== View the health of your website +image::images/lens_end_to_end_2_1_2.png[Table with top values of request.keyword by most unique visitors] -To detect unusual traffic, bad website links, and server errors, create a percentage stacked area chart that displays the associated response codes. +Next, customize the table. -. Open *Lens*. +. Click the *Top values of request.keyword* dimension. -. From the *Available fields* list, drag and drop the data fields to the *Drop a field or click to add* fields in the editor. +.. Increase the *Number of values*. The maximum allowed value is 1000. -.. Drag and drop *Records* to the *Vertical axis* field. +.. In the *Display name* field, enter `Page URL`, then click *Close*. -.. Drag and drop *@timestamp* to the *Horizontal axis* field. +. Click *Save and return*. -. From the *Chart Type* dropdown, select *Percentage bar*. +. Move the table panel so that it has its own row, but do not change the size. -. To remove the vertical axis label, click *Left axis*, then deselect *Show*. -+ -[role="screenshot"] -image::images/lens_end_to_end_4_3.png[Turn off axis name] +NOTE: You do not need a panel title because the table columns are clearly labeled. [discrete] -[[add-the-response-code-filters]] -==== Add the response code filters - -For each response code that you want to display, create a filter. - -. In the editor, click the *Drop a field or click to add* field for *Break down by*. - -. From *Select a function*, click *Filters*. - -. Add the filter for the successful response codes. +[[custom-ranges]] +=== Compare a subset of documents to all documents -.. Click *All records*. +To compare a field on subset of documents to all documents, you need to select two or more sets of documents that add up to 100%. +For this example, we are comparing documents where the `bytes` field is under 10 Kb to documents where `bytes` is over 10 Kb, +which are two sets that do not overlap. -.. In the *KQL* field, enter `response.keyword>=200 AND response.keyword<300`. +Use *Intervals* to select documents based on the number range of a field. Use *Filters* when your criteria +is not numeric, or when your query needs multiple clauses. -.. In the *Label* field, enter `2XX`, then press Return. -+ -[role="screenshot"] -image::images/lens_end_to_end_4_1.png[First filter in filters aggregation] +Use a proportion chart to display the values as a percentage of the sum of all values. Lens has 5 types of proportion charts: +pie, donut, treemap, percentage bar and percentage area. -. Add the filter for the redirect codes. +To determine if your users transfer more `bytes` from small files versus large files, +configure dimensions with *Intervals* and *Sum*, then switch to a pie chart to display as a percentage: -.. Click *Add a filter*. +. From the *Available fields* list, drag and drop `bytes` to *Vertical axis* in the editor. -.. In the *KQL* field, enter `response.keyword>=300 AND response.keyword<400`. +. Click *Median of bytes*, select *Sum*, then click *Close*. -.. In the *Label* field, enter `3XX`, then press Return. +. From the *Available fields* list, drag and drop `bytes` to *Break down by* in the editor, then specify the file size ranges. -. Add the filter for the client error codes. +.. In the editor, click *bytes*. -.. Click *Add a filter*. +.. Click *Create custom ranges*, enter the following, then press Return: -.. In the *KQL* field, enter `response.keyword>=400 AND response.keyword<500`. +* *Ranges* — `0` -> `10240` -.. In the *Label* field, enter `4XX`, then press Return. +* *Label* — `Below 10KB` -. Add the filter for the server error codes. +.. Click *Add range*, enter the following, then press Return: -.. Click *Add a filter*. +* *Ranges* — `10240` -> `+∞` -.. In the *KQL* field, enter `response.keyword>=500 AND response.keyword<600`. +* *Label* — `Above 10KB` ++ +[role="screenshot"] +image::images/lens_end_to_end_6_1.png[Custom ranges configuration] -.. In the *Label* field, enter `5XX`, then press Return. +.. From the *Value format* dropdown, select *Bytes (1024)*, then click *Close*. -. To change the color palette, select *Status* from the *Color palette* dropdown, then click *Close*. +. From the *Chart Type* dropdown, select *Pie*. . Click *Save and return*. [discrete] [[histogram]] -=== View the traffic for your website by the hour - -To find the best time to shut down your website for maintenance, create a histogram that displays the traffic for your website by the hour. - -. Open *Lens*. - -. From the *Available fields* list, drag and drop *bytes* to *Vertical axis* in the editor, then configure the options. +=== View a the distribution of a number field -.. Click *Average of bytes*. +Knowing the distribution of a number helps to find patterns. For example, you could +look at the website traffic per hour to find the best time to do routine maintenance. +Use *Intervals* to see an evenly spaced distribution of a number field. -.. From *Select a function*, click *Sum*. +. From the *Available fields* list, drag and drop `bytes` to *Vertical axis* in the editor. -.. In the *Display name* field, enter `Transferred bytes`. +. Click *Median of bytes*, then select *Sum*. -.. From the *Value format* dropdown, select `Bytes (1024)`, then click *Close*. +. In the *Display name* field, enter `Transferred bytes`. -. From the *Available fields* list, drag and drop *hour_of_day* to *Horizontal axis* in the editor, then configure the options. +. From the *Value format* dropdown, select `Bytes (1024)`, then click *Close*. -.. Click *hour_of_day*. +. From the *Available fields* list, drag and drop *hour_of_day* to *Horizontal axis* in the editor. -.. Click and slide the *Intervals granularity* slider until the horizontal axis displays hourly intervals. +. Click *hour_of_day*, and then slide the *Intervals granularity* slider until the horizontal axis displays hourly intervals. + [role="screenshot"] image::images/lens_end_to_end_5_2.png[Create custom ranges] . Click *Save and return*. -[discrete] -[[custom-ranges]] -=== View the percent of small versus large transferred files - -To determine if your users transfer more small files versus large files, create a pie chart that displays the percentage of each size. - -. Open *Lens*. - -. From the *Available fields* list, drag and drop *bytes* to *Vertical axis* in the editor, then configure the options. - -.. Click *Average of bytes*. - -.. From *Select a function*, click *Sum*, then click *Close*. - -. From the *Available fields* list, drag and drop *bytes* to *Break down by* in the editor, then specify the file size ranges. - -.. Click *bytes*. - -.. Click *Create custom ranges*, enter the following, then press Return: - -* *Ranges* — `0` -> `10240` - -* *Label* — `Below 10KB` - -.. Click *Add range*, enter the following, then press Return: - -* *Ranges* — `10240` -> `+∞` - -* *Label* — `Above 10KB` -+ -[role="screenshot"] -image::images/lens_end_to_end_6_1.png[Custom ranges configuration] - -.. From the *Value format* dropdown, select *Bytes (1024)*, then click *Close*. +. Decrease the panel size, then drag and drop it to the first row next to the `Median of bytes` panel. There +should be four panels in a row. -. From the *Chart Type* dropdown, select *Pie*. -+ -[role="screenshot"] -image::images/lens_end_to_end_6_2.png[Files size distribution] - -. Click *Save and return*. +. You do not need a panel title because the axis labels are self-explanatory. [discrete] [[treemap]] -=== View the top sources of website traffic - -To determine how users find out about your website and where your users are located, create a treemap that displays the percentage of users that -enter your website from specific social media websites, and the top countries where users are located. +=== Create a multi-level chart -. Open *Lens*. +*Lens* lets you use multiple functions in the data table and proportion charts. For example, +to create a chart that breaks down the traffic sources and user geography, use *Filters* and +*Top values*. -. From the *Chart Type* dropdown, select *Treemap*. +. Click *Bar vertical stacked*, then select *Treemap*. . From the *Available fields* list, drag and drop *Records* to the *Size by* field in the editor. @@ -377,21 +335,15 @@ enter your website from specific social media websites, and the top countries wh .. Click *Add a filter*, enter the following, then press Return: -* *KQL* — `NOT referer : *twitter* OR NOT referer: *facebook.com*` +* *KQL* — `NOT referer : *twitter.com* OR NOT referer: *facebook.com*` * *Label* — `Other` .. Click *Close*. -[discrete] -[[add-the-countries]] -==== Add the geographic data - -To determine the top countries where users are located, add the geographic data. +Add the next break down by geography: -Compare the top sources of website traffic data to the top three countries. - -. From the *Available fields* list, drag and drop *geo.src* to the visualization builder. +. From the *Available fields* list, drag and drop *geo.src* to the main workspace. . To change the *Group by* order, click and drag *Top values of geo.src* so that it appears first in the editor. + @@ -409,6 +361,12 @@ image::images/lens_end_to_end_7_3.png[Group other values as Other] . Click *Save and return*. +. Arrange the panel so that it is in the same row as the table. + +.. Click the gear icon and choose *Edit panel title*. + +.. Enter "Page views by location and referer" as the panel title, then click *Save*. + [discrete] === Save the dashboard @@ -417,3 +375,5 @@ Now that you have a complete overview of your web server data, save the dashboar . In the toolbar, click *Save*. . On the *Save dashboard* window, enter `Web server data`, then click *Save*. + +. If this was not the first time you saved the dashboard, click *Switch to view mode* diff --git a/docs/user/security/authentication/index.asciidoc b/docs/user/security/authentication/index.asciidoc index 805ae924a599e..54142a6fe39e3 100644 --- a/docs/user/security/authentication/index.asciidoc +++ b/docs/user/security/authentication/index.asciidoc @@ -7,6 +7,7 @@ {kib} supports the following authentication mechanisms: +- <> - <> - <> - <> @@ -16,7 +17,12 @@ - <> - <> -Enable multiple authentication mechanisms at the same time specifying a prioritized list of the authentication _providers_ (typically of various types) in the configuration. Providers are consulted in ascending order. Make sure each configured provider has a unique name (e.g. `basic1` or `saml1` in the configuration example) and `order` setting. In the event that two or more providers have the same name or `order`, {kib} will fail to start. +For an introduction to {kib}'s security features, including the login process, refer to <>. + +[[multiple-authentication-providers]] +==== Multiple authentication providers + +Enable multiple authentication mechanisms at the same time by specifying a prioritized list of the authentication _providers_ (typically of various types) in the configuration. Providers are consulted in ascending order. Make sure each configured provider has a unique name (e.g. `basic1` or `saml1` in the configuration example) and `order` setting. In the event that two or more providers have the same name or `order`, {kib} will fail to start. When two or more providers are configured, you can choose the provider you want to use on the Login Selector UI. The order the providers appear is determined by the `order` setting. The appearance of the specific provider entry can be customized with the `description`, `hint`, and `icon` settings. @@ -24,7 +30,7 @@ TIP: To provide login instructions to users, use the `xpack.security.loginHelp` If you don't want a specific provider to show up at the Login Selector UI (e.g. to only support third-party initiated login) you can hide it with `showInSelector` setting set to `false`. However, in this case, the provider is presented in the provider chain and may be consulted during authentication based on its `order`. To disable the provider, use the `enabled` setting. -TIP: The Login Selector UI can also be disabled or enabled with `xpack.security.authc.selector.enabled` setting. +TIP: The Login Selector UI can also be disabled or enabled with `xpack.security.authc.selector.enabled` setting. Here is how your `kibana.yml` and Login Selector UI can look like if you deal with multiple authentication providers: @@ -292,9 +298,9 @@ xpack.security.authc.providers: order: 1 ----------------------------------------------- -IMPORTANT: {kib} uses SPNEGO, which wraps the Kerberos protocol for use with HTTP, extending it to web applications. +IMPORTANT: {kib} uses SPNEGO, which wraps the Kerberos protocol for use with HTTP, extending it to web applications. At the end of the Kerberos handshake, {kib} forwards the service ticket to {es}, then {es} unpacks the service ticket and responds with an access and refresh token, which are used for subsequent authentication. -On every {es} node that {kib} connects to, the keytab file should always contain the HTTP service principal for the {kib} host. +On every {es} node that {kib} connects to, the keytab file should always contain the HTTP service principal for the {kib} host. The HTTP service principal name must have the `HTTP/kibana.domain.local@KIBANA.DOMAIN.LOCAL` format. @@ -386,7 +392,7 @@ xpack.security.authc.providers: [[anonymous-access-and-embedding]] ===== Anonymous access and embedding -One of the most popular use cases for anonymous access is when you embed {kib} into other applications and don't want to force your users to log in to view it. +One of the most popular use cases for anonymous access is when you embed {kib} into other applications and don't want to force your users to log in to view it. If you configured {kib} to use anonymous access as the sole authentication mechanism, you don't need to do anything special while embedding {kib}. If you have multiple authentication providers enabled, and you want to automatically log in anonymous users when embedding dashboards and visualizations: diff --git a/examples/search_examples/common/index.ts b/examples/search_examples/common/index.ts index cc47c0f575973..c61de9d3c6dee 100644 --- a/examples/search_examples/common/index.ts +++ b/examples/search_examples/common/index.ts @@ -6,17 +6,7 @@ * Side Public License, v 1. */ -import { IEsSearchResponse, IEsSearchRequest } from '../../../src/plugins/data/common'; - export const PLUGIN_ID = 'searchExamples'; export const PLUGIN_NAME = 'Search Examples'; -export interface IMyStrategyRequest extends IEsSearchRequest { - get_cool: boolean; -} -export interface IMyStrategyResponse extends IEsSearchResponse { - cool: string; - executed_at: number; -} - export const SERVER_SEARCH_ROUTE_PATH = '/api/examples/search'; diff --git a/examples/search_examples/common/types.ts b/examples/search_examples/common/types.ts new file mode 100644 index 0000000000000..8bb38ea0b2d0d --- /dev/null +++ b/examples/search_examples/common/types.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 { + IEsSearchRequest, + IEsSearchResponse, + IKibanaSearchRequest, + IKibanaSearchResponse, +} from '../../../src/plugins/data/common'; + +export interface IMyStrategyRequest extends IEsSearchRequest { + get_cool: boolean; +} +export interface IMyStrategyResponse extends IEsSearchResponse { + cool: string; + executed_at: number; +} + +export type FibonacciRequest = IKibanaSearchRequest<{ n: number }>; + +export type FibonacciResponse = IKibanaSearchResponse<{ values: number[] }>; diff --git a/examples/search_examples/public/search/app.tsx b/examples/search_examples/public/search/app.tsx index c9ede2ff2b45f..06f9426b4965c 100644 --- a/examples/search_examples/public/search/app.tsx +++ b/examples/search_examples/public/search/app.tsx @@ -26,27 +26,27 @@ import { EuiCode, EuiComboBox, EuiFormLabel, + EuiFieldNumber, + EuiProgress, EuiTabbedContent, + EuiTabbedContentTab, } from '@elastic/eui'; import { CoreStart } from '../../../../src/core/public'; import { mountReactNode } from '../../../../src/core/public/utils'; import { NavigationPublicPluginStart } from '../../../../src/plugins/navigation/public'; -import { - PLUGIN_ID, - PLUGIN_NAME, - IMyStrategyResponse, - SERVER_SEARCH_ROUTE_PATH, -} from '../../common'; +import { PLUGIN_ID, PLUGIN_NAME, SERVER_SEARCH_ROUTE_PATH } from '../../common'; import { DataPublicPluginStart, + IKibanaSearchResponse, IndexPattern, IndexPatternField, isCompleteResponse, isErrorResponse, } from '../../../../src/plugins/data/public'; +import { IMyStrategyResponse } from '../../common/types'; interface SearchExamplesAppDeps { notifications: CoreStart['notifications']; @@ -88,7 +88,10 @@ export const SearchExamplesApp = ({ }: SearchExamplesAppDeps) => { const { IndexPatternSelect } = data.ui; const [getCool, setGetCool] = useState(false); + const [fibonacciN, setFibonacciN] = useState(10); const [timeTook, setTimeTook] = useState(); + const [total, setTotal] = useState(100); + const [loaded, setLoaded] = useState(0); const [indexPattern, setIndexPattern] = useState(); const [fields, setFields] = useState(); const [selectedFields, setSelectedFields] = useState([]); @@ -99,7 +102,15 @@ export const SearchExamplesApp = ({ IndexPatternField | null | undefined >(); const [request, setRequest] = useState>({}); - const [response, setResponse] = useState>({}); + const [rawResponse, setRawResponse] = useState>({}); + const [selectedTab, setSelectedTab] = useState(0); + + function setResponse(response: IKibanaSearchResponse) { + setRawResponse(response.rawResponse); + setLoaded(response.loaded!); + setTotal(response.total!); + setTimeTook(response.rawResponse.took); + } // Fetch the default index pattern using the `data.indexPatterns` service, as the component is mounted. useEffect(() => { @@ -152,8 +163,7 @@ export const SearchExamplesApp = ({ .subscribe({ next: (res) => { if (isCompleteResponse(res)) { - setResponse(res.rawResponse); - setTimeTook(res.rawResponse.took); + setResponse(res); const avgResult: number | undefined = res.rawResponse.aggregations ? // @ts-expect-error @elastic/elasticsearch no way to declare a type for aggregation in the search response res.rawResponse.aggregations[1].value @@ -234,7 +244,7 @@ export const SearchExamplesApp = ({ setRequest(searchSource.getSearchRequestBody()); const { rawResponse: res } = await searchSource.fetch$().toPromise(); - setResponse(res); + setRawResponse(res); const message = Searched {res.hits.total} documents.; notifications.toasts.addSuccess( @@ -247,7 +257,7 @@ export const SearchExamplesApp = ({ } ); } catch (e) { - setResponse(e.body); + setRawResponse(e.body); notifications.toasts.addWarning(`An error has occurred: ${e.message}`); } }; @@ -260,6 +270,41 @@ export const SearchExamplesApp = ({ doAsyncSearch('myStrategy'); }; + const onPartialResultsClickHandler = () => { + setSelectedTab(1); + const req = { + params: { + n: fibonacciN, + }, + }; + + // Submit the search request using the `data.search` service. + setRequest(req.params); + const searchSubscription$ = data.search + .search(req, { + strategy: 'fibonacciStrategy', + }) + .subscribe({ + next: (res) => { + setResponse(res); + if (isCompleteResponse(res)) { + notifications.toasts.addSuccess({ + title: 'Query result', + text: 'Query finished', + }); + searchSubscription$.unsubscribe(); + } else if (isErrorResponse(res)) { + // TODO: Make response error status clearer + notifications.toasts.addWarning('An error has occurred'); + searchSubscription$.unsubscribe(); + } + }, + error: () => { + notifications.toasts.addDanger('Failed to run search'); + }, + }); + }; + const onClientSideSessionCacheClickHandler = () => { doAsyncSearch('myStrategy', data.search.session.getSessionId()); }; @@ -284,7 +329,7 @@ export const SearchExamplesApp = ({ doSearchSourceSearch(withOtherBucket); }; - const reqTabs = [ + const reqTabs: EuiTabbedContentTab[] = [ { id: 'request', name: Request, @@ -318,6 +363,7 @@ export const SearchExamplesApp = ({ values={{ time: timeTook ?? 'Unknown' }} /> + - {JSON.stringify(response, null, 2)} + {JSON.stringify(rawResponse, null, 2)} ), @@ -484,6 +530,37 @@ export const SearchExamplesApp = ({ + +

Handling partial results

+
+ + The observable returned from data.search provides partial results + when the response is not yet complete. These can be handled to update a chart or + simply a progress bar: + + + <EuiProgress value={response.loaded} max={response.total} + /> + + Below is an example showing a custom search strategy that emits partial Fibonacci + sequences up to the length provided, updates the response with each partial result, + and updates a progress bar (see the Response tab). + setFibonacciN(parseInt(event.target.value, 10))} + /> + + Request Fibonacci sequence + + +

Writing a custom search strategy

@@ -567,8 +644,13 @@ export const SearchExamplesApp = ({ + - + setSelectedTab(reqTabs.indexOf(tab))} + /> diff --git a/examples/search_examples/server/fibonacci_strategy.ts b/examples/search_examples/server/fibonacci_strategy.ts new file mode 100644 index 0000000000000..a37438aba7055 --- /dev/null +++ b/examples/search_examples/server/fibonacci_strategy.ts @@ -0,0 +1,52 @@ +/* + * 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 uuid from 'uuid'; +import { ISearchStrategy } from '../../../src/plugins/data/server'; +import { FibonacciRequest, FibonacciResponse } from '../common/types'; + +export const fibonacciStrategyProvider = (): ISearchStrategy< + FibonacciRequest, + FibonacciResponse +> => { + const responseMap = new Map(); + return ({ + search: (request: FibonacciRequest) => { + const id = request.id ?? uuid(); + const [sequence, total, started] = responseMap.get(id) ?? [ + [], + request.params?.n ?? 0, + Date.now(), + ]; + if (sequence.length < 2) { + if (total > 0) sequence.push(sequence.length); + } else { + const [a, b] = sequence.slice(-2); + sequence.push(a + b); + } + const loaded = sequence.length; + responseMap.set(id, [sequence, total, started]); + if (loaded >= total) { + responseMap.delete(id); + } + + const isRunning = loaded < total; + const isPartial = isRunning; + const took = Date.now() - started; + const values = sequence.slice(0, loaded); + + // Usually we'd do something like "of()" but for some reason it breaks in tests with the error + // "You provided an invalid object where a stream was expected." which is why we have to cast + // down below as well + return [{ id, loaded, total, isRunning, isPartial, rawResponse: { took, values } }]; + }, + cancel: async (id: string) => { + responseMap.delete(id); + }, + } as unknown) as ISearchStrategy; +}; diff --git a/examples/search_examples/server/my_strategy.ts b/examples/search_examples/server/my_strategy.ts index 0a64788960091..db8cd903f23d6 100644 --- a/examples/search_examples/server/my_strategy.ts +++ b/examples/search_examples/server/my_strategy.ts @@ -8,7 +8,7 @@ import { map } from 'rxjs/operators'; import { ISearchStrategy, PluginStart } from '../../../src/plugins/data/server'; -import { IMyStrategyResponse, IMyStrategyRequest } from '../common'; +import { IMyStrategyRequest, IMyStrategyResponse } from '../common/types'; export const mySearchStrategyProvider = ( data: PluginStart diff --git a/examples/search_examples/server/plugin.ts b/examples/search_examples/server/plugin.ts index 84f082d890bb0..984d3201220eb 100644 --- a/examples/search_examples/server/plugin.ts +++ b/examples/search_examples/server/plugin.ts @@ -24,6 +24,7 @@ import { } from './types'; import { mySearchStrategyProvider } from './my_strategy'; import { registerRoutes } from './routes'; +import { fibonacciStrategyProvider } from './fibonacci_strategy'; export class SearchExamplesPlugin implements @@ -48,7 +49,9 @@ export class SearchExamplesPlugin core.getStartServices().then(([_, depsStart]) => { const myStrategy = mySearchStrategyProvider(depsStart.data); + const fibonacciStrategy = fibonacciStrategyProvider(); deps.data.search.registerSearchStrategy('myStrategy', myStrategy); + deps.data.search.registerSearchStrategy('fibonacciStrategy', fibonacciStrategy); registerRoutes(router); }); diff --git a/package.json b/package.json index 070fde2f64c45..deb386f49c854 100644 --- a/package.json +++ b/package.json @@ -107,7 +107,7 @@ "@elastic/good": "^9.0.1-kibana3", "@elastic/maki": "6.3.0", "@elastic/node-crypto": "1.2.1", - "@elastic/numeral": "^2.5.0", + "@elastic/numeral": "^2.5.1", "@elastic/react-search-ui": "^1.5.1", "@elastic/request-crypto": "1.1.4", "@elastic/safer-lodash-set": "link:bazel-bin/packages/elastic-safer-lodash-set/npm_module", @@ -431,7 +431,7 @@ "@babel/traverse": "^7.12.12", "@babel/types": "^7.12.12", "@bazel/ibazel": "^0.15.10", - "@bazel/typescript": "^3.4.2", + "@bazel/typescript": "^3.5.0", "@cypress/snapshot": "^2.1.7", "@cypress/webpack-preprocessor": "^5.6.0", "@elastic/apm-rum": "^5.6.1", @@ -453,11 +453,11 @@ "@kbn/eslint-plugin-eslint": "link:bazel-bin/packages/kbn-eslint-plugin-eslint/npm_module", "@kbn/expect": "link:bazel-bin/packages/kbn-expect/npm_module", "@kbn/optimizer": "link:packages/kbn-optimizer", - "@kbn/plugin-generator": "link:packages/kbn-plugin-generator", + "@kbn/plugin-generator": "link:bazel-bin/packages/kbn-plugin-generator/npm_module", "@kbn/plugin-helpers": "link:packages/kbn-plugin-helpers", "@kbn/pm": "link:packages/kbn-pm", "@kbn/storybook": "link:packages/kbn-storybook", - "@kbn/telemetry-tools": "link:packages/kbn-telemetry-tools", + "@kbn/telemetry-tools": "link:bazel-bin/packages/kbn-telemetry-tools/npm_module", "@kbn/test": "link:packages/kbn-test", "@kbn/test-subj-selector": "link:packages/kbn-test-subj-selector", "@loaders.gl/polyfills": "^2.3.5", diff --git a/packages/BUILD.bazel b/packages/BUILD.bazel index 8b483f54344e2..f4465d439e9f8 100644 --- a/packages/BUILD.bazel +++ b/packages/BUILD.bazel @@ -22,10 +22,12 @@ filegroup( "//packages/kbn-expect:build", "//packages/kbn-legacy-logging:build", "//packages/kbn-logging:build", + "//packages/kbn-plugin-generator:build", "//packages/kbn-securitysolution-constants:build", "//packages/kbn-securitysolution-io-ts-utils:build", "//packages/kbn-securitysolution-utils:build", "//packages/kbn-std:build", + "//packages/kbn-telemetry-tools:build", "//packages/kbn-tinymath:build", "//packages/kbn-utility-types:build", "//packages/kbn-utils:build", diff --git a/packages/kbn-config/src/deprecation/apply_deprecations.test.ts b/packages/kbn-config/src/deprecation/apply_deprecations.test.ts index f2c0a43916343..47746967bbe5f 100644 --- a/packages/kbn-config/src/deprecation/apply_deprecations.test.ts +++ b/packages/kbn-config/src/deprecation/apply_deprecations.test.ts @@ -36,10 +36,9 @@ describe('applyDeprecations', () => { const addDeprecation = jest.fn(); const createAddDeprecation = jest.fn().mockReturnValue(addDeprecation); const initialConfig = { foo: 'bar', deprecated: 'deprecated' }; - const alteredConfig = { foo: 'bar' }; - const handlerA = jest.fn().mockReturnValue(alteredConfig); - const handlerB = jest.fn().mockImplementation((conf) => conf); + const handlerA = jest.fn().mockReturnValue({ unset: [{ path: 'deprecated' }] }); + const handlerB = jest.fn().mockReturnValue(undefined); applyDeprecations( initialConfig, @@ -47,8 +46,6 @@ describe('applyDeprecations', () => { createAddDeprecation ); - expect(handlerA).toHaveBeenCalledWith(initialConfig, 'pathA', addDeprecation); - expect(handlerB).toHaveBeenCalledWith(alteredConfig, 'pathB', addDeprecation); expect(createAddDeprecation).toBeCalledTimes(2); expect(createAddDeprecation).toHaveBeenNthCalledWith(1, 'pathA'); expect(createAddDeprecation).toHaveBeenNthCalledWith(2, 'pathB'); @@ -60,8 +57,15 @@ describe('applyDeprecations', () => { const initialConfig = { foo: 'bar', deprecated: 'deprecated' }; const alteredConfig = { foo: 'bar' }; - const handlerA = jest.fn().mockReturnValue(alteredConfig); - const handlerB = jest.fn().mockImplementation((conf) => conf); + const configs: Array<{ fn: string; config: Record }> = []; + const handlerA = jest.fn().mockImplementation((config) => { + // the first argument is mutated between calls, we store a copy of it + configs.push({ fn: 'handlerA', config: { ...config } }); + return { unset: [{ path: 'deprecated' }] }; + }); + const handlerB = jest.fn().mockImplementation((config) => { + configs.push({ fn: 'handlerB', config: { ...config } }); + }); applyDeprecations( initialConfig, @@ -69,8 +73,10 @@ describe('applyDeprecations', () => { createAddDeprecation ); - expect(handlerA).toHaveBeenCalledWith(initialConfig, 'pathA', addDeprecation); - expect(handlerB).toHaveBeenCalledWith(alteredConfig, 'pathB', addDeprecation); + expect(configs).toEqual([ + { fn: 'handlerA', config: initialConfig }, + { fn: 'handlerB', config: alteredConfig }, + ]); }); it('returns the migrated config', () => { @@ -94,4 +100,40 @@ describe('applyDeprecations', () => { expect(initialConfig).toEqual({ foo: 'bar', deprecated: 'deprecated' }); expect(migrated).toEqual({ foo: 'bar' }); }); + + it('ignores a command for unknown path', () => { + const addDeprecation = jest.fn(); + const createAddDeprecation = jest.fn().mockReturnValue(addDeprecation); + const initialConfig = { foo: 'bar', deprecated: 'deprecated' }; + + const handler = jest.fn().mockImplementation((config) => { + return { unset: [{ path: 'unknown' }] }; + }); + + const migrated = applyDeprecations( + initialConfig, + [wrapHandler(handler, 'pathA')], + createAddDeprecation + ); + + expect(migrated).toEqual(initialConfig); + }); + + it('ignores an unknown command', () => { + const addDeprecation = jest.fn(); + const createAddDeprecation = jest.fn().mockReturnValue(addDeprecation); + const initialConfig = { foo: 'bar', deprecated: 'deprecated' }; + + const handler = jest.fn().mockImplementation((config) => { + return { rewrite: [{ path: 'foo' }] }; + }); + + const migrated = applyDeprecations( + initialConfig, + [wrapHandler(handler, 'pathA')], + createAddDeprecation + ); + + expect(migrated).toEqual(initialConfig); + }); }); diff --git a/packages/kbn-config/src/deprecation/apply_deprecations.ts b/packages/kbn-config/src/deprecation/apply_deprecations.ts index 6aced541dc30d..092a5ced28371 100644 --- a/packages/kbn-config/src/deprecation/apply_deprecations.ts +++ b/packages/kbn-config/src/deprecation/apply_deprecations.ts @@ -6,7 +6,8 @@ * Side Public License, v 1. */ -import { cloneDeep } from 'lodash'; +import { cloneDeep, unset } from 'lodash'; +import { set } from '@elastic/safer-lodash-set'; import { ConfigDeprecationWithContext, AddConfigDeprecation } from './types'; const noopAddDeprecationFactory: () => AddConfigDeprecation = () => () => undefined; @@ -22,9 +23,21 @@ export const applyDeprecations = ( deprecations: ConfigDeprecationWithContext[], createAddDeprecation: (pluginId: string) => AddConfigDeprecation = noopAddDeprecationFactory ) => { - let processed = cloneDeep(config); + const result = cloneDeep(config); deprecations.forEach(({ deprecation, path }) => { - processed = deprecation(processed, path, createAddDeprecation(path)); + const commands = deprecation(result, path, createAddDeprecation(path)); + if (commands) { + if (commands.set) { + commands.set.forEach(function ({ path: commandPath, value }) { + set(result, commandPath, value); + }); + } + if (commands.unset) { + commands.unset.forEach(function ({ path: commandPath }) { + unset(result, commandPath); + }); + } + } }); - return processed; + return result; }; diff --git a/packages/kbn-config/src/deprecation/deprecation_factory.test.ts b/packages/kbn-config/src/deprecation/deprecation_factory.test.ts index 11a49ed79d170..563d4017f5ed9 100644 --- a/packages/kbn-config/src/deprecation/deprecation_factory.test.ts +++ b/packages/kbn-config/src/deprecation/deprecation_factory.test.ts @@ -29,15 +29,15 @@ describe('DeprecationFactory', () => { property: 'value', }, }; - const processed = rename('deprecated', 'renamed')(rawConfig, 'myplugin', addDeprecation); - expect(processed).toEqual({ - myplugin: { - renamed: 'toberenamed', - valid: 'valid', - }, - someOtherPlugin: { - property: 'value', - }, + const commands = rename('deprecated', 'renamed')(rawConfig, 'myplugin', addDeprecation); + expect(commands).toEqual({ + set: [ + { + path: 'myplugin.renamed', + value: 'toberenamed', + }, + ], + unset: [{ path: 'myplugin.deprecated' }], }); expect(addDeprecation.mock.calls).toMatchInlineSnapshot(` Array [ @@ -64,16 +64,8 @@ describe('DeprecationFactory', () => { property: 'value', }, }; - const processed = rename('deprecated', 'new')(rawConfig, 'myplugin', addDeprecation); - expect(processed).toEqual({ - myplugin: { - new: 'new', - valid: 'valid', - }, - someOtherPlugin: { - property: 'value', - }, - }); + const commands = rename('deprecated', 'new')(rawConfig, 'myplugin', addDeprecation); + expect(commands).toBeUndefined(); expect(addDeprecation).toHaveBeenCalledTimes(0); }); it('handles nested keys', () => { @@ -88,22 +80,19 @@ describe('DeprecationFactory', () => { property: 'value', }, }; - const processed = rename('oldsection.deprecated', 'newsection.renamed')( + const commands = rename('oldsection.deprecated', 'newsection.renamed')( rawConfig, 'myplugin', addDeprecation ); - expect(processed).toEqual({ - myplugin: { - oldsection: {}, - newsection: { - renamed: 'toberenamed', + expect(commands).toEqual({ + set: [ + { + path: 'myplugin.newsection.renamed', + value: 'toberenamed', }, - valid: 'valid', - }, - someOtherPlugin: { - property: 'value', - }, + ], + unset: [{ path: 'myplugin.oldsection.deprecated' }], }); expect(addDeprecation.mock.calls).toMatchInlineSnapshot(` Array [ @@ -127,11 +116,9 @@ describe('DeprecationFactory', () => { renamed: 'renamed', }, }; - const processed = rename('deprecated', 'renamed')(rawConfig, 'myplugin', addDeprecation); - expect(processed).toEqual({ - myplugin: { - renamed: 'renamed', - }, + const commands = rename('deprecated', 'renamed')(rawConfig, 'myplugin', addDeprecation); + expect(commands).toEqual({ + unset: [{ path: 'myplugin.deprecated' }], }); expect(addDeprecation.mock.calls).toMatchInlineSnapshot(` Array [ @@ -162,19 +149,19 @@ describe('DeprecationFactory', () => { property: 'value', }, }; - const processed = renameFromRoot('myplugin.deprecated', 'myplugin.renamed')( + const commands = renameFromRoot('myplugin.deprecated', 'myplugin.renamed')( rawConfig, 'does-not-matter', addDeprecation ); - expect(processed).toEqual({ - myplugin: { - renamed: 'toberenamed', - valid: 'valid', - }, - someOtherPlugin: { - property: 'value', - }, + expect(commands).toEqual({ + set: [ + { + path: 'myplugin.renamed', + value: 'toberenamed', + }, + ], + unset: [{ path: 'myplugin.deprecated' }], }); expect(addDeprecation.mock.calls).toMatchInlineSnapshot(` Array [ @@ -202,19 +189,19 @@ describe('DeprecationFactory', () => { property: 'value', }, }; - const processed = renameFromRoot('oldplugin.deprecated', 'newplugin.renamed')( + const commands = renameFromRoot('oldplugin.deprecated', 'newplugin.renamed')( rawConfig, 'does-not-matter', addDeprecation ); - expect(processed).toEqual({ - oldplugin: { - valid: 'valid', - }, - newplugin: { - renamed: 'toberenamed', - property: 'value', - }, + expect(commands).toEqual({ + set: [ + { + path: 'newplugin.renamed', + value: 'toberenamed', + }, + ], + unset: [{ path: 'oldplugin.deprecated' }], }); expect(addDeprecation.mock.calls).toMatchInlineSnapshot(` Array [ @@ -242,20 +229,12 @@ describe('DeprecationFactory', () => { property: 'value', }, }; - const processed = renameFromRoot('myplugin.deprecated', 'myplugin.new')( + const commands = renameFromRoot('myplugin.deprecated', 'myplugin.new')( rawConfig, 'does-not-matter', addDeprecation ); - expect(processed).toEqual({ - myplugin: { - new: 'new', - valid: 'valid', - }, - someOtherPlugin: { - property: 'value', - }, - }); + expect(commands).toBeUndefined(); expect(addDeprecation).toBeCalledTimes(0); }); @@ -266,15 +245,13 @@ describe('DeprecationFactory', () => { renamed: 'renamed', }, }; - const processed = renameFromRoot('myplugin.deprecated', 'myplugin.renamed')( + const commands = renameFromRoot('myplugin.deprecated', 'myplugin.renamed')( rawConfig, 'does-not-matter', addDeprecation ); - expect(processed).toEqual({ - myplugin: { - renamed: 'renamed', - }, + expect(commands).toEqual({ + unset: [{ path: 'myplugin.deprecated' }], }); expect(addDeprecation.mock.calls).toMatchInlineSnapshot(` @@ -306,14 +283,9 @@ describe('DeprecationFactory', () => { property: 'value', }, }; - const processed = unused('deprecated')(rawConfig, 'myplugin', addDeprecation); - expect(processed).toEqual({ - myplugin: { - valid: 'valid', - }, - someOtherPlugin: { - property: 'value', - }, + const commands = unused('deprecated')(rawConfig, 'myplugin', addDeprecation); + expect(commands).toEqual({ + unset: [{ path: 'myplugin.deprecated' }], }); expect(addDeprecation.mock.calls).toMatchInlineSnapshot(` Array [ @@ -343,17 +315,10 @@ describe('DeprecationFactory', () => { property: 'value', }, }; - const processed = unused('section.deprecated')(rawConfig, 'myplugin', addDeprecation); - expect(processed).toEqual({ - myplugin: { - valid: 'valid', - section: {}, - }, - someOtherPlugin: { - property: 'value', - }, + const commands = unused('section.deprecated')(rawConfig, 'myplugin', addDeprecation); + expect(commands).toEqual({ + unset: [{ path: 'myplugin.section.deprecated' }], }); - expect(addDeprecation.mock.calls).toMatchInlineSnapshot(` Array [ Array [ @@ -379,15 +344,8 @@ describe('DeprecationFactory', () => { property: 'value', }, }; - const processed = unused('deprecated')(rawConfig, 'myplugin', addDeprecation); - expect(processed).toEqual({ - myplugin: { - valid: 'valid', - }, - someOtherPlugin: { - property: 'value', - }, - }); + const commands = unused('deprecated')(rawConfig, 'myplugin', addDeprecation); + expect(commands).toBeUndefined(); expect(addDeprecation).toBeCalledTimes(0); }); }); @@ -403,20 +361,14 @@ describe('DeprecationFactory', () => { property: 'value', }, }; - const processed = unusedFromRoot('myplugin.deprecated')( + const commands = unusedFromRoot('myplugin.deprecated')( rawConfig, 'does-not-matter', addDeprecation ); - expect(processed).toEqual({ - myplugin: { - valid: 'valid', - }, - someOtherPlugin: { - property: 'value', - }, + expect(commands).toEqual({ + unset: [{ path: 'myplugin.deprecated' }], }); - expect(addDeprecation.mock.calls).toMatchInlineSnapshot(` Array [ Array [ @@ -442,19 +394,12 @@ describe('DeprecationFactory', () => { property: 'value', }, }; - const processed = unusedFromRoot('myplugin.deprecated')( + const commands = unusedFromRoot('myplugin.deprecated')( rawConfig, 'does-not-matter', addDeprecation ); - expect(processed).toEqual({ - myplugin: { - valid: 'valid', - }, - someOtherPlugin: { - property: 'value', - }, - }); + expect(commands).toBeUndefined(); expect(addDeprecation).toBeCalledTimes(0); }); }); diff --git a/packages/kbn-config/src/deprecation/deprecation_factory.ts b/packages/kbn-config/src/deprecation/deprecation_factory.ts index 140846d86ae0b..76bcc1958d0de 100644 --- a/packages/kbn-config/src/deprecation/deprecation_factory.ts +++ b/packages/kbn-config/src/deprecation/deprecation_factory.ts @@ -7,13 +7,12 @@ */ import { get } from 'lodash'; -import { set } from '@elastic/safer-lodash-set'; -import { unset } from '@kbn/std'; import { ConfigDeprecation, AddConfigDeprecation, ConfigDeprecationFactory, DeprecatedConfigDetails, + ConfigDeprecationCommand, } from './types'; const _rename = ( @@ -23,20 +22,16 @@ const _rename = ( oldKey: string, newKey: string, details?: Partial -) => { +): void | ConfigDeprecationCommand => { const fullOldPath = getPath(rootPath, oldKey); const oldValue = get(config, fullOldPath); if (oldValue === undefined) { - return config; + return; } - unset(config, fullOldPath); - const fullNewPath = getPath(rootPath, newKey); const newValue = get(config, fullNewPath); if (newValue === undefined) { - set(config, fullNewPath, oldValue); - addDeprecation({ message: `"${fullOldPath}" is deprecated and has been replaced by "${fullNewPath}"`, correctiveActions: { @@ -46,6 +41,10 @@ const _rename = ( }, ...details, }); + return { + set: [{ path: fullNewPath, value: oldValue }], + unset: [{ path: fullOldPath }], + }; } else { addDeprecation({ message: `"${fullOldPath}" is deprecated and has been replaced by "${fullNewPath}". However both key are present, ignoring "${fullOldPath}"`, @@ -59,7 +58,9 @@ const _rename = ( }); } - return config; + return { + unset: [{ path: fullOldPath }], + }; }; const _unused = ( @@ -68,12 +69,11 @@ const _unused = ( addDeprecation: AddConfigDeprecation, unusedKey: string, details?: Partial -) => { +): void | ConfigDeprecationCommand => { const fullPath = getPath(rootPath, unusedKey); if (get(config, fullPath) === undefined) { - return config; + return; } - unset(config, fullPath); addDeprecation({ message: `${fullPath} is deprecated and is no longer used`, correctiveActions: { @@ -83,7 +83,9 @@ const _unused = ( }, ...details, }); - return config; + return { + unset: [{ path: fullPath }], + }; }; const rename = ( diff --git a/packages/kbn-config/src/deprecation/index.ts b/packages/kbn-config/src/deprecation/index.ts index 3286acca9e584..48576e6d830be 100644 --- a/packages/kbn-config/src/deprecation/index.ts +++ b/packages/kbn-config/src/deprecation/index.ts @@ -8,6 +8,7 @@ export type { ConfigDeprecation, + ConfigDeprecationCommand, ConfigDeprecationWithContext, ConfigDeprecationFactory, AddConfigDeprecation, diff --git a/packages/kbn-config/src/deprecation/types.ts b/packages/kbn-config/src/deprecation/types.ts index 3b1d004d7ec76..6944f45c1e1d2 100644 --- a/packages/kbn-config/src/deprecation/types.ts +++ b/packages/kbn-config/src/deprecation/types.ts @@ -5,7 +5,7 @@ * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ - +import type { RecursiveReadonly } from '@kbn/utility-types'; /** * Config deprecation hook used when invoking a {@link ConfigDeprecation} * @@ -41,14 +41,29 @@ export interface DeprecatedConfigDetails { * @remarks * This should only be manually implemented if {@link ConfigDeprecationFactory} does not provide the proper helpers for a specific * deprecation need. + * @param config must not be mutated, return {@link ConfigDeprecationCommand} to change config shape. * - * @public + * @example + * ```typescript + * const provider: ConfigDeprecation = (config, path) => ({ unset: [{ key: 'path.to.key' }] }) + * ``` + * @internal */ export type ConfigDeprecation = ( - config: Record, + config: RecursiveReadonly>, fromPath: string, addDeprecation: AddConfigDeprecation -) => Record; +) => void | ConfigDeprecationCommand; + +/** + * Outcome of deprecation operation. Allows mutating config values in a declarative way. + * + * @public + */ +export interface ConfigDeprecationCommand { + set?: Array<{ path: string; value: any }>; + unset?: Array<{ path: string }>; +} /** * A provider that should returns a list of {@link ConfigDeprecation}. @@ -60,7 +75,7 @@ export type ConfigDeprecation = ( * const provider: ConfigDeprecationProvider = ({ rename, unused }) => [ * rename('oldKey', 'newKey'), * unused('deprecatedKey'), - * myCustomDeprecation, + * (config, path) => ({ unset: [{ key: 'path.to.key' }] }) * ] * ``` * diff --git a/packages/kbn-config/src/index.ts b/packages/kbn-config/src/index.ts index a9ea8265a3768..cf875d3daa4a2 100644 --- a/packages/kbn-config/src/index.ts +++ b/packages/kbn-config/src/index.ts @@ -12,6 +12,7 @@ export type { ConfigDeprecationProvider, ConfigDeprecationWithContext, ConfigDeprecation, + ConfigDeprecationCommand, } from './deprecation'; export { applyDeprecations, configDeprecationFactory } from './deprecation'; diff --git a/packages/kbn-crypto/BUILD.bazel b/packages/kbn-crypto/BUILD.bazel index 8f55f0e0f06a7..14e292c056db6 100644 --- a/packages/kbn-crypto/BUILD.bazel +++ b/packages/kbn-crypto/BUILD.bazel @@ -2,7 +2,7 @@ load("@npm//@bazel/typescript:index.bzl", "ts_config", "ts_project") load("@build_bazel_rules_nodejs//:index.bzl", "js_library", "pkg_npm") -PKG_BASE_NAME = "kbn-cypto" +PKG_BASE_NAME = "kbn-crypto" PKG_REQUIRE_NAME = "@kbn/crypto" SOURCE_FILES = glob( diff --git a/packages/kbn-es/README.md b/packages/kbn-es/README.md index 4d4c2aa94db07..80850c9e6a09c 100644 --- a/packages/kbn-es/README.md +++ b/packages/kbn-es/README.md @@ -7,6 +7,8 @@ If running elasticsearch from source, elasticsearch needs to be cloned to a sibl To run, go to the Kibana root and run `node scripts/es --help` to get the latest command line options. +The script attempts to preserve the existing interfaces used by Elasticsearch CLI. This includes passing through options with the `-E` argument and the `ES_JAVA_OPTS` environment variable for Java options. + ### Examples Run a snapshot install with a trial license diff --git a/packages/kbn-es/src/cluster.js b/packages/kbn-es/src/cluster.js index c55e5d3513c44..ad9ecb059031c 100644 --- a/packages/kbn-es/src/cluster.js +++ b/packages/kbn-es/src/cluster.js @@ -236,6 +236,7 @@ exports.Cluster = class Cluster { * @param {String} installPath * @param {Object} options * @property {string|Array} options.esArgs + * @property {string} options.esJavaOpts * @return {undefined} */ _exec(installPath, options = {}) { @@ -268,14 +269,17 @@ exports.Cluster = class Cluster { this._log.debug('%s %s', ES_BIN, args.join(' ')); - options.esEnvVars = options.esEnvVars || {}; + let esJavaOpts = `${options.esJavaOpts || ''} ${process.env.ES_JAVA_OPTS || ''}`; // ES now automatically sets heap size to 50% of the machine's available memory // so we need to set it to a smaller size for local dev and CI // especially because we currently run many instances of ES on the same machine during CI - options.esEnvVars.ES_JAVA_OPTS = - (options.esEnvVars.ES_JAVA_OPTS ? `${options.esEnvVars.ES_JAVA_OPTS} ` : '') + - '-Xms1g -Xmx1g'; + // inital and max must be the same, so we only need to check the max + if (!esJavaOpts.includes('Xmx')) { + esJavaOpts += ' -Xms1g -Xmx1g'; + } + + this._log.debug('ES_JAVA_OPTS: %s', esJavaOpts.trim()); this._process = execa(ES_BIN, args, { cwd: installPath, @@ -283,7 +287,7 @@ exports.Cluster = class Cluster { ...(installPath ? { ES_TMPDIR: path.resolve(installPath, 'ES_TMPDIR') } : {}), ...process.env, JAVA_HOME: '', // By default, we want to always unset JAVA_HOME so that the bundled JDK will be used - ...(options.esEnvVars || {}), + ES_JAVA_OPTS: esJavaOpts.trim(), }, stdio: ['ignore', 'pipe', 'pipe'], }); diff --git a/packages/kbn-es/src/integration_tests/cluster.test.js b/packages/kbn-es/src/integration_tests/cluster.test.js index 6b4025840283f..34220b08d2120 100644 --- a/packages/kbn-es/src/integration_tests/cluster.test.js +++ b/packages/kbn-es/src/integration_tests/cluster.test.js @@ -71,11 +71,17 @@ function mockEsBin({ exitCode, start }) { ); } +const initialEnv = { ...process.env }; + beforeEach(() => { jest.resetAllMocks(); extractConfigFiles.mockImplementation((config) => config); }); +afterEach(() => { + process.env = { ...initialEnv }; +}); + describe('#installSource()', () => { it('awaits installSource() promise and returns { installPath }', async () => { let resolveInstallSource; @@ -355,6 +361,25 @@ describe('#run()', () => { ] `); }); + + it('sets default Java heap', async () => { + mockEsBin({ start: true }); + + const cluster = new Cluster({ log }); + await cluster.run(); + + expect(execa.mock.calls[0][2].env.ES_JAVA_OPTS).toEqual('-Xms1g -Xmx1g'); + }); + + it('allows Java heap to be overwritten', async () => { + mockEsBin({ start: true }); + process.env.ES_JAVA_OPTS = '-Xms5g -Xmx5g'; + + const cluster = new Cluster({ log }); + await cluster.run(); + + expect(execa.mock.calls[0][2].env.ES_JAVA_OPTS).toEqual('-Xms5g -Xmx5g'); + }); }); describe('#stop()', () => { diff --git a/packages/kbn-optimizer/limits.yml b/packages/kbn-optimizer/limits.yml index 63dd64f9202b3..08e90ed829d4a 100644 --- a/packages/kbn-optimizer/limits.yml +++ b/packages/kbn-optimizer/limits.yml @@ -46,7 +46,7 @@ pageLoadAssetSize: lens: 96624 licenseManagement: 41817 licensing: 29004 - lists: 228500 + lists: 280504 logstash: 53548 management: 46112 maps: 80000 diff --git a/packages/kbn-plugin-generator/BUILD.bazel b/packages/kbn-plugin-generator/BUILD.bazel new file mode 100644 index 0000000000000..e22d41076db00 --- /dev/null +++ b/packages/kbn-plugin-generator/BUILD.bazel @@ -0,0 +1,106 @@ +load("@npm//@bazel/typescript:index.bzl", "ts_config", "ts_project") +load("@build_bazel_rules_nodejs//:index.bzl", "js_library", "pkg_npm") + +PKG_BASE_NAME = "kbn-plugin-generator" +PKG_REQUIRE_NAME = "@kbn/plugin-generator" + +SOURCE_FILES = glob( + [ + "src/**/*.ts", + ], + exclude = [ + "**/integration_tests/**/*", + ], +) + +SRCS = SOURCE_FILES + +filegroup( + name = "srcs", + srcs = SRCS, +) + +filegroup( + name = "template", + srcs = glob( + [ + "template/**/*", + ], + ), +) + +NPM_MODULE_EXTRA_FILES = [ + "package.json", + "README.md", + ":template", +] + +SRC_DEPS = [ + "//packages/kbn-utils", + "//packages/kbn-dev-utils", + "@npm//del", + "@npm//ejs", + "@npm//execa", + "@npm//globby", + "@npm//inquirer", + "@npm//minimatch", + "@npm//prettier", + "@npm//vinyl-fs", +] + +TYPES_DEPS = [ + "@npm//@types/ejs", + "@npm//@types/inquirer", + "@npm//@types/jest", + "@npm//@types/minimatch", + "@npm//@types/node", + "@npm//@types/prettier", + "@npm//@types/vinyl-fs", +] + +DEPS = SRC_DEPS + TYPES_DEPS + +ts_config( + name = "tsconfig", + src = "tsconfig.json", + deps = [ + "//:tsconfig.base.json", + ], +) + +ts_project( + name = "tsc", + args = ['--pretty'], + srcs = SRCS, + deps = DEPS, + declaration = True, + declaration_map = True, + incremental = True, + out_dir = "target", + source_map = True, + root_dir = "src", + tsconfig = ":tsconfig", +) + +js_library( + name = PKG_BASE_NAME, + srcs = NPM_MODULE_EXTRA_FILES, + deps = [":tsc"] + DEPS, + package_name = PKG_REQUIRE_NAME, + visibility = ["//visibility:public"], +) + +pkg_npm( + name = "npm_module", + deps = [ + ":%s" % PKG_BASE_NAME, + ] +) + +filegroup( + name = "build", + srcs = [ + ":npm_module", + ], + visibility = ["//visibility:public"], +) diff --git a/packages/kbn-plugin-generator/package.json b/packages/kbn-plugin-generator/package.json index 583085430d915..298373afd2f24 100644 --- a/packages/kbn-plugin-generator/package.json +++ b/packages/kbn-plugin-generator/package.json @@ -4,9 +4,5 @@ "private": true, "license": "SSPL-1.0 OR Elastic License 2.0", "main": "target/index.js", - "types": "target/index.d.ts", - "scripts": { - "kbn:bootstrap": "node scripts/build", - "kbn:watch": "node scripts/build --watch" - } + "types": "target/index.d.ts" } \ No newline at end of file diff --git a/packages/kbn-plugin-generator/scripts/build.js b/packages/kbn-plugin-generator/scripts/build.js deleted file mode 100644 index e17f564bc482c..0000000000000 --- a/packages/kbn-plugin-generator/scripts/build.js +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -const Path = require('path'); - -const { run } = require('@kbn/dev-utils'); -const del = require('del'); -const execa = require('execa'); - -run( - async ({ flags }) => { - await del(Path.resolve(__dirname, '../target')); - - await execa(require.resolve('typescript/bin/tsc'), flags.watch ? ['--watch'] : [], { - cwd: Path.resolve(__dirname, '..'), - stdio: 'inherit', - }); - }, - { - flags: { - boolean: ['watch'], - help: ` - --watch Watch files and rebuild on changes - `, - }, - } -); diff --git a/packages/kbn-plugin-generator/tsconfig.json b/packages/kbn-plugin-generator/tsconfig.json index 5e885527a7608..9165fd21ebea0 100644 --- a/packages/kbn-plugin-generator/tsconfig.json +++ b/packages/kbn-plugin-generator/tsconfig.json @@ -1,7 +1,7 @@ { "extends": "../../tsconfig.base.json", "compilerOptions": { - "incremental": false, + "incremental": true, "outDir": "target", "target": "ES2019", "declaration": true, diff --git a/packages/kbn-securitysolution-io-ts-utils/src/format_errors/index.ts b/packages/kbn-securitysolution-io-ts-utils/src/format_errors/index.ts index 7df66dcd13596..ec37adf1221f2 100644 --- a/packages/kbn-securitysolution-io-ts-utils/src/format_errors/index.ts +++ b/packages/kbn-securitysolution-io-ts-utils/src/format_errors/index.ts @@ -21,7 +21,9 @@ export const formatErrors = (errors: t.Errors): string[] => { .map((entry) => entry.key) .join(','); - const nameContext = error.context.find((entry) => entry.type?.name?.length > 0); + const nameContext = error.context.find( + (entry) => entry.type != null && entry.type.name != null && entry.type.name.length > 0 + ); const suppliedValue = keyContext !== '' ? keyContext : nameContext != null ? nameContext.type.name : ''; const value = isObject(error.value) ? JSON.stringify(error.value) : error.value; diff --git a/packages/kbn-securitysolution-io-ts-utils/src/index.ts b/packages/kbn-securitysolution-io-ts-utils/src/index.ts index bae90fed29dea..1a18293393af5 100644 --- a/packages/kbn-securitysolution-io-ts-utils/src/index.ts +++ b/packages/kbn-securitysolution-io-ts-utils/src/index.ts @@ -41,12 +41,14 @@ export * from './from'; export * from './id'; export * from './iso_date_string'; export * from './language'; +export * from './list_types'; export * from './max_signals'; export * from './meta'; export * from './name'; export * from './non_empty_array'; export * from './non_empty_or_nullable_string_array'; export * from './non_empty_string'; +export * from './non_empty_string_array'; export * from './normalized_ml_job_id'; export * from './only_false_allowed'; export * from './operator'; @@ -61,6 +63,7 @@ export * from './severity'; export * from './severity_mapping'; export * from './string_to_positive_number'; export * from './tags'; +export * from './test_utils'; export * from './threat'; export * from './threat_mapping'; export * from './threat_subtechnique'; diff --git a/packages/kbn-securitysolution-io-ts-utils/src/list_types/entries_exist/index.ts b/packages/kbn-securitysolution-io-ts-utils/src/list_types/entries_exist/index.ts index 79c58944ea3f5..f8f1ddecc9ff9 100644 --- a/packages/kbn-securitysolution-io-ts-utils/src/list_types/entries_exist/index.ts +++ b/packages/kbn-securitysolution-io-ts-utils/src/list_types/entries_exist/index.ts @@ -8,7 +8,7 @@ import * as t from 'io-ts'; -import { operator } from '../operator'; +import { listOperator as operator } from '../list_operator'; import { NonEmptyString } from '../../non_empty_string'; export const entriesExists = t.exact( diff --git a/packages/kbn-securitysolution-io-ts-utils/src/list_types/entries_list/index.ts b/packages/kbn-securitysolution-io-ts-utils/src/list_types/entries_list/index.ts index f5c662a67158b..b386ca35d2bbb 100644 --- a/packages/kbn-securitysolution-io-ts-utils/src/list_types/entries_list/index.ts +++ b/packages/kbn-securitysolution-io-ts-utils/src/list_types/entries_list/index.ts @@ -10,7 +10,7 @@ import * as t from 'io-ts'; import { NonEmptyString } from '../../non_empty_string'; import { type } from '../type'; -import { operator } from '../operator'; +import { listOperator as operator } from '../list_operator'; export const entriesList = t.exact( t.type({ diff --git a/packages/kbn-securitysolution-io-ts-utils/src/list_types/entry_match/index.ts b/packages/kbn-securitysolution-io-ts-utils/src/list_types/entry_match/index.ts index 668da1a398093..cab6d0dd4a07f 100644 --- a/packages/kbn-securitysolution-io-ts-utils/src/list_types/entry_match/index.ts +++ b/packages/kbn-securitysolution-io-ts-utils/src/list_types/entry_match/index.ts @@ -8,7 +8,7 @@ import * as t from 'io-ts'; import { NonEmptyString } from '../../non_empty_string'; -import { operator } from '../operator'; +import { listOperator as operator } from '../list_operator'; export const entriesMatch = t.exact( t.type({ diff --git a/packages/kbn-securitysolution-io-ts-utils/src/list_types/entry_match_any/index.ts b/packages/kbn-securitysolution-io-ts-utils/src/list_types/entry_match_any/index.ts index 4e7690a80f470..0add9a610f30b 100644 --- a/packages/kbn-securitysolution-io-ts-utils/src/list_types/entry_match_any/index.ts +++ b/packages/kbn-securitysolution-io-ts-utils/src/list_types/entry_match_any/index.ts @@ -8,7 +8,7 @@ import * as t from 'io-ts'; -import { operator } from '../operator'; +import { listOperator as operator } from '../list_operator'; import { nonEmptyOrNullableStringArray } from '../../non_empty_or_nullable_string_array'; import { NonEmptyString } from '../../non_empty_string'; diff --git a/packages/kbn-securitysolution-io-ts-utils/src/list_types/entry_match_wildcard/index.ts b/packages/kbn-securitysolution-io-ts-utils/src/list_types/entry_match_wildcard/index.ts index 100b0c665d91b..aab5ba5e8e32c 100644 --- a/packages/kbn-securitysolution-io-ts-utils/src/list_types/entry_match_wildcard/index.ts +++ b/packages/kbn-securitysolution-io-ts-utils/src/list_types/entry_match_wildcard/index.ts @@ -8,7 +8,7 @@ import * as t from 'io-ts'; import { NonEmptyString } from '../../non_empty_string'; -import { operator } from '../operator'; +import { listOperator as operator } from '../list_operator'; export const entriesMatchWildcard = t.exact( t.type({ diff --git a/packages/kbn-securitysolution-io-ts-utils/src/list_types/index.ts b/packages/kbn-securitysolution-io-ts-utils/src/list_types/index.ts index 652395fa651ea..9dd58e2a5a177 100644 --- a/packages/kbn-securitysolution-io-ts-utils/src/list_types/index.ts +++ b/packages/kbn-securitysolution-io-ts-utils/src/list_types/index.ts @@ -8,10 +8,11 @@ export * from './comment'; export * from './create_comment'; export * from './default_comments_array'; -export * from './default_comments_array'; +export * from './default_create_comments_array'; export * from './default_namespace'; export * from './default_namespace_array'; export * from './default_update_comments_array'; +export * from './endpoint'; export * from './entries'; export * from './entries_exist'; export * from './entries_list'; @@ -26,7 +27,7 @@ export * from './lists'; export * from './lists_default_array'; export * from './non_empty_entries_array'; export * from './non_empty_nested_entries_array'; -export * from './operator'; +export * from './list_operator'; export * from './os_type'; export * from './type'; export * from './update_comment'; diff --git a/packages/kbn-securitysolution-io-ts-utils/src/list_types/operator/index.ts b/packages/kbn-securitysolution-io-ts-utils/src/list_types/list_operator/index.ts similarity index 74% rename from packages/kbn-securitysolution-io-ts-utils/src/list_types/operator/index.ts rename to packages/kbn-securitysolution-io-ts-utils/src/list_types/list_operator/index.ts index 7fe2b6e4d8ba7..396577d46cd72 100644 --- a/packages/kbn-securitysolution-io-ts-utils/src/list_types/operator/index.ts +++ b/packages/kbn-securitysolution-io-ts-utils/src/list_types/list_operator/index.ts @@ -8,14 +8,14 @@ import * as t from 'io-ts'; -export const operator = t.keyof({ excluded: null, included: null }); -export type Operator = t.TypeOf; -export enum OperatorEnum { +export const listOperator = t.keyof({ excluded: null, included: null }); +export type ListOperator = t.TypeOf; +export enum ListOperatorEnum { INCLUDED = 'included', EXCLUDED = 'excluded', } -export enum OperatorTypeEnum { +export enum ListOperatorTypeEnum { NESTED = 'nested', MATCH = 'match', MATCH_ANY = 'match_any', diff --git a/packages/kbn-securitysolution-io-ts-utils/src/list_types/type/index.ts b/packages/kbn-securitysolution-io-ts-utils/src/list_types/type/index.ts index 0eebf2eeaace1..90a8c36eb8b31 100644 --- a/packages/kbn-securitysolution-io-ts-utils/src/list_types/type/index.ts +++ b/packages/kbn-securitysolution-io-ts-utils/src/list_types/type/index.ts @@ -37,3 +37,6 @@ export const type = t.keyof({ short: null, text: null, }); + +export const typeOrUndefined = t.union([type, t.undefined]); +export type Type = t.TypeOf; diff --git a/x-pack/plugins/lists/common/schemas/types/non_empty_string_array.test.ts b/packages/kbn-securitysolution-io-ts-utils/src/non_empty_string_array/index.test.ts similarity index 92% rename from x-pack/plugins/lists/common/schemas/types/non_empty_string_array.test.ts rename to packages/kbn-securitysolution-io-ts-utils/src/non_empty_string_array/index.test.ts index e4b4881d83f45..9fec36f46dd27 100644 --- a/x-pack/plugins/lists/common/schemas/types/non_empty_string_array.test.ts +++ b/packages/kbn-securitysolution-io-ts-utils/src/non_empty_string_array/index.test.ts @@ -1,16 +1,15 @@ /* * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. + * 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 { pipe } from 'fp-ts/lib/pipeable'; import { left } from 'fp-ts/lib/Either'; - -import { foldLeftRight, getPaths } from '../../shared_imports'; - -import { NonEmptyStringArray } from './non_empty_string_array'; +import { foldLeftRight, getPaths } from '../test_utils'; +import { NonEmptyStringArray } from '.'; describe('non_empty_string_array', () => { test('it should FAIL validation when given "null"', () => { diff --git a/x-pack/plugins/lists/common/schemas/types/non_empty_string_array.ts b/packages/kbn-securitysolution-io-ts-utils/src/non_empty_string_array/index.ts similarity index 81% rename from x-pack/plugins/lists/common/schemas/types/non_empty_string_array.ts rename to packages/kbn-securitysolution-io-ts-utils/src/non_empty_string_array/index.ts index 0afb318a6b33a..7eead15f69351 100644 --- a/x-pack/plugins/lists/common/schemas/types/non_empty_string_array.ts +++ b/packages/kbn-securitysolution-io-ts-utils/src/non_empty_string_array/index.ts @@ -1,8 +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. + * 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 * as t from 'io-ts'; @@ -13,7 +14,6 @@ import { Either } from 'fp-ts/lib/Either'; * - A string that is not empty (which will be turned into an array of size 1) * - A comma separated string that can turn into an array by splitting on it * - Example input converted to output: "a,b,c" -> ["a", "b", "c"] - * @deprecated Use packages/kbn-securitysolution-io-ts-utils */ export const NonEmptyStringArray = new t.Type( 'NonEmptyStringArray', @@ -37,12 +37,6 @@ export const NonEmptyStringArray = new t.Type( String ); -/** - * @deprecated Use packages/kbn-securitysolution-io-ts-utils - */ export type NonEmptyStringArray = t.OutputOf; -/** - * @deprecated Use packages/kbn-securitysolution-io-ts-utils - */ export type NonEmptyStringArrayDecoded = t.TypeOf; diff --git a/packages/kbn-securitysolution-io-ts-utils/src/parse_schedule_dates/index.ts b/packages/kbn-securitysolution-io-ts-utils/src/parse_schedule_dates/index.ts index d8aa9d2939589..a2cc15d82391c 100644 --- a/packages/kbn-securitysolution-io-ts-utils/src/parse_schedule_dates/index.ts +++ b/packages/kbn-securitysolution-io-ts-utils/src/parse_schedule_dates/index.ts @@ -22,5 +22,5 @@ export const parseScheduleDates = (time: string): moment.Moment | null => { ? dateMath.parse(time) : null; - return formattedDate ?? null; + return formattedDate != null ? formattedDate : null; }; diff --git a/packages/kbn-telemetry-tools/BUILD.bazel b/packages/kbn-telemetry-tools/BUILD.bazel new file mode 100644 index 0000000000000..4d1b4f21117c4 --- /dev/null +++ b/packages/kbn-telemetry-tools/BUILD.bazel @@ -0,0 +1,97 @@ +load("@npm//@bazel/typescript:index.bzl", "ts_config", "ts_project") +load("@build_bazel_rules_nodejs//:index.bzl", "js_library", "pkg_npm") + +PKG_BASE_NAME = "kbn-telemetry-tools" +PKG_REQUIRE_NAME = "@kbn/telemetry-tools" + +SOURCE_FILES = glob( + [ + "src/**/*.ts", + ], + exclude = [ + "**/*.test.*", + "**/__fixture__/**", + "**/__snapshots__/**", + ] +) + +SRCS = SOURCE_FILES + +filegroup( + name = "srcs", + srcs = SRCS, +) + +NPM_MODULE_EXTRA_FILES = [ + "package.json", + "README.md", + "GUIDELINE.md", +] + +SRC_DEPS = [ + "//packages/kbn-dev-utils", + "//packages/kbn-utility-types", + "@npm//glob", + "@npm//jest-styled-components", + "@npm//listr", + "@npm//normalize-path", + "@npm//tslib", +] + +TYPES_DEPS = [ + "@npm//@types/flot", + "@npm//@types/glob", + "@npm//@types/jest", + "@npm//@types/listr", + "@npm//@types/lodash", + "@npm//@types/node", + "@npm//@types/normalize-path", + "@npm//@types/testing-library__jest-dom", +] + +DEPS = SRC_DEPS + TYPES_DEPS + +ts_config( + name = "tsconfig", + src = "tsconfig.json", + deps = [ + "//:tsconfig.base.json", + ], +) + +ts_project( + name = "tsc", + args = ['--pretty'], + srcs = SRCS, + deps = DEPS, + declaration = True, + declaration_map = True, + incremental = True, + out_dir = "target", + source_map = True, + root_dir = "src", + tsconfig = ":tsconfig", +) + +js_library( + name = PKG_BASE_NAME, + srcs = NPM_MODULE_EXTRA_FILES, + deps = [":tsc"] + DEPS, + package_name = PKG_REQUIRE_NAME, + visibility = ["//visibility:public"], +) + +pkg_npm( + name = "npm_module", + deps = [ + ":%s" % PKG_BASE_NAME, + ] +) + +filegroup( + name = "build", + srcs = [ + ":npm_module", + ], + visibility = ["//visibility:public"], +) diff --git a/packages/kbn-telemetry-tools/package.json b/packages/kbn-telemetry-tools/package.json index 31fac5c043832..cd3fd3aa966c7 100644 --- a/packages/kbn-telemetry-tools/package.json +++ b/packages/kbn-telemetry-tools/package.json @@ -7,10 +7,5 @@ "private": true, "kibana": { "devOnly": true - }, - "scripts": { - "build": "../../node_modules/.bin/babel src --out-dir target --delete-dir-on-start --extensions .ts --source-maps=inline", - "kbn:bootstrap": "yarn build", - "kbn:watch": "yarn build --watch" } } \ No newline at end of file diff --git a/packages/kbn-telemetry-tools/tsconfig.json b/packages/kbn-telemetry-tools/tsconfig.json index 419af1d02f83b..926ebff17f639 100644 --- a/packages/kbn-telemetry-tools/tsconfig.json +++ b/packages/kbn-telemetry-tools/tsconfig.json @@ -1,10 +1,11 @@ { "extends": "../../tsconfig.base.json", "compilerOptions": { - "incremental": false, + "incremental": true, "outDir": "./target", "declaration": true, "declarationMap": true, + "rootDir": "src", "sourceMap": true, "sourceRoot": "../../../../packages/kbn-telemetry-tools/src", "isolatedModules": true diff --git a/packages/kbn-test/src/es/test_es_cluster.ts b/packages/kbn-test/src/es/test_es_cluster.ts index e802613fbaedb..658fc9382d616 100644 --- a/packages/kbn-test/src/es/test_es_cluster.ts +++ b/packages/kbn-test/src/es/test_es_cluster.ts @@ -36,7 +36,7 @@ interface TestClusterFactoryOptions { * */ dataArchive?: string; esArgs?: string[]; - esEnvVars?: Record; + esJavaOpts?: string; clusterName?: string; log: ToolingLog; ssl?: boolean; @@ -52,7 +52,7 @@ export function createTestEsCluster(options: TestClusterFactoryOptions) { esFrom = esTestConfig.getBuildFrom(), dataArchive, esArgs: customEsArgs = [], - esEnvVars, + esJavaOpts, clusterName: customClusterName = 'es-test-cluster', ssl, } = options; @@ -107,7 +107,7 @@ export function createTestEsCluster(options: TestClusterFactoryOptions) { await cluster.start(installPath, { password: config.password, esArgs, - esEnvVars, + esJavaOpts, }); } diff --git a/packages/kbn-test/src/functional_test_runner/lib/config/schema.ts b/packages/kbn-test/src/functional_test_runner/lib/config/schema.ts index d82b7b83e8f15..65573fdbd6647 100644 --- a/packages/kbn-test/src/functional_test_runner/lib/config/schema.ts +++ b/packages/kbn-test/src/functional_test_runner/lib/config/schema.ts @@ -172,7 +172,7 @@ export const schema = Joi.object() license: Joi.string().default('basic'), from: Joi.string().default('snapshot'), serverArgs: Joi.array(), - serverEnvVars: Joi.object(), + esJavaOpts: Joi.string(), dataArchive: Joi.string(), ssl: Joi.boolean().default(false), }) diff --git a/packages/kbn-test/src/functional_tests/lib/run_elasticsearch.ts b/packages/kbn-test/src/functional_tests/lib/run_elasticsearch.ts index 83368783da389..7ba9a3c1c4733 100644 --- a/packages/kbn-test/src/functional_tests/lib/run_elasticsearch.ts +++ b/packages/kbn-test/src/functional_tests/lib/run_elasticsearch.ts @@ -29,7 +29,7 @@ export async function runElasticsearch({ const ssl = config.get('esTestCluster.ssl'); const license = config.get('esTestCluster.license'); const esArgs = config.get('esTestCluster.serverArgs'); - const esEnvVars = config.get('esTestCluster.serverEnvVars'); + const esJavaOpts = config.get('esTestCluster.esJavaOpts'); const isSecurityEnabled = esArgs.includes('xpack.security.enabled=true'); const cluster = createTestEsCluster({ @@ -43,7 +43,7 @@ export async function runElasticsearch({ esFrom: esFrom || config.get('esTestCluster.from'), dataArchive: config.get('esTestCluster.dataArchive'), esArgs, - esEnvVars, + esJavaOpts, ssl, }); diff --git a/src/core/public/doc_links/doc_links_service.ts b/src/core/public/doc_links/doc_links_service.ts index 581d614c9a371..2a202a426c16c 100644 --- a/src/core/public/doc_links/doc_links_service.ts +++ b/src/core/public/doc_links/doc_links_service.ts @@ -184,7 +184,7 @@ export class DocLinksService { remoteClustersProxy: `${ELASTICSEARCH_DOCS}modules-remote-clusters.html#proxy-mode`, remoteClusersProxySettings: `${ELASTICSEARCH_DOCS}modules-remote-clusters.html#remote-cluster-proxy-settings`, scriptParameters: `${ELASTICSEARCH_DOCS}modules-scripting-using.html#prefer-params`, - transportSettings: `${ELASTICSEARCH_DOCS}modules-transport.html`, + transportSettings: `${ELASTICSEARCH_DOCS}modules-network.html#common-network-settings`, typesRemoval: `${ELASTICSEARCH_DOCS}removal-of-types.html`, deprecationLogging: `${ELASTICSEARCH_DOCS}logging.html#deprecation-logging`, }, @@ -319,6 +319,7 @@ export class DocLinksService { createSnapshotLifecyclePolicy: `${ELASTICSEARCH_DOCS}slm-api-put-policy.html`, createRoleMapping: `${ELASTICSEARCH_DOCS}security-api-put-role-mapping.html`, createRoleMappingTemplates: `${ELASTICSEARCH_DOCS}security-api-put-role-mapping.html#_role_templates`, + createRollupJobsRequest: `${ELASTICSEARCH_DOCS}rollup-put-job.html#rollup-put-job-api-request-body`, createApiKey: `${ELASTICSEARCH_DOCS}security-api-create-api-key.html`, createPipeline: `${ELASTICSEARCH_DOCS}put-pipeline-api.html`, createTransformRequest: `${ELASTICSEARCH_DOCS}put-transform.html#put-transform-request-body`, @@ -544,6 +545,7 @@ export interface DocLinksStart { createSnapshotLifecyclePolicy: string; createRoleMapping: string; createRoleMappingTemplates: string; + createRollupJobsRequest: string; createApiKey: string; createPipeline: string; createTransformRequest: string; diff --git a/src/core/public/public.api.md b/src/core/public/public.api.md index 0523c523baf6f..4ea3b56c60a8f 100644 --- a/src/core/public/public.api.md +++ b/src/core/public/public.api.md @@ -629,6 +629,7 @@ export interface DocLinksStart { createSnapshotLifecyclePolicy: string; createRoleMapping: string; createRoleMappingTemplates: string; + createRollupJobsRequest: string; createApiKey: string; createPipeline: string; createTransformRequest: string; diff --git a/src/core/server/config/deprecation/core_deprecations.ts b/src/core/server/config/deprecation/core_deprecations.ts index 2e77374e3068a..0722bbe0a71ad 100644 --- a/src/core/server/config/deprecation/core_deprecations.ts +++ b/src/core/server/config/deprecation/core_deprecations.ts @@ -6,29 +6,26 @@ * Side Public License, v 1. */ -import { has, get } from 'lodash'; import { ConfigDeprecationProvider, ConfigDeprecation } from '@kbn/config'; const configPathDeprecation: ConfigDeprecation = (settings, fromPath, addDeprecation) => { - if (has(process.env, 'CONFIG_PATH')) { + if (process.env?.CONFIG_PATH) { addDeprecation({ message: `Environment variable CONFIG_PATH is deprecated. It has been replaced with KBN_PATH_CONF pointing to a config folder`, }); } - return settings; }; const dataPathDeprecation: ConfigDeprecation = (settings, fromPath, addDeprecation) => { - if (has(process.env, 'DATA_PATH')) { + if (process.env?.DATA_PATH) { addDeprecation({ message: `Environment variable "DATA_PATH" will be removed. It has been replaced with kibana.yml setting "path.data"`, }); } - return settings; }; const rewriteBasePathDeprecation: ConfigDeprecation = (settings, fromPath, addDeprecation) => { - if (has(settings, 'server.basePath') && !has(settings, 'server.rewriteBasePath')) { + if (settings.server?.basePath && !settings.server?.rewriteBasePath) { addDeprecation({ message: 'You should set server.basePath along with server.rewriteBasePath. Starting in 7.0, Kibana ' + @@ -37,20 +34,19 @@ const rewriteBasePathDeprecation: ConfigDeprecation = (settings, fromPath, addDe 'current behavior and silence this warning.', }); } - return settings; }; const rewriteCorsSettings: ConfigDeprecation = (settings, fromPath, addDeprecation) => { - const corsSettings = get(settings, 'server.cors'); - if (typeof get(settings, 'server.cors') === 'boolean') { + const corsSettings = settings.server?.cors; + if (typeof corsSettings === 'boolean') { addDeprecation({ message: '"server.cors" is deprecated and has been replaced by "server.cors.enabled"', }); - settings.server.cors = { - enabled: corsSettings, + + return { + set: [{ path: 'server.cors', value: { enabled: corsSettings } }], }; } - return settings; }; const cspRulesDeprecation: ConfigDeprecation = (settings, fromPath, addDeprecation) => { @@ -59,7 +55,7 @@ const cspRulesDeprecation: ConfigDeprecation = (settings, fromPath, addDeprecati const SELF_POLICIES = Object.freeze(['script-src', 'style-src']); const SELF_STRING = `'self'`; - const rules: string[] = get(settings, 'csp.rules'); + const rules: string[] = settings.csp?.rules; if (rules) { const parsed = new Map( rules.map((ruleStr) => { @@ -68,34 +64,39 @@ const cspRulesDeprecation: ConfigDeprecation = (settings, fromPath, addDeprecati }) ); - settings.csp.rules = [...parsed].map(([policy, sourceList]) => { - if (sourceList.find((source) => source.includes(NONCE_STRING))) { - addDeprecation({ - message: `csp.rules no longer supports the {nonce} syntax. Replacing with 'self' in ${policy}`, - }); - sourceList = sourceList.filter((source) => !source.includes(NONCE_STRING)); + return { + set: [ + { + path: 'csp.rules', + value: [...parsed].map(([policy, sourceList]) => { + if (sourceList.find((source) => source.includes(NONCE_STRING))) { + addDeprecation({ + message: `csp.rules no longer supports the {nonce} syntax. Replacing with 'self' in ${policy}`, + }); + sourceList = sourceList.filter((source) => !source.includes(NONCE_STRING)); - // Add 'self' if not present - if (!sourceList.find((source) => source.includes(SELF_STRING))) { - sourceList.push(SELF_STRING); - } - } + // Add 'self' if not present + if (!sourceList.find((source) => source.includes(SELF_STRING))) { + sourceList.push(SELF_STRING); + } + } - if ( - SELF_POLICIES.includes(policy) && - !sourceList.find((source) => source.includes(SELF_STRING)) - ) { - addDeprecation({ - message: `csp.rules must contain the 'self' source. Automatically adding to ${policy}.`, - }); - sourceList.push(SELF_STRING); - } + if ( + SELF_POLICIES.includes(policy) && + !sourceList.find((source) => source.includes(SELF_STRING)) + ) { + addDeprecation({ + message: `csp.rules must contain the 'self' source. Automatically adding to ${policy}.`, + }); + sourceList.push(SELF_STRING); + } - return `${policy} ${sourceList.join(' ')}`.trim(); - }); + return `${policy} ${sourceList.join(' ')}`.trim(); + }), + }, + ], + }; } - - return settings; }; const mapManifestServiceUrlDeprecation: ConfigDeprecation = ( @@ -103,7 +104,7 @@ const mapManifestServiceUrlDeprecation: ConfigDeprecation = ( fromPath, addDeprecation ) => { - if (has(settings, 'map.manifestServiceUrl')) { + if (settings.map?.manifestServiceUrl) { addDeprecation({ message: 'You should no longer use the map.manifestServiceUrl setting in kibana.yml to configure the location ' + @@ -112,11 +113,10 @@ const mapManifestServiceUrlDeprecation: ConfigDeprecation = ( 'modified for use in production environments.', }); } - return settings; }; const opsLoggingEventDeprecation: ConfigDeprecation = (settings, fromPath, addDeprecation) => { - if (has(settings, 'logging.events.ops')) { + if (settings.logging?.events?.ops) { addDeprecation({ documentationUrl: 'https://github.com/elastic/kibana/blob/master/src/core/server/logging/README.mdx#loggingevents', @@ -127,11 +127,10 @@ const opsLoggingEventDeprecation: ConfigDeprecation = (settings, fromPath, addDe 'https://github.com/elastic/kibana/blob/master/src/core/server/logging/README.mdx', }); } - return settings; }; const requestLoggingEventDeprecation: ConfigDeprecation = (settings, fromPath, addDeprecation) => { - if (has(settings, 'logging.events.request') || has(settings, 'logging.events.response')) { + if (settings.logging?.events?.request || settings.logging?.events?.response) { addDeprecation({ documentationUrl: 'https://github.com/elastic/kibana/blob/master/src/core/server/logging/README.mdx#loggingevents', @@ -142,11 +141,10 @@ const requestLoggingEventDeprecation: ConfigDeprecation = (settings, fromPath, a 'https://github.com/elastic/kibana/blob/master/src/core/server/logging/README.mdx', }); } - return settings; }; const timezoneLoggingDeprecation: ConfigDeprecation = (settings, fromPath, addDeprecation) => { - if (has(settings, 'logging.timezone')) { + if (settings.logging?.timezone) { addDeprecation({ documentationUrl: 'https://github.com/elastic/kibana/blob/master/src/core/server/logging/README.mdx#loggingtimezone', @@ -157,11 +155,10 @@ const timezoneLoggingDeprecation: ConfigDeprecation = (settings, fromPath, addDe 'https://github.com/elastic/kibana/blob/master/src/core/server/logging/README.mdx', }); } - return settings; }; const destLoggingDeprecation: ConfigDeprecation = (settings, fromPath, addDeprecation) => { - if (has(settings, 'logging.dest')) { + if (settings.logging?.dest) { addDeprecation({ documentationUrl: 'https://github.com/elastic/kibana/blob/master/src/core/server/logging/README.mdx#loggingdest', @@ -172,11 +169,10 @@ const destLoggingDeprecation: ConfigDeprecation = (settings, fromPath, addDeprec 'https://github.com/elastic/kibana/blob/master/src/core/server/logging/README.mdx', }); } - return settings; }; const quietLoggingDeprecation: ConfigDeprecation = (settings, fromPath, addDeprecation) => { - if (has(settings, 'logging.quiet')) { + if (settings.logging?.quiet) { addDeprecation({ documentationUrl: 'https://github.com/elastic/kibana/blob/master/src/core/server/logging/README.mdx#loggingquiet', @@ -185,11 +181,10 @@ const quietLoggingDeprecation: ConfigDeprecation = (settings, fromPath, addDepre 'in 8.0. Moving forward, you can use "logging.root.level:error" in your logging configuration. ', }); } - return settings; }; const silentLoggingDeprecation: ConfigDeprecation = (settings, fromPath, addDeprecation) => { - if (has(settings, 'logging.silent')) { + if (settings.logging?.silent) { addDeprecation({ documentationUrl: 'https://github.com/elastic/kibana/blob/master/src/core/server/logging/README.mdx#loggingsilent', @@ -198,11 +193,10 @@ const silentLoggingDeprecation: ConfigDeprecation = (settings, fromPath, addDepr 'in 8.0. Moving forward, you can use "logging.root.level:off" in your logging configuration. ', }); } - return settings; }; const verboseLoggingDeprecation: ConfigDeprecation = (settings, fromPath, addDeprecation) => { - if (has(settings, 'logging.verbose')) { + if (settings.logging?.verbose) { addDeprecation({ documentationUrl: 'https://github.com/elastic/kibana/blob/master/src/core/server/logging/README.mdx#loggingverbose', @@ -211,7 +205,6 @@ const verboseLoggingDeprecation: ConfigDeprecation = (settings, fromPath, addDep 'in 8.0. Moving forward, you can use "logging.root.level:all" in your logging configuration. ', }); } - return settings; }; const jsonLoggingDeprecation: ConfigDeprecation = (settings, fromPath, addDeprecation) => { @@ -219,7 +212,7 @@ const jsonLoggingDeprecation: ConfigDeprecation = (settings, fromPath, addDeprec // the dev CLI code in src/dev/cli_dev_mode/using_server_process.ts manually // specifies `--logging.json=false`. Since it's executed in a child process, the // ` legacyLoggingConfigSchema` returns `true` for the TTY check on `process.stdout.isTTY` - if (has(settings, 'logging.json') && settings.env !== 'development') { + if (settings.logging?.json && settings.env !== 'development') { addDeprecation({ documentationUrl: 'https://github.com/elastic/kibana/blob/master/src/core/server/logging/README.mdx', @@ -232,11 +225,10 @@ const jsonLoggingDeprecation: ConfigDeprecation = (settings, fromPath, addDeprec 'https://github.com/elastic/kibana/blob/master/src/core/server/logging/README.mdx', }); } - return settings; }; const logRotateDeprecation: ConfigDeprecation = (settings, fromPath, addDeprecation) => { - if (has(settings, 'logging.rotate')) { + if (settings.logging?.rotate) { addDeprecation({ documentationUrl: 'https://github.com/elastic/kibana/blob/master/src/core/server/logging/README.mdx#rolling-file-appender', @@ -247,11 +239,10 @@ const logRotateDeprecation: ConfigDeprecation = (settings, fromPath, addDeprecat 'https://github.com/elastic/kibana/blob/master/src/core/server/logging/README.mdx#rolling-file-appender', }); } - return settings; }; const logEventsLogDeprecation: ConfigDeprecation = (settings, fromPath, addDeprecation) => { - if (has(settings, 'logging.events.log')) { + if (settings.logging?.events?.log) { addDeprecation({ documentationUrl: 'https://github.com/elastic/kibana/blob/master/src/core/server/logging/README.mdx#loggingevents', @@ -260,11 +251,10 @@ const logEventsLogDeprecation: ConfigDeprecation = (settings, fromPath, addDepre 'in 8.0. Moving forward, log levels can be customized on a per-logger basis using the new logging configuration. ', }); } - return settings; }; const logEventsErrorDeprecation: ConfigDeprecation = (settings, fromPath, addDeprecation) => { - if (has(settings, 'logging.events.error')) { + if (settings.logging?.events?.error) { addDeprecation({ documentationUrl: 'https://github.com/elastic/kibana/blob/master/src/core/server/logging/README.mdx#loggingevents', @@ -273,18 +263,16 @@ const logEventsErrorDeprecation: ConfigDeprecation = (settings, fromPath, addDep 'in 8.0. Moving forward, you can use "logging.root.level: error" in your logging configuration. ', }); } - return settings; }; const logFilterDeprecation: ConfigDeprecation = (settings, fromPath, addDeprecation) => { - if (has(settings, 'logging.filter')) { + if (settings.logging?.filter) { addDeprecation({ documentationUrl: 'https://github.com/elastic/kibana/blob/master/src/core/server/logging/README.mdx#loggingfilter', message: '"logging.filter" has been deprecated and will be removed in 8.0.', }); } - return settings; }; export const coreDeprecationProvider: ConfigDeprecationProvider = ({ rename, unusedFromRoot }) => [ diff --git a/src/core/server/config/test_utils.ts b/src/core/server/config/test_utils.ts index 2eaf462768724..8e20e87e6f7d8 100644 --- a/src/core/server/config/test_utils.ts +++ b/src/core/server/config/test_utils.ts @@ -5,6 +5,7 @@ * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ +import { set } from '@elastic/safer-lodash-set'; import type { ConfigDeprecationProvider } from '@kbn/config'; import { configDeprecationFactory, applyDeprecations } from '@kbn/config'; @@ -38,7 +39,7 @@ export const getDeprecationsFor = ({ settings?: Record; path: string; }) => { - return collectDeprecations(provider, { [path]: settings }, path); + return collectDeprecations(provider, set({}, path, settings), path); }; export const getDeprecationsForGlobalSettings = ({ diff --git a/src/core/server/elasticsearch/elasticsearch_config.ts b/src/core/server/elasticsearch/elasticsearch_config.ts index e731af4817955..b5ea0ec8c3456 100644 --- a/src/core/server/elasticsearch/elasticsearch_config.ts +++ b/src/core/server/elasticsearch/elasticsearch_config.ts @@ -147,7 +147,7 @@ const deprecations: ConfigDeprecationProvider = () => [ (settings, fromPath, addDeprecation) => { const es = settings[fromPath]; if (!es) { - return settings; + return; } if (es.username === 'elastic') { addDeprecation({ @@ -171,7 +171,7 @@ const deprecations: ConfigDeprecationProvider = () => [ message: `Setting [${fromPath}.logQueries] is deprecated and no longer used. You should set the log level to "debug" for the "elasticsearch.queries" context in "logging.loggers" or use "logging.verbose: true".`, }); } - return settings; + return; }, ]; diff --git a/src/core/server/elasticsearch/legacy/api_types.ts b/src/core/server/elasticsearch/legacy/api_types.ts index a3b442549942f..e4ff4816527b4 100644 --- a/src/core/server/elasticsearch/legacy/api_types.ts +++ b/src/core/server/elasticsearch/legacy/api_types.ts @@ -140,6 +140,7 @@ import { * * @public * @deprecated + * @removeBy 7.16 */ export interface LegacyCallAPIOptions { /** @@ -157,6 +158,7 @@ export interface LegacyCallAPIOptions { /** * @deprecated + * @removeBy 7.16 * @public * */ export interface LegacyAPICaller { @@ -312,6 +314,7 @@ export interface LegacyAPICaller { /** * @deprecated + * @removeBy 7.16 * @public * */ export interface AssistantAPIClientParams extends GenericParams { @@ -321,17 +324,20 @@ export interface AssistantAPIClientParams extends GenericParams { /** * @deprecated + * @removeBy 7.16 * @public * */ export type MIGRATION_ASSISTANCE_INDEX_ACTION = 'upgrade' | 'reindex'; /** * @deprecated + * @removeBy 7.16 * @public * */ export type MIGRATION_DEPRECATION_LEVEL = 'none' | 'info' | 'warning' | 'critical'; /** * @deprecated + * @removeBy 7.16 * @public * */ export interface AssistanceAPIResponse { @@ -344,6 +350,7 @@ export interface AssistanceAPIResponse { /** * @deprecated + * @removeBy 7.16 * @public * */ export interface DeprecationAPIClientParams extends GenericParams { @@ -353,6 +360,7 @@ export interface DeprecationAPIClientParams extends GenericParams { /** * @deprecated + * @removeBy 7.16 * @public * */ export interface DeprecationInfo { @@ -364,6 +372,7 @@ export interface DeprecationInfo { /** * @deprecated + * @removeBy 7.16 * @public * */ export interface IndexSettingsDeprecationInfo { @@ -372,6 +381,7 @@ export interface IndexSettingsDeprecationInfo { /** * @deprecated + * @removeBy 7.16 * @public * */ export interface DeprecationAPIResponse { diff --git a/src/core/server/elasticsearch/legacy/cluster_client.ts b/src/core/server/elasticsearch/legacy/cluster_client.ts index e4b72abe0bc1c..bdb2ca4d01b3c 100644 --- a/src/core/server/elasticsearch/legacy/cluster_client.ts +++ b/src/core/server/elasticsearch/legacy/cluster_client.ts @@ -78,6 +78,7 @@ const callAPI = async ( * See {@link LegacyClusterClient}. * * @deprecated Use {@link IClusterClient}. + * @removeBy 7.16 * @public */ export type ILegacyClusterClient = Pick; @@ -89,6 +90,7 @@ export type ILegacyClusterClient = Pick; /** * @deprecated + * @removeBy 7.16 * Use {@link ElasticsearchServiceStart.legacy | ElasticsearchServiceStart.legacy.createClient} instead. * * Create application specific Elasticsearch cluster API client with customized config. See {@link ILegacyClusterClient}. @@ -60,6 +61,7 @@ export interface ElasticsearchServiceSetup { ) => ILegacyCustomClusterClient; /** + * @removeBy 7.16 * @deprecated * Use {@link ElasticsearchServiceStart.legacy | ElasticsearchServiceStart.legacy.client} instead. * @@ -131,6 +133,9 @@ export interface ElasticsearchServiceStart { /** * Create application specific Elasticsearch cluster API client with customized config. See {@link ILegacyClusterClient}. * + * @deprecated + * @removeBy 7.16 + * * @param type Unique identifier of the client * @param clientConfig A config consists of Elasticsearch JS client options and * valid sub-set of Elasticsearch service config. @@ -153,6 +158,9 @@ export interface ElasticsearchServiceStart { /** * A pre-configured {@link ILegacyClusterClient | legacy Elasticsearch client}. * + * @deprecated + * @removeBy 7.16 + * * @example * ```js * const client = core.elasticsearch.legacy.client; diff --git a/src/core/server/saved_objects/migrations/core/document_migrator.test.ts b/src/core/server/saved_objects/migrations/core/document_migrator.test.ts index 1cf408ea96a56..45286f158edb1 100644 --- a/src/core/server/saved_objects/migrations/core/document_migrator.test.ts +++ b/src/core/server/saved_objects/migrations/core/document_migrator.test.ts @@ -11,6 +11,7 @@ import { set } from '@elastic/safer-lodash-set'; import _ from 'lodash'; import { SavedObjectUnsanitizedDoc } from '../../serialization'; import { DocumentMigrator } from './document_migrator'; +import { TransformSavedObjectDocumentError } from './transform_saved_object_document_error'; import { loggingSystemMock } from '../../../logging/logging_system.mock'; import { SavedObjectsType } from '../../types'; import { SavedObjectTypeRegistry } from '../../saved_objects_type_registry'; @@ -724,6 +725,12 @@ describe('DocumentMigrator', () => { it('logs the original error and throws a transform error if a document transform fails', () => { const log = mockLogger; + const failedDoc = { + id: 'smelly', + type: 'dog', + attributes: {}, + migrationVersion: {}, + }; const migrator = new DocumentMigrator({ ...testOpts(), typeRegistry: createRegistry({ @@ -737,12 +744,6 @@ describe('DocumentMigrator', () => { log, }); migrator.prepareMigrations(); - const failedDoc = { - id: 'smelly', - type: 'dog', - attributes: {}, - migrationVersion: {}, - }; try { migrator.migrate(_.cloneDeep(failedDoc)); expect('Did not throw').toEqual('But it should have!'); @@ -751,6 +752,7 @@ describe('DocumentMigrator', () => { "Failed to transform document smelly. Transform: dog:1.2.3 Doc: {\\"id\\":\\"smelly\\",\\"type\\":\\"dog\\",\\"attributes\\":{},\\"migrationVersion\\":{}}" `); + expect(error).toBeInstanceOf(TransformSavedObjectDocumentError); expect(loggingSystemMock.collect(mockLoggerFactory).error[0][0]).toMatchInlineSnapshot( `[Error: Dang diggity!]` ); diff --git a/src/core/server/saved_objects/migrations/core/document_migrator.ts b/src/core/server/saved_objects/migrations/core/document_migrator.ts index 1dd4a8fbf6388..4f58397866cfb 100644 --- a/src/core/server/saved_objects/migrations/core/document_migrator.ts +++ b/src/core/server/saved_objects/migrations/core/document_migrator.ts @@ -62,6 +62,7 @@ import { SavedObjectsType, } from '../../types'; import { MigrationLogger } from './migration_logger'; +import { TransformSavedObjectDocumentError } from '.'; import { ISavedObjectTypeRegistry } from '../../saved_objects_type_registry'; import { SavedObjectMigrationFn, SavedObjectMigrationMap } from '../types'; import { DEFAULT_NAMESPACE_STRING } from '../../service/lib/utils'; @@ -679,9 +680,15 @@ function wrapWithTry( const failedTransform = `${type.name}:${version}`; const failedDoc = JSON.stringify(doc); log.error(error); - - throw new Error( - `Failed to transform document ${doc?.id}. Transform: ${failedTransform}\nDoc: ${failedDoc}` + // To make debugging failed migrations easier, we add items needed to convert the + // saved object id to the full raw id (the id only contains the uuid part) and the full error itself + throw new TransformSavedObjectDocumentError( + doc.id, + doc.type, + doc.namespace, + failedTransform, + failedDoc, + error ); } }; diff --git a/src/core/server/saved_objects/migrations/core/index.ts b/src/core/server/saved_objects/migrations/core/index.ts index 1e51983a0ffbd..ca54d6876ad75 100644 --- a/src/core/server/saved_objects/migrations/core/index.ts +++ b/src/core/server/saved_objects/migrations/core/index.ts @@ -15,3 +15,9 @@ export type { MigrationResult, MigrationStatus } from './migration_coordinator'; export { createMigrationEsClient } from './migration_es_client'; export type { MigrationEsClient } from './migration_es_client'; export { excludeUnusedTypesQuery } from './elastic_index'; +export { TransformSavedObjectDocumentError } from './transform_saved_object_document_error'; +export type { + DocumentsTransformFailed, + DocumentsTransformSuccess, + TransformErrorObjects, +} from './migrate_raw_docs'; diff --git a/src/core/server/saved_objects/migrations/core/migrate_raw_docs.test.ts b/src/core/server/saved_objects/migrations/core/migrate_raw_docs.test.ts index 45e73f7dfae30..1d43e2f54a726 100644 --- a/src/core/server/saved_objects/migrations/core/migrate_raw_docs.test.ts +++ b/src/core/server/saved_objects/migrations/core/migrate_raw_docs.test.ts @@ -7,10 +7,17 @@ */ import { set } from '@elastic/safer-lodash-set'; +import * as Either from 'fp-ts/lib/Either'; import _ from 'lodash'; import { SavedObjectTypeRegistry } from '../../saved_objects_type_registry'; import { SavedObjectsSerializer } from '../../serialization'; -import { migrateRawDocs } from './migrate_raw_docs'; +import { + DocumentsTransformFailed, + DocumentsTransformSuccess, + migrateRawDocs, + migrateRawDocsSafely, +} from './migrate_raw_docs'; +import { TransformSavedObjectDocumentError } from './transform_saved_object_document_error'; describe('migrateRawDocs', () => { test('converts raw docs to saved objects', async () => { @@ -120,3 +127,156 @@ describe('migrateRawDocs', () => { ).rejects.toThrowErrorMatchingInlineSnapshot(`"error during transform"`); }); }); + +describe('migrateRawDocsSafely', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + test('converts raw docs to saved objects', async () => { + const transform = jest.fn((doc: any) => [ + set(_.cloneDeep(doc), 'attributes.name', 'HOI!'), + ]); + const task = migrateRawDocsSafely( + new SavedObjectsSerializer(new SavedObjectTypeRegistry()), + transform, + [ + { _id: 'a:b', _source: { type: 'a', a: { name: 'AAA' } } }, + { _id: 'c:d', _source: { type: 'c', c: { name: 'DDD' } } }, + ] + ); + const result = (await task()) as Either.Right; + expect(result._tag).toEqual('Right'); + expect(result.right.processedDocs).toEqual([ + { + _id: 'a:b', + _source: { type: 'a', a: { name: 'HOI!' }, migrationVersion: {}, references: [] }, + }, + { + _id: 'c:d', + _source: { type: 'c', c: { name: 'HOI!' }, migrationVersion: {}, references: [] }, + }, + ]); + + const obj1 = { + id: 'b', + type: 'a', + attributes: { name: 'AAA' }, + migrationVersion: {}, + references: [], + }; + const obj2 = { + id: 'd', + type: 'c', + attributes: { name: 'DDD' }, + migrationVersion: {}, + references: [], + }; + expect(transform).toHaveBeenCalledTimes(2); + expect(transform).toHaveBeenNthCalledWith(1, obj1); + expect(transform).toHaveBeenNthCalledWith(2, obj2); + }); + + test('returns a `left` tag when encountering a corrupt saved object document', async () => { + const transform = jest.fn((doc: any) => [ + set(_.cloneDeep(doc), 'attributes.name', 'TADA'), + ]); + const task = migrateRawDocsSafely( + new SavedObjectsSerializer(new SavedObjectTypeRegistry()), + transform, + [ + { _id: 'foo:b', _source: { type: 'a', a: { name: 'AAA' } } }, + { _id: 'c:d', _source: { type: 'c', c: { name: 'DDD' } } }, + ] + ); + const result = (await task()) as Either.Left; + expect(transform).toHaveBeenCalledTimes(1); + expect(result._tag).toEqual('Left'); + expect(Object.keys(result.left)).toEqual(['type', 'corruptDocumentIds', 'transformErrors']); + expect(result.left.corruptDocumentIds.length).toEqual(1); + expect(result.left.transformErrors.length).toEqual(0); + }); + + test('handles when one document is transformed into multiple documents', async () => { + const transform = jest.fn((doc: any) => [ + set(_.cloneDeep(doc), 'attributes.name', 'HOI!'), + { id: 'bar', type: 'foo', attributes: { name: 'baz' } }, + ]); + const task = migrateRawDocsSafely( + new SavedObjectsSerializer(new SavedObjectTypeRegistry()), + transform, + [{ _id: 'a:b', _source: { type: 'a', a: { name: 'AAA' } } }] + ); + const result = (await task()) as Either.Right; + expect(result._tag).toEqual('Right'); + expect(result.right.processedDocs).toEqual([ + { + _id: 'a:b', + _source: { type: 'a', a: { name: 'HOI!' }, migrationVersion: {}, references: [] }, + }, + { + _id: 'foo:bar', + _source: { type: 'foo', foo: { name: 'baz' }, references: [] }, + }, + ]); + + const obj = { + id: 'b', + type: 'a', + attributes: { name: 'AAA' }, + migrationVersion: {}, + references: [], + }; + expect(transform).toHaveBeenCalledTimes(1); + expect(transform).toHaveBeenCalledWith(obj); + }); + + test('instance of Either.left containing transform errors when the transform function throws a TransformSavedObjectDocument error', async () => { + const transform = jest.fn((doc: any) => { + throw new TransformSavedObjectDocumentError( + `${doc.id}`, + `${doc.type}`, + `${doc.namespace}`, + `${doc.type}1.2.3`, + JSON.stringify(doc), + new Error('error during transform') + ); + }); + const task = migrateRawDocsSafely( + new SavedObjectsSerializer(new SavedObjectTypeRegistry()), + transform, + [{ _id: 'a:b', _source: { type: 'a', a: { name: 'AAA' } } }] // this is the raw doc + ); + const result = (await task()) as Either.Left; + expect(transform).toHaveBeenCalledTimes(1); + expect(result._tag).toEqual('Left'); + expect(result.left.corruptDocumentIds.length).toEqual(0); + expect(result.left.transformErrors.length).toEqual(1); + expect(result.left.transformErrors[0].err.message).toMatchInlineSnapshot(` + "Failed to transform document b. Transform: a1.2.3 + Doc: {\\"type\\":\\"a\\",\\"id\\":\\"b\\",\\"attributes\\":{\\"name\\":\\"AAA\\"},\\"references\\":[],\\"migrationVersion\\":{}}" + `); + }); + + test("instance of Either.left containing errors when the transform function throws an error that isn't a TransformSavedObjectDocument error", async () => { + const transform = jest.fn((doc: any) => { + throw new Error('error during transform'); + }); + const task = migrateRawDocsSafely( + new SavedObjectsSerializer(new SavedObjectTypeRegistry()), + transform, + [{ _id: 'a:b', _source: { type: 'a', a: { name: 'AAA' } } }] // this is the raw doc + ); + const result = (await task()) as Either.Left; + expect(transform).toHaveBeenCalledTimes(1); + expect(result._tag).toEqual('Left'); + expect(result.left.corruptDocumentIds.length).toEqual(0); + expect(result.left.transformErrors.length).toEqual(1); + expect(result.left.transformErrors[0]).toMatchInlineSnapshot(` + Object { + "err": [Error: error during transform], + "rawId": "a:b", + } + `); + }); +}); diff --git a/src/core/server/saved_objects/migrations/core/migrate_raw_docs.ts b/src/core/server/saved_objects/migrations/core/migrate_raw_docs.ts index 102ec81646a92..461ae1df6bc3d 100644 --- a/src/core/server/saved_objects/migrations/core/migrate_raw_docs.ts +++ b/src/core/server/saved_objects/migrations/core/migrate_raw_docs.ts @@ -9,13 +9,32 @@ /* * This file provides logic for migrating raw documents. */ - +import * as TaskEither from 'fp-ts/lib/TaskEither'; +import * as Either from 'fp-ts/lib/Either'; import { + SavedObjectSanitizedDoc, SavedObjectsRawDoc, SavedObjectsSerializer, SavedObjectUnsanitizedDoc, } from '../../serialization'; import { MigrateAndConvertFn } from './document_migrator'; +import { TransformSavedObjectDocumentError } from '.'; + +export interface DocumentsTransformFailed { + readonly type: string; + readonly corruptDocumentIds: string[]; + readonly transformErrors: TransformErrorObjects[]; +} +export interface DocumentsTransformSuccess { + readonly processedDocs: SavedObjectsRawDoc[]; +} +export interface TransformErrorObjects { + readonly rawId: string; + readonly err: TransformSavedObjectDocumentError | Error; +} +type MigrateFn = ( + doc: SavedObjectUnsanitizedDoc +) => Promise>>; /** * Error thrown when saved object migrations encounter a corrupt saved object. @@ -37,7 +56,6 @@ export class CorruptSavedObjectError extends Error { /** * Applies the specified migration function to every saved object document in the list * of raw docs. Any raw docs that are not valid saved objects will simply be passed through. - * * @param {TransformFn} migrateDoc * @param {SavedObjectsRawDoc[]} rawDocs * @returns {SavedObjectsRawDoc[]} @@ -52,15 +70,9 @@ export async function migrateRawDocs( for (const raw of rawDocs) { const options = { namespaceTreatment: 'lax' as const }; if (serializer.isRawSavedObject(raw, options)) { - const savedObject = serializer.rawToSavedObject(raw, options); - savedObject.migrationVersion = savedObject.migrationVersion || {}; + const savedObject = convertToRawAddMigrationVersion(raw, options, serializer); processedDocs.push( - ...(await migrateDocWithoutBlocking(savedObject)).map((attrs) => - serializer.savedObjectToRaw({ - references: [], - ...attrs, - }) - ) + ...(await migrateMapToRawDoc(migrateDocWithoutBlocking, savedObject, serializer)) ); } else { throw new CorruptSavedObjectError(raw._id); @@ -69,6 +81,58 @@ export async function migrateRawDocs( return processedDocs; } +/** + * Applies the specified migration function to every saved object document provided + * and converts the saved object to a raw document. + * Captures the ids and errors from any documents that are not valid saved objects or + * for which the transformation function failed. + * @returns {TaskEither.TaskEither} + */ +export function migrateRawDocsSafely( + serializer: SavedObjectsSerializer, + migrateDoc: MigrateAndConvertFn, + rawDocs: SavedObjectsRawDoc[] +): TaskEither.TaskEither { + return async () => { + const migrateDocNonBlocking = transformNonBlocking(migrateDoc); + const processedDocs: SavedObjectsRawDoc[] = []; + const transformErrors: TransformErrorObjects[] = []; + const corruptSavedObjectIds: string[] = []; + const options = { namespaceTreatment: 'lax' as const }; + for (const raw of rawDocs) { + if (serializer.isRawSavedObject(raw, options)) { + try { + const savedObject = convertToRawAddMigrationVersion(raw, options, serializer); + processedDocs.push( + ...(await migrateMapToRawDoc(migrateDocNonBlocking, savedObject, serializer)) + ); + } catch (err) { + if (err instanceof TransformSavedObjectDocumentError) { + // the doc id we get from the error is only the uuid part + // we use the original raw document _id instead + transformErrors.push({ + rawId: raw._id, + err, + }); + } else { + transformErrors.push({ rawId: raw._id, err }); // cases we haven't accounted for yet + } + } + } else { + corruptSavedObjectIds.push(raw._id); + } + } + if (corruptSavedObjectIds.length > 0 || transformErrors.length > 0) { + return Either.left({ + type: 'documents_transform_failed', + corruptDocumentIds: [...corruptSavedObjectIds], + transformErrors, + }); + } + return Either.right({ processedDocs }); + }; +} + /** * Migration transform functions are potentially CPU heavy e.g. doing decryption/encryption * or (de)/serializing large JSON payloads. @@ -92,3 +156,40 @@ function transformNonBlocking( }); }); } + +/** + * Applies the specified migration function to every saved object document provided + * and converts the saved object to a raw document + * @param {MigrateFn} transformNonBlocking + * @param {SavedObjectsRawDoc[]} rawDoc + * @returns {Promise} + */ +async function migrateMapToRawDoc( + migrateMethod: MigrateFn, + savedObject: SavedObjectSanitizedDoc, + serializer: SavedObjectsSerializer +): Promise { + return [...(await migrateMethod(savedObject))].map((attrs) => + serializer.savedObjectToRaw({ + references: [], + ...attrs, + }) + ); +} + +/** + * Sanitizes the raw saved object document + * @param {SavedObjectRawDoc} rawDoc + * @param options + * @param {SavedObjectsSerializer} serializer + * @returns {SavedObjectSanitizedDoc} + */ +function convertToRawAddMigrationVersion( + rawDoc: SavedObjectsRawDoc, + options: { namespaceTreatment: 'lax' }, + serializer: SavedObjectsSerializer +): SavedObjectSanitizedDoc { + const savedObject = serializer.rawToSavedObject(rawDoc, options); + savedObject.migrationVersion = savedObject.migrationVersion || {}; + return savedObject; +} diff --git a/src/core/server/saved_objects/migrations/core/transform_saved_object_document_error.test.ts b/src/core/server/saved_objects/migrations/core/transform_saved_object_document_error.test.ts new file mode 100644 index 0000000000000..80c670edd39ba --- /dev/null +++ b/src/core/server/saved_objects/migrations/core/transform_saved_object_document_error.test.ts @@ -0,0 +1,60 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 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 { TransformSavedObjectDocumentError } from './transform_saved_object_document_error'; + +describe('TransformSavedObjectDocumentError', () => { + it('is a special error', () => { + const originalError = new Error('Dang diggity!'); + const err = new TransformSavedObjectDocumentError( + 'id', + 'type', + 'namespace', + 'failedTransform', + 'failedDoc', + originalError + ); + expect(err).toBeInstanceOf(TransformSavedObjectDocumentError); + expect(err.id).toEqual('id'); + expect(err.namespace).toEqual('namespace'); + expect(err.stack).not.toBeNull(); + }); + it('constructs an special error message', () => { + const originalError = new Error('Dang diggity!'); + const err = new TransformSavedObjectDocumentError( + 'id', + 'type', + 'namespace', + 'failedTransform', + 'failedDoc', + originalError + ); + expect(err.message).toMatchInlineSnapshot( + ` + "Failed to transform document id. Transform: failedTransform + Doc: failedDoc" + ` + ); + }); + it('handles undefined namespace', () => { + const originalError = new Error('Dang diggity!'); + const err = new TransformSavedObjectDocumentError( + 'id', + 'type', + undefined, + 'failedTransform', + 'failedDoc', + originalError + ); + expect(err.message).toMatchInlineSnapshot( + ` + "Failed to transform document id. Transform: failedTransform + Doc: failedDoc" + ` + ); + }); +}); diff --git a/src/core/server/saved_objects/migrations/core/transform_saved_object_document_error.ts b/src/core/server/saved_objects/migrations/core/transform_saved_object_document_error.ts new file mode 100644 index 0000000000000..6a6f87ea1eeb2 --- /dev/null +++ b/src/core/server/saved_objects/migrations/core/transform_saved_object_document_error.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. + */ + +/** + * Error thrown when saved object migrations encounter a transformation error. + * Transformation errors happen when a transform function throws an error for an unsanitized saved object + * The id (doc.id) reported in this error class is just the uuid part and doesn't tell users what the full elasticsearch id is. + * in order to convert the id to the serialized version further upstream using serializer.generateRawId, we need to provide the following items: + * - namespace: doc.namespace, + * - type: doc.type, + * - id: doc.id, + * The new error class helps with v2 migrations. + * For backward compatibility with v1 migrations, the error message is the same as what was previously thrown as a plain error + */ + +export class TransformSavedObjectDocumentError extends Error { + constructor( + public readonly id: string, + public readonly type: string, + public readonly namespace: string | undefined, + public readonly failedTransform: string, // created by document_migrator wrapWithTry as `${type.name}:${version}`; + public readonly failedDoc: string, + public readonly originalError: Error + ) { + super(`Failed to transform document ${id}. Transform: ${failedTransform}\nDoc: ${failedDoc}`); + } +} diff --git a/src/core/server/saved_objects/migrations/kibana/kibana_migrator.ts b/src/core/server/saved_objects/migrations/kibana/kibana_migrator.ts index e09284b49c86e..f74fe7e7a6e1c 100644 --- a/src/core/server/saved_objects/migrations/kibana/kibana_migrator.ts +++ b/src/core/server/saved_objects/migrations/kibana/kibana_migrator.ts @@ -35,7 +35,7 @@ import { SavedObjectsMigrationConfigType } from '../../saved_objects_config'; import { ISavedObjectTypeRegistry } from '../../saved_objects_type_registry'; import { SavedObjectsType } from '../../types'; import { runResilientMigrator } from '../../migrationsv2'; -import { migrateRawDocs } from '../core/migrate_raw_docs'; +import { migrateRawDocsSafely } from '../core/migrate_raw_docs'; export interface KibanaMigratorOptions { client: ElasticsearchClient; @@ -135,7 +135,6 @@ export class KibanaMigrator { if (!rerun) { this.status$.next({ status: 'running' }); } - this.migrationResult = this.runMigrationsInternal().then((result) => { // Similar to above, don't publish status updates when rerunning in CI. if (!rerun) { @@ -185,7 +184,11 @@ export class KibanaMigrator { logger: this.log, preMigrationScript: indexMap[index].script, transformRawDocs: (rawDocs: SavedObjectsRawDoc[]) => - migrateRawDocs(this.serializer, this.documentMigrator.migrateAndConvert, rawDocs), + migrateRawDocsSafely( + this.serializer, + this.documentMigrator.migrateAndConvert, + rawDocs + ), migrationVersionPerType: this.documentMigrator.migrationVersion, indexPrefix: index, migrationsConfig: this.soMigrationsConfig, diff --git a/src/core/server/saved_objects/migrationsv2/actions/index.test.ts b/src/core/server/saved_objects/migrationsv2/actions/index.test.ts index ba6aafbb2f651..df74a4e1282e4 100644 --- a/src/core/server/saved_objects/migrationsv2/actions/index.test.ts +++ b/src/core/server/saved_objects/migrationsv2/actions/index.test.ts @@ -129,18 +129,6 @@ describe('actions', () => { }); }); - describe('transformDocs', () => { - it('calls catchRetryableEsClientErrors when the promise rejects', async () => { - const task = Actions.transformDocs(client, () => Promise.resolve([]), [], 'my_index', false); - try { - await task(); - } catch (e) { - /** ignore */ - } - expect(catchRetryableEsClientErrors).toHaveBeenCalledWith(retryableError); - }); - }); - describe('reindex', () => { it('calls catchRetryableEsClientErrors when the promise rejects', async () => { const task = Actions.reindex( diff --git a/src/core/server/saved_objects/migrationsv2/actions/index.ts b/src/core/server/saved_objects/migrationsv2/actions/index.ts index 79261aecf675c..d0623de51e4c3 100644 --- a/src/core/server/saved_objects/migrationsv2/actions/index.ts +++ b/src/core/server/saved_objects/migrationsv2/actions/index.ts @@ -22,6 +22,10 @@ import { catchRetryableEsClientErrors, RetryableEsClientError, } from './catch_retryable_es_client_errors'; +import { + DocumentsTransformFailed, + DocumentsTransformSuccess, +} from '../../migrations/core/migrate_raw_docs'; export type { RetryableEsClientError }; /** @@ -46,6 +50,7 @@ export interface ActionErrorTypeMap { incompatible_mapping_exception: IncompatibleMappingException; alias_not_found_exception: AliasNotFound; remove_index_not_a_concrete_index: RemoveIndexNotAConcreteIndex; + documents_transform_failed: DocumentsTransformFailed; } /** @@ -523,28 +528,13 @@ export const closePit = ( }; /* - * Transform outdated docs and write them to the index. + * Transform outdated docs * */ export const transformDocs = ( - client: ElasticsearchClient, transformRawDocs: TransformRawDocs, - outdatedDocuments: SavedObjectsRawDoc[], - index: string, - // used for testing purposes only - refresh: estypes.Refresh -): TaskEither.TaskEither< - RetryableEsClientError | IndexNotFound | TargetIndexHadWriteBlock, - 'bulk_index_succeeded' -> => - pipe( - TaskEither.tryCatch( - () => transformRawDocs(outdatedDocuments), - (e) => { - throw e; - } - ), - TaskEither.chain((docs) => bulkOverwriteTransformedDocuments(client, index, docs, refresh)) - ); + outdatedDocuments: SavedObjectsRawDoc[] +): TaskEither.TaskEither => + transformRawDocs(outdatedDocuments); /** @internal */ export interface ReindexResponse { @@ -747,8 +737,6 @@ export const waitForPickupUpdatedMappingsTask = flow( } ) ); - -/** @internal */ export interface AliasNotFound { type: 'alias_not_found_exception'; } diff --git a/src/core/server/saved_objects/migrationsv2/integration_tests/actions.test.ts b/src/core/server/saved_objects/migrationsv2/integration_tests/actions.test.ts index 832d322037465..d0158a4c68f24 100644 --- a/src/core/server/saved_objects/migrationsv2/integration_tests/actions.test.ts +++ b/src/core/server/saved_objects/migrationsv2/integration_tests/actions.test.ts @@ -41,6 +41,8 @@ import { import * as Either from 'fp-ts/lib/Either'; import * as Option from 'fp-ts/lib/Option'; import { ResponseError } from '@elastic/elasticsearch/lib/errors'; +import { DocumentsTransformFailed, DocumentsTransformSuccess } from '../../migrations/core'; +import { TaskEither } from 'fp-ts/lib/TaskEither'; const { startES } = kbnTestServer.createTestServers({ adjustTimeout: (t: number) => jest.setTimeout(t), @@ -1014,41 +1016,30 @@ describe('migration actions', () => { }); describe('transformDocs', () => { - it('applies "transformRawDocs" and writes result into an index', async () => { - const index = 'transform_docs_index'; + it('applies "transformRawDocs" and returns the transformed documents', async () => { const originalDocs = [ { _id: 'foo:1', _source: { type: 'dashboard', value: 1 } }, { _id: 'foo:2', _source: { type: 'dashboard', value: 2 } }, ]; - const createIndexTask = createIndex(client, index, { - dynamic: true, - properties: {}, - }); - await createIndexTask(); - - async function tranformRawDocs(docs: SavedObjectsRawDoc[]): Promise { - for (const doc of docs) { - doc._source.value += 1; - } - return docs; + function innerTransformRawDocs( + docs: SavedObjectsRawDoc[] + ): TaskEither { + return async () => { + const processedDocs: SavedObjectsRawDoc[] = []; + for (const doc of docs) { + doc._source.value += 1; + processedDocs.push(doc); + } + return Either.right({ processedDocs }); + }; } + const transformTask = transformDocs(innerTransformRawDocs, originalDocs); - const transformTask = transformDocs(client, tranformRawDocs, originalDocs, index, 'wait_for'); - - const result = (await transformTask()) as Either.Right<'bulk_index_succeeded'>; - - expect(result.right).toBe('bulk_index_succeeded'); - - const { body } = await client.search<{ value: number }>({ - index, - }); - const hits = body.hits.hits; - - const foo1 = hits.find((h) => h._id === 'foo:1'); - expect(foo1?._source?.value).toBe(2); - - const foo2 = hits.find((h) => h._id === 'foo:2'); + const resultsWithProcessDocs = ((await transformTask()) as Either.Right) + .right.processedDocs; + expect(resultsWithProcessDocs.length).toEqual(2); + const foo2 = resultsWithProcessDocs.find((h) => h._id === 'foo:2'); expect(foo2?._source?.value).toBe(3); }); }); diff --git a/src/core/server/saved_objects/migrationsv2/integration_tests/archives/8.0.0_migrated_with_corrupt_outdated_docs.zip b/src/core/server/saved_objects/migrationsv2/integration_tests/archives/8.0.0_migrated_with_corrupt_outdated_docs.zip new file mode 100644 index 0000000000000..726df7782cda3 Binary files /dev/null and b/src/core/server/saved_objects/migrationsv2/integration_tests/archives/8.0.0_migrated_with_corrupt_outdated_docs.zip differ diff --git a/src/core/server/saved_objects/migrationsv2/integration_tests/cleanup.test.ts b/src/core/server/saved_objects/migrationsv2/integration_tests/cleanup.test.ts index 48bb282da18f6..1e494d4b55861 100644 --- a/src/core/server/saved_objects/migrationsv2/integration_tests/cleanup.test.ts +++ b/src/core/server/saved_objects/migrationsv2/integration_tests/cleanup.test.ts @@ -100,7 +100,7 @@ describe('migration v2', () => { await root.setup(); await expect(root.start()).rejects.toThrow( - /Unable to migrate the corrupt saved object document with _id: 'index-pattern:test_index\*'/ + 'Unable to complete saved object migrations for the [.kibana] index: Migrations failed. Reason: Corrupt saved object documents: index-pattern:test_index*. To allow migrations to proceed, please delete these documents.' ); const logFileContent = await asyncReadFile(logFilePath, 'utf-8'); diff --git a/src/core/server/saved_objects/migrationsv2/integration_tests/corrupt_outdated_docs.test.ts b/src/core/server/saved_objects/migrationsv2/integration_tests/corrupt_outdated_docs.test.ts new file mode 100644 index 0000000000000..e48f1e65c120f --- /dev/null +++ b/src/core/server/saved_objects/migrationsv2/integration_tests/corrupt_outdated_docs.test.ts @@ -0,0 +1,154 @@ +/* + * 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 Path from 'path'; +import Fs from 'fs'; +import Util from 'util'; +import * as kbnTestServer from '../../../../test_helpers/kbn_server'; +import { Root } from '../../../root'; + +const logFilePath = Path.join(__dirname, 'migration_test_corrupt_docs_kibana.log'); + +const asyncUnlink = Util.promisify(Fs.unlink); +async function removeLogFile() { + // ignore errors if it doesn't exist + await asyncUnlink(logFilePath).catch(() => void 0); +} + +describe('migration v2 with corrupt saved object documents', () => { + let esServer: kbnTestServer.TestElasticsearchUtils; + let root: Root; + + beforeAll(async () => { + await removeLogFile(); + }); + + afterAll(async () => { + if (root) { + await root.shutdown(); + } + if (esServer) { + await esServer.stop(); + } + + await new Promise((resolve) => setTimeout(resolve, 10000)); + }); + + it('collects corrupt saved object documents accross batches', async () => { + const { startES } = kbnTestServer.createTestServers({ + adjustTimeout: (t: number) => jest.setTimeout(t), + settings: { + es: { + license: 'basic', + // original uncorrupt SO: + // { + // type: 'foo', // 'bar', 'baz' + // foo: {}, // bar: {}, baz: {} + // migrationVersion: { + // foo: '7.13.0', + // }, + // }, + // original corrupt SO example: + // { + // id: 'bar:123' + // type: 'foo', + // foo: {}, + // migrationVersion: { + // foo: '7.13.0', + // }, + // }, + // contains migrated index with 8.0 aliases to skip migration, but run outdated doc search + dataArchive: Path.join( + __dirname, + 'archives', + '8.0.0_migrated_with_corrupt_outdated_docs.zip' + ), + }, + }, + }); + + root = createRoot(); + + esServer = await startES(); + const coreSetup = await root.setup(); + + coreSetup.savedObjects.registerType({ + name: 'foo', + hidden: false, + mappings: { properties: {} }, + namespaceType: 'agnostic', + migrations: { + '7.14.0': (doc) => doc, + }, + }); + coreSetup.savedObjects.registerType({ + name: 'bar', + hidden: false, + mappings: { properties: {} }, + namespaceType: 'agnostic', + migrations: { + '7.14.0': (doc) => doc, + }, + }); + coreSetup.savedObjects.registerType({ + name: 'baz', + hidden: false, + mappings: { properties: {} }, + namespaceType: 'agnostic', + migrations: { + '7.14.0': (doc) => doc, + }, + }); + try { + await root.start(); + } catch (err) { + const corruptFooSOs = /foo:/g; + const corruptBarSOs = /bar:/g; + const corruptBazSOs = /baz:/g; + expect( + [ + ...err.message.matchAll(corruptFooSOs), + ...err.message.matchAll(corruptBarSOs), + ...err.message.matchAll(corruptBazSOs), + ].length + ).toEqual(16); + } + }); +}); + +function createRoot() { + return kbnTestServer.createRootWithCorePlugins( + { + migrations: { + skip: false, + enableV2: true, + batchSize: 5, + }, + logging: { + appenders: { + file: { + type: 'file', + fileName: logFilePath, + layout: { + type: 'json', + }, + }, + }, + loggers: [ + { + name: 'root', + appenders: ['file'], + }, + ], + }, + }, + { + oss: true, + } + ); +} diff --git a/src/core/server/saved_objects/migrationsv2/migrations_state_action_machine.ts b/src/core/server/saved_objects/migrationsv2/migrations_state_action_machine.ts index 85cc86fe0a468..8443f837a7f1d 100644 --- a/src/core/server/saved_objects/migrationsv2/migrations_state_action_machine.ts +++ b/src/core/server/saved_objects/migrationsv2/migrations_state_action_machine.ts @@ -10,7 +10,6 @@ import { errors as EsErrors } from '@elastic/elasticsearch'; import * as Option from 'fp-ts/lib/Option'; import { Logger, LogMeta } from '../../logging'; import type { ElasticsearchClient } from '../../elasticsearch'; -import { CorruptSavedObjectError } from '../migrations/core/migrate_raw_docs'; import { Model, Next, stateActionMachine } from './state_action_machine'; import { cleanup } from './migrations_state_machine_cleanup'; import { State } from './types'; @@ -74,7 +73,6 @@ const logActionResponse = ( ) => { logger.debug(logMessagePrefix + `${state.controlState} RESPONSE`, res as LogMeta); }; - const dumpExecutionLog = (logger: Logger, logMessagePrefix: string, executionLog: ExecutionLog) => { logger.error(logMessagePrefix + 'migration failed, dumping execution log:'); executionLog.forEach((log) => { @@ -211,11 +209,6 @@ export async function migrationStateActionMachine({ logger.error(e); dumpExecutionLog(logger, logMessagePrefix, executionLog); - if (e instanceof CorruptSavedObjectError) { - throw new Error( - `${e.message} To allow migrations to proceed, please delete this document from the [${initialState.indexPrefix}_${initialState.kibanaVersion}_001] index.` - ); - } const newError = new Error( `Unable to complete saved object migrations for the [${initialState.indexPrefix}] index. ${e}` diff --git a/src/core/server/saved_objects/migrationsv2/model.test.ts b/src/core/server/saved_objects/migrationsv2/model.test.ts index 213e8b43c0ea0..bdaedba9c9ea3 100644 --- a/src/core/server/saved_objects/migrationsv2/model.test.ts +++ b/src/core/server/saved_objects/migrationsv2/model.test.ts @@ -36,12 +36,15 @@ import type { CloneTempToSource, SetTempWriteBlock, WaitForYellowSourceState, + TransformedDocumentsBulkIndex, + ReindexSourceToTempIndexBulk, } from './types'; import { SavedObjectsRawDoc } from '..'; import { AliasAction, RetryableEsClientError } from './actions'; import { createInitialState, model } from './model'; import { ResponseType } from './next'; import { SavedObjectsMigrationConfigType } from '../saved_objects_config'; +import { TransformErrorObjects, TransformSavedObjectDocumentError } from '../migrations/core'; describe('migrations v2 model', () => { const baseState: BaseState = { @@ -778,6 +781,8 @@ describe('migrations v2 model', () => { targetIndex: '.kibana_7.11.0_001', tempIndexMappings: { properties: {} }, lastHitSortValue: undefined, + corruptDocumentIds: [], + transformErrors: [], }; it('REINDEX_SOURCE_TO_TEMP_READ -> REINDEX_SOURCE_TO_TEMP_INDEX if the index has outdated documents to reindex', () => { @@ -802,6 +807,23 @@ describe('migrations v2 model', () => { expect(newState.controlState).toBe('REINDEX_SOURCE_TO_TEMP_CLOSE_PIT'); expect(newState.sourceIndexPitId).toBe('pit_id'); }); + + it('REINDEX_SOURCE_TO_TEMP_READ -> FATAL if no outdated documents to reindex and transform failures seen with previous outdated documents', () => { + const testState: ReindexSourceToTempRead = { + ...state, + corruptDocumentIds: ['a:b'], + transformErrors: [], + }; + const res: ResponseType<'REINDEX_SOURCE_TO_TEMP_READ'> = Either.right({ + outdatedDocuments: [], + lastHitSortValue: undefined, + }); + const newState = model(testState, res) as FatalState; + expect(newState.controlState).toBe('FATAL'); + expect(newState.reason).toMatchInlineSnapshot( + `"Migrations failed. Reason: Corrupt saved object documents: a:b. To allow migrations to proceed, please delete these documents."` + ); + }); }); describe('REINDEX_SOURCE_TO_TEMP_CLOSE_PIT', () => { @@ -833,38 +855,89 @@ describe('migrations v2 model', () => { sourceIndexPitId: 'pit_id', targetIndex: '.kibana_7.11.0_001', lastHitSortValue: undefined, + corruptDocumentIds: [], + transformErrors: [], }; + const processedDocs = [ + { + _id: 'a:b', + _source: { type: 'a', a: { name: 'HOI!' }, migrationVersion: {}, references: [] }, + }, + ] as SavedObjectsRawDoc[]; - it('REINDEX_SOURCE_TO_TEMP_INDEX -> REINDEX_SOURCE_TO_TEMP_READ if action succeeded', () => { - const res: ResponseType<'REINDEX_SOURCE_TO_TEMP_INDEX'> = Either.right( - 'bulk_index_succeeded' - ); + it('REINDEX_SOURCE_TO_TEMP_INDEX -> REINDEX_SOURCE_TO_TEMP_INDEX_BULK if action succeeded', () => { + const res: ResponseType<'REINDEX_SOURCE_TO_TEMP_INDEX'> = Either.right({ + processedDocs, + }); const newState = model(state, res); + expect(newState.controlState).toEqual('REINDEX_SOURCE_TO_TEMP_INDEX_BULK'); + }); + + it('REINDEX_SOURCE_TO_TEMP_INDEX -> REINDEX_SOURCE_TO_TEMP_READ if action succeeded but we have carried through previous failures', () => { + const res: ResponseType<'REINDEX_SOURCE_TO_TEMP_INDEX'> = Either.right({ + processedDocs, + }); + const testState = { + ...state, + corruptDocumentIds: ['a:b'], + transformErrors: [], + }; + const newState = model(testState, res) as ReindexSourceToTempIndex; expect(newState.controlState).toEqual('REINDEX_SOURCE_TO_TEMP_READ'); - expect(newState.retryCount).toEqual(0); - expect(newState.retryDelay).toEqual(0); + expect(newState.corruptDocumentIds.length).toEqual(1); + expect(newState.transformErrors.length).toEqual(0); }); - it('REINDEX_SOURCE_TO_TEMP_INDEX -> REINDEX_SOURCE_TO_TEMP_READ when response is left target_index_had_write_block', () => { + it('REINDEX_SOURCE_TO_TEMP_INDEX -> REINDEX_SOURCE_TO_TEMP_READ when response is left documents_transform_failed', () => { const res: ResponseType<'REINDEX_SOURCE_TO_TEMP_INDEX'> = Either.left({ - type: 'target_index_had_write_block', + type: 'documents_transform_failed', + corruptDocumentIds: ['a:b'], + transformErrors: [], }); const newState = model(state, res) as ReindexSourceToTempRead; expect(newState.controlState).toEqual('REINDEX_SOURCE_TO_TEMP_READ'); + expect(newState.corruptDocumentIds.length).toEqual(1); + expect(newState.transformErrors.length).toEqual(0); expect(newState.retryCount).toEqual(0); expect(newState.retryDelay).toEqual(0); }); - - it('REINDEX_SOURCE_TO_TEMP_INDEX -> REINDEX_SOURCE_TO_TEMP_READ when response is left index_not_found_exception for temp index', () => { - const res: ResponseType<'REINDEX_SOURCE_TO_TEMP_INDEX'> = Either.left({ - type: 'index_not_found_exception', - index: state.tempIndex, - }); - const newState = model(state, res) as ReindexSourceToTempRead; + }); + describe('REINDEX_SOURCE_TO_TEMP_INDEX_BULK', () => { + const transformedDocs = [ + { + _id: 'a:b', + _source: { type: 'a', a: { name: 'HOI!' }, migrationVersion: {}, references: [] }, + }, + ] as SavedObjectsRawDoc[]; + const reindexSourceToTempIndexBulkState: ReindexSourceToTempIndexBulk = { + ...baseState, + controlState: 'REINDEX_SOURCE_TO_TEMP_INDEX_BULK', + transformedDocs, + versionIndexReadyActions: Option.none, + sourceIndex: Option.some('.kibana') as Option.Some, + sourceIndexPitId: 'pit_id', + targetIndex: '.kibana_7.11.0_001', + lastHitSortValue: undefined, + }; + test('REINDEX_SOURCE_TO_TEMP_INDEX_BULK -> REINDEX_SOURCE_TO_TEMP_READ if action succeeded', () => { + const res: ResponseType<'REINDEX_SOURCE_TO_TEMP_INDEX_BULK'> = Either.right( + 'bulk_index_succeeded' + ); + const newState = model(reindexSourceToTempIndexBulkState, res); expect(newState.controlState).toEqual('REINDEX_SOURCE_TO_TEMP_READ'); expect(newState.retryCount).toEqual(0); expect(newState.retryDelay).toEqual(0); }); + test('REINDEX_SOURCE_TO_TEMP_INDEX_BULK should throw a throwBadResponse error if action failed', () => { + const res: ResponseType<'REINDEX_SOURCE_TO_TEMP_INDEX_BULK'> = Either.left({ + type: 'retryable_es_client_error', + message: 'random documents bulk index error', + }); + const newState = model(reindexSourceToTempIndexBulkState, res); + expect(newState.controlState).toEqual('REINDEX_SOURCE_TO_TEMP_INDEX_BULK'); + expect(newState.retryCount).toEqual(1); + expect(newState.retryDelay).toEqual(2000); + }); }); describe('SET_TEMP_WRITE_BLOCK', () => { @@ -943,6 +1016,8 @@ describe('migrations v2 model', () => { targetIndex: '.kibana_7.11.0_001', lastHitSortValue: undefined, hasTransformedDocs: false, + corruptDocumentIds: [], + transformErrors: [], }; it('OUTDATED_DOCUMENTS_SEARCH_READ -> OUTDATED_DOCUMENTS_TRANSFORM if found documents to transform', () => { @@ -967,6 +1042,37 @@ describe('migrations v2 model', () => { expect(newState.controlState).toBe('OUTDATED_DOCUMENTS_SEARCH_CLOSE_PIT'); expect(newState.pitId).toBe('pit_id'); }); + + it('OUTDATED_DOCUMENTS_SEARCH_READ -> FATAL if no outdated documents to transform and we have failed document migrations', () => { + const corruptDocumentIdsCarriedOver = ['a:somethingelse']; + const originalTransformError = new Error('something went wrong'); + const transFormErr = new TransformSavedObjectDocumentError( + '123', + 'vis', + undefined, + 'randomvis: 7.12.0', + 'failedDoc', + originalTransformError + ); + const transformationErrors = [ + { rawId: 'bob:tail', err: transFormErr }, + ] as TransformErrorObjects[]; + const res: ResponseType<'OUTDATED_DOCUMENTS_SEARCH_READ'> = Either.right({ + outdatedDocuments: [], + lastHitSortValue: undefined, + }); + const transformErrorsState: OutdatedDocumentsSearchRead = { + ...state, + corruptDocumentIds: [...corruptDocumentIdsCarriedOver], + transformErrors: [...transformationErrors], + }; + const newState = model(transformErrorsState, res) as FatalState; + expect(newState.controlState).toBe('FATAL'); + expect(newState.reason.includes('Migrations failed. Reason:')).toBe(true); + expect(newState.reason.includes('Corrupt saved object documents: ')).toBe(true); + expect(newState.reason.includes('Transformation errors: ')).toBe(true); + expect(newState.reason.includes('randomvis: 7.12.0')).toBe(true); + }); }); describe('OUTDATED_DOCUMENTS_SEARCH_CLOSE_PIT', () => { @@ -1006,9 +1112,20 @@ describe('migrations v2 model', () => { }); describe('OUTDATED_DOCUMENTS_TRANSFORM', () => { - const outdatedDocuments = ([ - Symbol('raw saved object doc'), - ] as unknown) as SavedObjectsRawDoc[]; + const outdatedDocuments = [{ _id: '1', _source: { type: 'vis' } }]; + const corruptDocumentIds = ['a:somethingelse']; + const originalTransformError = new Error('Dang diggity!'); + const transFormErr = new TransformSavedObjectDocumentError( + 'id', + 'type', + 'namespace', + 'failedTransform', + 'failedDoc', + originalTransformError + ); + const transformationErrors = [ + { rawId: 'bob:tail', err: transFormErr }, + ] as TransformErrorObjects[]; const outdatedDocumentsTransformState: OutdatedDocumentsTransform = { ...baseState, controlState: 'OUTDATED_DOCUMENTS_TRANSFORM', @@ -1016,18 +1133,132 @@ describe('migrations v2 model', () => { sourceIndex: Option.some('.kibana') as Option.Some, targetIndex: '.kibana_7.11.0_001', outdatedDocuments, + corruptDocumentIds: [], + transformErrors: [], pitId: 'pit_id', lastHitSortValue: [3, 4], hasTransformedDocs: false, }; - test('OUTDATED_DOCUMENTS_TRANSFORM -> OUTDATED_DOCUMENTS_SEARCH_READ if action succeeds', () => { - const res: ResponseType<'OUTDATED_DOCUMENTS_TRANSFORM'> = Either.right( - 'bulk_index_succeeded' - ); - const newState = model(outdatedDocumentsTransformState, res); - expect(newState.controlState).toEqual('OUTDATED_DOCUMENTS_SEARCH_READ'); - expect(newState.retryCount).toEqual(0); - expect(newState.retryDelay).toEqual(0); + describe('OUTDATED_DOCUMENTS_TRANSFORM if action succeeds', () => { + const processedDocs = [ + { + _id: 'a:b', + _source: { type: 'a', a: { name: 'HOI!' }, migrationVersion: {}, references: [] }, + }, + ] as SavedObjectsRawDoc[]; + test('OUTDATED_DOCUMENTS_TRANSFORM -> TRANSFORMED_DOCUMENTS_BULK_INDEX if action succeeds', () => { + const res: ResponseType<'OUTDATED_DOCUMENTS_TRANSFORM'> = Either.right({ processedDocs }); + const newState = model( + outdatedDocumentsTransformState, + res + ) as TransformedDocumentsBulkIndex; + expect(newState.controlState).toEqual('TRANSFORMED_DOCUMENTS_BULK_INDEX'); + expect(newState.transformedDocs).toEqual(processedDocs); + expect(newState.retryCount).toEqual(0); + expect(newState.retryDelay).toEqual(0); + }); + test('OUTDATED_DOCUMENTS_TRANSFORM -> OUTDATED_DOCUMENTS_SEARCH_READ if there are are existing documents that failed transformation', () => { + const outdatedDocumentsTransformStateWithFailedDocuments: OutdatedDocumentsTransform = { + ...outdatedDocumentsTransformState, + corruptDocumentIds: [...corruptDocumentIds], + transformErrors: [], + }; + const res: ResponseType<'OUTDATED_DOCUMENTS_TRANSFORM'> = Either.right({ processedDocs }); + const newState = model( + outdatedDocumentsTransformStateWithFailedDocuments, + res + ) as OutdatedDocumentsSearchRead; + expect(newState.controlState).toEqual('OUTDATED_DOCUMENTS_SEARCH_READ'); + expect(newState.corruptDocumentIds).toEqual(corruptDocumentIds); + expect(newState.retryCount).toEqual(0); + expect(newState.retryDelay).toEqual(0); + }); + test('OUTDATED_DOCUMENTS_TRANSFORM -> OUTDATED_DOCUMENTS_SEARCH_READ if there are are existing documents that failed transformation because of transform errors', () => { + const outdatedDocumentsTransformStateWithFailedDocuments: OutdatedDocumentsTransform = { + ...outdatedDocumentsTransformState, + corruptDocumentIds: [], + transformErrors: [...transformationErrors], + }; + const res: ResponseType<'OUTDATED_DOCUMENTS_TRANSFORM'> = Either.right({ processedDocs }); + const newState = model( + outdatedDocumentsTransformStateWithFailedDocuments, + res + ) as OutdatedDocumentsSearchRead; + expect(newState.controlState).toEqual('OUTDATED_DOCUMENTS_SEARCH_READ'); + expect(newState.corruptDocumentIds.length).toEqual(0); + expect(newState.transformErrors.length).toEqual(1); + expect(newState.retryCount).toEqual(0); + expect(newState.retryDelay).toEqual(0); + }); + }); + describe('OUTDATED_DOCUMENTS_TRANSFORM if action fails', () => { + test('OUTDATED_DOCUMENTS_TRANSFORM -> OUTDATED_DOCUMENTS_SEARCH_READ adding newly failed documents to state if documents failed the transform', () => { + const res: ResponseType<'OUTDATED_DOCUMENTS_TRANSFORM'> = Either.left({ + type: 'documents_transform_failed', + corruptDocumentIds, + transformErrors: [], + }); + const newState = model( + outdatedDocumentsTransformState, + res + ) as OutdatedDocumentsSearchRead; + expect(newState.controlState).toEqual('OUTDATED_DOCUMENTS_SEARCH_READ'); + expect(newState.corruptDocumentIds).toEqual(corruptDocumentIds); + }); + test('OUTDATED_DOCUMENTS_TRANSFORM -> OUTDATED_DOCUMENTS_SEARCH_READ combines newly failed documents with those already on state if documents failed the transform', () => { + const newFailedTransformDocumentIds = ['b:other', 'c:__']; + const outdatedDocumentsTransformStateWithFailedDocuments: OutdatedDocumentsTransform = { + ...outdatedDocumentsTransformState, + corruptDocumentIds: [...corruptDocumentIds], + transformErrors: [...transformationErrors], + }; + const res: ResponseType<'OUTDATED_DOCUMENTS_TRANSFORM'> = Either.left({ + type: 'documents_transform_failed', + corruptDocumentIds: newFailedTransformDocumentIds, + transformErrors: transformationErrors, + }); + const newState = model( + outdatedDocumentsTransformStateWithFailedDocuments, + res + ) as OutdatedDocumentsSearchRead; + expect(newState.controlState).toEqual('OUTDATED_DOCUMENTS_SEARCH_READ'); + expect(newState.corruptDocumentIds).toEqual([ + ...corruptDocumentIds, + ...newFailedTransformDocumentIds, + ]); + }); + }); + }); + describe('TRANSFORMED_DOCUMENTS_BULK_INDEX', () => { + const transformedDocs = [ + { + _id: 'a:b', + _source: { type: 'a', a: { name: 'HOI!' }, migrationVersion: {}, references: [] }, + }, + ] as SavedObjectsRawDoc[]; + const transformedDocumentsBulkIndexState: TransformedDocumentsBulkIndex = { + ...baseState, + controlState: 'TRANSFORMED_DOCUMENTS_BULK_INDEX', + transformedDocs, + versionIndexReadyActions: Option.none, + sourceIndex: Option.some('.kibana') as Option.Some, + targetIndex: '.kibana_7.11.0_001', + pitId: 'pit_id', + lastHitSortValue: [3, 4], + hasTransformedDocs: false, + }; + test('TRANSFORMED_DOCUMENTS_BULK_INDEX should throw a throwBadResponse error if action failed', () => { + const res: ResponseType<'TRANSFORMED_DOCUMENTS_BULK_INDEX'> = Either.left({ + type: 'retryable_es_client_error', + message: 'random documents bulk index error', + }); + const newState = model( + transformedDocumentsBulkIndexState, + res + ) as TransformedDocumentsBulkIndex; + expect(newState.controlState).toEqual('TRANSFORMED_DOCUMENTS_BULK_INDEX'); + expect(newState.retryCount).toEqual(1); + expect(newState.retryDelay).toEqual(2000); }); }); diff --git a/src/core/server/saved_objects/migrationsv2/model.ts b/src/core/server/saved_objects/migrationsv2/model.ts index 318eff19d5e24..cf9d6aec6b5b0 100644 --- a/src/core/server/saved_objects/migrationsv2/model.ts +++ b/src/core/server/saved_objects/migrationsv2/model.ts @@ -16,7 +16,7 @@ import { IndexMapping } from '../mappings'; import { ResponseType } from './next'; import { SavedObjectsMigrationVersion } from '../types'; import { disableUnknownTypeMappingFields } from '../migrations/core/migration_context'; -import { excludeUnusedTypesQuery } from '../migrations/core'; +import { excludeUnusedTypesQuery, TransformErrorObjects } from '../migrations/core'; import { SavedObjectsMigrationConfigType } from '../saved_objects_config'; /** @@ -97,6 +97,31 @@ function getAliases(indices: FetchIndexResponse) { }, {} as Record); } +/** + * Constructs migration failure message strings from corrupt document ids and document transformation errors + */ +function extractTransformFailuresReason( + corruptDocumentIds: string[], + transformErrors: TransformErrorObjects[] +): { corruptDocsReason: string; transformErrsReason: string } { + const corruptDocumentIdReason = + corruptDocumentIds.length > 0 + ? ` Corrupt saved object documents: ${corruptDocumentIds.join(',')}` + : ''; + // we have both the saved object Id and the stack trace in each `transformErrors` item. + const transformErrorsReason = + transformErrors.length > 0 + ? ' Transformation errors: ' + + transformErrors + .map((errObj) => `${errObj.rawId}: ${errObj.err.message}\n ${errObj.err.stack ?? ''}`) + .join('/n') + : ''; + return { + corruptDocsReason: corruptDocumentIdReason, + transformErrsReason: transformErrorsReason, + }; +} + const delayRetryState = ( state: S, errorMessage: string, @@ -481,11 +506,15 @@ export const model = (currentState: State, resW: ResponseType): controlState: 'REINDEX_SOURCE_TO_TEMP_READ', sourceIndexPitId: res.right.pitId, lastHitSortValue: undefined, + // placeholders to collect document transform problems + corruptDocumentIds: [], + transformErrors: [], }; } else { throwBadResponse(stateP, res); } } else if (stateP.controlState === 'REINDEX_SOURCE_TO_TEMP_READ') { + // we carry through any failures we've seen with transforming documents on state const res = resW as ExcludeRetryableEsError>; if (Either.isRight(res)) { if (res.right.outdatedDocuments.length > 0) { @@ -495,11 +524,27 @@ export const model = (currentState: State, resW: ResponseType): outdatedDocuments: res.right.outdatedDocuments, lastHitSortValue: res.right.lastHitSortValue, }; + } else { + // we don't have any more outdated documents and need to either fail or move on to updating the target mappings. + if (stateP.corruptDocumentIds.length > 0 || stateP.transformErrors.length > 0) { + const { corruptDocsReason, transformErrsReason } = extractTransformFailuresReason( + stateP.corruptDocumentIds, + stateP.transformErrors + ); + return { + ...stateP, + controlState: 'FATAL', + reason: `Migrations failed. Reason:${corruptDocsReason}${transformErrsReason}. To allow migrations to proceed, please delete these documents.`, + }; + } else { + // we don't have any more outdated documents and we haven't encountered any document transformation issues. + // Close the PIT search and carry on with the happy path. + return { + ...stateP, + controlState: 'REINDEX_SOURCE_TO_TEMP_CLOSE_PIT', + }; + } } - return { - ...stateP, - controlState: 'REINDEX_SOURCE_TO_TEMP_CLOSE_PIT', - }; } else { throwBadResponse(stateP, res); } @@ -516,34 +561,55 @@ export const model = (currentState: State, resW: ResponseType): throwBadResponse(stateP, res); } } else if (stateP.controlState === 'REINDEX_SOURCE_TO_TEMP_INDEX') { + // We follow a similar control flow as for + // outdated document search -> outdated document transform -> transform documents bulk index + // collecting issues along the way rather than failing + // REINDEX_SOURCE_TO_TEMP_INDEX handles the document transforms const res = resW as ExcludeRetryableEsError>; if (Either.isRight(res)) { - return { - ...stateP, - controlState: 'REINDEX_SOURCE_TO_TEMP_READ', - }; + if (stateP.corruptDocumentIds.length === 0 && stateP.transformErrors.length === 0) { + return { + ...stateP, + controlState: 'REINDEX_SOURCE_TO_TEMP_INDEX_BULK', // handles the actual bulk indexing into temp index + transformedDocs: [...res.right.processedDocs], + }; + } else { + // we don't have any transform issues with the current batch of outdated docs but + // we have carried through previous transformation issues. + // The migration will ultimately fail but before we do that, continue to + // search through remaining docs for more issues and pass the previous failures along on state + return { + ...stateP, + controlState: 'REINDEX_SOURCE_TO_TEMP_READ', + }; + } } else { + // we have failures from the current batch of documents and add them to the lists const left = res.left; - if ( - isLeftTypeof(left, 'target_index_had_write_block') || - (isLeftTypeof(left, 'index_not_found_exception') && left.index === stateP.tempIndex) - ) { - // index_not_found_exception: - // another instance completed the MARK_VERSION_INDEX_READY and - // removed the temp index. - // target_index_had_write_block - // another instance completed the SET_TEMP_WRITE_BLOCK step adding a - // write block to the temp index. - // - // For simplicity we continue linearly through the next steps even if - // we know another instance already completed these. + if (isLeftTypeof(left, 'documents_transform_failed')) { return { ...stateP, controlState: 'REINDEX_SOURCE_TO_TEMP_READ', + corruptDocumentIds: [...stateP.corruptDocumentIds, ...left.corruptDocumentIds], + transformErrors: [...stateP.transformErrors, ...left.transformErrors], }; + } else { + // should never happen + throwBadResponse(stateP, res as never); } - // should never happen - throwBadResponse(stateP, res as never); + } + } else if (stateP.controlState === 'REINDEX_SOURCE_TO_TEMP_INDEX_BULK') { + const res = resW as ExcludeRetryableEsError>; + if (Either.isRight(res)) { + return { + ...stateP, + controlState: 'REINDEX_SOURCE_TO_TEMP_READ', + // we're still on the happy path with no transformation failures seen. + corruptDocumentIds: [], + transformErrors: [], + }; + } else { + throwBadResponse(stateP, res); } } else if (stateP.controlState === 'SET_TEMP_WRITE_BLOCK') { const res = resW as ExcludeRetryableEsError>; @@ -611,6 +677,8 @@ export const model = (currentState: State, resW: ResponseType): pitId: res.right.pitId, lastHitSortValue: undefined, hasTransformedDocs: false, + corruptDocumentIds: [], + transformErrors: [], }; } else { throwBadResponse(stateP, res); @@ -626,59 +694,111 @@ export const model = (currentState: State, resW: ResponseType): lastHitSortValue: res.right.lastHitSortValue, }; } else { + // we don't have any more outdated documents and need to either fail or move on to updating the target mappings. + if (stateP.corruptDocumentIds.length > 0 || stateP.transformErrors.length > 0) { + const { corruptDocsReason, transformErrsReason } = extractTransformFailuresReason( + stateP.corruptDocumentIds, + stateP.transformErrors + ); + return { + ...stateP, + controlState: 'FATAL', + reason: `Migrations failed. Reason:${corruptDocsReason}${transformErrsReason}. To allow migrations to proceed, please delete these documents.`, + }; + } else { + // If there are no more results we have transformed all outdated + // documents and we didn't encounter any corrupt documents or transformation errors + // and can proceed to the next step + return { + ...stateP, + controlState: 'OUTDATED_DOCUMENTS_SEARCH_CLOSE_PIT', + }; + } + } + } else { + throwBadResponse(stateP, res); + } + } else if (stateP.controlState === 'OUTDATED_DOCUMENTS_TRANSFORM') { + const res = resW as ExcludeRetryableEsError>; + if (Either.isRight(res)) { + // we haven't seen corrupt documents or any transformation errors thus far in the migration + // index the migrated docs + if (stateP.corruptDocumentIds.length === 0 && stateP.transformErrors.length === 0) { + return { + ...stateP, + controlState: 'TRANSFORMED_DOCUMENTS_BULK_INDEX', + transformedDocs: [...res.right.processedDocs], + hasTransformedDocs: true, + }; + } else { + // We have seen corrupt documents and/or transformation errors + // skip indexing and go straight to reading and transforming more docs return { ...stateP, - controlState: 'OUTDATED_DOCUMENTS_SEARCH_CLOSE_PIT', + controlState: 'OUTDATED_DOCUMENTS_SEARCH_READ', }; } } else { - throwBadResponse(stateP, res); + if (isLeftTypeof(res.left, 'documents_transform_failed')) { + // continue to build up any more transformation errors before failing the migration. + return { + ...stateP, + controlState: 'OUTDATED_DOCUMENTS_SEARCH_READ', + corruptDocumentIds: [...stateP.corruptDocumentIds, ...res.left.corruptDocumentIds], + transformErrors: [...stateP.transformErrors, ...res.left.transformErrors], + hasTransformedDocs: false, + }; + } else { + throwBadResponse(stateP, res as never); + } } - } else if (stateP.controlState === 'OUTDATED_DOCUMENTS_REFRESH') { + } else if (stateP.controlState === 'TRANSFORMED_DOCUMENTS_BULK_INDEX') { const res = resW as ExcludeRetryableEsError>; if (Either.isRight(res)) { return { ...stateP, - controlState: 'UPDATE_TARGET_MAPPINGS', + controlState: 'OUTDATED_DOCUMENTS_SEARCH_READ', + corruptDocumentIds: [], + transformErrors: [], + hasTransformedDocs: true, }; } else { throwBadResponse(stateP, res); } - } else if (stateP.controlState === 'OUTDATED_DOCUMENTS_SEARCH_CLOSE_PIT') { + } else if (stateP.controlState === 'UPDATE_TARGET_MAPPINGS') { const res = resW as ExcludeRetryableEsError>; if (Either.isRight(res)) { - const { pitId, hasTransformedDocs, ...state } = stateP; - if (hasTransformedDocs) { - return { - ...state, - controlState: 'OUTDATED_DOCUMENTS_REFRESH', - }; - } return { - ...state, - controlState: 'UPDATE_TARGET_MAPPINGS', + ...stateP, + controlState: 'UPDATE_TARGET_MAPPINGS_WAIT_FOR_TASK', + updateTargetMappingsTaskId: res.right.taskId, }; } else { - throwBadResponse(stateP, res); + throwBadResponse(stateP, res as never); } - } else if (stateP.controlState === 'OUTDATED_DOCUMENTS_TRANSFORM') { + } else if (stateP.controlState === 'OUTDATED_DOCUMENTS_REFRESH') { const res = resW as ExcludeRetryableEsError>; if (Either.isRight(res)) { return { ...stateP, - controlState: 'OUTDATED_DOCUMENTS_SEARCH_READ', - hasTransformedDocs: true, + controlState: 'UPDATE_TARGET_MAPPINGS', }; } else { - throwBadResponse(stateP, res as never); + throwBadResponse(stateP, res); } - } else if (stateP.controlState === 'UPDATE_TARGET_MAPPINGS') { + } else if (stateP.controlState === 'OUTDATED_DOCUMENTS_SEARCH_CLOSE_PIT') { const res = resW as ExcludeRetryableEsError>; if (Either.isRight(res)) { + const { pitId, hasTransformedDocs, ...state } = stateP; + if (hasTransformedDocs) { + return { + ...state, + controlState: 'OUTDATED_DOCUMENTS_REFRESH', + }; + } return { - ...stateP, - controlState: 'UPDATE_TARGET_MAPPINGS_WAIT_FOR_TASK', - updateTargetMappingsTaskId: res.right.taskId, + ...state, + controlState: 'UPDATE_TARGET_MAPPINGS', }; } else { throwBadResponse(stateP, res); diff --git a/src/core/server/saved_objects/migrationsv2/next.ts b/src/core/server/saved_objects/migrationsv2/next.ts index 536c07d6a071d..07ebf80271d48 100644 --- a/src/core/server/saved_objects/migrationsv2/next.ts +++ b/src/core/server/saved_objects/migrationsv2/next.ts @@ -32,6 +32,8 @@ import type { SetTempWriteBlock, WaitForYellowSourceState, TransformRawDocs, + TransformedDocumentsBulkIndex, + ReindexSourceToTempIndexBulk, OutdatedDocumentsSearchOpenPit, OutdatedDocumentsSearchRead, OutdatedDocumentsSearchClosePit, @@ -82,11 +84,12 @@ export const nextActionMap = (client: ElasticsearchClient, transformRawDocs: Tra REINDEX_SOURCE_TO_TEMP_CLOSE_PIT: (state: ReindexSourceToTempClosePit) => Actions.closePit(client, state.sourceIndexPitId), REINDEX_SOURCE_TO_TEMP_INDEX: (state: ReindexSourceToTempIndex) => - Actions.transformDocs( + Actions.transformDocs(transformRawDocs, state.outdatedDocuments), + REINDEX_SOURCE_TO_TEMP_INDEX_BULK: (state: ReindexSourceToTempIndexBulk) => + Actions.bulkOverwriteTransformedDocuments( client, - transformRawDocs, - state.outdatedDocuments, state.tempIndex, + state.transformedDocs, /** * Since we don't run a search against the target index, we disable "refresh" to speed up * the migration process. @@ -121,11 +124,12 @@ export const nextActionMap = (client: ElasticsearchClient, transformRawDocs: Tra OUTDATED_DOCUMENTS_REFRESH: (state: OutdatedDocumentsRefresh) => Actions.refreshIndex(client, state.targetIndex), OUTDATED_DOCUMENTS_TRANSFORM: (state: OutdatedDocumentsTransform) => - Actions.transformDocs( + Actions.transformDocs(transformRawDocs, state.outdatedDocuments), + TRANSFORMED_DOCUMENTS_BULK_INDEX: (state: TransformedDocumentsBulkIndex) => + Actions.bulkOverwriteTransformedDocuments( client, - transformRawDocs, - state.outdatedDocuments, state.targetIndex, + state.transformedDocs, /** * Since we don't run a search against the target index, we disable "refresh" to speed up * the migration process. diff --git a/src/core/server/saved_objects/migrationsv2/types.ts b/src/core/server/saved_objects/migrationsv2/types.ts index ac807e9d61776..f5800a3cd9570 100644 --- a/src/core/server/saved_objects/migrationsv2/types.ts +++ b/src/core/server/saved_objects/migrationsv2/types.ts @@ -6,12 +6,18 @@ * Side Public License, v 1. */ +import * as TaskEither from 'fp-ts/lib/TaskEither'; import * as Option from 'fp-ts/lib/Option'; import { estypes } from '@elastic/elasticsearch'; import { ControlState } from './state_action_machine'; import { AliasAction } from './actions'; import { IndexMapping } from '../mappings'; import { SavedObjectsRawDoc } from '..'; +import { TransformErrorObjects } from '../migrations/core'; +import { + DocumentsTransformFailed, + DocumentsTransformSuccess, +} from '../migrations/core/migrate_raw_docs'; export type MigrationLogLevel = 'error' | 'info'; @@ -175,6 +181,8 @@ export interface ReindexSourceToTempRead extends PostInitState { readonly controlState: 'REINDEX_SOURCE_TO_TEMP_READ'; readonly sourceIndexPitId: string; readonly lastHitSortValue: number[] | undefined; + readonly corruptDocumentIds: string[]; + readonly transformErrors: TransformErrorObjects[]; } export interface ReindexSourceToTempClosePit extends PostInitState { @@ -187,6 +195,15 @@ export interface ReindexSourceToTempIndex extends PostInitState { readonly outdatedDocuments: SavedObjectsRawDoc[]; readonly sourceIndexPitId: string; readonly lastHitSortValue: number[] | undefined; + readonly corruptDocumentIds: string[]; + readonly transformErrors: TransformErrorObjects[]; +} + +export interface ReindexSourceToTempIndexBulk extends PostInitState { + readonly controlState: 'REINDEX_SOURCE_TO_TEMP_INDEX_BULK'; + readonly transformedDocs: SavedObjectsRawDoc[]; + readonly sourceIndexPitId: string; + readonly lastHitSortValue: number[] | undefined; } export type SetTempWriteBlock = PostInitState & { @@ -233,6 +250,8 @@ export interface OutdatedDocumentsSearchRead extends PostInitState { readonly pitId: string; readonly lastHitSortValue: number[] | undefined; readonly hasTransformedDocs: boolean; + readonly corruptDocumentIds: string[]; + readonly transformErrors: TransformErrorObjects[]; } export interface OutdatedDocumentsSearchClosePit extends PostInitState { @@ -249,12 +268,24 @@ export interface OutdatedDocumentsRefresh extends PostInitState { } export interface OutdatedDocumentsTransform extends PostInitState { - /** Transform a batch of outdated documents to their latest version and write them to the target index */ + /** Transform a batch of outdated documents to their latest version*/ readonly controlState: 'OUTDATED_DOCUMENTS_TRANSFORM'; readonly pitId: string; readonly outdatedDocuments: SavedObjectsRawDoc[]; readonly lastHitSortValue: number[] | undefined; readonly hasTransformedDocs: boolean; + readonly corruptDocumentIds: string[]; + readonly transformErrors: TransformErrorObjects[]; +} +export interface TransformedDocumentsBulkIndex extends PostInitState { + /** + * Write the up-to-date transformed documents to the target index + */ + readonly controlState: 'TRANSFORMED_DOCUMENTS_BULK_INDEX'; + readonly transformedDocs: SavedObjectsRawDoc[]; + readonly lastHitSortValue: number[] | undefined; + readonly hasTransformedDocs: boolean; + readonly pitId: string; } export interface MarkVersionIndexReady extends PostInitState { @@ -351,6 +382,7 @@ export type State = | ReindexSourceToTempRead | ReindexSourceToTempClosePit | ReindexSourceToTempIndex + | ReindexSourceToTempIndexBulk | SetTempWriteBlock | CloneTempToSource | UpdateTargetMappingsState @@ -363,6 +395,7 @@ export type State = | OutdatedDocumentsRefresh | MarkVersionIndexReady | MarkVersionIndexReadyConflict + | TransformedDocumentsBulkIndex | LegacyCreateReindexTargetState | LegacySetWriteBlockState | LegacyReindexState @@ -376,4 +409,6 @@ export type AllControlStates = State['controlState']; */ export type AllActionStates = Exclude; -export type TransformRawDocs = (rawDocs: SavedObjectsRawDoc[]) => Promise; +export type TransformRawDocs = ( + rawDocs: SavedObjectsRawDoc[] +) => TaskEither.TaskEither; diff --git a/src/dev/run_find_plugins_with_circular_deps.ts b/src/dev/run_find_plugins_with_circular_deps.ts index d97dc8e70cd9b..a737bc6a73004 100644 --- a/src/dev/run_find_plugins_with_circular_deps.ts +++ b/src/dev/run_find_plugins_with_circular_deps.ts @@ -20,9 +20,7 @@ interface Options { type CircularDepList = Set; const allowedList: CircularDepList = new Set([ - 'x-pack/plugins/apm -> x-pack/plugins/infra', 'x-pack/plugins/lists -> x-pack/plugins/security_solution', - 'x-pack/plugins/security -> x-pack/plugins/spaces', ]); run( diff --git a/src/plugins/dashboard/public/application/dashboard_router.tsx b/src/plugins/dashboard/public/application/dashboard_router.tsx index ed68afc5e97b1..9050aee5ce38c 100644 --- a/src/plugins/dashboard/public/application/dashboard_router.tsx +++ b/src/plugins/dashboard/public/application/dashboard_router.tsx @@ -84,7 +84,8 @@ export async function mountApp({ } = pluginsStart; const spacesApi = pluginsStart.spacesOss?.isSpacesAvailable ? pluginsStart.spacesOss : undefined; - const activeSpaceId = spacesApi && (await spacesApi.activeSpace$.pipe(first()).toPromise())?.id; + const activeSpaceId = + spacesApi && (await spacesApi.getActiveSpace$().pipe(first()).toPromise())?.id; let globalEmbedSettings: DashboardEmbedSettings | undefined; const dashboardServices: DashboardAppServices = { diff --git a/src/plugins/data/common/es_query/filters/range_filter.test.ts b/src/plugins/data/common/es_query/filters/range_filter.test.ts index c7ff84f61d0fc..bb7ecc09ebc34 100644 --- a/src/plugins/data/common/es_query/filters/range_filter.test.ts +++ b/src/plugins/data/common/es_query/filters/range_filter.test.ts @@ -64,6 +64,29 @@ describe('Range filter builder', () => { }); }); + it('should convert strings to numbers if the field is scripted and type number', () => { + const field = getField('script number'); + + expect(buildRangeFilter(field, { gte: '1', lte: '3' }, indexPattern)).toEqual({ + meta: { + field: 'script number', + index: 'id', + params: {}, + }, + script: { + script: { + lang: 'expression', + source: '(' + field!.script + ')>=gte && (' + field!.script + ')<=lte', + params: { + value: '>=1 <=3', + gte: 1, + lte: 3, + }, + }, + }, + }); + }); + it('should wrap painless scripts in comparator lambdas', () => { const field = getField('script date'); const expected = diff --git a/src/plugins/data/common/es_query/filters/range_filter.ts b/src/plugins/data/common/es_query/filters/range_filter.ts index ba9055e26dbda..fb8426655583e 100644 --- a/src/plugins/data/common/es_query/filters/range_filter.ts +++ b/src/plugins/data/common/es_query/filters/range_filter.ts @@ -138,7 +138,10 @@ export const buildRangeFilter = ( }; export const getRangeScript = (field: IFieldType, params: RangeFilterParams) => { - const knownParams = pickBy(params, (val, key: any) => key in operators); + const knownParams = mapValues( + pickBy(params, (val, key: any) => key in operators), + (value) => (field.type === 'number' && typeof value === 'string' ? parseFloat(value) : value) + ); let script = map( knownParams, (val: any, key: string) => '(' + field.script + ')' + get(operators, key) + key diff --git a/src/plugins/data/common/search/aggs/buckets/ip_range.ts b/src/plugins/data/common/search/aggs/buckets/ip_range.ts index c99676d71957a..5bcd614d9debf 100644 --- a/src/plugins/data/common/search/aggs/buckets/ip_range.ts +++ b/src/plugins/data/common/search/aggs/buckets/ip_range.ts @@ -18,7 +18,7 @@ import { KBN_FIELD_TYPES } from '../../../../common'; import { BaseAggParams } from '../types'; const ipRangeTitle = i18n.translate('data.search.aggs.buckets.ipRangeTitle', { - defaultMessage: 'IPv4 Range', + defaultMessage: 'IP Range', }); export enum IP_RANGE_TYPES { diff --git a/src/plugins/data/common/search/aggs/buckets/lib/cidr_mask.test.ts b/src/plugins/data/common/search/aggs/buckets/lib/cidr_mask.test.ts index 04363ee792289..b0466b78529e7 100644 --- a/src/plugins/data/common/search/aggs/buckets/lib/cidr_mask.test.ts +++ b/src/plugins/data/common/search/aggs/buckets/lib/cidr_mask.test.ts @@ -9,56 +9,47 @@ import { CidrMask } from './cidr_mask'; describe('CidrMask', () => { - test('should throw errors with invalid CIDR masks', () => { - expect( - () => - // @ts-ignore - new CidrMask() - ).toThrowError(); - - expect(() => new CidrMask('')).toThrowError(); - expect(() => new CidrMask('hello, world')).toThrowError(); - expect(() => new CidrMask('0.0.0.0')).toThrowError(); - expect(() => new CidrMask('0.0.0.0/0')).toThrowError(); - expect(() => new CidrMask('0.0.0.0/33')).toThrowError(); - expect(() => new CidrMask('256.0.0.0/32')).toThrowError(); - expect(() => new CidrMask('0.0.0.0/32/32')).toThrowError(); - expect(() => new CidrMask('1.2.3/1')).toThrowError(); - expect(() => new CidrMask('0.0.0.0/123d')).toThrowError(); + describe('constructor', () => { + it.each` + mask + ${''} + ${'hello, world'} + ${'0.0.0.0'} + ${'0.0.0.0/33'} + ${'256.0.0.0/32'} + ${'0.0.0.0/32/32'} + ${'0.0.0.0/123d'} + ${'::1'} + ${'::1/129'} + ${'fffff::/128'} + ${'ffff::/128/128'} + `('should throw an error on $mask', ({ mask }) => { + expect(() => new CidrMask(mask)).toThrowError(); + }); }); - test('should correctly grab IP address and prefix length', () => { - let mask = new CidrMask('0.0.0.0/1'); - expect(mask.initialAddress.toString()).toBe('0.0.0.0'); - expect(mask.prefixLength).toBe(1); - - mask = new CidrMask('128.0.0.1/31'); - expect(mask.initialAddress.toString()).toBe('128.0.0.1'); - expect(mask.prefixLength).toBe(31); + describe('toString', () => { + it.each` + mask | expected + ${'192.168.1.1/24'} | ${'192.168.1.1/24'} + ${'192.168.257/32'} | ${'192.168.1.1/32'} + ${'ffff:0:0:0:0:0:0:0/128'} | ${'ffff::/128'} + `('should format $mask as $expected', ({ mask, expected }) => { + expect(new CidrMask(mask).toString()).toBe(expected); + }); }); - test('should calculate a range of IP addresses', () => { - let mask = new CidrMask('0.0.0.0/1'); - let range = mask.getRange(); - expect(range.from.toString()).toBe('0.0.0.0'); - expect(range.to.toString()).toBe('127.255.255.255'); - - mask = new CidrMask('1.2.3.4/2'); - range = mask.getRange(); - expect(range.from.toString()).toBe('0.0.0.0'); - expect(range.to.toString()).toBe('63.255.255.255'); - - mask = new CidrMask('67.129.65.201/27'); - range = mask.getRange(); - expect(range.from.toString()).toBe('67.129.65.192'); - expect(range.to.toString()).toBe('67.129.65.223'); - }); - - test('toString()', () => { - let mask = new CidrMask('.../1'); - expect(mask.toString()).toBe('0.0.0.0/1'); - - mask = new CidrMask('128.0.0.1/31'); - expect(mask.toString()).toBe('128.0.0.1/31'); + describe('getRange', () => { + it.each` + mask | from | to + ${'0.0.0.0/0'} | ${'0.0.0.0'} | ${'255.255.255.255'} + ${'0.0.0.0/1'} | ${'0.0.0.0'} | ${'127.255.255.255'} + ${'1.2.3.4/2'} | ${'0.0.0.0'} | ${'63.255.255.255'} + ${'67.129.65.201/27'} | ${'67.129.65.192'} | ${'67.129.65.223'} + ${'::/1'} | ${'::'} | ${'7fff:ffff:ffff:ffff:ffff:ffff:ffff:ffff'} + ${'8000::/1'} | ${'8000::'} | ${'ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff'} + `('should return $from-$to for $mask', ({ mask, from, to }) => { + expect(new CidrMask(mask).getRange()).toEqual({ from, to }); + }); }); }); diff --git a/src/plugins/data/common/search/aggs/buckets/lib/cidr_mask.ts b/src/plugins/data/common/search/aggs/buckets/lib/cidr_mask.ts index 1a1c9b8157838..93ccbffaeb89d 100644 --- a/src/plugins/data/common/search/aggs/buckets/lib/cidr_mask.ts +++ b/src/plugins/data/common/search/aggs/buckets/lib/cidr_mask.ts @@ -6,42 +6,60 @@ * Side Public License, v 1. */ -import { Ipv4Address } from '../../utils'; - -const NUM_BITS = 32; - -function throwError(mask: string) { - throw Error('Invalid CIDR mask: ' + mask); -} +import ipaddr from 'ipaddr.js'; +import { IpAddress } from '../../utils'; export class CidrMask { - public readonly initialAddress: Ipv4Address; - public readonly prefixLength: number; + private address: number[]; + private netmask: number; - constructor(mask: string) { - const splits = mask.split('/'); - if (splits.length !== 2) { - throwError(mask); - } - this.initialAddress = new Ipv4Address(splits[0]); - this.prefixLength = Number(splits[1]); - if (isNaN(this.prefixLength) || this.prefixLength < 1 || this.prefixLength > NUM_BITS) { - throwError(mask); + constructor(cidr: string) { + try { + const [address, netmask] = ipaddr.parseCIDR(cidr); + + this.address = address.toByteArray(); + this.netmask = netmask; + } catch { + throw Error('Invalid CIDR mask: ' + cidr); } } - public getRange() { - const variableBits = NUM_BITS - this.prefixLength; - // eslint-disable-next-line no-bitwise - const fromAddress = ((this.initialAddress.valueOf() >> variableBits) << variableBits) >>> 0; // >>> 0 coerces to unsigned - const numAddresses = Math.pow(2, variableBits); + private getBroadcastAddress() { + /* eslint-disable no-bitwise */ + const netmask = (1n << BigInt(this.address.length * 8 - this.netmask)) - 1n; + const broadcast = this.address.map((byte, index) => { + const offset = BigInt(this.address.length - index - 1) * 8n; + const mask = Number((netmask >> offset) & 255n); + + return byte | mask; + }); + /* eslint-enable no-bitwise */ + + return new IpAddress(broadcast).toString(); + } + + private getNetworkAddress() { + /* eslint-disable no-bitwise */ + const netmask = (1n << BigInt(this.address.length * 8 - this.netmask)) - 1n; + const network = this.address.map((byte, index) => { + const offset = BigInt(this.address.length - index - 1) * 8n; + const mask = Number((netmask >> offset) & 255n) ^ 255; + + return byte & mask; + }); + /* eslint-enable no-bitwise */ + + return new IpAddress(network).toString(); + } + + getRange() { return { - from: new Ipv4Address(fromAddress).toString(), - to: new Ipv4Address(fromAddress + numAddresses - 1).toString(), + from: this.getNetworkAddress(), + to: this.getBroadcastAddress(), }; } - public toString() { - return this.initialAddress.toString() + '/' + this.prefixLength; + toString() { + return `${new IpAddress(this.address)}/${this.netmask}`; } } diff --git a/src/plugins/data/common/search/aggs/buckets/lib/time_buckets/calc_es_interval.ts b/src/plugins/data/common/search/aggs/buckets/lib/time_buckets/calc_es_interval.ts index 72d59af037d90..4b3ec90bb2cc2 100644 --- a/src/plugins/data/common/search/aggs/buckets/lib/time_buckets/calc_es_interval.ts +++ b/src/plugins/data/common/search/aggs/buckets/lib/time_buckets/calc_es_interval.ts @@ -12,7 +12,7 @@ import dateMath, { Unit } from '@elastic/datemath'; import { parseEsInterval } from '../../../utils'; const unitsDesc = dateMath.unitsDesc; -const largeMax = unitsDesc.indexOf('M'); +const largeMax = unitsDesc.indexOf('w'); export interface EsInterval { expression: string; diff --git a/src/plugins/data/common/search/aggs/buckets/lib/time_buckets/time_buckets.test.ts b/src/plugins/data/common/search/aggs/buckets/lib/time_buckets/time_buckets.test.ts index 6fbaddb09b226..fd3f861dce07e 100644 --- a/src/plugins/data/common/search/aggs/buckets/lib/time_buckets/time_buckets.test.ts +++ b/src/plugins/data/common/search/aggs/buckets/lib/time_buckets/time_buckets.test.ts @@ -10,6 +10,7 @@ import moment from 'moment'; import { TimeBuckets, TimeBucketsConfig } from './time_buckets'; import { autoInterval } from '../../_interval_options'; +import { InvalidEsCalendarIntervalError } from '../../../utils'; describe('TimeBuckets', () => { const timeBucketConfig: TimeBucketsConfig = { @@ -137,4 +138,14 @@ describe('TimeBuckets', () => { const format = timeBuckets.getScaledDateFormat(); expect(format).toEqual('HH:mm'); }); + + test('allows days but throws error on weeks', () => { + const timeBuckets = new TimeBuckets(timeBucketConfig); + timeBuckets.setInterval('14d'); + const interval = timeBuckets.getInterval(false); + expect(interval.esUnit).toEqual('d'); + + timeBuckets.setInterval('2w'); + expect(() => timeBuckets.getInterval(false)).toThrow(InvalidEsCalendarIntervalError); + }); }); diff --git a/src/plugins/data/common/search/aggs/utils/index.ts b/src/plugins/data/common/search/aggs/utils/index.ts index c92653e843233..f1625070b6f75 100644 --- a/src/plugins/data/common/search/aggs/utils/index.ts +++ b/src/plugins/data/common/search/aggs/utils/index.ts @@ -11,7 +11,7 @@ export { getNumberHistogramIntervalByDatatableColumn } from './get_number_histog export { getDateHistogramMetaDataByDatatableColumn } from './get_date_histogram_meta'; export * from './date_interval_utils'; export * from './get_format_with_aggs'; -export * from './ipv4_address'; +export * from './ip_address'; export * from './prop_filter'; export * from './to_angular_json'; export * from './infer_time_zone'; diff --git a/src/plugins/data/common/search/aggs/utils/ip_address.test.ts b/src/plugins/data/common/search/aggs/utils/ip_address.test.ts new file mode 100644 index 0000000000000..966408cf6fe27 --- /dev/null +++ b/src/plugins/data/common/search/aggs/utils/ip_address.test.ts @@ -0,0 +1,67 @@ +/* + * 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 { IpAddress } from './ip_address'; + +describe('IpAddress', () => { + describe('constructor', () => { + it.each` + address + ${''} + ${'hello, world'} + ${'256.0.0.0'} + ${'-1.0.0.0'} + ${Number.MAX_SAFE_INTEGER} + ${'fffff::'} + ${'ffff:0:0:0:0:0:0:0:0'} + `('should throw an error on $address', ({ address }) => { + expect(() => new IpAddress(address)).toThrowError(); + }); + + it.each` + address | expected + ${'192.168.257'} | ${'192.168.1.1'} + ${2116932386} | ${'126.45.211.34'} + ${'126.45.211.34'} | ${'126.45.211.34'} + ${[126, 45, 211, 34]} | ${'126.45.211.34'} + ${'ffff:0:0:0:0:0:0:0'} | ${'ffff::'} + ${'ffff::'} | ${'ffff::'} + ${[0xff, 0xff, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]} | ${'ffff::'} + `('should parse $address', ({ address, expected }) => { + expect(new IpAddress(address).toString()).toBe(expected); + }); + }); + + describe('valueOf', () => { + it.each` + address | expected + ${'0.0.0.0'} | ${'0'} + ${'0.0.0.1'} | ${'1'} + ${'126.45.211.34'} | ${'2116932386'} + ${'ffff::'} | ${'340277174624079928635746076935438991360'} + `( + 'should return $expected as a decimal representation of $address', + ({ address, expected }) => { + expect(new IpAddress(address).valueOf().toString()).toBe(expected); + } + ); + }); + + describe('toString()', () => { + it.each` + address | expected + ${'0.000.00000.1'} | ${'0.0.0.1'} + ${'192.168.257'} | ${'192.168.1.1'} + ${'ffff:0:0:0:0:0:0:0'} | ${'ffff::'} + ${'0:0:0:0:0:0:0:ffff'} | ${'::ffff'} + ${'f:0:0:0:0:0:0:f'} | ${'f::f'} + `('should serialize $address as $expected', ({ address, expected }) => { + expect(new IpAddress(address).toString()).toBe(expected); + }); + }); +}); diff --git a/src/plugins/data/common/search/aggs/utils/ip_address.ts b/src/plugins/data/common/search/aggs/utils/ip_address.ts new file mode 100644 index 0000000000000..2fffbc468046f --- /dev/null +++ b/src/plugins/data/common/search/aggs/utils/ip_address.ts @@ -0,0 +1,47 @@ +/* + * 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 ipaddr, { IPv4, IPv6 } from 'ipaddr.js'; + +function isIPv6(value: IPv4 | IPv6): value is IPv6 { + return value.kind() === 'ipv6'; +} + +export class IpAddress { + private value: IPv4 | IPv6; + + constructor(ipAddress: string | number | number[]) { + try { + this.value = Array.isArray(ipAddress) + ? ipaddr.fromByteArray(ipAddress) + : ipaddr.parse(`${ipAddress}`); + } catch { + throw Error('Invalid IP address: ' + ipAddress); + } + } + + toString() { + if (isIPv6(this.value)) { + return this.value.toRFC5952String(); + } + + return this.value.toString(); + } + + valueOf(): number | bigint { + const value = this.value + .toByteArray() + .reduce((result, octet) => result * 256n + BigInt(octet), 0n); + + if (value > Number.MAX_SAFE_INTEGER) { + return value; + } + + return Number(value); + } +} diff --git a/src/plugins/data/common/search/aggs/utils/ipv4_address.test.ts b/src/plugins/data/common/search/aggs/utils/ipv4_address.test.ts deleted file mode 100644 index 4be406f54390f..0000000000000 --- a/src/plugins/data/common/search/aggs/utils/ipv4_address.test.ts +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import expect from '@kbn/expect'; -import { Ipv4Address } from './ipv4_address'; - -describe('Ipv4Address', () => { - it('should throw errors with invalid IP addresses', () => { - // @ts-ignore - expect(() => new Ipv4Address()).to.throwError(); - - expect(() => new Ipv4Address('')).to.throwError(); - - expect(() => new Ipv4Address('hello, world')).to.throwError(); - - expect(() => new Ipv4Address('0.0.0')).to.throwError(); - - expect(() => new Ipv4Address('256.0.0.0')).to.throwError(); - - expect(() => new Ipv4Address('-1.0.0.0')).to.throwError(); - - expect(() => new Ipv4Address(Number.MAX_SAFE_INTEGER)).to.throwError(); - }); - - it('should allow creation with an integer or string', () => { - expect(new Ipv4Address(2116932386).toString()).to.be( - new Ipv4Address('126.45.211.34').toString() - ); - }); - - it('should correctly calculate the decimal representation of an IP address', () => { - let ipAddress = new Ipv4Address('0.0.0.0'); - expect(ipAddress.valueOf()).to.be(0); - - ipAddress = new Ipv4Address('0.0.0.1'); - expect(ipAddress.valueOf()).to.be(1); - - ipAddress = new Ipv4Address('126.45.211.34'); - expect(ipAddress.valueOf()).to.be(2116932386); - }); - - it('toString()', () => { - let ipAddress = new Ipv4Address('0.000.00000.1'); - expect(ipAddress.toString()).to.be('0.0.0.1'); - - ipAddress = new Ipv4Address('123.123.123.123'); - expect(ipAddress.toString()).to.be('123.123.123.123'); - }); -}); diff --git a/src/plugins/data/common/search/aggs/utils/ipv4_address.ts b/src/plugins/data/common/search/aggs/utils/ipv4_address.ts deleted file mode 100644 index aa6588349c9db..0000000000000 --- a/src/plugins/data/common/search/aggs/utils/ipv4_address.ts +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -const NUM_BYTES = 4; -const BYTE_SIZE = 256; - -function throwError(ipAddress: string | number) { - throw Error('Invalid IPv4 address: ' + ipAddress); -} - -function isIntegerInRange(integer: number, min: number, max: number) { - return ( - !isNaN(integer as number) && integer >= min && integer < max && (integer as number) % 1 === 0 - ); -} - -export class Ipv4Address { - private value: number; - - constructor(ipAddress: string | number) { - if (typeof ipAddress === 'string') { - this.value = 0; - - const bytes = ipAddress.split('.'); - if (bytes.length !== NUM_BYTES) { - throwError(ipAddress); - } - - for (let i = 0; i < bytes.length; i++) { - const byte = Number(bytes[i]); - if (!isIntegerInRange(byte, 0, BYTE_SIZE)) { - throwError(ipAddress); - } - this.value += Math.pow(BYTE_SIZE, NUM_BYTES - 1 - i) * byte; - } - } else { - this.value = ipAddress; - } - - if (!isIntegerInRange(this.value, 0, Math.pow(BYTE_SIZE, NUM_BYTES))) { - throwError(ipAddress); - } - } - - public toString() { - let value = this.value; - const bytes = []; - for (let i = 0; i < NUM_BYTES; i++) { - bytes.unshift(value % 256); - value = Math.floor(value / 256); - } - return bytes.join('.'); - } - - public valueOf() { - return this.value; - } -} diff --git a/src/plugins/data/public/actions/filters/create_filters_from_value_click.test.ts b/src/plugins/data/public/actions/filters/create_filters_from_value_click.test.ts index 30667d59eff84..14ae24a2a5626 100644 --- a/src/plugins/data/public/actions/filters/create_filters_from_value_click.test.ts +++ b/src/plugins/data/public/actions/filters/create_filters_from_value_click.test.ts @@ -105,4 +105,11 @@ describe('createFiltersFromValueClick', () => { expect(rangeFilter.range.bytes.lt).toEqual(2078); } }); + + test('handles non-unique filters', async () => { + const [point] = dataPoints; + const filters = await createFiltersFromValueClickAction({ data: [point, point] }); + + expect(filters.length).toEqual(1); + }); }); diff --git a/src/plugins/data/public/actions/filters/create_filters_from_value_click.ts b/src/plugins/data/public/actions/filters/create_filters_from_value_click.ts index fb8240a0cdc27..13cb9ba419bf9 100644 --- a/src/plugins/data/public/actions/filters/create_filters_from_value_click.ts +++ b/src/plugins/data/public/actions/filters/create_filters_from_value_click.ts @@ -6,6 +6,7 @@ * Side Public License, v 1. */ +import _ from 'lodash'; import { Datatable } from '../../../../../plugins/expressions/public'; import { esFilters, Filter } from '../../../public'; import { getIndexPatterns, getSearchService } from '../../../public/services'; @@ -140,5 +141,7 @@ export const createFiltersFromValueClickAction = async ({ }) ); - return esFilters.mapAndFlattenFilters(filters); + return _.uniqWith(esFilters.mapAndFlattenFilters(filters), (a, b) => + esFilters.compareFilters(a, b, esFilters.COMPARE_ALL_OPTIONS) + ); }; diff --git a/src/plugins/data/public/index.ts b/src/plugins/data/public/index.ts index f2a61e94a07d9..ba873952c9841 100644 --- a/src/plugins/data/public/index.ts +++ b/src/plugins/data/public/index.ts @@ -305,7 +305,7 @@ import { dateHistogramInterval, InvalidEsCalendarIntervalError, InvalidEsIntervalFormatError, - Ipv4Address, + IpAddress, isValidEsInterval, isValidInterval, parseEsInterval, @@ -411,7 +411,7 @@ export const search = { intervalOptions, InvalidEsCalendarIntervalError, InvalidEsIntervalFormatError, - Ipv4Address, + IpAddress, isDateHistogramBucketAggConfig, // TODO: remove in build_pipeline refactor isNumberType, isStringType, diff --git a/src/plugins/data/public/public.api.md b/src/plugins/data/public/public.api.md index d4f0ccfe810c6..54cea5e09121b 100644 --- a/src/plugins/data/public/public.api.md +++ b/src/plugins/data/public/public.api.md @@ -2299,7 +2299,7 @@ export const search: { })[]; InvalidEsCalendarIntervalError: typeof InvalidEsCalendarIntervalError; InvalidEsIntervalFormatError: typeof InvalidEsIntervalFormatError; - Ipv4Address: typeof Ipv4Address; + IpAddress: typeof IpAddress; isDateHistogramBucketAggConfig: typeof isDateHistogramBucketAggConfig; isNumberType: (agg: import("../common").AggConfig) => boolean; isStringType: (agg: import("../common").AggConfig) => boolean; @@ -2737,7 +2737,7 @@ export interface WaitUntilNextSessionCompletesOptions { // src/plugins/data/public/index.ts:410:1 - (ae-forgotten-export) The symbol "dateHistogramInterval" needs to be exported by the entry point index.d.ts // src/plugins/data/public/index.ts:419:1 - (ae-forgotten-export) The symbol "InvalidEsCalendarIntervalError" needs to be exported by the entry point index.d.ts // src/plugins/data/public/index.ts:420:1 - (ae-forgotten-export) The symbol "InvalidEsIntervalFormatError" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:421:1 - (ae-forgotten-export) The symbol "Ipv4Address" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:421:1 - (ae-forgotten-export) The symbol "IpAddress" needs to be exported by the entry point index.d.ts // src/plugins/data/public/index.ts:422:1 - (ae-forgotten-export) The symbol "isDateHistogramBucketAggConfig" needs to be exported by the entry point index.d.ts // src/plugins/data/public/index.ts:426:1 - (ae-forgotten-export) The symbol "isValidEsInterval" needs to be exported by the entry point index.d.ts // src/plugins/data/public/index.ts:427:1 - (ae-forgotten-export) The symbol "isValidInterval" needs to be exported by the entry point index.d.ts diff --git a/src/plugins/data/public/query/filter_manager/lib/generate_filter.test.ts b/src/plugins/data/public/query/filter_manager/lib/generate_filter.test.ts index 1a29a57ff058a..0daaf804e7b40 100644 --- a/src/plugins/data/public/query/filter_manager/lib/generate_filter.test.ts +++ b/src/plugins/data/public/query/filter_manager/lib/generate_filter.test.ts @@ -174,4 +174,22 @@ describe('Generate filters', () => { [FIELD.name]: ANOTHER_PHRASE, }); }); + + it('should use only distinct values', () => { + const ANOTHER_PHRASE = 'another-value'; + const filters = generateFilters( + mockFilterManager, + FIELD, + [PHRASE_VALUE, ANOTHER_PHRASE, PHRASE_VALUE, ANOTHER_PHRASE], + '', + INDEX_NAME + ); + expect(filters).toHaveLength(2); + expect((filters[0] as PhraseFilter).query.match_phrase).toEqual({ + [FIELD.name]: PHRASE_VALUE, + }); + expect((filters[1] as PhraseFilter).query.match_phrase).toEqual({ + [FIELD.name]: ANOTHER_PHRASE, + }); + }); }); diff --git a/src/plugins/data/public/query/filter_manager/lib/generate_filters.ts b/src/plugins/data/public/query/filter_manager/lib/generate_filters.ts index 9a52a02edcd4e..0a4998a159523 100644 --- a/src/plugins/data/public/query/filter_manager/lib/generate_filters.ts +++ b/src/plugins/data/public/query/filter_manager/lib/generate_filters.ts @@ -71,7 +71,7 @@ export function generateFilters( operation: string, index: string ): Filter[] { - values = Array.isArray(values) ? values : [values]; + values = Array.isArray(values) ? _.uniq(values) : [values]; const fieldObj = (_.isObject(field) ? field : { diff --git a/src/plugins/data/public/ui/filter_bar/filter_editor/filter_editor.test.tsx b/src/plugins/data/public/ui/filter_bar/filter_editor/filter_editor.test.tsx new file mode 100644 index 0000000000000..4760968b65539 --- /dev/null +++ b/src/plugins/data/public/ui/filter_bar/filter_editor/filter_editor.test.tsx @@ -0,0 +1,70 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { registerTestBed, TestBed } from '@kbn/test/jest'; +import { FilterEditor, Props } from '.'; +import React from 'react'; + +jest.mock('@elastic/eui', () => { + const original = jest.requireActual('@elastic/eui'); + + return { + ...original, + EuiCodeEditor: (props: any) => ( + { + props.onChange(eve.target.value); + }} + /> + ), + }; +}); + +describe('', () => { + describe('writing query dsl', () => { + let testBed: TestBed; + + beforeEach(async () => { + const defaultProps: Omit = { + filter: { + meta: { + type: 'phase', + } as any, + }, + indexPatterns: [], + onCancel: jest.fn(), + onSubmit: jest.fn(), + }; + testBed = await registerTestBed(FilterEditor, { defaultProps })(); + }); + + it('requires a non-empty JSON object', async () => { + const { exists, find } = testBed; + + expect(exists('customEditorInput')).toBe(true); + + find('customEditorInput').simulate('change', { + target: { value: '{ }' }, + }); + expect(find('saveFilter').props().disabled).toBe(true); + + find('customEditorInput').simulate('change', { + target: { value: '{' }, // bad JSON + }); + expect(find('saveFilter').props().disabled).toBe(true); + + find('customEditorInput').simulate('change', { + target: { value: '{ "something": "here" }' }, + }); + + expect(find('saveFilter').props().disabled).toBe(false); + }); + }); +}); diff --git a/src/plugins/data/public/ui/filter_bar/filter_editor/index.tsx b/src/plugins/data/public/ui/filter_bar/filter_editor/index.tsx index d2f04228ed396..2b8978a125bca 100644 --- a/src/plugins/data/public/ui/filter_bar/filter_editor/index.tsx +++ b/src/plugins/data/public/ui/filter_bar/filter_editor/index.tsx @@ -48,7 +48,7 @@ import { getFilterParams, } from '../../../../common'; -interface Props { +export interface Props { filter: Filter; indexPatterns: IIndexPattern[]; onSubmit: (filter: Filter) => void; @@ -333,6 +333,7 @@ class FilterEditorUI extends Component { mode="json" width="100%" height="250px" + data-test-subj="customEditorInput" /> ); @@ -415,7 +416,8 @@ class FilterEditorUI extends Component { if (isCustomEditorOpen) { try { - return Boolean(JSON.parse(queryDsl)); + const queryDslJson = JSON.parse(queryDsl); + return Object.keys(queryDslJson).length > 0; } catch (e) { return false; } diff --git a/src/plugins/data/public/ui/filter_bar/filter_editor/lib/filter_editor_utils.ts b/src/plugins/data/public/ui/filter_bar/filter_editor/lib/filter_editor_utils.ts index d6e5b57f5878b..e15f667128c4f 100644 --- a/src/plugins/data/public/ui/filter_bar/filter_editor/lib/filter_editor_utils.ts +++ b/src/plugins/data/public/ui/filter_bar/filter_editor/lib/filter_editor_utils.ts @@ -12,7 +12,7 @@ import { isFilterable, IIndexPattern, IFieldType, - Ipv4Address, + IpAddress, Filter, FieldFilter, } from '../../../../../common'; @@ -44,7 +44,7 @@ export function validateParams(params: any, type: string) { return Boolean(typeof params === 'string' && moment && moment.isValid()); case 'ip': try { - return Boolean(new Ipv4Address(params)); + return Boolean(new IpAddress(params)); } catch (e) { return false; } diff --git a/src/plugins/data/public/ui/search_bar/search_bar.tsx b/src/plugins/data/public/ui/search_bar/search_bar.tsx index 9c84b9b73d5e5..1608b68ed01b2 100644 --- a/src/plugins/data/public/ui/search_bar/search_bar.tsx +++ b/src/plugins/data/public/ui/search_bar/search_bar.tsx @@ -10,7 +10,6 @@ import { compact } from 'lodash'; import { InjectedIntl, injectI18n } from '@kbn/i18n/react'; import classNames from 'classnames'; import React, { Component } from 'react'; -import ResizeObserver from 'resize-observer-polyfill'; import { get, isEqual } from 'lodash'; import { EuiIconProps } from '@elastic/eui'; @@ -100,8 +99,6 @@ class SearchBarUI extends Component { private services = this.props.kibana.services; private savedQueryService = this.services.data.query.savedQueries; - public filterBarRef: Element | null = null; - public filterBarWrapperRef: Element | null = null; public static getDerivedStateFromProps(nextProps: SearchBarProps, prevState: State) { if (isEqual(prevState.currentProps, nextProps)) { @@ -212,19 +209,6 @@ class SearchBarUI extends Component { ); } - public setFilterBarHeight = () => { - requestAnimationFrame(() => { - const height = - this.filterBarRef && this.state.isFiltersVisible ? this.filterBarRef.clientHeight : 0; - if (this.filterBarWrapperRef) { - this.filterBarWrapperRef.setAttribute('style', `height: ${height}px`); - } - }); - }; - - // member-ordering rules conflict with use-before-declaration rules - public ro = new ResizeObserver(this.setFilterBarHeight); - public onSave = async (savedQueryMeta: SavedQueryMeta, saveAsNew = false) => { if (!this.state.query) return; @@ -352,20 +336,6 @@ class SearchBarUI extends Component { } }; - public componentDidMount() { - if (this.filterBarRef) { - this.setFilterBarHeight(); - this.ro.observe(this.filterBarRef); - } - } - - public componentDidUpdate() { - if (this.filterBarRef) { - this.setFilterBarHeight(); - this.ro.unobserve(this.filterBarRef); - } - } - public render() { const savedQueryManagement = this.state.query && this.props.onClearSavedQuery && ( { 'globalFilterGroup__wrapper-isVisible': this.state.isFiltersVisible, }); filterBar = ( -
{ - this.filterBarWrapperRef = node; - }} - className={filterGroupClasses} - > -
{ - this.filterBarRef = node; - }} - > - -
+
+
); } diff --git a/src/plugins/data/server/index.ts b/src/plugins/data/server/index.ts index c4e132e33fc3b..e782fc0d4ffea 100644 --- a/src/plugins/data/server/index.ts +++ b/src/plugins/data/server/index.ts @@ -170,7 +170,7 @@ import { dateHistogramInterval, InvalidEsCalendarIntervalError, InvalidEsIntervalFormatError, - Ipv4Address, + IpAddress, isValidEsInterval, isValidInterval, parseEsInterval, @@ -247,7 +247,7 @@ export const search = { intervalOptions, InvalidEsCalendarIntervalError, InvalidEsIntervalFormatError, - Ipv4Address, + IpAddress, isNumberType, isStringType, isType, diff --git a/src/plugins/data/server/search/strategies/ese_search/ese_search_strategy.ts b/src/plugins/data/server/search/strategies/ese_search/ese_search_strategy.ts index e7f5005e7e837..cec4b9a2dbf9f 100644 --- a/src/plugins/data/server/search/strategies/ese_search/ese_search_strategy.ts +++ b/src/plugins/data/server/search/strategies/ese_search/ese_search_strategy.ts @@ -39,7 +39,7 @@ export const enhancedEsSearchStrategyProvider = ( legacyConfig$: Observable, logger: Logger, usage?: SearchUsage -): ISearchStrategy => { +): ISearchStrategy => { async function cancelAsyncSearch(id: string, esClient: IScopedClusterClient) { try { await esClient.asCurrentUser.asyncSearch.delete({ id }); diff --git a/src/plugins/data/server/server.api.md b/src/plugins/data/server/server.api.md index e66eaab672e1c..a71ce360a2190 100644 --- a/src/plugins/data/server/server.api.md +++ b/src/plugins/data/server/server.api.md @@ -1341,7 +1341,7 @@ export const search: { })[]; InvalidEsCalendarIntervalError: typeof InvalidEsCalendarIntervalError; InvalidEsIntervalFormatError: typeof InvalidEsIntervalFormatError; - Ipv4Address: typeof Ipv4Address; + IpAddress: typeof IpAddress; isNumberType: (agg: import("../common").AggConfig) => boolean; isStringType: (agg: import("../common").AggConfig) => boolean; isType: (...types: string[]) => (agg: import("../common").AggConfig) => boolean; @@ -1544,7 +1544,7 @@ export function usageProvider(core: CoreSetup_2): SearchUsage; // src/plugins/data/server/index.ts:246:1 - (ae-forgotten-export) The symbol "dateHistogramInterval" needs to be exported by the entry point index.d.ts // src/plugins/data/server/index.ts:255:1 - (ae-forgotten-export) The symbol "InvalidEsCalendarIntervalError" needs to be exported by the entry point index.d.ts // src/plugins/data/server/index.ts:256:1 - (ae-forgotten-export) The symbol "InvalidEsIntervalFormatError" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:257:1 - (ae-forgotten-export) The symbol "Ipv4Address" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:257:1 - (ae-forgotten-export) The symbol "IpAddress" needs to be exported by the entry point index.d.ts // src/plugins/data/server/index.ts:261:1 - (ae-forgotten-export) The symbol "isValidEsInterval" needs to be exported by the entry point index.d.ts // src/plugins/data/server/index.ts:262:1 - (ae-forgotten-export) The symbol "isValidInterval" needs to be exported by the entry point index.d.ts // src/plugins/data/server/index.ts:266:1 - (ae-forgotten-export) The symbol "propFilter" needs to be exported by the entry point index.d.ts diff --git a/src/plugins/discover/public/application/components/discover_grid/discover_grid.test.tsx b/src/plugins/discover/public/application/components/discover_grid/discover_grid.test.tsx index 8037022085f02..b2be40c008200 100644 --- a/src/plugins/discover/public/application/components/discover_grid/discover_grid.test.tsx +++ b/src/plugins/discover/public/application/components/discover_grid/discover_grid.test.tsx @@ -124,6 +124,33 @@ describe('DiscoverGrid', () => { expect(getDisplayedDocNr(component)).toBe(5); }); + test('showing selected documents, underlying data changes, all documents are displayed, selection is gone', async () => { + await toggleDocSelection(component, esHits[0]); + await toggleDocSelection(component, esHits[1]); + expect(getSelectedDocNr(component)).toBe(2); + findTestSubject(component, 'dscGridSelectionBtn').simulate('click'); + findTestSubject(component, 'dscGridShowSelectedDocuments').simulate('click'); + expect(getDisplayedDocNr(component)).toBe(2); + component.setProps({ + rows: [ + { + _index: 'i', + _id: '6', + _score: 1, + _type: '_doc', + _source: { + date: '2020-20-02T12:12:12.128', + name: 'test6', + extension: 'doc', + bytes: 50, + }, + }, + ], + }); + expect(getDisplayedDocNr(component)).toBe(1); + expect(getSelectedDocNr(component)).toBe(0); + }); + test('showing only selected documents and remove filter deselecting each doc manually', async () => { await toggleDocSelection(component, esHits[0]); findTestSubject(component, 'dscGridSelectionBtn').simulate('click'); diff --git a/src/plugins/discover/public/application/components/discover_grid/discover_grid.tsx b/src/plugins/discover/public/application/components/discover_grid/discover_grid.tsx index be38f166fa1c0..f969eb32f3791 100644 --- a/src/plugins/discover/public/application/components/discover_grid/discover_grid.tsx +++ b/src/plugins/discover/public/application/components/discover_grid/discover_grid.tsx @@ -164,17 +164,33 @@ export const DiscoverGrid = ({ const [isFilterActive, setIsFilterActive] = useState(false); const displayedColumns = getDisplayedColumns(columns, indexPattern); const defaultColumns = displayedColumns.includes('_source'); + const usedSelectedDocs = useMemo(() => { + if (!selectedDocs.length || !rows?.length) { + return []; + } + const idMap = rows.reduce((map, row) => map.set(getDocId(row), true), new Map()); + // filter out selected docs that are no longer part of the current data + const result = selectedDocs.filter((docId) => idMap.get(docId)); + if (result.length === 0 && isFilterActive) { + setIsFilterActive(false); + } + return result; + }, [selectedDocs, rows, isFilterActive]); + const displayedRows = useMemo(() => { if (!rows) { return []; } - if (!isFilterActive || selectedDocs.length === 0) { + if (!isFilterActive || usedSelectedDocs.length === 0) { + return rows; + } + const rowsFiltered = rows.filter((row) => usedSelectedDocs.includes(getDocId(row))); + if (!rowsFiltered.length) { + // in case the selected docs are no longer part of the sample of 500, show all docs return rows; } - return rows.filter((row) => { - return selectedDocs.includes(getDocId(row)); - }); - }, [rows, selectedDocs, isFilterActive]); + return rowsFiltered; + }, [rows, usedSelectedDocs, isFilterActive]); /** * Pagination @@ -258,16 +274,16 @@ export const DiscoverGrid = ({ const additionalControls = useMemo( () => - selectedDocs.length ? ( + usedSelectedDocs.length ? ( ) : null, - [selectedDocs, isFilterActive, rows, setIsFilterActive] + [usedSelectedDocs, isFilterActive, rows, setIsFilterActive] ); if (!rowCount) { @@ -291,7 +307,7 @@ export const DiscoverGrid = ({ onFilter, indexPattern, isDarkMode: services.uiSettings.get('theme:darkMode'), - selectedDocs, + selectedDocs: usedSelectedDocs, setSelectedDocs: (newSelectedDocs) => { setSelectedDocs(newSelectedDocs); if (isFilterActive && newSelectedDocs.length === 0) { diff --git a/src/plugins/discover/public/application/components/top_nav/get_top_nav_links.ts b/src/plugins/discover/public/application/components/top_nav/get_top_nav_links.ts index 9a12cb51eac0c..38bc831225c68 100644 --- a/src/plugins/discover/public/application/components/top_nav/get_top_nav_links.ts +++ b/src/plugins/discover/public/application/components/top_nav/get_top_nav_links.ts @@ -7,6 +7,7 @@ */ import { i18n } from '@kbn/i18n'; +import moment from 'moment'; import { showOpenSearchPanel } from './show_open_search_panel'; import { getSharingData, showPublicUrlSwitch } from '../../helpers/get_sharing_data'; import { unhashUrl } from '../../../../../kibana_utils/public'; @@ -122,7 +123,13 @@ export const getTopNavLinks = ({ objectType: 'search', sharingData: { ...sharingData, - title: savedSearch.title, + // CSV reports can be generated without a saved search so we provide a fallback title + title: + savedSearch.title || + i18n.translate('discover.localMenu.fallbackReportTitle', { + defaultMessage: 'Discover search [{date}]', + values: { date: moment().toISOString(true) }, + }), }, isDirty: !savedSearch.id || state.isAppStateDirty(), showPublicUrlSwitch, diff --git a/src/plugins/kibana_legacy/server/index.ts b/src/plugins/kibana_legacy/server/index.ts index 1402416d69c96..90c9c2888c9da 100644 --- a/src/plugins/kibana_legacy/server/index.ts +++ b/src/plugins/kibana_legacy/server/index.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { AddConfigDeprecation, CoreSetup, CoreStart, PluginConfigDescriptor } from 'kibana/server'; +import type { CoreSetup, CoreStart, PluginConfigDescriptor } from 'kibana/server'; import { get } from 'lodash'; import { configSchema, ConfigSchema } from '../config'; @@ -19,16 +19,12 @@ export const config: PluginConfigDescriptor = { deprecations: ({ renameFromRoot }) => [ // TODO: Remove deprecation once defaultAppId is deleted renameFromRoot('kibana.defaultAppId', 'kibana_legacy.defaultAppId', { silent: true }), - ( - completeConfig: Record, - rootPath: string, - addDeprecation: AddConfigDeprecation - ) => { + (completeConfig, rootPath, addDeprecation) => { if ( get(completeConfig, 'kibana.defaultAppId') === undefined && get(completeConfig, 'kibana_legacy.defaultAppId') === undefined ) { - return completeConfig; + return; } addDeprecation({ message: `kibana.defaultAppId is deprecated and will be removed in 8.0. Please use the \`defaultRoute\` advanced setting instead`, @@ -40,7 +36,6 @@ export const config: PluginConfigDescriptor = { ], }, }); - return completeConfig; }, ], }; diff --git a/src/plugins/kibana_react/public/code_editor/code_editor.tsx b/src/plugins/kibana_react/public/code_editor/code_editor.tsx index 51344e2d28ab6..55e10e7861e51 100644 --- a/src/plugins/kibana_react/public/code_editor/code_editor.tsx +++ b/src/plugins/kibana_react/public/code_editor/code_editor.tsx @@ -34,14 +34,14 @@ export interface Props { value: string; /** Function invoked when text in editor is changed */ - onChange: (value: string) => void; + onChange: (value: string, event: monaco.editor.IModelContentChangedEvent) => void; /** * Options for the Monaco Code Editor * Documentation of options can be found here: - * https://microsoft.github.io/monaco-editor/api/interfaces/monaco.editor.ieditorconstructionoptions.html + * https://microsoft.github.io/monaco-editor/api/interfaces/monaco.editor.istandaloneeditorconstructionoptions.html */ - options?: monaco.editor.IEditorConstructionOptions; + options?: monaco.editor.IStandaloneEditorConstructionOptions; /** * Suggestion provider for autocompletion diff --git a/src/plugins/kibana_react/public/code_editor/index.tsx b/src/plugins/kibana_react/public/code_editor/index.tsx index 635e84b1d8c20..2440974c3b1d1 100644 --- a/src/plugins/kibana_react/public/code_editor/index.tsx +++ b/src/plugins/kibana_react/public/code_editor/index.tsx @@ -16,7 +16,7 @@ import { import darkTheme from '@elastic/eui/dist/eui_theme_dark.json'; import lightTheme from '@elastic/eui/dist/eui_theme_light.json'; import { useUiSetting } from '../ui_settings'; -import type { Props } from './code_editor'; +import { Props } from './code_editor'; const LazyBaseEditor = React.lazy(() => import('./code_editor')); @@ -26,6 +26,8 @@ const Fallback = () => ( ); +export type CodeEditorProps = Props; + /** * Renders a Monaco code editor with EUI color theme. * diff --git a/src/plugins/presentation_util/common/labs.ts b/src/plugins/presentation_util/common/labs.ts index d551b733ecb8a..902c22681e55e 100644 --- a/src/plugins/presentation_util/common/labs.ts +++ b/src/plugins/presentation_util/common/labs.ts @@ -8,8 +8,9 @@ import { i18n } from '@kbn/i18n'; -export const USE_DATA_SERVICE = 'labs:canvas:useDataService'; -export const TIME_TO_PRESENT = 'labs:presentation:timeToPresent'; +export const LABS_PROJECT_PREFIX = 'labs:'; +export const USE_DATA_SERVICE = `${LABS_PROJECT_PREFIX}canvas:useDataService` as const; +export const TIME_TO_PRESENT = `${LABS_PROJECT_PREFIX}presentation:timeToPresent` as const; export const projectIDs = [TIME_TO_PRESENT, USE_DATA_SERVICE] as const; export const environmentNames = ['kibana', 'browser', 'session'] as const; diff --git a/src/plugins/presentation_util/public/services/kibana/labs.ts b/src/plugins/presentation_util/public/services/kibana/labs.ts index db78103469880..fe0767ff09d8f 100644 --- a/src/plugins/presentation_util/public/services/kibana/labs.ts +++ b/src/plugins/presentation_util/public/services/kibana/labs.ts @@ -7,7 +7,6 @@ */ import { - environmentNames, EnvironmentName, projectIDs, projects, @@ -15,6 +14,7 @@ import { Project, getProjectIDs, SolutionName, + LABS_PROJECT_PREFIX, } from '../../../common'; import { PresentationUtilPluginStartDeps } from '../../types'; import { KibanaPluginServiceFactory } from '../create'; @@ -31,6 +31,16 @@ export type LabsServiceFactory = KibanaPluginServiceFactory< PresentationUtilPluginStartDeps >; +const clearLabsFromStorage = (storage: Storage) => { + projectIDs.forEach((projectID) => storage.removeItem(projectID)); + + // This is a redundancy, to catch any labs that may have been removed above. + // We could consider gathering telemetry to see how often this happens, or this may be unnecessary. + Object.keys(storage) + .filter((key) => key.startsWith(LABS_PROJECT_PREFIX)) + .forEach((key) => storage.removeItem(key)); +}; + export const labsServiceFactory: LabsServiceFactory = ({ coreStart }) => { const { uiSettings } = coreStart; const localStorage = window.localStorage; @@ -75,17 +85,18 @@ export const labsServiceFactory: LabsServiceFactory = ({ coreStart }) => { }; const reset = () => { - localStorage.clear(); - sessionStorage.clear(); - environmentNames.forEach((env) => - projectIDs.forEach((id) => setProjectStatus(id, env, projects[id].isActive)) - ); + clearLabsFromStorage(localStorage); + clearLabsFromStorage(sessionStorage); + projectIDs.forEach((id) => setProjectStatus(id, 'kibana', projects[id].isActive)); }; + const isProjectEnabled = (id: ProjectID) => getProject(id).status.isEnabled; + return { getProjectIDs, getProjects, getProject, + isProjectEnabled, reset, setProjectStatus, }; diff --git a/src/plugins/presentation_util/public/services/labs.ts b/src/plugins/presentation_util/public/services/labs.ts index ef583bd4189a9..70c40eaafa2ef 100644 --- a/src/plugins/presentation_util/public/services/labs.ts +++ b/src/plugins/presentation_util/public/services/labs.ts @@ -20,6 +20,7 @@ import { } from '../../common'; export interface PresentationLabsService { + isProjectEnabled: (id: ProjectID) => boolean; getProjectIDs: () => typeof projectIDs; getProject: (id: ProjectID) => Project; getProjects: (solutions?: SolutionName[]) => Record; diff --git a/src/plugins/presentation_util/public/services/storybook/labs.ts b/src/plugins/presentation_util/public/services/storybook/labs.ts index 396db52460053..8bc526987d95f 100644 --- a/src/plugins/presentation_util/public/services/storybook/labs.ts +++ b/src/plugins/presentation_util/public/services/storybook/labs.ts @@ -46,13 +46,17 @@ export const labsServiceFactory: LabsServiceFactory = () => { }; const reset = () => { + // This is normally not ok, but it's our isolated Storybook instance. storage.clear(); }; + const isProjectEnabled = (id: ProjectID) => getProject(id).status.isEnabled; + return { getProjectIDs, getProjects, getProject, + isProjectEnabled, reset, setProjectStatus, }; diff --git a/src/plugins/presentation_util/public/services/stub/labs.ts b/src/plugins/presentation_util/public/services/stub/labs.ts index c511ed26ef32e..aee7ce20bd86a 100644 --- a/src/plugins/presentation_util/public/services/stub/labs.ts +++ b/src/plugins/presentation_util/public/services/stub/labs.ts @@ -64,11 +64,13 @@ export const labsServiceFactory: LabsServiceFactory = () => { const setProjectStatus = (id: ProjectID, env: EnvironmentName, value: boolean) => { statuses[id] = { ...statuses[id], [env]: value }; }; + const isProjectEnabled = (id: ProjectID) => getProject(id).status.isEnabled; return { getProjectIDs, getProject, getProjects, + isProjectEnabled, setProjectStatus, reset: () => { statuses = reset(); diff --git a/src/plugins/spaces_oss/public/api.mock.ts b/src/plugins/spaces_oss/public/api.mock.ts index 84b22459a96e2..9ad7599b5ae61 100644 --- a/src/plugins/spaces_oss/public/api.mock.ts +++ b/src/plugins/spaces_oss/public/api.mock.ts @@ -11,7 +11,7 @@ import { of } from 'rxjs'; import type { SpacesApi, SpacesApiUi, SpacesApiUiComponent } from './api'; const createApiMock = (): jest.Mocked => ({ - activeSpace$: of(), + getActiveSpace$: jest.fn().mockReturnValue(of()), getActiveSpace: jest.fn(), ui: createApiUiMock(), }); diff --git a/src/plugins/spaces_oss/public/api.ts b/src/plugins/spaces_oss/public/api.ts index bd452c2fca00e..e460d9a43ef6b 100644 --- a/src/plugins/spaces_oss/public/api.ts +++ b/src/plugins/spaces_oss/public/api.ts @@ -19,7 +19,7 @@ export interface SpacesApi { * Observable representing the currently active space. * The details of the space can change without a full page reload (such as display name, color, etc.) */ - readonly activeSpace$: Observable; + getActiveSpace$(): Observable; /** * Retrieve the currently active space. diff --git a/src/plugins/vis_default_editor/public/components/controls/components/from_to_list.tsx b/src/plugins/vis_default_editor/public/components/controls/components/from_to_list.tsx index a6e0ee0d2aec4..1c721cde37b2b 100644 --- a/src/plugins/vis_default_editor/public/components/controls/components/from_to_list.tsx +++ b/src/plugins/vis_default_editor/public/components/controls/components/from_to_list.tsx @@ -38,7 +38,7 @@ const defaultConfig = { from: { value: '0.0.0.0', model: '0.0.0.0', isInvalid: false }, to: { value: '255.255.255.255', model: '255.255.255.255', isInvalid: false }, }, - validateClass: search.aggs.Ipv4Address, + validateClass: search.aggs.IpAddress, getModelValue: (item: FromToObject = {}) => ({ from: { value: item.from || EMPTY_STRING, diff --git a/src/plugins/vis_type_timeseries/common/enums/metric_types.ts b/src/plugins/vis_type_timeseries/common/enums/metric_types.ts index 17352f0f9da25..8e2bc8f346eb6 100644 --- a/src/plugins/vis_type_timeseries/common/enums/metric_types.ts +++ b/src/plugins/vis_type_timeseries/common/enums/metric_types.ts @@ -27,6 +27,7 @@ export enum METRIC_TYPES { // We should probably use BUCKET_TYPES from data plugin in future. export enum BUCKET_TYPES { TERMS = 'terms', + FILTERS = 'filters', } export const EXTENDED_STATS_TYPES = [ diff --git a/src/plugins/vis_type_timeseries/public/application/components/lib/convert_series_to_datatable.test.ts b/src/plugins/vis_type_timeseries/public/application/components/lib/convert_series_to_datatable.test.ts index df0874fdd73ec..ff7b2f497fd70 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/lib/convert_series_to_datatable.test.ts +++ b/src/plugins/vis_type_timeseries/public/application/components/lib/convert_series_to_datatable.test.ts @@ -15,6 +15,18 @@ jest.mock('../../../services', () => { getDataStart: jest.fn(() => { return { indexPatterns: jest.fn(), + query: { + timefilter: { + timefilter: { + getTime: jest.fn(() => { + return { + from: '2021-04-30T16:42:24.502Z', + to: '2021-05-05T14:42:24.502Z', + }; + }), + }, + }, + }, }; }), }; @@ -25,9 +37,12 @@ describe('convert series to datatables', () => { beforeEach(() => { const fieldMap: Record = { - test1: { name: 'test1', spec: { type: 'date' } } as IndexPatternField, - test2: { name: 'test2' } as IndexPatternField, - test3: { name: 'test3', spec: { type: 'boolean' } } as IndexPatternField, + test1: { name: 'test1', spec: { type: 'date', name: 'test1' } } as IndexPatternField, + test2: { + name: 'test2', + spec: { type: 'number', name: 'Average of test2' }, + } as IndexPatternField, + test3: { name: 'test3', spec: { type: 'boolean', name: 'test3' } } as IndexPatternField, }; const getFieldByName = (name: string): IndexPatternField | undefined => fieldMap[name]; @@ -41,8 +56,8 @@ describe('convert series to datatables', () => { describe('addMetaColumns()', () => { test('adds the correct meta to a date column', () => { - const columns = [{ id: 0, name: 'test1', isSplit: false }]; - const columnsWithMeta = addMetaToColumns(columns, indexPattern, 'count'); + const columns = [{ id: 0, name: 'test1', isMetric: true, type: 'date_histogram' }]; + const columnsWithMeta = addMetaToColumns(columns, indexPattern); expect(columnsWithMeta).toEqual([ { id: '0', @@ -54,6 +69,13 @@ describe('convert series to datatables', () => { enabled: true, indexPatternId: 'index1', type: 'date_histogram', + schema: 'metric', + params: { + timeRange: { + from: '2021-04-30T16:42:24.502Z', + to: '2021-05-05T14:42:24.502Z', + }, + }, }, type: 'date', }, @@ -63,8 +85,8 @@ describe('convert series to datatables', () => { }); test('adds the correct meta to a non date column', () => { - const columns = [{ id: 1, name: 'Average of test2', isSplit: false }]; - const columnsWithMeta = addMetaToColumns(columns, indexPattern, 'avg'); + const columns = [{ id: 1, name: 'test2', isMetric: true, type: 'avg' }]; + const columnsWithMeta = addMetaToColumns(columns, indexPattern); expect(columnsWithMeta).toEqual([ { id: '1', @@ -76,17 +98,21 @@ describe('convert series to datatables', () => { enabled: true, indexPatternId: 'index1', type: 'avg', + schema: 'metric', + params: { + field: 'Average of test2', + }, }, type: 'number', }, - name: 'Average of test2', + name: 'test2', }, ]); }); test('adds the correct meta for a split column', () => { - const columns = [{ id: 2, name: 'test3', isSplit: true }]; - const columnsWithMeta = addMetaToColumns(columns, indexPattern, 'avg'); + const columns = [{ id: 2, name: 'test3', isMetric: false, type: 'terms' }]; + const columnsWithMeta = addMetaToColumns(columns, indexPattern); expect(columnsWithMeta).toEqual([ { id: '2', @@ -98,6 +124,10 @@ describe('convert series to datatables', () => { enabled: true, indexPatternId: 'index1', type: 'terms', + schema: 'group', + params: { + field: 'test3', + }, }, type: 'boolean', }, diff --git a/src/plugins/vis_type_timeseries/public/application/components/lib/convert_series_to_datatable.ts b/src/plugins/vis_type_timeseries/public/application/components/lib/convert_series_to_datatable.ts index 35506f9e62081..19a1910afbe2f 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/lib/convert_series_to_datatable.ts +++ b/src/plugins/vis_type_timeseries/public/application/components/lib/convert_series_to_datatable.ts @@ -6,51 +6,74 @@ * Side Public License, v 1. */ import { IndexPattern } from 'src/plugins/data/public'; -import { - Datatable, - DatatableRow, - DatatableColumn, - DatatableColumnType, -} from 'src/plugins/expressions/public'; +import { DatatableRow, DatatableColumn, DatatableColumnType } from 'src/plugins/expressions/public'; +import { Query } from 'src/plugins/data/common'; import { TimeseriesVisParams } from '../../../types'; import type { PanelData } from '../../../../common/types'; +import { BUCKET_TYPES } from '../../../../common/enums'; import { fetchIndexPattern } from '../../../../common/index_patterns_utils'; import { getDataStart } from '../../../services'; import { X_ACCESSOR_INDEX } from '../../visualizations/constants'; +import type { TSVBTables } from './types'; -interface TSVBTables { - [key: string]: Datatable; +interface FilterParams { + filter?: Query; + label?: string; + field?: string; } interface TSVBColumns { id: number; name: string; - isSplit: boolean; + isMetric: boolean; + type: string; + params?: FilterParams[]; } export const addMetaToColumns = ( columns: TSVBColumns[], - indexPattern: IndexPattern, - metricsType: string + indexPattern: IndexPattern ): DatatableColumn[] => { return columns.map((column) => { const field = indexPattern.getFieldByName(column.name); const type = (field?.spec.type as DatatableColumnType) || 'number'; + + let params: unknown = { + field: field?.spec.name, + }; + if (column.type === BUCKET_TYPES.FILTERS && column.params) { + const filters = column.params.map((col) => ({ + input: col.filter, + label: col.label, + })); + params = { + filters, + }; + } else if (column.type === 'date_histogram') { + const { query } = getDataStart(); + const timeRange = query.timefilter.timefilter.getTime(); + params = { + timeRange, + }; + } + const cleanedColumn = { id: column.id.toString(), name: column.name, meta: { type, - field: column.name, + field: field?.spec.name, index: indexPattern.title, source: 'esaggs', sourceParams: { enabled: true, indexPatternId: indexPattern?.id, - type: type === 'date' ? 'date_histogram' : column.isSplit ? 'terms' : metricsType, + type: column.type, + schema: column.isMetric ? 'metric' : 'group', + params, }, }, - }; + } as DatatableColumn; return cleanedColumn; }); }; @@ -73,30 +96,58 @@ export const convertSeriesToDataTable = async ( usedIndexPattern = indexPattern; } } - const isGroupedByTerms = layer.split_mode === 'terms'; + const isGroupedByTerms = layer.split_mode === BUCKET_TYPES.TERMS; + const isGroupedByFilters = layer.split_mode === BUCKET_TYPES.FILTERS; const seriesPerLayer = series.filter((s) => s.seriesId === layer.id); let id = X_ACCESSOR_INDEX; const columns: TSVBColumns[] = [ - { id, name: usedIndexPattern.timeFieldName || '', isSplit: false }, + { + id, + name: usedIndexPattern.timeFieldName || '', + isMetric: false, + type: 'date_histogram', + }, ]; + if (seriesPerLayer.length) { id++; - columns.push({ id, name: seriesPerLayer[0].splitByLabel, isSplit: false }); - // Adds an extra column, if the layer is split by terms aggregation + const metrics = layer.metrics; + columns.push({ + id, + name: metrics[metrics.length - 1].field || seriesPerLayer[0].splitByLabel, + isMetric: true, + type: metrics[metrics.length - 1].type, + }); + + // Adds an extra column, if the layer is split by terms or filters aggregation if (isGroupedByTerms) { id++; - columns.push({ id, name: layer.terms_field || '', isSplit: true }); + columns.push({ + id, + name: layer.terms_field || '', + isMetric: false, + type: BUCKET_TYPES.TERMS, + }); + } else if (isGroupedByFilters) { + id++; + columns.push({ + id, + name: BUCKET_TYPES.FILTERS, + isMetric: false, + params: layer?.split_filters as FilterParams[], + type: BUCKET_TYPES.FILTERS, + }); } } - const columnsWithMeta = addMetaToColumns(columns, usedIndexPattern, layer.metrics[0].type); - + const columnsWithMeta = addMetaToColumns(columns, usedIndexPattern); + const filtersColumn = columns.find((col) => col.type === BUCKET_TYPES.FILTERS); let rows: DatatableRow[] = []; for (let j = 0; j < seriesPerLayer.length; j++) { const data = seriesPerLayer[j].data.map((rowData) => { const row: DatatableRow = [rowData[0], rowData[1]]; // If the layer is split by terms aggregation, the data array should also contain the split value. - if (isGroupedByTerms) { + if (isGroupedByTerms || filtersColumn) { row.push(seriesPerLayer[j].label); } return row; diff --git a/src/plugins/vis_type_timeseries/public/application/components/lib/get_click_filter_data.test.ts b/src/plugins/vis_type_timeseries/public/application/components/lib/get_click_filter_data.test.ts new file mode 100644 index 0000000000000..2b9a1f930c15a --- /dev/null +++ b/src/plugins/vis_type_timeseries/public/application/components/lib/get_click_filter_data.test.ts @@ -0,0 +1,207 @@ +/* + * 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 { XYChartSeriesIdentifier, GeometryValue } from '@elastic/charts'; +import { getClickFilterData } from './get_click_filter_data'; +import type { TSVBTables } from './types'; +import { TimeseriesVisParams } from '../../../types'; + +describe('getClickFilterData', () => { + test('gets the correct data for a group by everything timeseries chart', () => { + const points = [ + [ + { + x: 1620032400000, + y: 72, + accessor: 'y1', + datum: [1620032400000, 72], + }, + { + key: + 'groupId{yaxis_50589930-ad98-11eb-b6f6-59011d7388b7_main_group}spec{61ca57f1-469d-11e7-af02-69e470af7417}yAccessor{1}splitAccessors{}', + specId: '61ca57f1-469d-11e7-af02-69e470af7417', + yAccessor: 1, + }, + ], + ] as Array<[GeometryValue, XYChartSeriesIdentifier]>; + const tables = { + '61ca57f1-469d-11e7-af02-69e470af7417': { + type: 'datatable', + rows: [ + [1620010800000, 8], + [1620021600000, 33], + [1620032400000, 72], + [1620043200000, 66], + [1620054000000, 36], + [1620064800000, 11], + ], + columns: [ + { + id: '0', + name: 'timestamp', + meta: { + type: 'date', + field: 'timestamp', + index: 'kibana_sample_data_logs', + source: 'esaggs', + sourceParams: { + enabled: true, + indexPatternId: '90943e30-9a47-11e8-b64d-95841ca0b247', + type: 'date_histogram', + schema: 'group', + params: { + field: 'timestamp', + }, + }, + }, + }, + { + id: '1', + name: 'Count', + meta: { + type: 'number', + index: 'kibana_sample_data_logs', + source: 'esaggs', + sourceParams: { + enabled: true, + indexPatternId: '90943e30-9a47-11e8-b64d-95841ca0b247', + type: 'count', + schema: 'metric', + params: {}, + }, + }, + }, + ], + }, + } as TSVBTables; + const model = { + series: [ + { + id: '61ca57f1-469d-11e7-af02-69e470af7417', + split_mode: 'everything', + }, + ], + } as TimeseriesVisParams; + const data = getClickFilterData(points, tables, model); + expect(data[0].column).toEqual(0); + expect(data[0].row).toEqual(2); + expect(data[0].value).toEqual(points[0][0].x); + }); + + test('gets the correct data for a group by terms timeseries chart', () => { + const points = [ + [ + { + x: 1619481600000, + y: 3, + accessor: 'y1', + datum: [1619481600000, 3], + }, + { + key: + 'groupId{yaxis_6e0353a0-ad9b-11eb-b112-89cce8e43380_main_group}spec{61ca57f1-469d-11e7-af02-69e470af7417:1}yAccessor{1}splitAccessors{}', + specId: '61ca57f1-469d-11e7-af02-69e470af7417:1', + }, + ], + ] as Array<[GeometryValue, XYChartSeriesIdentifier]>; + const tables = { + '61ca57f1-469d-11e7-af02-69e470af7417': { + type: 'datatable', + rows: [ + [1619449200000, 31, 0], + [1619460000000, 36, 0], + [1619470800000, 35, 0], + [1619481600000, 40, 0], + [1619492400000, 38, 0], + [1619503200000, 30, 0], + [1620172800000, 0, 0], + [1619449200000, 4, 1], + [1619460000000, 4, 1], + [1619470800000, 3, 1], + [1619481600000, 3, 1], + [1619492400000, 2, 1], + [1619503200000, 3, 1], + ], + columns: [ + { + id: '0', + name: 'timestamp', + meta: { + type: 'date', + field: 'timestamp', + index: 'kibana_sample_data_flights', + source: 'esaggs', + sourceParams: { + enabled: true, + indexPatternId: 'd3d7af60-4c81-11e8-b3d7-01146121b73d', + type: 'date_histogram', + schema: 'group', + params: { + field: 'timestamp', + }, + }, + }, + }, + { + id: '1', + name: 'Count', + meta: { + type: 'number', + index: 'kibana_sample_data_flights', + source: 'esaggs', + sourceParams: { + enabled: true, + indexPatternId: 'd3d7af60-4c81-11e8-b3d7-01146121b73d', + type: 'count', + schema: 'metric', + params: {}, + }, + }, + }, + { + id: '2', + name: 'Cancelled', + meta: { + type: 'boolean', + field: 'Cancelled', + index: 'kibana_sample_data_flights', + source: 'esaggs', + sourceParams: { + enabled: true, + indexPatternId: 'd3d7af60-4c81-11e8-b3d7-01146121b73d', + type: 'terms', + schema: 'group', + params: { + field: 'Cancelled', + }, + }, + }, + }, + ], + }, + } as TSVBTables; + const model = { + series: [ + { + id: '61ca57f1-469d-11e7-af02-69e470af7417', + split_mode: 'terms', + }, + ], + } as TimeseriesVisParams; + const data = getClickFilterData(points, tables, model); + expect(data.length).toEqual(2); + expect(data[0].column).toEqual(0); + expect(data[0].row).toEqual(10); + expect(data[0].value).toEqual(points[0][0].x); + + expect(data[1].column).toEqual(2); + expect(data[1].row).toEqual(10); + // expect(data).toEqual([]); + const splitValue = points[0][1].specId.split(':'); + expect(data[1].value).toEqual(parseInt(splitValue[1], 10)); + }); +}); diff --git a/src/plugins/vis_type_timeseries/public/application/components/lib/get_click_filter_data.ts b/src/plugins/vis_type_timeseries/public/application/components/lib/get_click_filter_data.ts new file mode 100644 index 0000000000000..a6e35aa6e1032 --- /dev/null +++ b/src/plugins/vis_type_timeseries/public/application/components/lib/get_click_filter_data.ts @@ -0,0 +1,57 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 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 { XYChartSeriesIdentifier, GeometryValue } from '@elastic/charts'; +import { ValueClickContext } from 'src/plugins/embeddable/public'; +import { X_ACCESSOR_INDEX } from '../../visualizations/constants'; +import { BUCKET_TYPES } from '../../../../common/enums'; +import { TimeseriesVisParams } from '../../../types'; +import type { TSVBTables } from './types'; + +export const getClickFilterData = ( + points: Array<[GeometryValue, XYChartSeriesIdentifier]>, + tables: TSVBTables, + model: TimeseriesVisParams +) => { + const data: ValueClickContext['data']['data'] = []; + points.forEach((point) => { + const [geometry] = point; + const { specId } = point[1]; + // specId for a split series has the format + // 61ca57f1-469d-11e7-af02-69e470af7417:Men's Accessories, : + const [layerId, splitLabel] = specId.split(':'); + const table = tables[layerId]; + + const layer = model.series.filter(({ id }) => id === layerId); + let label = splitLabel; + // compute label for filters split mode + if (splitLabel && layer.length && layer[0].split_mode === BUCKET_TYPES.FILTERS) { + const filter = layer[0]?.split_filters?.filter(({ id }) => id === splitLabel); + label = filter?.[0].label || (filter?.[0].filter?.query as string); + } + const index = table.rows.findIndex((row) => { + const condition = + geometry.x === row[X_ACCESSOR_INDEX] && geometry.y === row[X_ACCESSOR_INDEX + 1]; + return splitLabel ? condition && row[X_ACCESSOR_INDEX + 2].toString() === label : condition; + }); + if (index < 0) return; + + // Filter out the metric column + const bucketCols = table.columns.filter((col) => col.meta.sourceParams?.schema === 'group'); + + const newData = bucketCols.map(({ id }) => ({ + table, + column: parseInt(id, 10), + row: index, + value: table.rows[index][id] ?? null, + })); + if (newData.length) { + data.push(...newData); + } + }); + return data; +}; diff --git a/packages/kbn-telemetry-tools/babel.config.js b/src/plugins/vis_type_timeseries/public/application/components/lib/types.ts similarity index 74% rename from packages/kbn-telemetry-tools/babel.config.js rename to src/plugins/vis_type_timeseries/public/application/components/lib/types.ts index 68c67e549b74b..fee6670b49b9c 100644 --- a/packages/kbn-telemetry-tools/babel.config.js +++ b/src/plugins/vis_type_timeseries/public/application/components/lib/types.ts @@ -5,8 +5,8 @@ * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ +import { Datatable } from 'src/plugins/expressions/public'; -module.exports = { - presets: ['@kbn/babel-preset/node_preset'], - ignore: ['**/*.test.ts', '**/__fixture__/**'], -}; +export interface TSVBTables { + [key: string]: Datatable; +} diff --git a/src/plugins/vis_type_timeseries/public/application/components/timeseries_visualization.tsx b/src/plugins/vis_type_timeseries/public/application/components/timeseries_visualization.tsx index 9683089094054..5391bf319ee57 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/timeseries_visualization.tsx +++ b/src/plugins/vis_type_timeseries/public/application/components/timeseries_visualization.tsx @@ -12,7 +12,7 @@ import React, { useCallback, useEffect } from 'react'; import { get } from 'lodash'; import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; - +import { XYChartSeriesIdentifier, GeometryValue } from '@elastic/charts'; import { IUiSettingsClient } from 'src/core/public'; import { IInterpreterRenderHandlers } from 'src/plugins/expressions'; import { PersistedState } from 'src/plugins/visualizations/public'; @@ -27,6 +27,7 @@ import { fetchIndexPattern } from '../../../common/index_patterns_utils'; import { TimeseriesVisParams } from '../../types'; import { getDataStart } from '../../services'; import { convertSeriesToDataTable } from './lib/convert_series_to_datatable'; +import { getClickFilterData } from './lib/get_click_filter_data'; import { X_ACCESSOR_INDEX } from '../visualizations/constants'; import { LastValueModeIndicator } from './last_value_mode_indicator'; import { getInterval } from './lib/get_interval'; @@ -102,6 +103,37 @@ function TimeseriesVisualization({ [handlers, model] ); + const handleFilterClick = useCallback( + async (series: PanelData[], points: Array<[GeometryValue, XYChartSeriesIdentifier]>) => { + const indexPatternValue = model.index_pattern || ''; + const { indexPatterns } = getDataStart(); + const { indexPattern } = await fetchIndexPattern(indexPatternValue, indexPatterns); + + // it should work only if index pattern is found + if (!indexPattern) return; + + const tables = indexPattern + ? await convertSeriesToDataTable(model, series, indexPattern) + : null; + + if (!tables) return; + + const data = getClickFilterData(points, tables, model); + + const event = { + name: 'filterBucket', + data: { + data, + negate: false, + timeFieldName: indexPattern.timeFieldName, + }, + }; + + handlers.event(event); + }, + [handlers, model] + ); + const handleUiState = useCallback( (field: string, value: { column: string; order: string }) => { uiState.set(field, value); @@ -156,6 +188,7 @@ function TimeseriesVisualization({ visData={visData} uiState={uiState} onBrush={onBrush} + onFilterClick={handleFilterClick} onUiState={handleUiState} syncColors={syncColors} palettesService={palettesService} diff --git a/src/plugins/vis_type_timeseries/public/application/components/vis_editor.tsx b/src/plugins/vis_type_timeseries/public/application/components/vis_editor.tsx index de3163c44f95b..99f2564328402 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/vis_editor.tsx +++ b/src/plugins/vis_type_timeseries/public/application/components/vis_editor.tsx @@ -8,6 +8,7 @@ import React, { Component } from 'react'; import * as Rx from 'rxjs'; +import uuid from 'uuid/v4'; import { share } from 'rxjs/operators'; import { isEqual, isEmpty, debounce } from 'lodash'; import { EventEmitter } from 'events'; @@ -79,6 +80,9 @@ export class VisEditor extends Component Promise; + onFilterClick: ( + series: PanelData[], + points: Array<[GeometryValue, XYChartSeriesIdentifier]> + ) => Promise; onUiState: ( field: string, value: { diff --git a/src/plugins/vis_type_timeseries/public/application/components/vis_types/markdown/vis.js b/src/plugins/vis_type_timeseries/public/application/components/vis_types/markdown/vis.js index 6510ad24615a6..ef6b30be30a30 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/vis_types/markdown/vis.js +++ b/src/plugins/vis_type_timeseries/public/application/components/vis_types/markdown/vis.js @@ -91,6 +91,7 @@ MarkdownVisualization.propTypes = { className: PropTypes.string, model: PropTypes.object, onBrush: PropTypes.func, + onFilterClick: PropTypes.func, onChange: PropTypes.func, visData: PropTypes.object, getConfig: PropTypes.func, diff --git a/src/plugins/vis_type_timeseries/public/application/components/vis_types/metric/vis.js b/src/plugins/vis_type_timeseries/public/application/components/vis_types/metric/vis.js index 2b58145a4928d..3029bba04b450 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/vis_types/metric/vis.js +++ b/src/plugins/vis_type_timeseries/public/application/components/vis_types/metric/vis.js @@ -79,6 +79,7 @@ MetricVisualization.propTypes = { additionalLabel: PropTypes.string, model: PropTypes.object, onBrush: PropTypes.func, + onFilterClick: PropTypes.func, onChange: PropTypes.func, visData: PropTypes.object, getConfig: PropTypes.func, diff --git a/src/plugins/vis_type_timeseries/public/application/components/vis_types/timeseries/vis.js b/src/plugins/vis_type_timeseries/public/application/components/vis_types/timeseries/vis.js index 29560c4bd9368..8e59e8e1bb628 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/vis_types/timeseries/vis.js +++ b/src/plugins/vis_type_timeseries/public/application/components/vis_types/timeseries/vis.js @@ -26,6 +26,7 @@ class TimeseriesVisualization extends Component { static propTypes = { model: PropTypes.object, onBrush: PropTypes.func, + onFilterClick: PropTypes.func, visData: PropTypes.object, getConfig: PropTypes.func, }; @@ -136,7 +137,7 @@ class TimeseriesVisualization extends Component { }; render() { - const { model, visData, onBrush, syncColors, palettesService } = this.props; + const { model, visData, onBrush, onFilterClick, syncColors, palettesService } = this.props; const series = get(visData, `${model.id}.series`, []); const interval = getInterval(visData, model); const yAxisIdGenerator = htmlIdGenerator('yaxis'); @@ -230,6 +231,7 @@ class TimeseriesVisualization extends Component { series={series} yAxis={yAxis} onBrush={onBrush} + onFilterClick={onFilterClick} backgroundColor={model.background_color} showGrid={Boolean(model.show_grid)} legend={Boolean(model.show_legend)} diff --git a/src/plugins/vis_type_timeseries/public/application/components/vis_types/top_n/vis.js b/src/plugins/vis_type_timeseries/public/application/components/vis_types/top_n/vis.js index 8150d2e4d6e1b..41e6236cbc39b 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/vis_types/top_n/vis.js +++ b/src/plugins/vis_type_timeseries/public/application/components/vis_types/top_n/vis.js @@ -96,6 +96,7 @@ TopNVisualization.propTypes = { className: PropTypes.string, model: PropTypes.object, onBrush: PropTypes.func, + onFilterClick: PropTypes.func, onChange: PropTypes.func, visData: PropTypes.object, getConfig: PropTypes.func, diff --git a/src/plugins/vis_type_timeseries/public/application/components/vis_with_splits.js b/src/plugins/vis_type_timeseries/public/application/components/vis_with_splits.js index 7dc6a26185e2b..4b933bc81d882 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/vis_with_splits.js +++ b/src/plugins/vis_type_timeseries/public/application/components/vis_with_splits.js @@ -109,6 +109,7 @@ export function visWithSplits(WrappedComponent) { model={model} visData={newVisData} onBrush={props.onBrush} + onFilterClick={props.onFilterClick} additionalLabel={additionalLabel || emptyLabel} backgroundColor={props.backgroundColor} getConfig={props.getConfig} diff --git a/src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/index.js b/src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/index.js index 44c1742e144be..a4d834ea8d217 100644 --- a/src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/index.js +++ b/src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/index.js @@ -61,6 +61,7 @@ export const TimeSeries = ({ series, yAxis, onBrush, + onFilterClick, xAxisFormatter, annotations, syncColors, @@ -118,6 +119,10 @@ export const TimeSeries = ({ onBrush(min, max, series); }; + const handleElementClick = (points) => { + onFilterClick(series, points); + }; + const getSeriesColor = useCallback( (seriesName, seriesGroupId, seriesId) => { const seriesById = series.filter((s) => s.seriesId === seriesGroupId); @@ -142,6 +147,7 @@ export const TimeSeries = ({ showLegendExtra={true} legendPosition={legendPosition} onBrushEnd={onBrushEndListener} + onElementClick={(args) => handleElementClick(args)} animateData={false} onPointerUpdate={handleCursorUpdate} theme={[ diff --git a/src/plugins/vis_type_timeseries/public/metrics_type.ts b/src/plugins/vis_type_timeseries/public/metrics_type.ts index 66128b2abe856..3cb4faaacf25b 100644 --- a/src/plugins/vis_type_timeseries/public/metrics_type.ts +++ b/src/plugins/vis_type_timeseries/public/metrics_type.ts @@ -7,7 +7,7 @@ */ import { i18n } from '@kbn/i18n'; - +import uuid from 'uuid/v4'; import { TSVB_EDITOR_NAME } from './application/editor_controller'; import { PANEL_TYPES, TOOLTIP_MODES } from '../common/enums'; import { isStringTypeIndexPattern } from '../common/index_patterns_utils'; @@ -25,11 +25,11 @@ export const metricsVisDefinition = { group: VisGroups.PROMOTED, visConfig: { defaults: { - id: '61ca57f0-469d-11e7-af02-69e470af7417', + id: uuid(), type: PANEL_TYPES.TIMESERIES, series: [ { - id: '61ca57f1-469d-11e7-af02-69e470af7417', + id: uuid(), color: '#68BC00', split_mode: 'everything', palette: { @@ -38,7 +38,7 @@ export const metricsVisDefinition = { }, metrics: [ { - id: '61ca57f2-469d-11e7-af02-69e470af7417', + id: uuid(), type: 'count', }, ], @@ -75,7 +75,7 @@ export const metricsVisDefinition = { }, toExpressionAst, getSupportedTriggers: () => { - return [VIS_EVENT_TO_TRIGGER.brush]; + return [VIS_EVENT_TO_TRIGGER.filter, VIS_EVENT_TO_TRIGGER.brush]; }, inspectorAdapters: {}, getUsedIndexPattern: async (params: VisParams) => { diff --git a/src/plugins/vis_type_timeseries/public/plugin.ts b/src/plugins/vis_type_timeseries/public/plugin.ts index 1c1212add3d8c..479c3f47435cc 100644 --- a/src/plugins/vis_type_timeseries/public/plugin.ts +++ b/src/plugins/vis_type_timeseries/public/plugin.ts @@ -15,7 +15,6 @@ import { EditorController, TSVB_EDITOR_NAME } from './application/editor_control import { createMetricsFn } from './metrics_fn'; import { metricsVisDefinition } from './metrics_type'; import { - setSavedObjectsClient, setUISettings, setI18n, setFieldFormats, @@ -65,7 +64,6 @@ export class MetricsPlugin implements Plugin { } public start(core: CoreStart, { data }: MetricsPluginStartDependencies) { - setSavedObjectsClient(core.savedObjects); setI18n(core.i18n); setFieldFormats(data.fieldFormats); setDataStart(data); diff --git a/src/plugins/vis_type_timeseries/public/services.ts b/src/plugins/vis_type_timeseries/public/services.ts index 17c4a6c7de153..22f99f95691de 100644 --- a/src/plugins/vis_type_timeseries/public/services.ts +++ b/src/plugins/vis_type_timeseries/public/services.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { I18nStart, SavedObjectsStart, IUiSettingsClient, CoreStart } from 'src/core/public'; +import { I18nStart, IUiSettingsClient, CoreStart } from 'src/core/public'; import { createGetterSetter } from '../../kibana_utils/public'; import { ChartsPluginSetup } from '../../charts/public'; import { DataPublicPluginStart } from '../../data/public'; @@ -17,10 +17,6 @@ export const [getFieldFormats, setFieldFormats] = createGetterSetter< DataPublicPluginStart['fieldFormats'] >('FieldFormats'); -export const [getSavedObjectsClient, setSavedObjectsClient] = createGetterSetter( - 'SavedObjectsClient' -); - export const [getCoreStart, setCoreStart] = createGetterSetter('CoreStart'); export const [getDataStart, setDataStart] = createGetterSetter('DataStart'); diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/date_histogram.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/date_histogram.js index 29cf3f274dc24..f82f332df19fd 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/date_histogram.js +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/date_histogram.js @@ -68,6 +68,7 @@ export function dateHistogram( bucketSize, seriesId: series.id, index: panel.use_kibana_indexes ? seriesIndex.indexPattern?.id : undefined, + panelId: panel.id, }); return next(doc); diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/date_histogram.test.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/date_histogram.test.js index a1fd242dc150e..741eb93267f4c 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/date_histogram.test.js +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/date_histogram.test.js @@ -32,6 +32,7 @@ describe('dateHistogram(req, panel, series)', () => { index_pattern: '*', time_field: '@timestamp', interval: '10s', + id: 'panelId', }; series = { id: 'test' }; config = { @@ -89,6 +90,7 @@ describe('dateHistogram(req, panel, series)', () => { intervalString: '10s', timeField: '@timestamp', seriesId: 'test', + panelId: 'panelId', }, }, }, @@ -130,6 +132,7 @@ describe('dateHistogram(req, panel, series)', () => { intervalString: '10s', timeField: '@timestamp', seriesId: 'test', + panelId: 'panelId', }, }, }, @@ -174,6 +177,7 @@ describe('dateHistogram(req, panel, series)', () => { intervalString: '20s', timeField: 'timestamp', seriesId: 'test', + panelId: 'panelId', }, }, }, @@ -230,6 +234,7 @@ describe('dateHistogram(req, panel, series)', () => { seriesId: 'test', bucketSize: 10, intervalString: '10s', + panelId: 'panelId', }, }, }, diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/normalize_query.test.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/normalize_query.test.js index 0194e1ce3e5d9..a3a9022a67191 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/normalize_query.test.js +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/normalize_query.test.js @@ -11,6 +11,7 @@ import { normalizeQuery } from './normalize_query'; describe('normalizeQuery', () => { const req = 'req'; const seriesId = '61ca57f1-469d-11e7-af02-69e470af7417'; + const panelId = '39d49073-a924-426b-aa32-35acb40a9bb7'; let next; let panel; @@ -48,6 +49,7 @@ describe('normalizeQuery', () => { intervalString: '10s', bucketSize: 10, seriesId: [seriesId], + panelId, }, }, }, @@ -78,6 +80,7 @@ describe('normalizeQuery', () => { intervalString: '10s', bucketSize: 10, seriesId: [seriesId], + panelId: panelId, }); }); diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/date_histogram.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/date_histogram.js index f0989cf0fa08b..3e883abc9e5e0 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/date_histogram.js +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/date_histogram.js @@ -24,6 +24,7 @@ export function dateHistogram(req, panel, esQueryConfig, seriesIndex, capabiliti const meta = { timeField, index: panel.use_kibana_indexes ? seriesIndex.indexPattern?.id : undefined, + panelId: panel.id, }; const getDateHistogramForLastBucketMode = () => { diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/normalize_query.test.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/normalize_query.test.js index e2a7e7b17b548..71381f32599e3 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/normalize_query.test.js +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/normalize_query.test.js @@ -11,6 +11,7 @@ import { normalizeQuery } from './normalize_query'; describe('normalizeQuery', () => { const req = 'req'; const seriesId = '61ca57f1-469d-11e7-af02-69e470af7417'; + const panelId = '39d49073-a924-426b-aa32-35acb40a9bb7'; let next; let panel; @@ -56,6 +57,7 @@ describe('normalizeQuery', () => { timeField: 'order_date', intervalString: '10s', bucketSize: 10, + panelId, }, }, }, @@ -87,6 +89,7 @@ describe('normalizeQuery', () => { timeField: 'order_date', intervalString: '10s', bucketSize: 10, + panelId, }); }); diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/series/build_request_body.test.ts b/src/plugins/vis_type_timeseries/server/lib/vis_data/series/build_request_body.test.ts index 89d87da5f3d7e..5b865d451003a 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/series/build_request_body.test.ts +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/series/build_request_body.test.ts @@ -157,6 +157,7 @@ describe('buildRequestBody(req)', () => { intervalString: '10s', seriesId: 'c9b5f9c0-e403-11e6-be91-6f7688e9fac7', timeField: '@timestamp', + panelId: 'c9b5d2b0-e403-11e6-be91-6f7688e9fac7', }, }, }, diff --git a/src/plugins/vis_type_vega/public/vega_view/vega_map_view/view.test.ts b/src/plugins/vis_type_vega/public/vega_view/vega_map_view/view.test.ts index d5a67f2eac064..4fd19aa45e69e 100644 --- a/src/plugins/vis_type_vega/public/vega_view/vega_map_view/view.test.ts +++ b/src/plugins/vis_type_vega/public/vega_view/vega_map_view/view.test.ts @@ -28,9 +28,12 @@ import { setUISettings, } from '../../services'; import { initVegaLayer, initTmsRasterLayer } from './layers'; -import { Map, NavigationControl, Style } from 'mapbox-gl'; -jest.mock('mapbox-gl', () => ({ +// @ts-expect-error +import mapboxgl from 'mapbox-gl/dist/mapbox-gl-csp'; + +jest.mock('mapbox-gl/dist/mapbox-gl-csp', () => ({ + setRTLTextPlugin: jest.fn(), Map: jest.fn().mockImplementation(() => ({ getLayer: () => '', removeLayer: jest.fn(), @@ -75,9 +78,10 @@ describe('vega_map_view/view', () => { setUISettings(coreStart.uiSettings); const getTmsService = jest.fn().mockReturnValue(({ - getVectorStyleSheet: (): Style => ({ + getVectorStyleSheet: () => ({ version: 8, sources: {}, + // @ts-expect-error layers: [], }), getMaxZoom: async () => 20, @@ -144,7 +148,7 @@ describe('vega_map_view/view', () => { await vegaMapView.init(); const { longitude, latitude, scrollWheelZoom } = vegaMapView._parser.mapConfig; - expect(Map).toHaveBeenCalledWith({ + expect(mapboxgl.Map).toHaveBeenCalledWith({ style: { version: 8, sources: {}, @@ -170,7 +174,7 @@ describe('vega_map_view/view', () => { await vegaMapView.init(); const { longitude, latitude, scrollWheelZoom } = vegaMapView._parser.mapConfig; - expect(Map).toHaveBeenCalledWith({ + expect(mapboxgl.Map).toHaveBeenCalledWith({ style: { version: 8, sources: {}, @@ -195,7 +199,7 @@ describe('vega_map_view/view', () => { await vegaMapView.init(); - expect(NavigationControl).toHaveBeenCalled(); + expect(mapboxgl.NavigationControl).toHaveBeenCalled(); }); }); }); diff --git a/src/plugins/vis_type_vega/public/vega_view/vega_map_view/view.ts b/src/plugins/vis_type_vega/public/vega_view/vega_map_view/view.ts index 453e9596a2a4c..e899057819a19 100644 --- a/src/plugins/vis_type_vega/public/vega_view/vega_map_view/view.ts +++ b/src/plugins/vis_type_vega/public/vega_view/vega_map_view/view.ts @@ -7,9 +7,11 @@ */ import { i18n } from '@kbn/i18n'; -import { Map, Style, NavigationControl, MapboxOptions } from 'mapbox-gl'; +import type { Map, Style, MapboxOptions } from 'mapbox-gl'; import { View, parse } from 'vega'; +// @ts-expect-error +import mapboxgl from 'mapbox-gl/dist/mapbox-gl-csp'; import { initTmsRasterLayer, initVegaLayer } from './layers'; import { VegaBaseView } from '../vega_base_view'; import { getMapServiceSettings } from '../../services'; @@ -22,11 +24,17 @@ import { userConfiguredLayerId, vegaLayerId, } from './constants'; - import { validateZoomSettings, injectMapPropsIntoSpec } from './utils'; - import './vega_map_view.scss'; +// @ts-expect-error +import mbRtlPlugin from '!!file-loader!@mapbox/mapbox-gl-rtl-text/mapbox-gl-rtl-text.min.js'; +// @ts-expect-error +import mbWorkerUrl from '!!file-loader!mapbox-gl/dist/mapbox-gl-csp-worker'; + +mapboxgl.workerUrl = mbWorkerUrl; +mapboxgl.setRTLTextPlugin(mbRtlPlugin); + async function updateVegaView(mapBoxInstance: Map, vegaView: View) { const mapCanvas = mapBoxInstance.getCanvas(); const { lat, lng } = mapBoxInstance.getCenter(); @@ -115,7 +123,7 @@ export class VegaMapView extends VegaBaseView { // In some cases, Vega may be initialized twice, e.g. after awaiting... if (!this._$container) return; - const mapBoxInstance = new Map({ + const mapBoxInstance = new mapboxgl.Map({ style, customAttribution, container: this._$container.get(0), @@ -142,7 +150,7 @@ export class VegaMapView extends VegaBaseView { private initControls(mapBoxInstance: Map) { if (this.shouldShowZoomControl) { - mapBoxInstance.addControl(new NavigationControl({ showCompass: false }), 'top-left'); + mapBoxInstance.addControl(new mapboxgl.NavigationControl({ showCompass: false }), 'top-left'); } // disable map rotation using right click + drag diff --git a/src/plugins/visualizations/public/embeddable/visualize_embeddable.ts b/src/plugins/visualizations/public/embeddable/visualize_embeddable.ts index 3bb52eb15758a..f5bf6b59aa0ae 100644 --- a/src/plugins/visualizations/public/embeddable/visualize_embeddable.ts +++ b/src/plugins/visualizations/public/embeddable/visualize_embeddable.ts @@ -337,6 +337,14 @@ export class VisualizeEmbeddable data: { timeFieldName: this.vis.data.indexPattern?.timeFieldName!, ...event.data }, }; } + // do not trigger the filter click event if the filter bar is not visible + if ( + triggerId === VIS_EVENT_TO_TRIGGER.filter && + !this.input.id && + !this.vis.type.options.showFilterBar + ) { + return; + } getUiActions().getTrigger(triggerId).exec(context); } diff --git a/test/functional/apps/visualize/_area_chart.ts b/test/functional/apps/visualize/_area_chart.ts index 1e074686c1c4f..99f65458bb606 100644 --- a/test/functional/apps/visualize/_area_chart.ts +++ b/test/functional/apps/visualize/_area_chart.ts @@ -508,7 +508,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); it('should show error when calendar interval invalid', async () => { - await PageObjects.visEditor.setInterval('14d', { type: 'custom' }); + await PageObjects.visEditor.setInterval('2w', { type: 'custom' }); const intervalErrorMessage = await find.byCssSelector( '[data-test-subj="visEditorInterval"] + .euiFormErrorText' ); diff --git a/test/functional/apps/visualize/_tsvb_time_series.ts b/test/functional/apps/visualize/_tsvb_time_series.ts index e93cc8bdeda99..a0c9d806facc6 100644 --- a/test/functional/apps/visualize/_tsvb_time_series.ts +++ b/test/functional/apps/visualize/_tsvb_time_series.ts @@ -11,9 +11,18 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ getPageObjects, getService }: FtrProviderContext) { - const { visualize, visualBuilder } = getPageObjects(['visualBuilder', 'visualize']); + const { visualize, visualBuilder, timeToVisualize, dashboard } = getPageObjects([ + 'visualBuilder', + 'visualize', + 'timeToVisualize', + 'dashboard', + ]); + const testSubjects = getService('testSubjects'); const retry = getService('retry'); + const filterBar = getService('filterBar'); + const elasticChart = getService('elasticChart'); const log = getService('log'); + const browser = getService('browser'); const kibanaServer = getService('kibanaServer'); describe('visual builder', function describeIndexTests() { @@ -143,6 +152,36 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { }); }); }); + + describe('Clicking on the chart', () => { + it(`should create a filter`, async () => { + await visualBuilder.setMetricsGroupByTerms('machine.os.raw'); + await visualBuilder.clickSeriesOption(); + await testSubjects.click('visualizeSaveButton'); + + await timeToVisualize.saveFromModal('My TSVB viz 1', { + addToDashboard: 'new', + saveToLibrary: false, + }); + + await dashboard.waitForRenderComplete(); + const el = await elasticChart.getCanvas(); + // click on specific coordinates + await browser + .getActions() + .move({ x: 100, y: 110, origin: el._webElement }) + .click() + .perform(); + + await retry.try(async () => { + await testSubjects.click('applyFiltersPopoverButton'); + await testSubjects.missingOrFail('applyFiltersPopoverButton'); + }); + + const hasMachineRawFilter = await filterBar.hasFilter('machine.os.raw', 'win 7'); + expect(hasMachineRawFilter).to.be(true); + }); + }); }); }); } diff --git a/vars/githubPr.groovy b/vars/githubPr.groovy index a2a3a81f253a0..594d54f2c5b5e 100644 --- a/vars/githubPr.groovy +++ b/vars/githubPr.groovy @@ -64,6 +64,10 @@ def isPr() { return !!(env.ghprbPullId && env.ghprbPullLink && env.ghprbPullLink =~ /\/elastic\/kibana\//) } +def isTrackedBranchPr() { + return isPr() && (env.ghprbTargetBranch == 'master' || env.ghprbTargetBranch == '6.8' || env.ghprbTargetBranch =~ /[7-8]\.[x0-9]+/) +} + def getLatestBuildComment() { return getComments() .reverse() @@ -234,8 +238,10 @@ def getNextCommentMessage(previousCommentInfo = [:], isFinal = false) { messages << getTestFailuresMessage() - if (isFinal) { - messages << ciStats.getMetricsReport() + catchErrors { + if (isFinal && isTrackedBranchPr()) { + messages << ciStats.getMetricsReport() + } } if (info.builds && info.builds.size() > 0) { diff --git a/x-pack/plugins/alerting/server/health/get_health.test.ts b/x-pack/plugins/alerting/server/health/get_health.test.ts index 3c494dac6785b..c31a71138248b 100644 --- a/x-pack/plugins/alerting/server/health/get_health.test.ts +++ b/x-pack/plugins/alerting/server/health/get_health.test.ts @@ -5,9 +5,12 @@ * 2.0. */ -import { savedObjectsRepositoryMock } from '../../../../../src/core/server/mocks'; +import { + savedObjectsRepositoryMock, + savedObjectsServiceMock, +} from '../../../../../src/core/server/mocks'; import { AlertExecutionStatusErrorReasons, HealthStatus } from '../types'; -import { getHealth } from './get_health'; +import { getAlertingHealthStatus, getHealth } from './get_health'; const savedObjectsRepository = savedObjectsRepositoryMock.create(); @@ -221,3 +224,70 @@ describe('getHealth()', () => { }); }); }); + +describe('getAlertingHealthStatus()', () => { + test('return the proper framework state if some of alerts has a decryption error', async () => { + const savedObjects = savedObjectsServiceMock.createStartContract(); + const lastExecutionDateError = new Date().toISOString(); + savedObjectsRepository.find.mockResolvedValueOnce({ + total: 1, + per_page: 1, + page: 1, + saved_objects: [ + { + id: '1', + type: 'alert', + attributes: { + alertTypeId: 'myType', + schedule: { interval: '10s' }, + params: { + bar: true, + }, + createdAt: new Date().toISOString(), + actions: [ + { + group: 'default', + actionRef: 'action_0', + params: { + foo: true, + }, + }, + ], + executionStatus: { + status: 'error', + lastExecutionDate: lastExecutionDateError, + error: { + reason: AlertExecutionStatusErrorReasons.Decrypt, + message: 'Failed decrypt', + }, + }, + }, + score: 1, + references: [ + { + name: 'action_0', + type: 'action', + id: '1', + }, + ], + }, + ], + }); + savedObjectsRepository.find.mockResolvedValue({ + total: 0, + per_page: 10, + page: 1, + saved_objects: [], + }); + const result = await getAlertingHealthStatus( + { ...savedObjects, createInternalRepository: () => savedObjectsRepository }, + 1 + ); + expect(result).toStrictEqual({ + state: { + runs: 2, + health_status: HealthStatus.Warning, + }, + }); + }); +}); diff --git a/x-pack/plugins/alerting/server/health/get_health.ts b/x-pack/plugins/alerting/server/health/get_health.ts index f00e79a0d96ea..4a0266c9b729f 100644 --- a/x-pack/plugins/alerting/server/health/get_health.ts +++ b/x-pack/plugins/alerting/server/health/get_health.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { ISavedObjectsRepository } from 'src/core/server'; +import { ISavedObjectsRepository, SavedObjectsServiceStart } from 'src/core/server'; import { AlertsHealth, HealthStatus, RawAlert, AlertExecutionStatusErrorReasons } from '../types'; export const getHealth = async ( @@ -97,3 +97,16 @@ export const getHealth = async ( return healthStatuses; }; + +export const getAlertingHealthStatus = async ( + savedObjects: SavedObjectsServiceStart, + stateRuns?: number +) => { + const alertingHealthStatus = await getHealth(savedObjects.createInternalRepository(['alert'])); + return { + state: { + runs: (stateRuns || 0) + 1, + health_status: alertingHealthStatus.decryptionHealth.status, + }, + }; +}; diff --git a/x-pack/plugins/alerting/server/health/get_state.test.ts b/x-pack/plugins/alerting/server/health/get_state.test.ts index 7b36bf34377f7..643d966d1fad0 100644 --- a/x-pack/plugins/alerting/server/health/get_state.test.ts +++ b/x-pack/plugins/alerting/server/health/get_state.test.ts @@ -14,6 +14,16 @@ import { } from './get_state'; import { ConcreteTaskInstance, TaskStatus } from '../../../task_manager/server'; import { HealthStatus } from '../types'; +import { loggingSystemMock, savedObjectsServiceMock } from 'src/core/server/mocks'; + +jest.mock('./get_health', () => ({ + getAlertingHealthStatus: jest.fn().mockReturnValue({ + state: { + runs: 0, + health_status: 'warn', + }, + }), +})); const tick = () => new Promise((resolve) => setImmediate(resolve)); @@ -38,6 +48,9 @@ const getHealthCheckTask = (overrides = {}): ConcreteTaskInstance => ({ ...overrides, }); +const logger = loggingSystemMock.create().get(); +const savedObjects = savedObjectsServiceMock.createStartContract(); + describe('getHealthServiceStatusWithRetryAndErrorHandling', () => { beforeEach(() => jest.useFakeTimers()); @@ -47,7 +60,21 @@ describe('getHealthServiceStatusWithRetryAndErrorHandling', () => { const pollInterval = 100; const halfInterval = Math.floor(pollInterval / 2); - getHealthStatusStream(mockTaskManager, pollInterval).subscribe(); + getHealthStatusStream( + mockTaskManager, + logger, + savedObjects, + Promise.resolve({ + healthCheck: { + interval: '5m', + }, + invalidateApiKeysTask: { + interval: '5m', + removalDelay: '1h', + }, + }), + pollInterval + ).subscribe(); // shouldn't fire before poll interval passes // should fire once each poll interval @@ -68,7 +95,22 @@ describe('getHealthServiceStatusWithRetryAndErrorHandling', () => { const pollInterval = 100; const halfInterval = Math.floor(pollInterval / 2); - getHealthStatusStream(mockTaskManager, pollInterval, retryDelay).subscribe(); + getHealthStatusStream( + mockTaskManager, + logger, + savedObjects, + Promise.resolve({ + healthCheck: { + interval: '5m', + }, + invalidateApiKeysTask: { + interval: '5m', + removalDelay: '1h', + }, + }), + pollInterval, + retryDelay + ).subscribe(); jest.advanceTimersByTime(halfInterval); expect(mockTaskManager.get).toHaveBeenCalledTimes(0); @@ -99,7 +141,18 @@ describe('getHealthServiceStatusWithRetryAndErrorHandling', () => { mockTaskManager.get.mockResolvedValue(getHealthCheckTask()); const status = await getHealthServiceStatusWithRetryAndErrorHandling( - mockTaskManager + mockTaskManager, + logger, + savedObjects, + Promise.resolve({ + healthCheck: { + interval: '5m', + }, + invalidateApiKeysTask: { + interval: '5m', + removalDelay: '1h', + }, + }) ).toPromise(); expect(status.level).toEqual(ServiceStatusLevels.available); @@ -118,7 +171,18 @@ describe('getHealthServiceStatusWithRetryAndErrorHandling', () => { ); const status = await getHealthServiceStatusWithRetryAndErrorHandling( - mockTaskManager + mockTaskManager, + logger, + savedObjects, + Promise.resolve({ + healthCheck: { + interval: '5m', + }, + invalidateApiKeysTask: { + interval: '5m', + removalDelay: '1h', + }, + }) ).toPromise(); expect(status.level).toEqual(ServiceStatusLevels.degraded); @@ -137,7 +201,18 @@ describe('getHealthServiceStatusWithRetryAndErrorHandling', () => { ); const status = await getHealthServiceStatusWithRetryAndErrorHandling( - mockTaskManager + mockTaskManager, + logger, + savedObjects, + Promise.resolve({ + healthCheck: { + interval: '5m', + }, + invalidateApiKeysTask: { + interval: '5m', + removalDelay: '1h', + }, + }) ).toPromise(); expect(status.level).toEqual(ServiceStatusLevels.unavailable); @@ -152,12 +227,25 @@ describe('getHealthServiceStatusWithRetryAndErrorHandling', () => { .mockRejectedValueOnce(new Error('Failure')) .mockResolvedValue(getHealthCheckTask()); - getHealthServiceStatusWithRetryAndErrorHandling(mockTaskManager, retryDelay).subscribe( - (status) => { - expect(status.level).toEqual(ServiceStatusLevels.available); - expect(status.summary).toEqual('Alerting framework is available'); - } - ); + getHealthServiceStatusWithRetryAndErrorHandling( + mockTaskManager, + logger, + savedObjects, + Promise.resolve({ + healthCheck: { + interval: '5m', + }, + invalidateApiKeysTask: { + interval: '5m', + removalDelay: '1h', + }, + }), + retryDelay + ).subscribe((status) => { + expect(status.level).toEqual(ServiceStatusLevels.available); + expect(logger.warn).toHaveBeenCalledTimes(1); + expect(status.summary).toEqual('Alerting framework is available'); + }); await tick(); jest.advanceTimersByTime(retryDelay * 2); @@ -169,13 +257,25 @@ describe('getHealthServiceStatusWithRetryAndErrorHandling', () => { const mockTaskManager = taskManagerMock.createStart(); mockTaskManager.get.mockRejectedValue(err); - getHealthServiceStatusWithRetryAndErrorHandling(mockTaskManager, retryDelay).subscribe( - (status) => { - expect(status.level).toEqual(ServiceStatusLevels.unavailable); - expect(status.summary).toEqual('Alerting framework is unavailable'); - expect(status.meta).toEqual({ error: err }); - } - ); + getHealthServiceStatusWithRetryAndErrorHandling( + mockTaskManager, + logger, + savedObjects, + Promise.resolve({ + healthCheck: { + interval: '5m', + }, + invalidateApiKeysTask: { + interval: '5m', + removalDelay: '1h', + }, + }), + retryDelay + ).subscribe((status) => { + expect(status.level).toEqual(ServiceStatusLevels.unavailable); + expect(status.summary).toEqual('Alerting framework is unavailable'); + expect(status.meta).toEqual({ error: err }); + }); for (let i = 0; i < MAX_RETRY_ATTEMPTS + 1; i++) { await tick(); @@ -183,4 +283,34 @@ describe('getHealthServiceStatusWithRetryAndErrorHandling', () => { } expect(mockTaskManager.get).toHaveBeenCalledTimes(MAX_RETRY_ATTEMPTS + 1); }); + + it('should schedule a new health check task if it does not exist without throwing an error', async () => { + const mockTaskManager = taskManagerMock.createStart(); + mockTaskManager.get.mockRejectedValue({ + output: { + statusCode: 404, + message: 'Not Found', + }, + }); + + const status = await getHealthServiceStatusWithRetryAndErrorHandling( + mockTaskManager, + logger, + savedObjects, + Promise.resolve({ + healthCheck: { + interval: '5m', + }, + invalidateApiKeysTask: { + interval: '5m', + removalDelay: '1h', + }, + }) + ).toPromise(); + + expect(mockTaskManager.ensureScheduled).toHaveBeenCalledTimes(1); + expect(status.level).toEqual(ServiceStatusLevels.degraded); + expect(status.summary).toEqual('Alerting framework is degraded'); + expect(status.meta).toBeUndefined(); + }); }); diff --git a/x-pack/plugins/alerting/server/health/get_state.ts b/x-pack/plugins/alerting/server/health/get_state.ts index 5bd80f2c6d29f..30099614ea42b 100644 --- a/x-pack/plugins/alerting/server/health/get_state.ts +++ b/x-pack/plugins/alerting/server/health/get_state.ts @@ -8,27 +8,38 @@ import { i18n } from '@kbn/i18n'; import { defer, of, interval, Observable, throwError, timer } from 'rxjs'; import { catchError, mergeMap, retryWhen, switchMap } from 'rxjs/operators'; -import { ServiceStatus, ServiceStatusLevels } from '../../../../../src/core/server'; +import { + Logger, + SavedObjectsServiceStart, + ServiceStatus, + ServiceStatusLevels, +} from '../../../../../src/core/server'; import { TaskManagerStartContract } from '../../../task_manager/server'; -import { HEALTH_TASK_ID } from './task'; +import { HEALTH_TASK_ID, scheduleAlertingHealthCheck } from './task'; import { HealthStatus } from '../types'; +import { getAlertingHealthStatus } from './get_health'; +import { AlertsConfig } from '../config'; export const MAX_RETRY_ATTEMPTS = 3; const HEALTH_STATUS_INTERVAL = 60000 * 5; // Five minutes const RETRY_DELAY = 5000; // Wait 5 seconds before retrying on errors -async function getLatestTaskState(taskManager: TaskManagerStartContract) { +async function getLatestTaskState( + taskManager: TaskManagerStartContract, + logger: Logger, + savedObjects: SavedObjectsServiceStart, + config: Promise +) { try { - const result = await taskManager.get(HEALTH_TASK_ID); - return result; + return await taskManager.get(HEALTH_TASK_ID); } catch (err) { - const errMessage = err && err.message ? err.message : err.toString(); - if (!errMessage.includes('NotInitialized')) { - throw err; + // if task is not found + if (err?.output?.statusCode === 404) { + await scheduleAlertingHealthCheck(logger, config, taskManager); + return await getAlertingHealthStatus(savedObjects); } + throw err; } - - return null; } const LEVEL_SUMMARY = { @@ -53,13 +64,16 @@ const LEVEL_SUMMARY = { }; const getHealthServiceStatus = async ( - taskManager: TaskManagerStartContract + taskManager: TaskManagerStartContract, + logger: Logger, + savedObjects: SavedObjectsServiceStart, + config: Promise ): Promise> => { - const doc = await getLatestTaskState(taskManager); + const doc = await getLatestTaskState(taskManager, logger, savedObjects, config); const level = - doc?.state?.health_status === HealthStatus.OK + doc.state?.health_status === HealthStatus.OK ? ServiceStatusLevels.available - : doc?.state?.health_status === HealthStatus.Warning + : doc.state?.health_status === HealthStatus.Warning ? ServiceStatusLevels.degraded : ServiceStatusLevels.unavailable; return { @@ -70,9 +84,12 @@ const getHealthServiceStatus = async ( export const getHealthServiceStatusWithRetryAndErrorHandling = ( taskManager: TaskManagerStartContract, + logger: Logger, + savedObjects: SavedObjectsServiceStart, + config: Promise, retryDelay?: number ): Observable> => { - return defer(() => getHealthServiceStatus(taskManager)).pipe( + return defer(() => getHealthServiceStatus(taskManager, logger, savedObjects, config)).pipe( retryWhen((errors) => { return errors.pipe( mergeMap((error, i) => { @@ -85,6 +102,7 @@ export const getHealthServiceStatusWithRetryAndErrorHandling = ( ); }), catchError((error) => { + logger.warn(`Alerting framework is unavailable due to the error: ${error}`); return of({ level: ServiceStatusLevels.unavailable, summary: LEVEL_SUMMARY[ServiceStatusLevels.unavailable.toString()], @@ -96,9 +114,20 @@ export const getHealthServiceStatusWithRetryAndErrorHandling = ( export const getHealthStatusStream = ( taskManager: TaskManagerStartContract, + logger: Logger, + savedObjects: SavedObjectsServiceStart, + config: Promise, healthStatusInterval?: number, retryDelay?: number ): Observable> => interval(healthStatusInterval ?? HEALTH_STATUS_INTERVAL).pipe( - switchMap(() => getHealthServiceStatusWithRetryAndErrorHandling(taskManager, retryDelay)) + switchMap(() => + getHealthServiceStatusWithRetryAndErrorHandling( + taskManager, + logger, + savedObjects, + config, + retryDelay + ) + ) ); diff --git a/x-pack/plugins/alerting/server/health/task.ts b/x-pack/plugins/alerting/server/health/task.ts index a6f1237c43583..999e76fde696e 100644 --- a/x-pack/plugins/alerting/server/health/task.ts +++ b/x-pack/plugins/alerting/server/health/task.ts @@ -14,7 +14,7 @@ import { import { AlertsConfig } from '../config'; import { AlertingPluginsStart } from '../plugin'; import { HealthStatus } from '../types'; -import { getHealth } from './get_health'; +import { getAlertingHealthStatus } from './get_health'; export const HEALTH_TASK_TYPE = 'alerting_health_check'; @@ -71,15 +71,10 @@ export function healthCheckTaskRunner( return { async run() { try { - const alertingHealthStatus = await getHealth( - (await coreStartServices)[0].savedObjects.createInternalRepository(['alert']) + return await getAlertingHealthStatus( + (await coreStartServices)[0].savedObjects, + state.runs ); - return { - state: { - runs: (state.runs || 0) + 1, - health_status: alertingHealthStatus.decryptionHealth.status, - }, - }; } catch (errMsg) { logger.warn(`Error executing alerting health check task: ${errMsg}`); return { diff --git a/x-pack/plugins/alerting/server/plugin.ts b/x-pack/plugins/alerting/server/plugin.ts index 1155cfa93337d..3d3b478c6480c 100644 --- a/x-pack/plugins/alerting/server/plugin.ts +++ b/x-pack/plugins/alerting/server/plugin.ts @@ -220,11 +220,16 @@ export class AlertingPlugin { this.config ); - core.getStartServices().then(async ([, startPlugins]) => { + core.getStartServices().then(async ([coreStart, startPlugins]) => { core.status.set( combineLatest([ core.status.derivedStatus$, - getHealthStatusStream(startPlugins.taskManager), + getHealthStatusStream( + startPlugins.taskManager, + this.logger, + coreStart.savedObjects, + this.config + ), ]).pipe( map(([derivedStatus, healthStatus]) => { if (healthStatus.level > derivedStatus.level) { diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdownMap/__stories__/MapTooltip.stories.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdownMap/__stories__/MapTooltip.stories.tsx index b96fb42c3d1f4..1aad25fc89c0b 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdownMap/__stories__/MapTooltip.stories.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdownMap/__stories__/MapTooltip.stories.tsx @@ -44,6 +44,7 @@ storiesOf('app/RumDashboard/VisitorsRegionMap', module) '__kbnjoin__count__3657625d-17b0-41ef-99ba-3a2b2938655c': 439145, '__kbnjoin__avg_of_transaction.duration.us__3657625d-17b0-41ef-99ba-3a2b2938655c': 2041665.6350131081, }, + actions: [], }, ]} /> diff --git a/x-pack/plugins/apm/server/lib/helpers/input_validation.ts b/x-pack/plugins/apm/server/lib/helpers/input_validation.ts deleted file mode 100644 index 0a34711b9b40d..0000000000000 --- a/x-pack/plugins/apm/server/lib/helpers/input_validation.ts +++ /dev/null @@ -1,23 +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 Joi, { Schema } from 'joi'; -export const dateValidation = Joi.alternatives() - .try(Joi.date().iso(), Joi.number()) - .required(); - -export const withDefaultValidators = ( - validators: { [key: string]: Schema } = {} -) => { - return Joi.object().keys({ - _inspect: Joi.bool(), - start: dateValidation, - end: dateValidation, - uiFilters: Joi.string(), - ...validators, - }); -}; diff --git a/x-pack/plugins/banners/server/config.test.ts b/x-pack/plugins/banners/server/config.test.ts new file mode 100644 index 0000000000000..f080281cf730d --- /dev/null +++ b/x-pack/plugins/banners/server/config.test.ts @@ -0,0 +1,38 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { config } from './config'; +import { getDeprecationsFor } from '../../../../src/core/server/test_utils'; + +function applyDeprecations(settings?: Record) { + 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 ec1c7006a84ce..5ee01ec2b0b19 100644 --- a/x-pack/plugins/banners/server/config.ts +++ b/x-pack/plugins/banners/server/config.ts @@ -46,10 +46,10 @@ export const config: PluginConfigDescriptor = { addDeprecation({ message: 'The `header` value for xpack.banners.placement has been replaced by `top`', }); - pluginConfig.placement = 'top'; + return { + set: [{ path: `${fromPath}.placement`, value: 'top' }], + }; } - - return rootConfig; }, ], }; diff --git a/x-pack/plugins/canvas/public/services/expressions.ts b/x-pack/plugins/canvas/public/services/expressions.ts index fd733862c4b67..35493341e0e88 100644 --- a/x-pack/plugins/canvas/public/services/expressions.ts +++ b/x-pack/plugins/canvas/public/services/expressions.ts @@ -25,8 +25,7 @@ export const expressionsServiceFactory: CanvasServiceFactory if (!cached) { cached = (async () => { const labService = startPlugins.presentationUtil.labsService; - const useDataSearchProject = labService.getProject('labs:canvas:useDataService'); - const hasDataSearch = useDataSearchProject.status.isEnabled; + const hasDataSearch = labService.isProjectEnabled('labs:canvas:useDataService'); const dataSearchFns = ['essql', 'esdocs', 'escount']; const serverFunctionList = await coreSetup.http.get(API_ROUTE_FUNCTIONS); diff --git a/x-pack/plugins/canvas/public/services/stubs/labs.ts b/x-pack/plugins/canvas/public/services/stubs/labs.ts index 7caa1d0139a70..db89c5c35d5fb 100644 --- a/x-pack/plugins/canvas/public/services/stubs/labs.ts +++ b/x-pack/plugins/canvas/public/services/stubs/labs.ts @@ -14,6 +14,7 @@ export const labsService: CanvasLabsService = { getProject: noop, getProjects: noop, getProjectIDs: () => projectIDs, + isProjectEnabled: () => false, isLabsEnabled: () => true, projectIDs, reset: noop, diff --git a/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/follower_indices_list.test.js b/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/follower_indices_list.test.js index c2b414ddbd845..9c881702ba3c6 100644 --- a/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/follower_indices_list.test.js +++ b/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/follower_indices_list.test.js @@ -12,10 +12,11 @@ at createWorker (//node_modules/brace/index.js:17992:5) */ import { stubWebWorker } from '@kbn/test/jest'; // eslint-disable-line no-unused-vars +import { act } from 'react-dom/test-utils'; import { getFollowerIndexMock } from './fixtures/follower_index'; import './mocks'; -import { setupEnvironment, pageHelpers, nextTick, delay, getRandomString } from './helpers'; +import { setupEnvironment, pageHelpers, getRandomString } from './helpers'; const { setup } = pageHelpers.followerIndexList; @@ -53,9 +54,10 @@ describe('', () => { let component; beforeEach(async () => { - ({ exists, component } = setup()); + await act(async () => { + ({ exists, component } = setup()); + }); - await nextTick(); // We need to wait next tick for the mock server response to comes in component.update(); }); @@ -91,15 +93,15 @@ describe('', () => { beforeEach(async () => { httpRequestsMockHelpers.setLoadFollowerIndicesResponse({ indices: followerIndices }); - // Mount the component - ({ component, table, actions, form } = setup()); + await act(async () => { + ({ component, table, actions, form } = setup()); + }); - await nextTick(); // Make sure that the http request is fulfilled component.update(); }); - test('pagination works', () => { - actions.clickPaginationNextButton(); + test('pagination works', async () => { + await actions.clickPaginationNextButton(); const { tableCellsValues } = table.getMetaData('followerIndexListTable'); // Pagination defaults to 20 follower indices per page. We loaded 30 follower indices, @@ -134,21 +136,16 @@ describe('', () => { httpRequestsMockHelpers.setLoadFollowerIndicesResponse({ indices: followerIndices }); // Mount the component - ({ find, exists, component, table, actions } = setup()); + await act(async () => { + ({ find, exists, component, table, actions } = setup()); + }); - await nextTick(); // Make sure that the Http request is fulfilled component.update(); // Read the index list table ({ tableCellsValues } = table.getMetaData('followerIndexListTable')); }); - afterEach(async () => { - // The updates are not all synchronouse - // We need to wait for all the updates to ran before unmounting our component - await delay(100); - }); - test('should not display the empty prompt', () => { expect(exists('emptyPrompt')).toBe(false); }); @@ -173,17 +170,17 @@ describe('', () => { }); describe('action menu', () => { - test('should be visible when a follower index is selected', () => { + test('should be visible when a follower index is selected', async () => { expect(exists('contextMenuButton')).toBe(false); - actions.selectFollowerIndexAt(0); + await actions.selectFollowerIndexAt(0); expect(exists('contextMenuButton')).toBe(true); }); test('should have a "pause", "edit" and "unfollow" action when the follower index is active', async () => { - actions.selectFollowerIndexAt(0); - actions.openContextMenu(); + await actions.selectFollowerIndexAt(0); + await actions.openContextMenu(); const contextMenu = find('contextMenu'); @@ -199,8 +196,8 @@ describe('', () => { }); test('should have a "resume", "edit" and "unfollow" action when the follower index is active', async () => { - actions.selectFollowerIndexAt(1); // Select the second follower that is "paused" - actions.openContextMenu(); + await actions.selectFollowerIndexAt(1); // Select the second follower that is "paused" + await actions.openContextMenu(); const contextMenu = find('contextMenu'); @@ -213,22 +210,22 @@ describe('', () => { ]); }); - test('should open a confirmation modal when clicking on "pause replication"', () => { + test('should open a confirmation modal when clicking on "pause replication"', async () => { expect(exists('pauseReplicationConfirmation')).toBe(false); - actions.selectFollowerIndexAt(0); - actions.openContextMenu(); - actions.clickContextMenuButtonAt(0); // first button is the "pause" action + await actions.selectFollowerIndexAt(0); + await actions.openContextMenu(); + await actions.clickContextMenuButtonAt(0); // first button is the "pause" action expect(exists('pauseReplicationConfirmation')).toBe(true); }); - test('should open a confirmation modal when clicking on "unfollow leader index"', () => { + test('should open a confirmation modal when clicking on "unfollow leader index"', async () => { expect(exists('unfollowLeaderConfirmation')).toBe(false); - actions.selectFollowerIndexAt(0); - actions.openContextMenu(); - actions.clickContextMenuButtonAt(2); // third button is the "unfollow" action + await actions.selectFollowerIndexAt(0); + await actions.openContextMenu(); + await actions.clickContextMenuButtonAt(2); // third button is the "unfollow" action expect(exists('unfollowLeaderConfirmation')).toBe(true); }); @@ -238,13 +235,13 @@ describe('', () => { test('should open a context menu when clicking on the button of each row', async () => { expect(component.find('.euiContextMenuPanel').length).toBe(0); - actions.openTableRowContextMenuAt(0); + await actions.openTableRowContextMenuAt(0); expect(component.find('.euiContextMenuPanel').length).toBe(1); }); test('should have the "pause", "edit" and "unfollow" options in the row context menu', async () => { - actions.openTableRowContextMenuAt(0); + await actions.openTableRowContextMenuAt(0); const buttonLabels = component .find('.euiContextMenuPanel') @@ -260,7 +257,7 @@ describe('', () => { test('should have the "resume", "edit" and "unfollow" options in the row context menu', async () => { // We open the context menu of the second row (index 1) as followerIndices[1].status is "paused" - actions.openTableRowContextMenuAt(1); + await actions.openTableRowContextMenuAt(1); const buttonLabels = component .find('.euiContextMenuPanel') @@ -277,8 +274,13 @@ describe('', () => { test('should open a confirmation modal when clicking on "pause replication"', async () => { expect(exists('pauseReplicationConfirmation')).toBe(false); - actions.openTableRowContextMenuAt(0); - find('pauseButton').simulate('click'); + await actions.openTableRowContextMenuAt(0); + + await act(async () => { + find('pauseButton').simulate('click'); + }); + + component.update(); expect(exists('pauseReplicationConfirmation')).toBe(true); }); @@ -286,51 +288,60 @@ describe('', () => { test('should open a confirmation modal when clicking on "resume"', async () => { expect(exists('resumeReplicationConfirmation')).toBe(false); - actions.openTableRowContextMenuAt(1); // open the second row context menu, as it is a "paused" follower index - find('resumeButton').simulate('click'); + await actions.openTableRowContextMenuAt(1); // open the second row context menu, as it is a "paused" follower index + + await act(async () => { + find('resumeButton').simulate('click'); + }); + + component.update(); expect(exists('resumeReplicationConfirmation')).toBe(true); }); - test('should open a confirmation modal when clicking on "unfollow leader index"', () => { + test('should open a confirmation modal when clicking on "unfollow leader index"', async () => { expect(exists('unfollowLeaderConfirmation')).toBe(false); - actions.openTableRowContextMenuAt(0); - find('unfollowButton').simulate('click'); + await actions.openTableRowContextMenuAt(0); + + await act(async () => { + find('unfollowButton').simulate('click'); + }); + + component.update(); expect(exists('unfollowLeaderConfirmation')).toBe(true); }); }); - // FLAKY: https://github.com/elastic/kibana/issues/75124 - describe.skip('detail panel', () => { - test('should open a detail panel when clicking on a follower index', () => { + describe('detail panel', () => { + test('should open a detail panel when clicking on a follower index', async () => { expect(exists('followerIndexDetail')).toBe(false); - actions.clickFollowerIndexAt(0); + await actions.clickFollowerIndexAt(0); expect(exists('followerIndexDetail')).toBe(true); }); - test('should set the title the index that has been selected', () => { - actions.clickFollowerIndexAt(0); // Open the detail panel + test('should set the title the index that has been selected', async () => { + await actions.clickFollowerIndexAt(0); // Open the detail panel expect(find('followerIndexDetail.title').text()).toEqual(index1.name); }); - test('should indicate the correct "status", "remote cluster" and "leader index"', () => { - actions.clickFollowerIndexAt(0); + test('should indicate the correct "status", "remote cluster" and "leader index"', async () => { + await actions.clickFollowerIndexAt(0); expect(find('followerIndexDetail.status').text()).toEqual(index1.status); expect(find('followerIndexDetail.remoteCluster').text()).toEqual(index1.remoteCluster); expect(find('followerIndexDetail.leaderIndex').text()).toEqual(index1.leaderIndex); }); - test('should have a "settings" section', () => { - actions.clickFollowerIndexAt(0); + test('should have a "settings" section', async () => { + await actions.clickFollowerIndexAt(0); expect(find('followerIndexDetail.settingsSection').find('h3').text()).toEqual('Settings'); expect(exists('followerIndexDetail.settingsValues')).toBe(true); }); - test('should set the correct follower index settings values', () => { + test('should set the correct follower index settings values', async () => { const mapSettingsToFollowerIndexProp = { maxReadReqOpCount: 'maxReadRequestOperationCount', maxOutstandingReadReq: 'maxOutstandingReadRequests', @@ -344,7 +355,7 @@ describe('', () => { readPollTimeout: 'readPollTimeout', }; - actions.clickFollowerIndexAt(0); + await actions.clickFollowerIndexAt(0); Object.entries(mapSettingsToFollowerIndexProp).forEach(([setting, prop]) => { const wrapper = find(`settingsValues.${setting}`); @@ -357,21 +368,21 @@ describe('', () => { }); }); - test('should not have settings values for a "paused" follower index', () => { - actions.clickFollowerIndexAt(1); // the second follower index is paused + test('should not have settings values for a "paused" follower index', async () => { + await actions.clickFollowerIndexAt(1); // the second follower index is paused expect(exists('followerIndexDetail.settingsValues')).toBe(false); expect(find('followerIndexDetail.settingsSection').text()).toContain( 'paused follower index does not have settings' ); }); - test('should have a section to render the follower index shards stats', () => { - actions.clickFollowerIndexAt(0); + test('should have a section to render the follower index shards stats', async () => { + await actions.clickFollowerIndexAt(0); expect(exists('followerIndexDetail.shardsStatsSection')).toBe(true); }); - test('should render a EuiCodeEditor for each shards stats', () => { - actions.clickFollowerIndexAt(0); + test('should render a EuiCodeEditor for each shards stats', async () => { + await actions.clickFollowerIndexAt(0); const codeEditors = component.find(`EuiCodeEditor`); diff --git a/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/helpers/follower_index_list.helpers.js b/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/helpers/follower_index_list.helpers.js index d1fd205c683c0..5e6a48413b18b 100644 --- a/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/helpers/follower_index_list.helpers.js +++ b/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/helpers/follower_index_list.helpers.js @@ -4,6 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ +import { act } from 'react-dom/test-utils'; import { registerTestBed, findTestSubject } from '@kbn/test/jest'; import { FollowerIndicesList } from '../../../app/sections/home/follower_indices_list'; @@ -37,24 +38,44 @@ export const setup = (props) => { * User Actions */ - const selectFollowerIndexAt = (index = 0) => { - const { rows } = testBed.table.getMetaData(EUI_TABLE); + const selectFollowerIndexAt = async (index = 0) => { + const { table, component } = testBed; + const { rows } = table.getMetaData(EUI_TABLE); const row = rows[index]; const checkBox = row.reactWrapper.find('input').hostNodes(); - checkBox.simulate('change', { target: { checked: true } }); + + await act(async () => { + checkBox.simulate('change', { target: { checked: true } }); + }); + + component.update(); }; - const openContextMenu = () => { - testBed.find('contextMenuButton').simulate('click'); + const openContextMenu = async () => { + const { find, component } = testBed; + + await act(async () => { + find('contextMenuButton').simulate('click'); + }); + + component.update(); }; - const clickContextMenuButtonAt = (index = 0) => { - const contextMenu = testBed.find('contextMenu'); - contextMenu.find('button').at(index).simulate('click'); + const clickContextMenuButtonAt = async (index = 0) => { + const { find, component } = testBed; + + const contextMenu = find('contextMenu'); + + await act(async () => { + contextMenu.find('button').at(index).simulate('click'); + }); + + component.update(); }; - const openTableRowContextMenuAt = (index = 0) => { - const { rows } = testBed.table.getMetaData(EUI_TABLE); + const openTableRowContextMenuAt = async (index = 0) => { + const { table, component } = testBed; + const { rows } = table.getMetaData(EUI_TABLE); const actionsColumnIndex = rows[0].columns.length - 1; // Actions are in the last column const actionsTableCell = rows[index].columns[actionsColumnIndex]; const button = actionsTableCell.reactWrapper.find('button'); @@ -63,17 +84,34 @@ export const setup = (props) => { `No button to open context menu were found on Follower index list table row ${index}` ); } - button.simulate('click'); + + await act(async () => { + button.simulate('click'); + }); + + component.update(); }; - const clickFollowerIndexAt = (index = 0) => { - const { rows } = testBed.table.getMetaData(EUI_TABLE); + const clickFollowerIndexAt = async (index = 0) => { + const { table, component } = testBed; + const { rows } = table.getMetaData(EUI_TABLE); const followerIndexLink = findTestSubject(rows[index].reactWrapper, 'followerIndexLink'); - followerIndexLink.simulate('click'); + + await act(async () => { + followerIndexLink.simulate('click'); + }); + + component.update(); }; - const clickPaginationNextButton = () => { - testBed.find('followerIndexListTable.pagination-button-next').simulate('click'); + const clickPaginationNextButton = async () => { + const { find, component } = testBed; + + await act(async () => { + find('followerIndexListTable.pagination-button-next').simulate('click'); + }); + + component.update(); }; return { diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/tables/utils.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/tables/utils.ts index d9f41f8558b78..13cad349d75b0 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/tables/utils.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/tables/utils.ts @@ -10,7 +10,7 @@ import { EngineDetails } from '../../../engine/types'; export const getConflictingEnginesFromConflictingField = ( conflictingField: SchemaConflictFieldTypes -): string[] => Object.values(conflictingField).flat(); +) => Object.values(conflictingField).flat() as string[]; export const getConflictingEnginesFromSchemaConflicts = ( schemaConflicts: SchemaConflicts diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/constants.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/constants.ts index b9c4e2c0454d5..0ee428e87873e 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/constants.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/constants.ts @@ -10,3 +10,18 @@ import { i18n } from '@kbn/i18n'; export const SCHEMA_TITLE = i18n.translate('xpack.enterpriseSearch.appSearch.engine.schema.title', { defaultMessage: 'Schema', }); + +export const ADD_SCHEMA_ERROR = (fieldName: string) => + i18n.translate('xpack.enterpriseSearch.appSearch.engine.schema.addSchemaErrorMessage', { + defaultMessage: 'Field name already exists: {fieldName}', + values: { fieldName }, + }); +export const ADD_SCHEMA_SUCCESS = (fieldName: string) => + i18n.translate('xpack.enterpriseSearch.appSearch.engine.schema.addSchemaSuccessMessage', { + defaultMessage: 'New field added: {fieldName}', + values: { fieldName }, + }); +export const UPDATE_SCHEMA_SUCCESS = i18n.translate( + 'xpack.enterpriseSearch.appSearch.engine.schema.updateSchemaSuccessMessage', + { defaultMessage: 'Schema updated' } +); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/schema_base_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/schema_base_logic.test.ts new file mode 100644 index 0000000000000..e5dbf97b971d9 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/schema_base_logic.test.ts @@ -0,0 +1,98 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { LogicMounter, mockFlashMessageHelpers, mockHttpValues } from '../../../__mocks__'; +import '../../__mocks__/engine_logic.mock'; + +import { nextTick } from '@kbn/test/jest'; + +import { SchemaType } from '../../../shared/schema/types'; + +import { SchemaBaseLogic } from './schema_base_logic'; + +describe('SchemaBaseLogic', () => { + const { mount } = new LogicMounter(SchemaBaseLogic); + const { http } = mockHttpValues; + const { flashAPIErrors } = mockFlashMessageHelpers; + + const MOCK_SCHEMA = { + some_text_field: SchemaType.Text, + some_number_field: SchemaType.Number, + }; + const MOCK_RESPONSE = { + schema: MOCK_SCHEMA, + } as any; + + const DEFAULT_VALUES = { + dataLoading: true, + schema: {}, + }; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('has expected default values', () => { + mount(); + expect(SchemaBaseLogic.values).toEqual(DEFAULT_VALUES); + }); + + describe('actions', () => { + describe('onSchemaLoad', () => { + it('stores schema state and sets dataLoading to false', () => { + mount({ schema: {}, dataLoading: true }); + + SchemaBaseLogic.actions.onSchemaLoad(MOCK_RESPONSE); + + expect(SchemaBaseLogic.values).toEqual({ + ...DEFAULT_VALUES, + dataLoading: false, + schema: MOCK_SCHEMA, + }); + }); + }); + + describe('setSchema', () => { + it('updates schema state', () => { + mount({ schema: {} }); + + SchemaBaseLogic.actions.setSchema(MOCK_SCHEMA); + + expect(SchemaBaseLogic.values).toEqual({ + ...DEFAULT_VALUES, + schema: MOCK_SCHEMA, + }); + }); + }); + }); + + describe('listeners', () => { + describe('loadSchema', () => { + it('should make an API call and then set schema state', async () => { + http.get.mockReturnValueOnce(Promise.resolve(MOCK_RESPONSE)); + mount(); + jest.spyOn(SchemaBaseLogic.actions, 'onSchemaLoad'); + + SchemaBaseLogic.actions.loadSchema(); + await nextTick(); + + expect(http.get).toHaveBeenCalledWith('/api/app_search/engines/some-engine/schema'); + expect(SchemaBaseLogic.actions.onSchemaLoad).toHaveBeenCalledWith(MOCK_RESPONSE); + }); + + it('handles errors', async () => { + http.get.mockReturnValueOnce(Promise.reject('error')); + mount(); + + SchemaBaseLogic.actions.loadSchema(); + await nextTick(); + + expect(flashAPIErrors).toHaveBeenCalledWith('error'); + }); + }); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/schema_base_logic.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/schema_base_logic.ts new file mode 100644 index 0000000000000..c2196c01d402b --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/schema_base_logic.ts @@ -0,0 +1,66 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { kea, MakeLogicType } from 'kea'; + +import { flashAPIErrors } from '../../../shared/flash_messages'; +import { HttpLogic } from '../../../shared/http'; +import { Schema } from '../../../shared/schema/types'; +import { EngineLogic } from '../engine'; + +import { SchemaApiResponse, MetaEngineSchemaApiResponse } from './types'; + +export interface SchemaBaseValues { + dataLoading: boolean; + schema: Schema; +} + +export interface SchemaBaseActions { + loadSchema(): void; + onSchemaLoad( + response: SchemaApiResponse | MetaEngineSchemaApiResponse + ): SchemaApiResponse | MetaEngineSchemaApiResponse; + setSchema(schema: Schema): { schema: Schema }; +} + +export const SchemaBaseLogic = kea>({ + path: ['enterprise_search', 'app_search', 'schema_base_logic'], + actions: { + loadSchema: true, + onSchemaLoad: (response) => response, + setSchema: (schema) => ({ schema }), + }, + reducers: { + dataLoading: [ + true, + { + loadSchema: () => true, + onSchemaLoad: () => false, + }, + ], + schema: [ + {}, + { + onSchemaLoad: (_, { schema }) => schema, + setSchema: (_, { schema }) => schema, + }, + ], + }, + listeners: ({ actions }) => ({ + loadSchema: async () => { + const { http } = HttpLogic.values; + const { engineName } = EngineLogic.values; + + try { + const response = await http.get(`/api/app_search/engines/${engineName}/schema`); + actions.onSchemaLoad(response); + } catch (e) { + flashAPIErrors(e); + } + }, + }), +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/schema_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/schema_logic.test.ts new file mode 100644 index 0000000000000..123f62af4eeba --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/schema_logic.test.ts @@ -0,0 +1,291 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { LogicMounter, mockFlashMessageHelpers, mockHttpValues } from '../../../__mocks__'; +import { mockEngineActions } from '../../__mocks__/engine_logic.mock'; + +import { nextTick } from '@kbn/test/jest'; + +import { SchemaType, Schema } from '../../../shared/schema/types'; + +import { SchemaLogic } from './schema_logic'; + +describe('SchemaLogic', () => { + const { mount } = new LogicMounter(SchemaLogic); + const { http } = mockHttpValues; + const { flashAPIErrors, flashSuccessToast, setErrorMessage } = mockFlashMessageHelpers; + + const MOCK_RESPONSE = { + schema: { + some_text_field: SchemaType.Text, + some_number_field: SchemaType.Number, + }, + mostRecentIndexJob: { + percentageComplete: 100, + numDocumentsWithErrors: 10, + activeReindexJobId: 'some-id', + isActive: false, + hasErrors: true, + }, + unconfirmedFields: ['some_field'], + unsearchedUnconfirmedFields: true, + }; + + const DEFAULT_VALUES = { + dataLoading: true, + schema: {}, + isUpdating: false, + hasSchema: false, + hasSchemaChanged: false, + cachedSchema: {}, + mostRecentIndexJob: {}, + unconfirmedFields: [], + hasUnconfirmedFields: false, + hasNewUnsearchedFields: false, + isModalOpen: false, + }; + + /* + * Unfortunately, we can't mount({ schema: ... }) & have to use an action to set schema + * because of the separate connected logic file - our LogicMounter test helper sets context + * for only the currently tested file + */ + const mountAndSetSchema = ({ schema, ...values }: { schema: Schema; [key: string]: unknown }) => { + mount(values); + SchemaLogic.actions.setSchema(schema); + }; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('has expected default values', () => { + mount(); + expect(SchemaLogic.values).toEqual(DEFAULT_VALUES); + }); + + describe('actions', () => { + describe('onSchemaLoad', () => { + it('stores the API response in state and sets isUpdating & isModalOpen to false', () => { + mount({ isModalOpen: true }); + + SchemaLogic.actions.onSchemaLoad(MOCK_RESPONSE); + + expect(SchemaLogic.values).toEqual({ + ...DEFAULT_VALUES, + // SchemaBaseLogic + dataLoading: false, + schema: MOCK_RESPONSE.schema, + // SchemaLogic + isUpdating: false, + isModalOpen: false, + cachedSchema: MOCK_RESPONSE.schema, + hasSchema: true, + mostRecentIndexJob: MOCK_RESPONSE.mostRecentIndexJob, + unconfirmedFields: MOCK_RESPONSE.unconfirmedFields, + hasUnconfirmedFields: true, + hasNewUnsearchedFields: MOCK_RESPONSE.unsearchedUnconfirmedFields, + }); + }); + }); + + describe('onSchemaUpdateError', () => { + it('sets isUpdating & isModalOpen to false', () => { + mount({ isUpdating: true, isModalOpen: true }); + + SchemaLogic.actions.onSchemaUpdateError(); + + expect(SchemaLogic.values).toEqual({ + ...DEFAULT_VALUES, + isUpdating: false, + isModalOpen: false, + }); + }); + }); + + describe('openModal', () => { + it('sets isModalOpen to true', () => { + mount({ isModalOpen: false }); + + SchemaLogic.actions.openModal(); + + expect(SchemaLogic.values).toEqual({ + ...DEFAULT_VALUES, + isModalOpen: true, + }); + }); + }); + + describe('closeModal', () => { + it('sets isModalOpen to false', () => { + mount({ isModalOpen: true }); + + SchemaLogic.actions.closeModal(); + + expect(SchemaLogic.values).toEqual({ + ...DEFAULT_VALUES, + isModalOpen: false, + }); + }); + }); + }); + + describe('selectors', () => { + describe('hasSchema', () => { + it('returns true when the schema obj has items', () => { + mountAndSetSchema({ schema: { test: SchemaType.Text } }); + expect(SchemaLogic.values.hasSchema).toEqual(true); + }); + + it('returns false when the schema obj is empty', () => { + mountAndSetSchema({ schema: {} }); + expect(SchemaLogic.values.hasSchema).toEqual(false); + }); + }); + + describe('hasSchemaChanged', () => { + it('returns true when the schema state is different from the cachedSchema state', () => { + mountAndSetSchema({ + schema: { test: SchemaType.Text }, + cachedSchema: { test: SchemaType.Number }, + }); + + expect(SchemaLogic.values.hasSchemaChanged).toEqual(true); + }); + + it('returns false when the stored schema is the same as cachedSchema', () => { + mountAndSetSchema({ + schema: { test: SchemaType.Text }, + cachedSchema: { test: SchemaType.Text }, + }); + + expect(SchemaLogic.values.hasSchemaChanged).toEqual(false); + }); + }); + + describe('hasUnconfirmedFields', () => { + it('returns true when the unconfirmedFields array has items', () => { + mount({ unconfirmedFields: ['hello_world'] }); + expect(SchemaLogic.values.hasUnconfirmedFields).toEqual(true); + }); + + it('returns false when the unconfirmedFields array is empty', () => { + mount({ unconfirmedFields: [] }); + expect(SchemaLogic.values.hasUnconfirmedFields).toEqual(false); + }); + }); + }); + + describe('listeners', () => { + describe('addSchemaField', () => { + describe('if the schema field already exists', () => { + it('flashes an error and closes the modal', () => { + mountAndSetSchema({ schema: { existing_field: SchemaType.Text } }); + jest.spyOn(SchemaLogic.actions, 'closeModal'); + + SchemaLogic.actions.addSchemaField('existing_field', SchemaType.Text); + + expect(setErrorMessage).toHaveBeenCalledWith('Field name already exists: existing_field'); + expect(SchemaLogic.actions.closeModal).toHaveBeenCalled(); + }); + }); + + describe('if the schema field does not already exist', () => { + it('updates the schema state and calls updateSchema with a custom success message', () => { + mount(); + jest.spyOn(SchemaLogic.actions, 'setSchema'); + jest.spyOn(SchemaLogic.actions, 'updateSchema'); + + SchemaLogic.actions.addSchemaField('new_field', SchemaType.Date); + + expect(SchemaLogic.actions.setSchema).toHaveBeenCalledWith({ + new_field: SchemaType.Date, + }); + expect(SchemaLogic.actions.updateSchema).toHaveBeenCalledWith( + 'New field added: new_field' + ); + }); + }); + }); + + describe('updateSchemaFieldType', () => { + it("updates an existing schema key's field type value", async () => { + mountAndSetSchema({ schema: { existing_field: SchemaType.Text } }); + jest.spyOn(SchemaLogic.actions, 'setSchema'); + + SchemaLogic.actions.updateSchemaFieldType('existing_field', SchemaType.Geolocation); + + expect(SchemaLogic.actions.setSchema).toHaveBeenCalledWith({ + existing_field: SchemaType.Geolocation, + }); + }); + }); + + describe('updateSchema', () => { + it('sets isUpdating to true', () => { + mount({ isUpdating: false }); + + SchemaLogic.actions.updateSchema(); + + expect(SchemaLogic.values).toEqual({ + ...DEFAULT_VALUES, + isUpdating: true, + }); + }); + + it('should make an API call and then set schema state', async () => { + http.post.mockReturnValueOnce(Promise.resolve(MOCK_RESPONSE)); + mount(); + jest.spyOn(SchemaLogic.actions, 'onSchemaLoad'); + + SchemaLogic.actions.updateSchema(); + await nextTick(); + + expect(http.post).toHaveBeenCalledWith('/api/app_search/engines/some-engine/schema', { + body: '{}', + }); + expect(SchemaLogic.actions.onSchemaLoad).toHaveBeenCalledWith(MOCK_RESPONSE); + }); + + it('should call flashSuccessToast with a custom success message if passed', async () => { + http.post.mockReturnValueOnce(Promise.resolve(MOCK_RESPONSE)); + mount(); + + SchemaLogic.actions.updateSchema('wow it worked!!'); + await nextTick(); + + expect(flashSuccessToast).toHaveBeenCalledWith('wow it worked!!'); + }); + + it('should always call EngineLogic.actions.initializeEngine to refresh engine-wide state', async () => { + mount(); + + SchemaLogic.actions.updateSchema(); + await nextTick(); + + expect(mockEngineActions.initializeEngine).toHaveBeenCalled(); + }); + + it('handles errors and resets bad schema state back to cached/server values', async () => { + const MOCK_ERROR = 'Fields cannot contain more than 64 characters'; + const MOCK_CACHED_SCHEMA = { ok_field: SchemaType.Text }; + + http.post.mockReturnValueOnce(Promise.reject(MOCK_ERROR)); + mount({ cachedSchema: MOCK_CACHED_SCHEMA }); + jest.spyOn(SchemaLogic.actions, 'onSchemaUpdateError'); + jest.spyOn(SchemaLogic.actions, 'setSchema'); + + SchemaLogic.actions.updateSchema(); + await nextTick(); + + expect(flashAPIErrors).toHaveBeenCalledWith(MOCK_ERROR); + expect(SchemaLogic.actions.onSchemaUpdateError).toHaveBeenCalled(); + expect(SchemaLogic.actions.setSchema).toHaveBeenCalledWith(MOCK_CACHED_SCHEMA); + }); + }); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/schema_logic.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/schema_logic.ts new file mode 100644 index 0000000000000..3215a46c8e299 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/schema_logic.ts @@ -0,0 +1,164 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { kea, MakeLogicType } from 'kea'; +import { isEqual } from 'lodash'; + +import { + flashAPIErrors, + setErrorMessage, + flashSuccessToast, + clearFlashMessages, +} from '../../../shared/flash_messages'; +import { HttpLogic } from '../../../shared/http'; +import { Schema, SchemaType, IndexJob } from '../../../shared/schema/types'; +import { EngineLogic } from '../engine'; + +import { ADD_SCHEMA_ERROR, ADD_SCHEMA_SUCCESS, UPDATE_SCHEMA_SUCCESS } from './constants'; +import { SchemaBaseLogic, SchemaBaseValues, SchemaBaseActions } from './schema_base_logic'; +import { SchemaApiResponse } from './types'; + +interface SchemaValues extends SchemaBaseValues { + isUpdating: boolean; + hasSchema: boolean; + hasSchemaChanged: boolean; + cachedSchema: Schema; + mostRecentIndexJob: Partial; + unconfirmedFields: string[]; + hasUnconfirmedFields: boolean; + hasNewUnsearchedFields: boolean; + isModalOpen: boolean; +} + +interface SchemaActions extends SchemaBaseActions { + onSchemaLoad(response: SchemaApiResponse): SchemaApiResponse; + addSchemaField( + fieldName: string, + fieldType: SchemaType + ): { fieldName: string; fieldType: SchemaType }; + updateSchemaFieldType( + fieldName: string, + fieldType: SchemaType + ): { fieldName: string; fieldType: SchemaType }; + updateSchema(successMessage?: string): string | undefined; + onSchemaUpdateError(): void; + openModal(): void; + closeModal(): void; +} + +export const SchemaLogic = kea>({ + path: ['enterprise_search', 'app_search', 'schema_logic'], + connect: { + values: [SchemaBaseLogic, ['dataLoading', 'schema']], + actions: [SchemaBaseLogic, ['loadSchema', 'onSchemaLoad', 'setSchema']], + }, + actions: { + addSchemaField: (fieldName, fieldType) => ({ fieldName, fieldType }), + updateSchemaFieldType: (fieldName, fieldType) => ({ fieldName, fieldType }), + updateSchema: (successMessage) => successMessage, + onSchemaUpdateError: true, + openModal: true, + closeModal: true, + }, + reducers: { + isUpdating: [ + false, + { + updateSchema: () => true, + onSchemaLoad: () => false, + onSchemaUpdateError: () => false, + }, + ], + cachedSchema: [ + {}, + { + onSchemaLoad: (_, { schema }) => schema, + }, + ], + mostRecentIndexJob: [ + {}, + { + onSchemaLoad: (_, { mostRecentIndexJob }) => mostRecentIndexJob, + }, + ], + unconfirmedFields: [ + [], + { + onSchemaLoad: (_, { unconfirmedFields }) => unconfirmedFields, + }, + ], + hasNewUnsearchedFields: [ + false, + { + onSchemaLoad: (_, { unsearchedUnconfirmedFields }) => unsearchedUnconfirmedFields, + }, + ], + isModalOpen: [ + false, + { + openModal: () => true, + closeModal: () => false, + onSchemaLoad: () => false, + onSchemaUpdateError: () => false, + }, + ], + }, + selectors: { + hasSchema: [(selectors) => [selectors.schema], (schema) => Object.keys(schema).length > 0], + hasSchemaChanged: [ + (selectors) => [selectors.schema, selectors.cachedSchema], + (schema, cachedSchema) => !isEqual(schema, cachedSchema), + ], + hasUnconfirmedFields: [ + (selectors) => [selectors.unconfirmedFields], + (unconfirmedFields) => unconfirmedFields.length > 0, + ], + }, + listeners: ({ actions, values }) => ({ + addSchemaField: ({ fieldName, fieldType }) => { + if (values.schema.hasOwnProperty(fieldName)) { + setErrorMessage(ADD_SCHEMA_ERROR(fieldName)); + actions.closeModal(); + } else { + const updatedSchema = { ...values.schema }; + updatedSchema[fieldName] = fieldType; + actions.setSchema(updatedSchema); + actions.updateSchema(ADD_SCHEMA_SUCCESS(fieldName)); + } + }, + updateSchemaFieldType: ({ fieldName, fieldType }) => { + const updatedSchema = { ...values.schema }; + updatedSchema[fieldName] = fieldType; + actions.setSchema(updatedSchema); + }, + updateSchema: async (successMessage) => { + const { schema } = values; + const { http } = HttpLogic.values; + const { engineName } = EngineLogic.values; + + clearFlashMessages(); + + try { + const response = await http.post(`/api/app_search/engines/${engineName}/schema`, { + body: JSON.stringify(schema), + }); + actions.onSchemaLoad(response); + flashSuccessToast(successMessage || UPDATE_SCHEMA_SUCCESS); + } catch (e) { + flashAPIErrors(e); + actions.onSchemaUpdateError(); + // Restore updated schema back to server/cached schema, so we don't keep + // erroneous or bad fields in-state + actions.setSchema(values.cachedSchema); + } finally { + // Re-fetch engine data so that other views also dynamically update + // (e.g. Documents results, nav icons for invalid boosts or unconfirmed flags) + EngineLogic.actions.initializeEngine(); + } + }, + }), +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/schema_meta_engine_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/schema_meta_engine_logic.test.ts new file mode 100644 index 0000000000000..f265fb2d74113 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/schema_meta_engine_logic.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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { LogicMounter } from '../../../__mocks__'; + +import { SchemaType } from '../../../shared/schema/types'; + +import { MetaEngineSchemaLogic } from './schema_meta_engine_logic'; + +describe('MetaEngineSchemaLogic', () => { + const { mount } = new LogicMounter(MetaEngineSchemaLogic); + + const MOCK_RESPONSE = { + schema: { + some_text_field: SchemaType.Text, + some_number_field: SchemaType.Number, + }, + fields: { + some_text_field: { + text: ['source-engine-a', 'source-engine-b'], + }, + }, + conflictingFields: { + some_number_field: { + number: ['source-engine-a'], + text: ['source-engine-b'], + }, + }, + }; + + const DEFAULT_VALUES = { + dataLoading: true, + schema: {}, + fields: {}, + conflictingFields: {}, + conflictingFieldsCount: 0, + hasConflicts: false, + }; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('has expected default values', () => { + mount(); + expect(MetaEngineSchemaLogic.values).toEqual(DEFAULT_VALUES); + }); + + describe('actions', () => { + describe('onSchemaLoad', () => { + it('stores the API response in state', () => { + mount(); + + MetaEngineSchemaLogic.actions.onSchemaLoad(MOCK_RESPONSE); + + expect(MetaEngineSchemaLogic.values).toEqual({ + ...DEFAULT_VALUES, + // SchemaBaseLogic + dataLoading: false, + schema: MOCK_RESPONSE.schema, + // MetaEngineSchemaLogic + fields: MOCK_RESPONSE.fields, + conflictingFields: MOCK_RESPONSE.conflictingFields, + hasConflicts: true, + conflictingFieldsCount: 1, + }); + }); + }); + }); + + describe('selectors', () => { + describe('conflictingFieldsCount', () => { + it('returns the number of conflicting fields', () => { + mount({ conflictingFields: { field_a: {}, field_b: {} } }); + expect(MetaEngineSchemaLogic.values.conflictingFieldsCount).toEqual(2); + }); + }); + + describe('hasConflictingFields', () => { + it('returns true when the conflictingFields obj has items', () => { + mount({ conflictingFields: { field_c: {} } }); + expect(MetaEngineSchemaLogic.values.hasConflicts).toEqual(true); + }); + + it('returns false when the conflictingFields obj is empty', () => { + mount({ conflictingFields: {} }); + expect(MetaEngineSchemaLogic.values.hasConflicts).toEqual(false); + }); + }); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/schema_meta_engine_logic.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/schema_meta_engine_logic.ts new file mode 100644 index 0000000000000..5c8ab0f4662e2 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/schema_meta_engine_logic.ts @@ -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 { kea, MakeLogicType } from 'kea'; + +import { SchemaBaseLogic, SchemaBaseValues, SchemaBaseActions } from './schema_base_logic'; +import { MetaEngineSchemaApiResponse } from './types'; + +interface MetaEngineSchemaValues extends SchemaBaseValues { + fields: MetaEngineSchemaApiResponse['fields']; + conflictingFields: MetaEngineSchemaApiResponse['conflictingFields']; + conflictingFieldsCount: number; + hasConflicts: boolean; +} + +interface MetaEngineSchemaActions extends SchemaBaseActions { + onSchemaLoad(response: MetaEngineSchemaApiResponse): MetaEngineSchemaApiResponse; +} + +export const MetaEngineSchemaLogic = kea< + MakeLogicType +>({ + path: ['enterprise_search', 'app_search', 'meta_engine_schema_logic'], + connect: { + values: [SchemaBaseLogic, ['dataLoading', 'schema']], + actions: [SchemaBaseLogic, ['loadSchema', 'onSchemaLoad']], + }, + reducers: { + fields: [ + {}, + { + onSchemaLoad: (_, { fields }) => fields, + }, + ], + conflictingFields: [ + {}, + { + onSchemaLoad: (_, { conflictingFields }) => conflictingFields, + }, + ], + }, + selectors: { + conflictingFieldsCount: [ + (selectors) => [selectors.conflictingFields], + (conflictingFields) => Object.keys(conflictingFields).length, + ], + hasConflicts: [ + (selectors) => [selectors.conflictingFieldsCount], + (conflictingFieldsCount) => conflictingFieldsCount > 0, + ], + }, +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/types.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/types.ts new file mode 100644 index 0000000000000..9d79909e5549b --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/types.ts @@ -0,0 +1,30 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { + Schema, + IndexJob, + SchemaConflicts, + FieldCoercionErrors, +} from '../../../shared/schema/types'; + +export interface SchemaApiResponse { + schema: Schema; + mostRecentIndexJob: IndexJob; + unconfirmedFields: string[]; + unsearchedUnconfirmedFields: boolean; +} + +export interface MetaEngineSchemaApiResponse { + schema: Schema; + fields: SchemaConflicts; + conflictingFields: SchemaConflicts; +} + +export interface ReindexJobApiResponse { + fieldCoercionErrors: FieldCoercionErrors; +} 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 index 8412af6455285..a6e9eef8efa70 100644 --- 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 @@ -5,15 +5,29 @@ * 2.0. */ +import { setMockValues, setMockActions } from '../../../../__mocks__'; +import '../../../../__mocks__/shallow_useeffect.mock'; + import React from 'react'; import { shallow } from 'enzyme'; +import { Loading } from '../../../../shared/loading'; + import { MetaEngineSchema } from './'; describe('MetaEngineSchema', () => { + const values = { + dataLoading: false, + }; + const actions = { + loadSchema: jest.fn(), + }; + beforeEach(() => { jest.clearAllMocks(); + setMockValues(values); + setMockActions(actions); }); it('renders', () => { @@ -22,4 +36,17 @@ describe('MetaEngineSchema', () => { expect(wrapper.isEmptyRender()).toBe(false); // TODO: Check for schema components }); + + it('calls loadSchema on mount', () => { + shallow(); + + expect(actions.loadSchema).toHaveBeenCalled(); + }); + + it('renders a loading state', () => { + setMockValues({ ...values, dataLoading: true }); + const wrapper = shallow(); + + expect(wrapper.find(Loading)).toHaveLength(1); + }); }); 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 index d79ddae3d9b78..234fcdb5a5a50 100644 --- 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 @@ -5,14 +5,28 @@ * 2.0. */ -import React from 'react'; +import React, { useEffect } from 'react'; + +import { useValues, useActions } from 'kea'; import { EuiPageHeader, EuiPageContentBody } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FlashMessages } from '../../../../shared/flash_messages'; +import { Loading } from '../../../../shared/loading'; + +import { MetaEngineSchemaLogic } from '../schema_meta_engine_logic'; export const MetaEngineSchema: React.FC = () => { + const { loadSchema } = useActions(MetaEngineSchemaLogic); + const { dataLoading } = useValues(MetaEngineSchemaLogic); + + useEffect(() => { + loadSchema(); + }, []); + + if (dataLoading) return ; + return ( <> { + const values = { + dataLoading: false, + }; + const actions = { + loadSchema: jest.fn(), + }; + beforeEach(() => { jest.clearAllMocks(); + setMockValues(values); + setMockActions(actions); }); it('renders', () => { @@ -25,6 +39,19 @@ describe('Schema', () => { // TODO: Check for schema components }); + it('calls loadSchema on mount', () => { + shallow(); + + expect(actions.loadSchema).toHaveBeenCalled(); + }); + + it('renders a loading state', () => { + setMockValues({ ...values, dataLoading: true }); + const wrapper = shallow(); + + expect(wrapper.find(Loading)).toHaveLength(1); + }); + it('renders page action buttons', () => { const wrapper = shallow() .find(EuiPageHeader) 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 index ad53fd2c718b2..21dd52b04f4a7 100644 --- 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 @@ -5,14 +5,28 @@ * 2.0. */ -import React from 'react'; +import React, { useEffect } from 'react'; + +import { useValues, useActions } from 'kea'; import { EuiPageHeader, EuiButton, EuiPageContentBody } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FlashMessages } from '../../../../shared/flash_messages'; +import { Loading } from '../../../../shared/loading'; + +import { SchemaLogic } from '../schema_logic'; export const Schema: React.FC = () => { + const { loadSchema } = useActions(SchemaLogic); + const { dataLoading } = useValues(SchemaLogic); + + useEffect(() => { + loadSchema(); + }, []); + + if (dataLoading) return ; + return ( <> ({ + generatePreviewUrl: jest.fn(), +})); + +import { setMockValues, setMockActions } from '../../../../__mocks__'; + +import React from 'react'; + +import { shallow } from 'enzyme'; + +import { ActiveField } from '../types'; +import { generatePreviewUrl } from '../utils'; + +import { SearchUIForm } from './search_ui_form'; + +describe('SearchUIForm', () => { + const values = { + validFields: ['title', 'url', 'category', 'size'], + validSortFields: ['title', 'url', 'category', 'size'], + validFacetFields: ['title', 'url', 'category', 'size'], + titleField: 'title', + urlField: 'url', + facetFields: ['category'], + sortFields: ['size'], + }; + const actions = { + onActiveFieldChange: jest.fn(), + onFacetFieldsChange: jest.fn(), + onSortFieldsChange: jest.fn(), + onTitleFieldChange: jest.fn(), + onUrlFieldChange: jest.fn(), + }; + + beforeAll(() => { + setMockValues(values); + setMockActions(actions); + }); + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('renders', () => { + const wrapper = shallow(); + expect(wrapper.find('[data-test-subj="selectTitle"]').exists()).toBe(true); + expect(wrapper.find('[data-test-subj="selectFilters"]').exists()).toBe(true); + expect(wrapper.find('[data-test-subj="selectSort"]').exists()).toBe(true); + expect(wrapper.find('[data-test-subj="selectUrl"]').exists()).toBe(true); + }); + + describe('title field', () => { + const subject = () => shallow().find('[data-test-subj="selectTitle"]'); + + it('renders with its value set from state', () => { + setMockValues({ + ...values, + titleField: 'foo', + }); + + expect(subject().prop('value')).toBe('foo'); + }); + + it('updates state with new value when changed', () => { + subject().simulate('change', { target: { value: 'foo' } }); + expect(actions.onTitleFieldChange).toHaveBeenCalledWith('foo'); + }); + + it('updates active field in state on focus', () => { + subject().simulate('focus'); + expect(actions.onActiveFieldChange).toHaveBeenCalledWith(ActiveField.Title); + }); + + it('removes active field in state on blur', () => { + subject().simulate('blur'); + expect(actions.onActiveFieldChange).toHaveBeenCalledWith(ActiveField.None); + }); + }); + + describe('url field', () => { + const subject = () => shallow().find('[data-test-subj="selectUrl"]'); + + it('renders with its value set from state', () => { + setMockValues({ + ...values, + urlField: 'foo', + }); + + expect(subject().prop('value')).toBe('foo'); + }); + + it('updates state with new value when changed', () => { + subject().simulate('change', { target: { value: 'foo' } }); + expect(actions.onUrlFieldChange).toHaveBeenCalledWith('foo'); + }); + + it('updates active field in state on focus', () => { + subject().simulate('focus'); + expect(actions.onActiveFieldChange).toHaveBeenCalledWith(ActiveField.Url); + }); + + it('removes active field in state on blur', () => { + subject().simulate('blur'); + expect(actions.onActiveFieldChange).toHaveBeenCalledWith(ActiveField.None); + }); + }); + + describe('filters field', () => { + const subject = () => shallow().find('[data-test-subj="selectFilters"]'); + + it('renders with its value set from state', () => { + setMockValues({ + ...values, + facetFields: ['foo'], + }); + + expect(subject().prop('selectedOptions')).toEqual([ + { label: 'foo', text: 'foo', value: 'foo' }, + ]); + }); + + it('updates state with new value when changed', () => { + subject().simulate('change', [ + { label: 'foo', text: 'foo', value: 'foo' }, + { label: 'bar', text: 'bar', value: 'bar' }, + ]); + expect(actions.onFacetFieldsChange).toHaveBeenCalledWith(['foo', 'bar']); + }); + + it('updates active field in state on focus', () => { + subject().simulate('focus'); + expect(actions.onActiveFieldChange).toHaveBeenCalledWith(ActiveField.Filter); + }); + + it('removes active field in state on blur', () => { + subject().simulate('blur'); + expect(actions.onActiveFieldChange).toHaveBeenCalledWith(ActiveField.None); + }); + }); + + describe('sorts field', () => { + const subject = () => shallow().find('[data-test-subj="selectSort"]'); + + it('renders with its value set from state', () => { + setMockValues({ + ...values, + sortFields: ['foo'], + }); + + expect(subject().prop('selectedOptions')).toEqual([ + { label: 'foo', text: 'foo', value: 'foo' }, + ]); + }); + + it('updates state with new value when changed', () => { + subject().simulate('change', [ + { label: 'foo', text: 'foo', value: 'foo' }, + { label: 'bar', text: 'bar', value: 'bar' }, + ]); + expect(actions.onSortFieldsChange).toHaveBeenCalledWith(['foo', 'bar']); + }); + + it('updates active field in state on focus', () => { + subject().simulate('focus'); + expect(actions.onActiveFieldChange).toHaveBeenCalledWith(ActiveField.Sort); + }); + + it('removes active field in state on blur', () => { + subject().simulate('blur'); + expect(actions.onActiveFieldChange).toHaveBeenCalledWith(ActiveField.None); + }); + }); + + it('includes a link to generate the preview', () => { + (generatePreviewUrl as jest.Mock).mockReturnValue('http://www.example.com?foo=bar'); + + setMockValues({ + ...values, + urlField: 'foo', + titleField: 'bar', + facetFields: ['baz'], + sortFields: ['qux'], + }); + + const subject = () => + shallow().find('[data-test-subj="generateSearchUiPreview"]'); + + expect(subject().prop('href')).toBe('http://www.example.com?foo=bar'); + expect(generatePreviewUrl).toHaveBeenCalledWith({ + urlField: 'foo', + titleField: 'bar', + facets: ['baz'], + sortFields: ['qux'], + }); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/components/search_ui_form.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/components/search_ui_form.tsx new file mode 100644 index 0000000000000..15bd699be721c --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/components/search_ui_form.tsx @@ -0,0 +1,133 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; + +import { useValues, useActions } from 'kea'; + +import { EuiForm, EuiFormRow, EuiSelect, EuiComboBox, EuiButton } from '@elastic/eui'; + +import { + TITLE_FIELD_LABEL, + TITLE_FIELD_HELP_TEXT, + FILTER_FIELD_LABEL, + FILTER_FIELD_HELP_TEXT, + SORT_FIELD_LABEL, + SORT_FIELD_HELP_TEXT, + URL_FIELD_LABEL, + URL_FIELD_HELP_TEXT, + GENERATE_PREVIEW_BUTTON_LABEL, +} from '../i18n'; +import { SearchUILogic } from '../search_ui_logic'; +import { ActiveField } from '../types'; +import { generatePreviewUrl } from '../utils'; + +export const SearchUIForm: React.FC = () => { + const { + validFields, + validSortFields, + validFacetFields, + titleField, + urlField, + facetFields, + sortFields, + } = useValues(SearchUILogic); + const { + onActiveFieldChange, + onFacetFieldsChange, + onSortFieldsChange, + onTitleFieldChange, + onUrlFieldChange, + } = useActions(SearchUILogic); + + const previewHref = generatePreviewUrl({ + titleField, + urlField, + facets: facetFields, + sortFields, + }); + + const formatSelectOption = (fieldName: string) => { + return { text: fieldName, value: fieldName }; + }; + const formatMultiOptions = (fieldNames: string[]) => + fieldNames.map((fieldName) => ({ label: fieldName, text: fieldName, value: fieldName })); + const formatMultiOptionsWithEmptyOption = (fieldNames: string[]) => [ + { label: '', text: '', value: '' }, + ...formatMultiOptions(fieldNames), + ]; + + const optionFields = formatMultiOptionsWithEmptyOption(validFields); + const sortOptionFields = formatMultiOptions(validSortFields); + const facetOptionFields = formatMultiOptions(validFacetFields); + const selectedTitleOption = formatSelectOption(titleField); + const selectedURLOption = formatSelectOption(urlField); + const selectedSortOptions = formatMultiOptions(sortFields); + const selectedFacetOptions = formatMultiOptions(facetFields); + + return ( + + + onTitleFieldChange(e.target.value)} + fullWidth + onFocus={() => onActiveFieldChange(ActiveField.Title)} + onBlur={() => onActiveFieldChange(ActiveField.None)} + hasNoInitialSelection + data-test-subj="selectTitle" + /> + + + onFacetFieldsChange(newValues.map((field) => field.value!))} + onFocus={() => onActiveFieldChange(ActiveField.Filter)} + onBlur={() => onActiveFieldChange(ActiveField.None)} + fullWidth + data-test-subj="selectFilters" + /> + + + onSortFieldsChange(newValues.map((field) => field.value!))} + onFocus={() => onActiveFieldChange(ActiveField.Sort)} + onBlur={() => onActiveFieldChange(ActiveField.None)} + fullWidth + data-test-subj="selectSort" + /> + + + + onUrlFieldChange(e.target.value)} + fullWidth + onFocus={() => onActiveFieldChange(ActiveField.Url)} + onBlur={() => onActiveFieldChange(ActiveField.None)} + hasNoInitialSelection + data-test-subj="selectUrl" + /> + + + {GENERATE_PREVIEW_BUTTON_LABEL} + + + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/components/search_ui_graphic.scss b/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/components/search_ui_graphic.scss new file mode 100644 index 0000000000000..cc49789358a28 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/components/search_ui_graphic.scss @@ -0,0 +1,188 @@ +.searchUIGraphic { + transform-style: preserve-3d; + transform: rotate3d(0, 1, 0, -8deg); + + #search-area { + .outerBox { + fill: $euiColorLightShade; + } + .field { + fill: $euiColorEmptyShade; + } + .searchIcon { + fill: $euiColorDarkShade; + } + .type { + fill: $euiColorDarkShade; + } + } + #background { + fill: $euiColorLightestShade; + } + #results { + .outerBox { + fill: $euiColorEmptyShade; + stroke: $euiColorLightShade; + stroke-width: $euiBorderWidthThin; + } + .shoe { + fill: $euiColorMediumShade; + } + .url { + fill: $euiColorDarkShade; + transform: translateY(5px); + } + .titleCopy { + fill: $euiColorDarkShade; + } + .titleBox { + fill: $euiColorEmptyShade; + } + .blockIn { + fill: $euiColorLightShade; + } + } + #filter { + .outerBox { + fill: $euiColorEmptyShade; + stroke: $euiColorLightShade; + stroke-width: 1px; + } + .header { + fill: $euiColorDarkShade; + } + .checkbox { + fill: $euiColorDarkShade; + } + .check { + fill: $euiColorEmptyShade; + } + .filterCopy { + fill: $euiColorDarkShade; + } + } + #sort { + .outerBox { + fill: $euiColorEmptyShade; + stroke: $euiColorLightShade; + stroke-width: 1px; + } + .header { + fill: $euiColorDarkShade; + } + .selectCopy { + fill: $euiColorDarkShade; + } + .selectBox { + fill: $euiColorEmptyShade; + stroke: $euiColorLightShade; + stroke-width: 1px; + } + .selectControl { + fill: $euiColorDarkShade; + } + } + #pagination { + .outerBox { + fill: $euiColorLightShade; + } + .arrow { + fill: $euiColorEmptyShade; + } + } + + &.activeTitle { + #results { + .titleBox { + fill: $euiColorEmptyShade; + stroke: $euiColorPrimary; + stroke-width: 1px; + } + .titleCopy { + fill: $euiColorPrimary; + } + .outerBox { + fill: $euiColorEmptyShade; + stroke: $euiColorPrimary; + } + .url { + fill: $euiColorPrimary; + opacity: .1; + } + .shoe { + fill: $euiColorPrimary; + opacity: .1; + } + } + } + + &.activeUrl { + #results { + .outerBox { + fill: $euiColorEmptyShade; + stroke: $euiColorPrimary; + stroke-width: 1px; + } + .url { + fill: $euiColorPrimary; + } + .titleBox { + fill: $euiColorEmptyShade; + } + .titleCopy { + fill: $euiColorPrimary; + opacity: .1; + } + .shoe { + fill: $euiColorPrimary; + opacity: .1; + } + } + } + + &.activeFilter { + #filter { + .outerBox { + fill: $euiColorEmptyShade; + stroke: $euiColorPrimary; + stroke-width: $euiBorderWidthThick; + } + .header { + fill: $euiColorPrimary; + } + .checkbox { + fill: $euiColorPrimary; + } + .check { + fill: $euiColorEmptyShade; + } + .filterCopy { + fill: $euiColorPrimary; + } + } + } + + &.activeSort { + #sort { + .outerBox { + fill: $euiColorEmptyShade; + stroke: $euiColorPrimary; + stroke-width: 2px; + } + .header { + fill: $euiColorPrimary; + } + .selectCopy { + fill: $euiColorPrimary; + } + .selectBox { + fill: $euiColorEmptyShade; + stroke: $euiColorPrimary; + stroke-width: 1px; + } + .selectControl { + fill: $euiColorPrimary; + } + } + } +} diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/components/search_ui_graphic.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/components/search_ui_graphic.test.tsx new file mode 100644 index 0000000000000..0326cef9a2455 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/components/search_ui_graphic.test.tsx @@ -0,0 +1,32 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { setMockValues } from '../../../../__mocks__'; + +import React from 'react'; + +import { shallow } from 'enzyme'; + +import { ActiveField } from '../types'; + +import { SearchUIGraphic } from './search_ui_graphic'; + +describe('SearchUIGraphic', () => { + const values = { + activeField: ActiveField.Sort, + }; + + beforeEach(() => { + jest.clearAllMocks(); + setMockValues(values); + }); + + it('renders an svg with a className determined by the currently active field', () => { + const wrapper = shallow(); + expect(wrapper.hasClass('activeSort')).toBe(true); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/components/search_ui_graphic.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/components/search_ui_graphic.tsx new file mode 100644 index 0000000000000..02b7ac3423227 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/components/search_ui_graphic.tsx @@ -0,0 +1,232 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; + +import { useValues } from 'kea'; + +import { SearchUILogic } from '../search_ui_logic'; + +import './search_ui_graphic.scss'; + +export const SearchUIGraphic: React.FC = () => { + const { activeField } = useValues(SearchUILogic); + const svgClass = 'searchUIGraphic'; + + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/i18n.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/i18n.ts new file mode 100644 index 0000000000000..dfdc1d1db4c39 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/i18n.ts @@ -0,0 +1,50 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; + +export const SEARCH_UI_TITLE = i18n.translate( + 'xpack.enterpriseSearch.appSearch.engine.searchUI.title', + { defaultMessage: 'Search UI' } +); + +export const TITLE_FIELD_LABEL = i18n.translate( + 'xpack.enterpriseSearch.appSearch.engine.searchUI.titleFieldLabel', + { defaultMessage: 'Title field (Optional)' } +); +export const TITLE_FIELD_HELP_TEXT = i18n.translate( + 'xpack.enterpriseSearch.appSearch.engine.searchUI.titleFieldHelpText', + { defaultMessage: 'Used as the top-level visual identifier for every rendered result' } +); +export const FILTER_FIELD_LABEL = i18n.translate( + 'xpack.enterpriseSearch.appSearch.engine.searchUI.filterFieldLabel', + { defaultMessage: 'Filter fields (Optional)' } +); +export const FILTER_FIELD_HELP_TEXT = i18n.translate( + 'xpack.enterpriseSearch.appSearch.engine.searchUI.filterFieldHelpText', + { defaultMessage: 'Faceted values rendered as filters and available as query refinement' } +); +export const SORT_FIELD_LABEL = i18n.translate( + 'xpack.enterpriseSearch.appSearch.engine.searchUI.sortFieldLabel', + { defaultMessage: 'Sort fields (Optional)' } +); +export const SORT_FIELD_HELP_TEXT = i18n.translate( + 'xpack.enterpriseSearch.appSearch.engine.searchUI.sortHelpText', + { defaultMessage: 'Used to display result sorting options, ascending and descending' } +); +export const URL_FIELD_LABEL = i18n.translate( + 'xpack.enterpriseSearch.appSearch.engine.searchUI.urlFieldLabel', + { defaultMessage: 'URL field (Optional)' } +); +export const URL_FIELD_HELP_TEXT = i18n.translate( + 'xpack.enterpriseSearch.appSearch.engine.searchUI.urlFieldHelpText', + { defaultMessage: "Used as a result's link target, if applicable" } +); +export const GENERATE_PREVIEW_BUTTON_LABEL = i18n.translate( + 'xpack.enterpriseSearch.appSearch.engine.searchUI.generatePreviewButtonLabel', + { defaultMessage: 'Generate search experience' } +); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/index.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/index.ts index 5de1224a9f28a..e87606fa04c65 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/index.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/index.ts @@ -5,6 +5,6 @@ * 2.0. */ -export { SEARCH_UI_TITLE } from './constants'; +export { SEARCH_UI_TITLE } from './i18n'; export { SearchUI } from './search_ui'; export { SearchUILogic } from './search_ui_logic'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/search_ui.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/search_ui.tsx index d4e4d72e4740a..e75bc36177151 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/search_ui.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/search_ui.tsx @@ -9,14 +9,26 @@ import React, { useEffect } from 'react'; import { useActions } from 'kea'; -import { EuiPageHeader, EuiPageContentBody } from '@elastic/eui'; +import { + EuiPageHeader, + EuiPageContentBody, + EuiText, + EuiFlexItem, + EuiFlexGroup, + EuiSpacer, + EuiLink, +} from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; import { FlashMessages } from '../../../shared/flash_messages'; import { SetAppSearchChrome as SetPageChrome } from '../../../shared/kibana_chrome'; +import { DOCS_PREFIX } from '../../routes'; import { getEngineBreadcrumbs } from '../engine'; -import { SEARCH_UI_TITLE } from './constants'; +import { SearchUIForm } from './components/search_ui_form'; +import { SearchUIGraphic } from './components/search_ui_graphic'; +import { SEARCH_UI_TITLE } from './i18n'; import { SearchUILogic } from './search_ui_logic'; export const SearchUI: React.FC = () => { @@ -31,7 +43,51 @@ export const SearchUI: React.FC = () => { - TODO + + + + +

+ + + + ), + }} + /> +

+

+ + + + ), + }} + /> +

+
+ + +
+ + + +
+
); }; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/search_ui_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/search_ui_logic.test.ts index 29261f3a4031f..2191c633131bb 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/search_ui_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/search_ui_logic.test.ts @@ -78,10 +78,10 @@ describe('SearchUILogic', () => { }); }); - describe('onURLFieldChange', () => { + describe('onUrlFieldChange', () => { it('sets the urlField value', () => { mount({ urlField: '' }); - SearchUILogic.actions.onURLFieldChange('foo'); + SearchUILogic.actions.onUrlFieldChange('foo'); expect(SearchUILogic.values).toEqual({ ...DEFAULT_VALUES, urlField: 'foo', @@ -128,6 +128,10 @@ describe('SearchUILogic', () => { validFields: ['test'], validSortFields: ['test'], validFacetFields: ['test'], + defaultValues: { + urlField: 'url', + titleField: 'title', + }, }; describe('loadFieldData', () => { @@ -142,7 +146,13 @@ describe('SearchUILogic', () => { expect(http.get).toHaveBeenCalledWith( '/api/app_search/engines/engine1/search_ui/field_config' ); - expect(SearchUILogic.actions.onFieldDataLoaded).toHaveBeenCalledWith(MOCK_RESPONSE); + expect(SearchUILogic.actions.onFieldDataLoaded).toHaveBeenCalledWith({ + validFields: ['test'], + validSortFields: ['test'], + validFacetFields: ['test'], + urlField: 'url', + titleField: 'title', + }); }); it('handles errors', async () => { diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/search_ui_logic.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/search_ui_logic.ts index 7b3454c9e8413..c9e2e5623d9fd 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/search_ui_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/search_ui_logic.ts @@ -17,6 +17,8 @@ interface InitialFieldValues { validFields: string[]; validSortFields: string[]; validFacetFields: string[]; + urlField?: string; + titleField?: string; } interface SearchUIActions { loadFieldData(): void; @@ -25,7 +27,7 @@ interface SearchUIActions { onFacetFieldsChange(facetFields: string[]): { facetFields: string[] }; onSortFieldsChange(sortFields: string[]): { sortFields: string[] }; onTitleFieldChange(titleField: string): { titleField: string }; - onURLFieldChange(urlField: string): { urlField: string }; + onUrlFieldChange(urlField: string): { urlField: string }; } interface SearchUIValues { @@ -49,7 +51,7 @@ export const SearchUILogic = kea> onFacetFieldsChange: (facetFields) => ({ facetFields }), onSortFieldsChange: (sortFields) => ({ sortFields }), onTitleFieldChange: (titleField) => ({ titleField }), - onURLFieldChange: (urlField) => ({ urlField }), + onUrlFieldChange: (urlField) => ({ urlField }), }), reducers: () => ({ dataLoading: [ @@ -61,8 +63,20 @@ export const SearchUILogic = kea> validFields: [[], { onFieldDataLoaded: (_, { validFields }) => validFields }], validSortFields: [[], { onFieldDataLoaded: (_, { validSortFields }) => validSortFields }], validFacetFields: [[], { onFieldDataLoaded: (_, { validFacetFields }) => validFacetFields }], - titleField: ['', { onTitleFieldChange: (_, { titleField }) => titleField }], - urlField: ['', { onURLFieldChange: (_, { urlField }) => urlField }], + titleField: [ + '', + { + onTitleFieldChange: (_, { titleField }) => titleField, + onFieldDataLoaded: (_, { titleField }) => titleField || '', + }, + ], + urlField: [ + '', + { + onUrlFieldChange: (_, { urlField }) => urlField, + onFieldDataLoaded: (_, { urlField }) => urlField || '', + }, + ], facetFields: [[], { onFacetFieldsChange: (_, { facetFields }) => facetFields }], sortFields: [[], { onSortFieldsChange: (_, { sortFields }) => sortFields }], activeField: [ActiveField.None, { onActiveFieldChange: (_, { activeField }) => activeField }], @@ -76,8 +90,20 @@ export const SearchUILogic = kea> try { const initialFieldValues = await http.get(url); + const { + defaultValues: { urlField, titleField }, + validFields, + validSortFields, + validFacetFields, + } = initialFieldValues; - actions.onFieldDataLoaded(initialFieldValues); + actions.onFieldDataLoaded({ + validFields, + validSortFields, + validFacetFields, + urlField, + titleField, + }); } catch (e) { flashAPIErrors(e); } diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/types.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/types.ts index 132ce46bc13fb..224aeab05af35 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/types.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/types.ts @@ -6,9 +6,9 @@ */ export enum ActiveField { - Title, - Filter, - Sort, - Url, - None, + Title = 'Title', + Filter = 'Filter', + Sort = 'Sort', + Url = 'Url', + None = '', } diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/utils.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/utils.test.ts new file mode 100644 index 0000000000000..8d99ffc514b3f --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/utils.test.ts @@ -0,0 +1,39 @@ +/* + * 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__/enterprise_search_url.mock'; + +import { generatePreviewUrl } from './utils'; + +jest.mock('../engine', () => ({ + EngineLogic: { + values: { + engineName: 'national-parks-demo', + }, + }, +})); + +describe('generatePreviewUrl', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('generates a url to the preview application from state', () => { + expect( + generatePreviewUrl({ + titleField: 'foo', + urlField: 'bar', + facets: ['baz', 'qux'], + sortFields: ['quux', 'quuz'], + empty: '', // Empty fields should be stripped + empty2: [''], // Empty fields should be stripped + }) + ).toEqual( + 'http://localhost:3002/as/engines/national-parks-demo/reference_application/preview?facets[]=baz&facets[]=qux&sortFields[]=quux&sortFields[]=quuz&titleField=foo&urlField=bar' + ); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/utils.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/utils.ts new file mode 100644 index 0000000000000..72d90514ea0a0 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/utils.ts @@ -0,0 +1,22 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import queryString, { ParsedQuery } from 'query-string'; + +import { getAppSearchUrl } from '../../../shared/enterprise_search_url'; +import { EngineLogic } from '../engine'; + +export const generatePreviewUrl = (query: ParsedQuery) => { + const { engineName } = EngineLogic.values; + return queryString.stringifyUrl( + { + query, + url: getAppSearchUrl(`/engines/${engineName}/reference_application/preview`), + }, + { arrayFormat: 'bracket', skipEmptyString: true } + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/flash_messages/flash_messages.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/flash_messages/flash_messages.tsx index 10f80d9a6345a..cf4e1a2d6cabc 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/flash_messages/flash_messages.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/flash_messages/flash_messages.tsx @@ -10,6 +10,7 @@ import React, { Fragment } from 'react'; import { useValues, useActions } from 'kea'; import { EuiCallOut, EuiSpacer, EuiGlobalToastList } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; import { FLASH_MESSAGE_TYPES, DEFAULT_TOAST_TIMEOUT } from './constants'; import { FlashMessagesLogic } from './flash_messages_logic'; @@ -25,7 +26,14 @@ export const Callouts: React.FC = ({ children }) => { const { messages } = useValues(FlashMessagesLogic); return ( -
+
{messages.map(({ type, message, description }, index) => ( ; // This is a mapping of schema field types ("text", "number", "geolocation", "date") // to the names of source engines which utilize that type -export type SchemaConflictFieldTypes = Record; +export type SchemaConflictFieldTypes = Partial>; export interface SchemaConflict { fieldTypes: SchemaConflictFieldTypes; diff --git a/x-pack/plugins/enterprise_search/server/routes/app_search/index.ts b/x-pack/plugins/enterprise_search/server/routes/app_search/index.ts index 6ecdb8d8857c6..99aaaeeec38b3 100644 --- a/x-pack/plugins/enterprise_search/server/routes/app_search/index.ts +++ b/x-pack/plugins/enterprise_search/server/routes/app_search/index.ts @@ -16,6 +16,7 @@ import { registerEnginesRoutes } from './engines'; import { registerOnboardingRoutes } from './onboarding'; import { registerResultSettingsRoutes } from './result_settings'; import { registerRoleMappingsRoutes } from './role_mappings'; +import { registerSchemaRoutes } from './schema'; import { registerSearchSettingsRoutes } from './search_settings'; import { registerSearchUIRoutes } from './search_ui'; import { registerSettingsRoutes } from './settings'; @@ -28,6 +29,7 @@ export const registerAppSearchRoutes = (dependencies: RouteDependencies) => { registerAnalyticsRoutes(dependencies); registerDocumentsRoutes(dependencies); registerDocumentRoutes(dependencies); + registerSchemaRoutes(dependencies); registerCurationsRoutes(dependencies); registerSynonymsRoutes(dependencies); registerSearchSettingsRoutes(dependencies); diff --git a/x-pack/plugins/enterprise_search/server/routes/app_search/schema.test.ts b/x-pack/plugins/enterprise_search/server/routes/app_search/schema.test.ts new file mode 100644 index 0000000000000..408838a4de31b --- /dev/null +++ b/x-pack/plugins/enterprise_search/server/routes/app_search/schema.test.ts @@ -0,0 +1,81 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { MockRouter, mockRequestHandler, mockDependencies } from '../../__mocks__'; + +import { registerSchemaRoutes } from './schema'; + +describe('schema routes', () => { + describe('GET /api/app_search/engines/{engineName}/schema', () => { + let mockRouter: MockRouter; + + beforeEach(() => { + jest.clearAllMocks(); + mockRouter = new MockRouter({ + method: 'get', + path: '/api/app_search/engines/{engineName}/schema', + }); + + registerSchemaRoutes({ + ...mockDependencies, + router: mockRouter.router, + }); + }); + + it('creates a request handler', () => { + expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ + path: '/as/engines/:engineName/schema', + }); + }); + }); + + describe('POST /api/app_search/engines/{engineName}/schema', () => { + let mockRouter: MockRouter; + + beforeEach(() => { + jest.clearAllMocks(); + mockRouter = new MockRouter({ + method: 'post', + path: '/api/app_search/engines/{engineName}/schema', + }); + + registerSchemaRoutes({ + ...mockDependencies, + router: mockRouter.router, + }); + }); + + it('creates a request handler', () => { + expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ + path: '/as/engines/:engineName/schema', + }); + }); + }); + + describe('GET /api/app_search/engines/{engineName}/reindex_job/{reindexJobId}', () => { + let mockRouter: MockRouter; + + beforeEach(() => { + jest.clearAllMocks(); + mockRouter = new MockRouter({ + method: 'get', + path: '/api/app_search/engines/{engineName}/reindex_job/{reindexJobId}', + }); + + registerSchemaRoutes({ + ...mockDependencies, + router: mockRouter.router, + }); + }); + + it('creates a request handler', () => { + expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ + path: '/as/engines/:engineName/reindex_job/:reindexJobId', + }); + }); + }); +}); diff --git a/x-pack/plugins/enterprise_search/server/routes/app_search/schema.ts b/x-pack/plugins/enterprise_search/server/routes/app_search/schema.ts new file mode 100644 index 0000000000000..74d07bd2bf75d --- /dev/null +++ b/x-pack/plugins/enterprise_search/server/routes/app_search/schema.ts @@ -0,0 +1,59 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { schema } from '@kbn/config-schema'; + +import { skipBodyValidation } from '../../lib/route_config_helpers'; +import { RouteDependencies } from '../../plugin'; + +export function registerSchemaRoutes({ + router, + enterpriseSearchRequestHandler, +}: RouteDependencies) { + router.get( + { + path: '/api/app_search/engines/{engineName}/schema', + validate: { + params: schema.object({ + engineName: schema.string(), + }), + }, + }, + enterpriseSearchRequestHandler.createRequest({ + path: '/as/engines/:engineName/schema', + }) + ); + + router.post( + skipBodyValidation({ + path: '/api/app_search/engines/{engineName}/schema', + validate: { + params: schema.object({ + engineName: schema.string(), + }), + }, + }), + enterpriseSearchRequestHandler.createRequest({ + path: '/as/engines/:engineName/schema', + }) + ); + + router.get( + { + path: '/api/app_search/engines/{engineName}/reindex_job/{reindexJobId}', + validate: { + params: schema.object({ + engineName: schema.string(), + reindexJobId: schema.string(), + }), + }, + }, + enterpriseSearchRequestHandler.createRequest({ + path: '/as/engines/:engineName/reindex_job/:reindexJobId', + }) + ); +} diff --git a/x-pack/plugins/enterprise_search/server/routes/workplace_search/role_mappings.ts b/x-pack/plugins/enterprise_search/server/routes/workplace_search/role_mappings.ts index 8c7792f56fd6c..5a6359c1cd836 100644 --- a/x-pack/plugins/enterprise_search/server/routes/workplace_search/role_mappings.ts +++ b/x-pack/plugins/enterprise_search/server/routes/workplace_search/role_mappings.ts @@ -66,10 +66,7 @@ export function registerOrgRoleMappingRoute({ { path: '/api/workplace_search/org/role_mappings/{id}', validate: { - body: schema.object({ - ...roleMappingBaseSchema, - id: schema.string(), - }), + body: schema.object(roleMappingBaseSchema), params: schema.object({ id: schema.string(), }), diff --git a/x-pack/plugins/file_data_visualizer/public/application/components/stats_table/components/field_data_expanded_row/choropleth_map.tsx b/x-pack/plugins/file_data_visualizer/public/application/components/stats_table/components/field_data_expanded_row/choropleth_map.tsx new file mode 100644 index 0000000000000..acbb77a7e0cac --- /dev/null +++ b/x-pack/plugins/file_data_visualizer/public/application/components/stats_table/components/field_data_expanded_row/choropleth_map.tsx @@ -0,0 +1,126 @@ +/* + * 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, useMemo } from 'react'; +import { EuiFlexItem, EuiSpacer, EuiText, htmlIdGenerator } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { + FIELD_ORIGIN, + SOURCE_TYPES, + STYLE_TYPE, + COLOR_MAP_TYPE, +} from '../../../../../../../maps/common/constants'; +import { EMSTermJoinConfig } from '../../../../../../../maps/public'; +import { FieldVisStats } from '../../types'; +import { VectorLayerDescriptor } from '../../../../../../../maps/common/descriptor_types'; +import { EmbeddedMapComponent } from '../../../embedded_map'; + +export const getChoroplethTopValuesLayer = ( + fieldName: string, + topValues: Array<{ key: any; doc_count: number }>, + { layerId, field }: EMSTermJoinConfig +): VectorLayerDescriptor => { + return { + id: htmlIdGenerator()(), + label: i18n.translate('xpack.fileDataVisualizer.choroplethMap.topValuesCount', { + defaultMessage: 'Top values count for {fieldName}', + values: { fieldName }, + }), + joins: [ + { + // Left join is the id from the type of field (e.g. world_countries) + leftField: field, + right: { + id: 'anomaly_count', + type: SOURCE_TYPES.TABLE_SOURCE, + __rows: topValues, + __columns: [ + { + name: 'key', + type: 'string', + }, + { + name: 'doc_count', + type: 'number', + }, + ], + // Right join/term is the field in the doc you’re trying to join it to (foreign key - e.g. US) + term: 'key', + }, + }, + ], + sourceDescriptor: { + type: 'EMS_FILE', + id: layerId, + }, + style: { + type: 'VECTOR', + // @ts-ignore missing style properties. Remove once 'VectorLayerDescriptor' type is updated + properties: { + icon: { type: STYLE_TYPE.STATIC, options: { value: 'marker' } }, + fillColor: { + type: STYLE_TYPE.DYNAMIC, + options: { + color: 'Blue to Red', + colorCategory: 'palette_0', + fieldMetaOptions: { isEnabled: true, sigma: 3 }, + type: COLOR_MAP_TYPE.ORDINAL, + field: { + name: 'doc_count', + origin: FIELD_ORIGIN.JOIN, + }, + useCustomColorRamp: false, + }, + }, + lineColor: { + type: STYLE_TYPE.DYNAMIC, + options: { fieldMetaOptions: { isEnabled: true } }, + }, + lineWidth: { type: STYLE_TYPE.STATIC, options: { size: 1 } }, + }, + isTimeAware: true, + }, + type: 'VECTOR', + }; +}; + +interface Props { + stats: FieldVisStats | undefined; + suggestion: EMSTermJoinConfig; +} + +export const ChoroplethMap: FC = ({ stats, suggestion }) => { + const { fieldName, isTopValuesSampled, topValues, topValuesSamplerShardSize } = stats!; + + const layerList: VectorLayerDescriptor[] = useMemo( + () => [getChoroplethTopValuesLayer(fieldName || '', topValues || [], suggestion)], + [suggestion, fieldName, topValues] + ); + + return ( + +
+ +
+ {isTopValuesSampled === true && ( + <> + + + + + + )} +
+ ); +}; diff --git a/x-pack/plugins/file_data_visualizer/public/application/components/stats_table/components/field_data_expanded_row/keyword_content.tsx b/x-pack/plugins/file_data_visualizer/public/application/components/stats_table/components/field_data_expanded_row/keyword_content.tsx index 3f1a7aad5463f..6448883bfce73 100644 --- a/x-pack/plugins/file_data_visualizer/public/application/components/stats_table/components/field_data_expanded_row/keyword_content.tsx +++ b/x-pack/plugins/file_data_visualizer/public/application/components/stats_table/components/field_data_expanded_row/keyword_content.tsx @@ -5,21 +5,55 @@ * 2.0. */ -import React, { FC } from 'react'; +import React, { FC, useCallback, useEffect, useState } from 'react'; import type { FieldDataRowProps } from '../../types/field_data_row'; import { TopValues } from '../../../top_values'; +import { EMSTermJoinConfig } from '../../../../../../../maps/public'; +import { useFileDataVisualizerKibana } from '../../../../kibana_context'; import { DocumentStatsTable } from './document_stats'; import { ExpandedRowContent } from './expanded_row_content'; +import { ChoroplethMap } from './choropleth_map'; + +const COMMON_EMS_LAYER_IDS = [ + 'world_countries', + 'administrative_regions_lvl2', + 'usa_zip_codes', + 'usa_states', +]; export const KeywordContent: FC = ({ config }) => { - const { stats } = config; + const [EMSSuggestion, setEMSSuggestion] = useState(); + const { stats, fieldName } = config; const fieldFormat = 'fieldFormat' in config ? config.fieldFormat : undefined; + const { + services: { maps: mapsPlugin }, + } = useFileDataVisualizerKibana(); + + const loadEMSTermSuggestions = useCallback(async () => { + if (!mapsPlugin) return; + const suggestion: EMSTermJoinConfig | null = await mapsPlugin.suggestEMSTermJoinConfig({ + emsLayerIds: COMMON_EMS_LAYER_IDS, + sampleValues: Array.isArray(stats?.topValues) + ? stats?.topValues.map((value) => value.key) + : [], + sampleValuesColumnName: fieldName || '', + }); + setEMSSuggestion(suggestion); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [fieldName]); + + useEffect( + function getInitialEMSTermSuggestion() { + loadEMSTermSuggestions(); + }, + [loadEMSTermSuggestions] + ); return ( - + {EMSSuggestion && stats && } ); }; diff --git a/x-pack/plugins/fleet/common/types/models/package_policy.ts b/x-pack/plugins/fleet/common/types/models/package_policy.ts index f30cc0f87d05b..04362e6ff9402 100644 --- a/x-pack/plugins/fleet/common/types/models/package_policy.ts +++ b/x-pack/plugins/fleet/common/types/models/package_policy.ts @@ -21,6 +21,7 @@ export type PackagePolicyConfigRecord = Record } checked={packagePolicyInput.enabled} + disabled={packagePolicyInput.keep_enabled} onChange={(e) => { const enabled = e.target.checked; updatePackagePolicyInput({ diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/package_policy_input_stream.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/package_policy_input_stream.tsx index 84f097813d484..5cc1fc4130256 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/package_policy_input_stream.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/package_policy_input_stream.tsx @@ -83,6 +83,7 @@ export const PackagePolicyInputStreamConfig: React.FunctionComponent<{ { const enabled = e.target.checked; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_logs/agent_logs.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_logs/agent_logs.tsx index 6bc1db2fc4c7d..fa3b6b56a18b9 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_logs/agent_logs.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_logs/agent_logs.tsx @@ -6,11 +6,11 @@ */ import url from 'url'; +import { stringify } from 'querystring'; import React, { memo, useMemo, useState, useCallback, useEffect } from 'react'; import styled from 'styled-components'; import { encode } from 'rison-node'; -import { stringify } from 'query-string'; import { EuiFlexGroup, EuiFlexItem, @@ -170,20 +170,17 @@ export const AgentLogsUI: React.FunctionComponent = memo(({ agen http.basePath.prepend( url.format({ pathname: '/app/logs/stream', - search: stringify( - { - logPosition: encode({ - start: state.start, - end: state.end, - streamLive: false, - }), - logFilter: encode({ - expression: logStreamQuery, - kind: 'kuery', - }), - }, - { sort: false, encode: false } - ), + search: stringify({ + logPosition: encode({ + start: state.start, + end: state.end, + streamLive: false, + }), + logFilter: encode({ + expression: logStreamQuery, + kind: 'kuery', + }), + }), }) ), [http.basePath, state.start, state.end, logStreamQuery] diff --git a/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/install.ts b/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/install.ts index 98ba970fda39b..9e8277eb6171f 100644 --- a/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/install.ts +++ b/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/install.ts @@ -198,15 +198,6 @@ function buildComponentTemplates(registryElasticsearch: RegistryElasticsearch | template: { mappings: { ...registryElasticsearch['index_template.mappings'], - // temporary change until https://github.com/elastic/elasticsearch/issues/58956 is resolved - // hopefully we'll be able to remove the entire properties section once that issue is resolved - properties: { - // if the timestamp_field changes here: https://github.com/elastic/kibana/blob/master/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/template.ts#L309 - // we'll need to update this as well - '@timestamp': { - type: 'date', - }, - }, }, }, }; diff --git a/x-pack/plugins/fleet/server/services/epm/registry/index.ts b/x-pack/plugins/fleet/server/services/epm/registry/index.ts index 0a755228e0b98..9e04d75f36a16 100644 --- a/x-pack/plugins/fleet/server/services/epm/registry/index.ts +++ b/x-pack/plugins/fleet/server/services/epm/registry/index.ts @@ -77,8 +77,6 @@ export const pkgToPkgKey = ({ name, version }: { name: string; version: string } export async function fetchList(params?: SearchParams): Promise { const registryUrl = getRegistryUrl(); const url = new URL(`${registryUrl}/search`); - const kibanaVersion = appContextService.getKibanaVersion().split('-')[0]; // may be x.y.z-SNAPSHOT - const kibanaBranch = appContextService.getKibanaBranch(); if (params) { if (params.category) { url.searchParams.set('category', params.category); @@ -88,10 +86,7 @@ export async function fetchList(params?: SearchParams): Promise { return getResponse(`${registryUrl}${filePath}`); } +function setKibanaVersion(url: URL) { + const kibanaVersion = appContextService.getKibanaVersion().split('-')[0]; // may be x.y.z-SNAPSHOT + const kibanaBranch = appContextService.getKibanaBranch(); + + // on master, request all packages regardless of version + if (kibanaVersion && kibanaBranch !== 'master') { + url.searchParams.set('kibana.version', kibanaVersion); + } +} + export async function fetchCategories(params?: CategoriesParams): Promise { const registryUrl = getRegistryUrl(); const url = new URL(`${registryUrl}/categories`); @@ -154,6 +159,8 @@ export async function fetchCategories(params?: CategoriesParams): Promise { type: 'logs', dataset: 'package.dataset1', streams: [{ input: 'log', template_path: 'some_template_path.yml' }], + path: 'dataset1', }, ], policy_templates: [ @@ -151,6 +163,57 @@ describe('Package policy service', () => { ]); }); + it('should work with a two level dataset name', async () => { + const inputs = await packagePolicyService.compilePackagePolicyInputs( + ({ + data_streams: [ + { + type: 'logs', + dataset: 'package.dataset1.level1', + streams: [{ input: 'log', template_path: 'some_template_path.yml' }], + path: 'dataset1_level1', + }, + ], + policy_templates: [ + { + inputs: [{ type: 'log' }], + }, + ], + } as unknown) as PackageInfo, + [ + { + type: 'log', + enabled: true, + streams: [ + { + id: 'datastream01', + data_stream: { dataset: 'package.dataset1.level1', type: 'logs' }, + enabled: true, + }, + ], + }, + ] + ); + + expect(inputs).toEqual([ + { + type: 'log', + enabled: true, + streams: [ + { + id: 'datastream01', + data_stream: { dataset: 'package.dataset1.level1', type: 'logs' }, + enabled: true, + compiled_stream: { + metricset: ['dataset1.level1'], + type: 'log', + }, + }, + ], + }, + ]); + }); + it('should work with config variables at the input level', async () => { const inputs = await packagePolicyService.compilePackagePolicyInputs( ({ @@ -159,6 +222,7 @@ describe('Package policy service', () => { dataset: 'package.dataset1', type: 'logs', streams: [{ input: 'log', template_path: 'some_template_path.yml' }], + path: 'dataset1', }, ], policy_templates: [ @@ -261,6 +325,7 @@ describe('Package policy service', () => { dataset: 'package.dataset1', type: 'logs', streams: [{ input: 'log', template_path: 'some_template_path.yml' }], + path: 'dataset1', }, ], policy_templates: [ @@ -390,6 +455,7 @@ describe('Package policy service', () => { { config: {}, enabled: true, + keep_enabled: true, type: 'endpoint', vars: { dog: { @@ -428,7 +494,7 @@ describe('Package policy service', () => { const inputsUpdate = [ { config: {}, - enabled: true, + enabled: false, type: 'endpoint', vars: { dog: { @@ -501,6 +567,7 @@ describe('Package policy service', () => { ); const [modifiedInput] = result.inputs; + expect(modifiedInput.enabled).toEqual(true); expect(modifiedInput.vars!.dog.value).toEqual('labrador'); expect(modifiedInput.vars!.cat.value).toEqual('siamese'); const [modifiedStream] = modifiedInput.streams; diff --git a/x-pack/plugins/fleet/server/services/package_policy.ts b/x-pack/plugins/fleet/server/services/package_policy.ts index 234fa4df51688..7c870415bc521 100644 --- a/x-pack/plugins/fleet/server/services/package_policy.ts +++ b/x-pack/plugins/fleet/server/services/package_policy.ts @@ -56,10 +56,6 @@ import { appContextService } from '.'; const SAVED_OBJECT_TYPE = PACKAGE_POLICY_SAVED_OBJECT_TYPE; -function getDataset(st: string) { - return st.split('.')[1]; -} - class PackagePolicyService { public async create( soClient: SavedObjectsClientContract, @@ -562,7 +558,7 @@ async function _compilePackageStream( if (!stream.enabled) { return { ...stream, compiled_stream: undefined }; } - const datasetPath = getDataset(stream.data_stream.dataset); + const packageDataStreams = pkgInfo.data_streams; if (!packageDataStreams) { throw new Error('Stream template not found, no data streams'); @@ -571,8 +567,11 @@ async function _compilePackageStream( const packageDataStream = packageDataStreams.find( (pkgDataStream) => pkgDataStream.dataset === stream.data_stream.dataset ); + if (!packageDataStream) { - throw new Error(`Stream template not found, unable to find dataset ${datasetPath}`); + throw new Error( + `Stream template not found, unable to find dataset ${stream.data_stream.dataset}` + ); } const streamFromPkg = (packageDataStream.streams || []).find( @@ -583,9 +582,11 @@ async function _compilePackageStream( } if (!streamFromPkg.template_path) { - throw new Error(`Stream template path not found for dataset ${datasetPath}`); + throw new Error(`Stream template path not found for dataset ${stream.data_stream.dataset}`); } + const datasetPath = packageDataStream.path; + const [pkgStreamTemplate] = await getAssetsData( registryPkgInfo, (path: string) => path.endsWith(streamFromPkg.template_path), @@ -594,7 +595,7 @@ async function _compilePackageStream( if (!pkgStreamTemplate || !pkgStreamTemplate.buffer) { throw new Error( - `Unable to load stream template ${streamFromPkg.template_path} for dataset ${datasetPath}` + `Unable to load stream template ${streamFromPkg.template_path} for dataset ${stream.data_stream.dataset}` ); } @@ -614,12 +615,14 @@ function enforceFrozenInputs(oldInputs: PackagePolicyInput[], newInputs: Package for (const input of resultInputs) { const oldInput = oldInputs.find((i) => i.type === input.type); + if (oldInput?.keep_enabled) input.enabled = oldInput.enabled; if (input.vars && oldInput?.vars) { input.vars = _enforceFrozenVars(oldInput.vars, input.vars); } if (input.streams && oldInput?.streams) { for (const stream of input.streams) { const oldStream = oldInput.streams.find((s) => s.id === stream.id); + if (oldStream?.keep_enabled) stream.enabled = oldStream.enabled; if (stream.vars && oldStream?.vars) { stream.vars = _enforceFrozenVars(oldStream.vars, stream.vars); } diff --git a/x-pack/plugins/fleet/server/services/preconfiguration.ts b/x-pack/plugins/fleet/server/services/preconfiguration.ts index 308abece9f4f5..a8be94ca61c0a 100644 --- a/x-pack/plugins/fleet/server/services/preconfiguration.ts +++ b/x-pack/plugins/fleet/server/services/preconfiguration.ts @@ -285,6 +285,8 @@ function overridePackageInputs( } if (typeof override.enabled !== 'undefined') originalInput.enabled = override.enabled; + if (typeof override.keep_enabled !== 'undefined') + originalInput.keep_enabled = override.keep_enabled; if (override.vars) { try { diff --git a/x-pack/plugins/fleet/server/types/models/package_policy.ts b/x-pack/plugins/fleet/server/types/models/package_policy.ts index 5a8fd70a9b84e..fa467a4185bd4 100644 --- a/x-pack/plugins/fleet/server/types/models/package_policy.ts +++ b/x-pack/plugins/fleet/server/types/models/package_policy.ts @@ -46,6 +46,7 @@ const PackagePolicyBaseSchema = { schema.object({ type: schema.string(), enabled: schema.boolean(), + keep_enabled: schema.maybe(schema.boolean()), vars: schema.maybe(ConfigRecordSchema), config: schema.maybe( schema.recordOf( @@ -60,6 +61,7 @@ const PackagePolicyBaseSchema = { schema.object({ id: schema.maybe(schema.string()), // BWC < 7.11 enabled: schema.boolean(), + keep_enabled: schema.maybe(schema.boolean()), data_stream: schema.object({ dataset: schema.string(), type: schema.string() }), vars: schema.maybe(ConfigRecordSchema), config: schema.maybe( diff --git a/x-pack/plugins/fleet/server/types/models/preconfiguration.ts b/x-pack/plugins/fleet/server/types/models/preconfiguration.ts index 5b871b80a6bbd..e988283c4aad9 100644 --- a/x-pack/plugins/fleet/server/types/models/preconfiguration.ts +++ b/x-pack/plugins/fleet/server/types/models/preconfiguration.ts @@ -67,6 +67,7 @@ export const PreconfiguredAgentPoliciesSchema = schema.arrayOf( schema.object({ type: schema.string(), enabled: schema.maybe(schema.boolean()), + keep_enabled: schema.maybe(schema.boolean()), vars: varsSchema, streams: schema.maybe( schema.arrayOf( @@ -76,6 +77,7 @@ export const PreconfiguredAgentPoliciesSchema = schema.arrayOf( dataset: schema.string(), }), enabled: schema.maybe(schema.boolean()), + keep_enabled: schema.maybe(schema.boolean()), vars: varsSchema, }) ) diff --git a/x-pack/plugins/index_management/__jest__/components/index_table.test.js b/x-pack/plugins/index_management/__jest__/components/index_table.test.js index 6b8aa5b445af2..4ac94319d4711 100644 --- a/x-pack/plugins/index_management/__jest__/components/index_table.test.js +++ b/x-pack/plugins/index_management/__jest__/components/index_table.test.js @@ -196,6 +196,20 @@ describe('index table', () => { button = findTestSubject(rendered, 'indexActionsContextMenuButton'); expect(button.length).toEqual(1); }); + test('should update the Actions menu button text when more than one row is selected', () => { + const rendered = mountWithIntl(component); + let button = findTestSubject(rendered, 'indexTableContextMenuButton'); + expect(button.length).toEqual(0); + const checkboxes = findTestSubject(rendered, 'indexTableRowCheckbox'); + checkboxes.at(0).simulate('change', { target: { checked: true } }); + rendered.update(); + button = findTestSubject(rendered, 'indexActionsContextMenuButton'); + expect(button.text()).toEqual('Manage index'); + checkboxes.at(1).simulate('change', { target: { checked: true } }); + rendered.update(); + button = findTestSubject(rendered, 'indexActionsContextMenuButton'); + expect(button.text()).toEqual('Manage 2 indices'); + }); test('should show system indices only when the switch is turned on', () => { const rendered = mountWithIntl(component); snapshot(rendered.find('.euiPagination li').map((item) => item.text())); diff --git a/x-pack/plugins/index_management/public/application/sections/home/index_list/index_actions_context_menu/index_actions_context_menu.js b/x-pack/plugins/index_management/public/application/sections/home/index_list/index_actions_context_menu/index_actions_context_menu.js index 20a4af59bab11..944992fbc9146 100644 --- a/x-pack/plugins/index_management/public/application/sections/home/index_list/index_actions_context_menu/index_actions_context_menu.js +++ b/x-pack/plugins/index_management/public/application/sections/home/index_list/index_actions_context_menu/index_actions_context_menu.js @@ -696,7 +696,8 @@ export class IndexActionsContextMenu extends Component { iconSide = 'right', anchorPosition = 'rightUp', label = i18n.translate('xpack.idxMgmt.indexActionsMenu.manageButtonLabel', { - defaultMessage: 'Manage {selectedIndexCount, plural, one {index} other {indices}}', + defaultMessage: + 'Manage {selectedIndexCount, plural, one {index} other {{selectedIndexCount} indices}}', values: { selectedIndexCount }, }), iconType = 'arrowDown', diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/layout.tsx b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/layout.tsx index 534132eb75fa1..2e79e7e4ab8c4 100644 --- a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/layout.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/layout.tsx @@ -9,6 +9,7 @@ import React, { useCallback, useEffect, useState } from 'react'; import useInterval from 'react-use/lib/useInterval'; import { EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui'; +import { SavedView } from '../../../../containers/saved_view/saved_view'; import { AutoSizer } from '../../../../components/auto_sizer'; import { convertIntervalToString } from '../../../../utils/convert_interval_to_string'; import { NodesOverview } from './nodes_overview'; @@ -26,182 +27,191 @@ import { ViewSwitcher } from './waffle/view_switcher'; import { IntervalLabel } from './waffle/interval_label'; import { createInventoryMetricFormatter } from '../lib/create_inventory_metric_formatter'; import { createLegend } from '../lib/create_legend'; -import { useSavedViewContext } from '../../../../containers/saved_view/saved_view'; import { useWaffleViewState } from '../hooks/use_waffle_view_state'; import { SavedViewsToolbarControls } from '../../../../components/saved_views/toolbar_control'; import { BottomDrawer } from './bottom_drawer'; import { Legend } from './waffle/legend'; -export const Layout = () => { - const [showLoading, setShowLoading] = useState(true); - const { sourceId, source } = useSourceContext(); - const { currentView, shouldLoadDefault } = useSavedViewContext(); - const { - metric, - groupBy, - sort, - nodeType, - accountId, - region, - changeView, - view, - autoBounds, - boundsOverride, - legend, - } = useWaffleOptionsContext(); - const { currentTime, jumpToTime, isAutoReloading } = useWaffleTimeContext(); - const { filterQueryAsJson, applyFilterQuery } = useWaffleFiltersContext(); - const { loading, nodes, reload, interval } = useSnapshot( - filterQueryAsJson, - [metric], - groupBy, - nodeType, - sourceId, - currentTime, - accountId, - region, - false - ); - - const legendPalette = legend?.palette ?? DEFAULT_LEGEND.palette; - const legendSteps = legend?.steps ?? DEFAULT_LEGEND.steps; - const legendReverseColors = legend?.reverseColors ?? DEFAULT_LEGEND.reverseColors; - - const options = { - formatter: InfraFormatterType.percent, - formatTemplate: '{{value}}', - legend: createLegend(legendPalette, legendSteps, legendReverseColors), - metric, - sort, - fields: source?.configuration?.fields, - groupBy, - }; - - useInterval( - () => { - if (!loading) { - jumpToTime(Date.now()); - } - }, - isAutoReloading ? 5000 : null - ); - - const intervalAsString = convertIntervalToString(interval); - const dataBounds = calculateBoundsFromNodes(nodes); - const bounds = autoBounds ? dataBounds : boundsOverride; - /* eslint-disable-next-line react-hooks/exhaustive-deps */ - const formatter = useCallback(createInventoryMetricFormatter(options.metric), [options.metric]); - const { viewState, onViewChange } = useWaffleViewState(); - - useEffect(() => { - if (currentView) { - onViewChange(currentView); - } - }, [currentView, onViewChange]); - - useEffect(() => { - // load snapshot data after default view loaded, unless we're not loading a view - if (currentView != null || !shouldLoadDefault) { - reload(); - } - - /** - * INFO: why disable exhaustive-deps - * We need to wait on the currentView not to be null because it is loaded async and could change the view state. - * We don't actually need to watch the value of currentView though, since the view state will be synched up by the - * changing params in the reload method so we should only "watch" the reload method. - * - * TODO: Should refactor this in the future to make it more clear where all the view state is coming - * from and it's precedence [query params, localStorage, defaultView, out of the box view] - */ +export const Layout = React.memo( + ({ + shouldLoadDefault, + currentView, + }: { + shouldLoadDefault: boolean; + currentView: SavedView | null; + }) => { + const [showLoading, setShowLoading] = useState(true); + const { sourceId, source } = useSourceContext(); + const { + metric, + groupBy, + sort, + nodeType, + accountId, + region, + changeView, + view, + autoBounds, + boundsOverride, + legend, + } = useWaffleOptionsContext(); + const { currentTime, jumpToTime, isAutoReloading } = useWaffleTimeContext(); + const { filterQueryAsJson, applyFilterQuery } = useWaffleFiltersContext(); + const { loading, nodes, reload, interval } = useSnapshot( + filterQueryAsJson, + [metric], + groupBy, + nodeType, + sourceId, + currentTime, + accountId, + region, + false + ); + + const legendPalette = legend?.palette ?? DEFAULT_LEGEND.palette; + const legendSteps = legend?.steps ?? DEFAULT_LEGEND.steps; + const legendReverseColors = legend?.reverseColors ?? DEFAULT_LEGEND.reverseColors; + + const options = { + formatter: InfraFormatterType.percent, + formatTemplate: '{{value}}', + legend: createLegend(legendPalette, legendSteps, legendReverseColors), + metric, + sort, + fields: source?.configuration?.fields, + groupBy, + }; + + useInterval( + () => { + if (!loading) { + jumpToTime(Date.now()); + } + }, + isAutoReloading ? 5000 : null + ); + + const intervalAsString = convertIntervalToString(interval); + const dataBounds = calculateBoundsFromNodes(nodes); + const bounds = autoBounds ? dataBounds : boundsOverride; /* eslint-disable-next-line react-hooks/exhaustive-deps */ - }, [reload, shouldLoadDefault]); - - useEffect(() => { - setShowLoading(true); - }, [options.metric, nodeType]); - - useEffect(() => { - const hasNodes = nodes && nodes.length; - // Don't show loading screen when we're auto-reloading - setShowLoading(!hasNodes); - }, [nodes]); - - return ( - <> - - - {({ measureRef: pageMeasureRef, bounds: { width = 0 } }) => ( - - - {({ measureRef: topActionMeasureRef, bounds: { height: topActionHeight = 0 } }) => ( - <> - - - - - - - - - - - - - - - - - {({ measureRef, bounds: { height = 0 } }) => ( - <> - - {view === 'map' && ( - { + if (currentView) { + onViewChange(currentView); + } + }, [currentView, onViewChange]); + + useEffect(() => { + // load snapshot data after default view loaded, unless we're not loading a view + if (currentView != null || !shouldLoadDefault) { + reload(); + } + + /** + * INFO: why disable exhaustive-deps + * We need to wait on the currentView not to be null because it is loaded async and could change the view state. + * We don't actually need to watch the value of currentView though, since the view state will be synched up by the + * changing params in the reload method so we should only "watch" the reload method. + * + * TODO: Should refactor this in the future to make it more clear where all the view state is coming + * from and it's precedence [query params, localStorage, defaultView, out of the box view] + */ + /* eslint-disable-next-line react-hooks/exhaustive-deps */ + }, [reload, shouldLoadDefault]); + + useEffect(() => { + setShowLoading(true); + }, [options.metric, nodeType]); + + useEffect(() => { + const hasNodes = nodes && nodes.length; + // Don't show loading screen when we're auto-reloading + setShowLoading(!hasNodes); + }, [nodes]); + + return ( + <> + + + {({ measureRef: pageMeasureRef, bounds: { width = 0 } }) => ( + + + {({ + measureRef: topActionMeasureRef, + bounds: { height: topActionHeight = 0 }, + }) => ( + <> + + + + + + + + + + + + + + + + + {({ measureRef, bounds: { height = 0 } }) => ( + <> + - + {view === 'map' && ( + - - )} - - )} - - - )} - - - )} - - - - ); -}; + width={width} + > + + + )} + + )} + + + )} + + + )} + + + + ); + } +); const MainContainer = euiStyled.div` position: relative; diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/layout_view.tsx b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/layout_view.tsx new file mode 100644 index 0000000000000..1e66fe22ac45e --- /dev/null +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/layout_view.tsx @@ -0,0 +1,15 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { useSavedViewContext } from '../../../../containers/saved_view/saved_view'; +import { Layout } from './layout'; + +export const LayoutView = () => { + const { shouldLoadDefault, currentView } = useSavedViewContext(); + return ; +}; diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/index.tsx b/x-pack/plugins/infra/public/pages/metrics/inventory_view/index.tsx index 57073fee13c18..81a8d1ae8d294 100644 --- a/x-pack/plugins/infra/public/pages/metrics/inventory_view/index.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/index.tsx @@ -21,7 +21,7 @@ import { ViewSourceConfigurationButton } from '../../../components/source_config import { Source } from '../../../containers/metrics_source'; import { useTrackPageview } from '../../../../../observability/public'; import { useKibana } from '../../../../../../../src/plugins/kibana_react/public'; -import { Layout } from './components/layout'; +import { LayoutView } from './components/layout_view'; import { useLinkProps } from '../../../hooks/use_link_props'; import { SavedViewProvider } from '../../../containers/saved_view/saved_view'; import { DEFAULT_WAFFLE_VIEW_STATE } from './hooks/use_waffle_view_state'; @@ -69,7 +69,7 @@ export const SnapshotPage = () => { viewType={'inventory-view'} defaultViewState={DEFAULT_WAFFLE_VIEW_STATE} > - + ) : hasFailedLoadingSource ? ( diff --git a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/lib/metric_query.ts b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/lib/metric_query.ts index fdf96a583479a..42ba918694482 100644 --- a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/lib/metric_query.ts +++ b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/lib/metric_query.ts @@ -13,6 +13,7 @@ import { createPercentileAggregation } from './create_percentile_aggregation'; import { calculateDateHistogramOffset } from '../../../metrics/lib/calculate_date_histogram_offset'; const MINIMUM_BUCKETS = 5; +const COMPOSITE_RESULTS_PER_PAGE = 100; const getParsedFilterQuery: (filterQuery: string | undefined) => Record | null = ( filterQuery @@ -83,7 +84,7 @@ export const getElasticsearchMetricQuery = ( ? { groupings: { composite: { - size: 10, + size: COMPOSITE_RESULTS_PER_PAGE, sources: Array.isArray(groupBy) ? groupBy.map((field, index) => ({ [`groupBy${index}`]: { diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/append.test.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/append.test.tsx new file mode 100644 index 0000000000000..af3651525d6a3 --- /dev/null +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/append.test.tsx @@ -0,0 +1,118 @@ +/* + * 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 { act } from 'react-dom/test-utils'; +import { setup, SetupResult, getProcessorValue } from './processor.helpers'; + +const APPEND_TYPE = 'append'; + +describe('Processor: Append', () => { + let onUpdate: jest.Mock; + let testBed: SetupResult; + + beforeAll(() => { + jest.useFakeTimers(); + }); + + afterAll(() => { + jest.useRealTimers(); + }); + + beforeEach(async () => { + onUpdate = jest.fn(); + + await act(async () => { + testBed = await setup({ + value: { + processors: [], + }, + onFlyoutOpen: jest.fn(), + onUpdate, + }); + }); + testBed.component.update(); + const { + actions: { addProcessor, addProcessorType }, + } = testBed; + // Open the processor flyout + addProcessor(); + + // Add type (the other fields are not visible until a type is selected) + await addProcessorType(APPEND_TYPE); + }); + + test('prevents form submission if required fields are not provided', async () => { + const { + actions: { saveNewProcessor }, + form, + } = testBed; + + // Click submit button with only the type defined + await saveNewProcessor(); + + // Expect form error as "field" and "value" are required parameters + expect(form.getErrorsMessages()).toEqual([ + 'A field value is required.', + 'A value is required.', + ]); + }); + + test('saves with required parameter values', async () => { + const { + actions: { saveNewProcessor }, + form, + find, + component, + } = testBed; + + // Add "field" value (required) + form.setInputValue('fieldNameField.input', 'field_1'); + + await act(async () => { + find('appendValueField.input').simulate('change', [{ label: 'Some_Value' }]); + }); + component.update(); + + // Save the field + await saveNewProcessor(); + + const processors = getProcessorValue(onUpdate, APPEND_TYPE); + expect(processors[0].append).toEqual({ + field: 'field_1', + value: ['Some_Value'], + }); + }); + + test('allows optional parameters to be set', async () => { + const { + actions: { saveNewProcessor }, + form, + find, + component, + } = testBed; + + // Add "field" value (required) + form.setInputValue('fieldNameField.input', 'field_1'); + + // Set optional parameteres + await act(async () => { + find('appendValueField.input').simulate('change', [{ label: 'Some_Value' }]); + component.update(); + }); + + form.toggleEuiSwitch('ignoreFailureSwitch.input'); + // Save the field with new changes + await saveNewProcessor(); + + const processors = getProcessorValue(onUpdate, APPEND_TYPE); + expect(processors[0].append).toEqual({ + field: 'field_1', + ignore_failure: true, + value: ['Some_Value'], + }); + }); +}); diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/bytes.test.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/bytes.test.tsx index af4f6e468ca36..51117c3b517f9 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/bytes.test.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/bytes.test.tsx @@ -35,24 +35,21 @@ describe('Processor: Bytes', () => { }); }); testBed.component.update(); - }); - - test('prevents form submission if required fields are not provided', async () => { const { - actions: { addProcessor, saveNewProcessor, addProcessorType }, - form, + actions: { addProcessor, addProcessorType }, } = testBed; - - // Open flyout to add new processor + // Open the processor flyout addProcessor(); - // Click submit button without entering any fields - await saveNewProcessor(); - - // Expect form error as a processor type is required - expect(form.getErrorsMessages()).toEqual(['A type is required.']); // Add type (the other fields are not visible until a type is selected) await addProcessorType(BYTES_TYPE); + }); + + test('prevents form submission if required fields are not provided', async () => { + const { + actions: { saveNewProcessor }, + form, + } = testBed; // Click submit button with only the type defined await saveNewProcessor(); @@ -61,16 +58,12 @@ describe('Processor: Bytes', () => { expect(form.getErrorsMessages()).toEqual(['A field value is required.']); }); - test('saves with default parameter values', async () => { + test('saves with required parameter values', async () => { const { - actions: { addProcessor, saveNewProcessor, addProcessorType }, + actions: { saveNewProcessor }, form, } = testBed; - // Open flyout to add new processor - addProcessor(); - // Add type (the other fields are not visible until a type is selected) - await addProcessorType(BYTES_TYPE); // Add "field" value (required) form.setInputValue('fieldNameField.input', 'field_1'); // Save the field @@ -84,14 +77,10 @@ describe('Processor: Bytes', () => { test('allows optional parameters to be set', async () => { const { - actions: { addProcessor, addProcessorType, saveNewProcessor }, + actions: { saveNewProcessor }, form, } = testBed; - // Open flyout to add new processor - addProcessor(); - // Add type (the other fields are not visible until a type is selected) - await addProcessorType(BYTES_TYPE); // Add "field" value (required) form.setInputValue('fieldNameField.input', 'field_1'); diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/common_processor_fields.test.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/common_processor_fields.test.tsx index af4c187df75e6..af5c8c50abf7b 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/common_processor_fields.test.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/common_processor_fields.test.tsx @@ -37,6 +37,21 @@ describe('Processor: Common Fields For All Processors', () => { testBed.component.update(); }); + test('prevents form submission if required type field is not provided', async () => { + const { + actions: { addProcessor, saveNewProcessor }, + form, + } = testBed; + + // Open flyout to add new processor + addProcessor(); + // Click submit button without entering any fields + await saveNewProcessor(); + + // Expect form error as a processor type is required + expect(form.getErrorsMessages()).toEqual(['A type is required.']); + }); + test('saves with common fields set', async () => { const { actions: { addProcessor, saveNewProcessor, addProcessorType }, diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/fail.test.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/fail.test.tsx new file mode 100644 index 0000000000000..db5840379536a --- /dev/null +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/fail.test.tsx @@ -0,0 +1,77 @@ +/* + * 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 { act } from 'react-dom/test-utils'; +import { setup, SetupResult, getProcessorValue } from './processor.helpers'; + +const FAIL_TYPE = 'fail'; +describe('Processor: Fail', () => { + let onUpdate: jest.Mock; + let testBed: SetupResult; + + beforeAll(() => { + jest.useFakeTimers(); + }); + + afterAll(() => { + jest.useRealTimers(); + }); + + beforeEach(async () => { + onUpdate = jest.fn(); + + await act(async () => { + testBed = await setup({ + value: { + processors: [], + }, + onFlyoutOpen: jest.fn(), + onUpdate, + }); + }); + testBed.component.update(); + + const { + actions: { addProcessor, addProcessorType }, + } = testBed; + // Open the processor flyout + addProcessor(); + + // Add type (the other fields are not visible until a type is selected) + await addProcessorType(FAIL_TYPE); + }); + + test('prevents form submission if required message field is not provided', async () => { + const { + actions: { saveNewProcessor }, + form, + } = testBed; + + // Click submit button with only the type defined + await saveNewProcessor(); + + // Expect form error as "field" is required parameter + expect(form.getErrorsMessages()).toEqual(['A message is required.']); + }); + + test('saves with required parameter value', async () => { + const { + actions: { saveNewProcessor }, + form, + } = testBed; + + // Add "message" value (required) + form.setInputValue('messageField.input', 'Test Error Message'); + // Save the field + await saveNewProcessor(); + + const processors = getProcessorValue(onUpdate, FAIL_TYPE); + expect(processors[0].fail).toEqual({ + message: 'Test Error Message', + }); + }); +}); diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/processor.helpers.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/processor.helpers.tsx index 8616241a43d8d..797d26f2404ab 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/processor.helpers.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/processor.helpers.tsx @@ -137,8 +137,10 @@ type TestSubject = | 'addProcessorForm.submitButton' | 'addProcessorButton' | 'addProcessorForm.submitButton' + | 'appendValueField.input' | 'processorTypeSelector.input' | 'fieldNameField.input' + | 'messageField.input' | 'mockCodeEditor' | 'tagField.input' | 'ignoreMissingSwitch.input' diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/uri_parts.test.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/uri_parts.test.tsx index 573adad3247f5..442788a7f75aa 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/uri_parts.test.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/uri_parts.test.tsx @@ -12,8 +12,6 @@ import { setup, SetupResult, getProcessorValue } from './processor.helpers'; const defaultUriPartsParameters = { keep_original: undefined, remove_if_successful: undefined, - ignore_failure: undefined, - description: undefined, }; const URI_PARTS_TYPE = 'uri_parts'; @@ -43,19 +41,22 @@ describe('Processor: URI parts', () => { }); }); testBed.component.update(); - }); - test('prevents form submission if required fields are not provided', async () => { const { - actions: { addProcessor, saveNewProcessor, addProcessorType }, - form, + actions: { addProcessor, addProcessorType }, } = testBed; - - // Open flyout to add new processor + // Open the processor flyout addProcessor(); // Add type (the other fields are not visible until a type is selected) await addProcessorType(URI_PARTS_TYPE); + }); + + test('prevents form submission if required fields are not provided', async () => { + const { + actions: { saveNewProcessor }, + form, + } = testBed; // Click submit button with only the type defined await saveNewProcessor(); @@ -64,16 +65,12 @@ describe('Processor: URI parts', () => { expect(form.getErrorsMessages()).toEqual(['A field value is required.']); }); - test('saves with default parameter values', async () => { + test('saves with required parameter values', async () => { const { - actions: { addProcessor, saveNewProcessor, addProcessorType }, + actions: { saveNewProcessor }, form, } = testBed; - // Open flyout to add new processor - addProcessor(); - // Add type (the other fields are not visible until a type is selected) - await addProcessorType(URI_PARTS_TYPE); // Add "field" value (required) form.setInputValue('fieldNameField.input', 'field_1'); // Save the field @@ -88,14 +85,10 @@ describe('Processor: URI parts', () => { test('allows optional parameters to be set', async () => { const { - actions: { addProcessor, addProcessorType, saveNewProcessor }, + actions: { saveNewProcessor }, form, } = testBed; - // Open flyout to add new processor - addProcessor(); - // Add type (the other fields are not visible until a type is selected) - await addProcessorType(URI_PARTS_TYPE); // Add "field" value (required) form.setInputValue('fieldNameField.input', 'field_1'); @@ -109,9 +102,7 @@ describe('Processor: URI parts', () => { const processors = getProcessorValue(onUpdate, URI_PARTS_TYPE); expect(processors[0].uri_parts).toEqual({ - description: undefined, field: 'field_1', - ignore_failure: undefined, keep_original: false, remove_if_successful: true, target_field: 'target_field', diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/append.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/append.tsx index d10347b4d9655..fde39e7462009 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/append.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/append.tsx @@ -52,7 +52,12 @@ export const Append: FunctionComponent = () => { })} /> - + ); }; diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/fail.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/fail.tsx index f2fe6d6ef95f6..156aa9ae1545a 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/fail.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/fail.tsx @@ -37,5 +37,12 @@ const fieldsConfig: FieldsConfig = { }; export const Fail: FunctionComponent = () => { - return ; + return ( + + ); }; diff --git a/x-pack/plugins/lens/public/assets/globe_illustration.tsx b/x-pack/plugins/lens/public/assets/globe_illustration.tsx new file mode 100644 index 0000000000000..af2f2c7a48e46 --- /dev/null +++ b/x-pack/plugins/lens/public/assets/globe_illustration.tsx @@ -0,0 +1,26 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import * as React from 'react'; +import { EuiIconProps } from '@elastic/eui'; + +export const GlobeIllustration = ({ title, titleId, ...props }: Omit) => ( + + + + + +); diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/data_panel_wrapper.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/data_panel_wrapper.tsx index 3296ddac7bc29..9bf03025e400f 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/data_panel_wrapper.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/data_panel_wrapper.tsx @@ -15,6 +15,7 @@ import { Action } from './state_management'; import { DragContext, DragDropIdentifier } from '../../drag_drop'; import { StateSetter, FramePublicAPI, DatasourceDataPanelProps, Datasource } from '../../types'; import { Query, Filter } from '../../../../../../src/plugins/data/public'; +import { UiActionsStart } from '../../../../../../src/plugins/ui_actions/public'; interface DataPanelWrapperProps { datasourceState: unknown; @@ -29,6 +30,7 @@ interface DataPanelWrapperProps { filters: Filter[]; dropOntoWorkspace: (field: DragDropIdentifier) => void; hasSuggestionForField: (field: DragDropIdentifier) => boolean; + plugins: { uiActions: UiActionsStart }; } export const DataPanelWrapper = memo((props: DataPanelWrapperProps) => { @@ -56,6 +58,7 @@ export const DataPanelWrapper = memo((props: DataPanelWrapperProps) => { showNoDataPopover: props.showNoDataPopover, dropOntoWorkspace: props.dropOntoWorkspace, hasSuggestionForField: props.hasSuggestionForField, + uiActions: props.plugins.uiActions, }; const [showDatasourceSwitcher, setDatasourceSwitcher] = useState(false); diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.tsx index 362787ea91c4f..91b59664ada83 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.tsx @@ -332,6 +332,7 @@ export function EditorFrame(props: EditorFrameProps) { showNoDataPopover={props.showNoDataPopover} dropOntoWorkspace={dropOntoWorkspace} hasSuggestionForField={hasSuggestionForField} + plugins={props.plugins} /> } configPanel={ diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/geo_field_workspace_panel.scss b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/geo_field_workspace_panel.scss new file mode 100644 index 0000000000000..ca1a62cae64d6 --- /dev/null +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/geo_field_workspace_panel.scss @@ -0,0 +1,18 @@ +@import '../../../mixins'; + +.lnsVisualizeGeoFieldWorkspacePanel__dragDrop { + padding: $euiSizeXXL ($euiSizeXL * 2); + border: $euiBorderThin; + border-radius: $euiBorderRadius; + + &.lnsDragDrop-isDropTarget { + @include lnsDroppable; + @include lnsDroppableActive; + + } + + &.lnsDragDrop-isActiveDropTarget { + @include lnsDroppableActiveHover; + + } +} diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/geo_field_workspace_panel.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/geo_field_workspace_panel.tsx new file mode 100644 index 0000000000000..b50b6463c5a25 --- /dev/null +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/geo_field_workspace_panel.tsx @@ -0,0 +1,75 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { EuiPageContentBody, EuiText } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { i18n } from '@kbn/i18n'; +import { + UiActionsStart, + VISUALIZE_GEO_FIELD_TRIGGER, +} from '../../../../../../../src/plugins/ui_actions/public'; +import { getVisualizeGeoFieldMessage } from '../../../utils'; +import { DragDrop } from '../../../drag_drop'; +import { GlobeIllustration } from '../../../assets/globe_illustration'; +import './geo_field_workspace_panel.scss'; + +interface Props { + fieldType: string; + fieldName: string; + indexPatternId: string; + uiActions: UiActionsStart; +} + +const dragDropIdentifier = { + id: 'lnsGeoFieldWorkspace', + humanData: { + label: i18n.translate('xpack.lens.geoFieldWorkspace.dropZoneLabel', { + defaultMessage: 'drop zone to open in maps', + }), + }, +}; + +const dragDropOrder = [1, 0, 0, 0]; + +export function GeoFieldWorkspacePanel(props: Props) { + function onDrop() { + props.uiActions.getTrigger(VISUALIZE_GEO_FIELD_TRIGGER).exec({ + indexPatternId: props.indexPatternId, + fieldName: props.fieldName, + }); + } + + return ( + + +

+ {getVisualizeGeoFieldMessage(props.fieldType)} +

+ + +

+ + + +

+
+
+
+ ); +} diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.tsx index a31146e500434..3d5d9a6d84d81 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.tsx @@ -338,17 +338,22 @@ export const InnerWorkspacePanel = React.memo(function InnerWorkspacePanel({ ); }; - return ( - + const dragDropContext = useContext(DragContext); + + const renderDragDrop = () => { + const customWorkspaceRenderer = + activeDatasourceId && + datasourceMap[activeDatasourceId]?.getCustomWorkspaceRenderer && + dragDropContext.dragging + ? datasourceMap[activeDatasourceId].getCustomWorkspaceRenderer!( + datasourceStates[activeDatasourceId].state, + dragDropContext.dragging + ) + : undefined; + + return customWorkspaceRenderer ? ( + customWorkspaceRenderer() + ) : ( + ); + }; + + return ( + + {renderDragDrop()} ); }); diff --git a/x-pack/plugins/lens/public/editor_frame_service/service.tsx b/x-pack/plugins/lens/public/editor_frame_service/service.tsx index 849baa93652cc..46dc326a015a8 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/service.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/service.tsx @@ -44,7 +44,7 @@ export interface EditorFrameStartPlugins { embeddable?: EmbeddableStart; dashboard?: DashboardStart; expressions: ExpressionsStart; - uiActions?: UiActionsStart; + uiActions: UiActionsStart; charts: ChartsPluginSetup; } diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/datapanel.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/datapanel.test.tsx index 6c5116436dddb..eeec7871a262c 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/datapanel.test.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/datapanel.test.tsx @@ -22,6 +22,7 @@ import { documentField } from './document_field'; import { chartPluginMock } from '../../../../../src/plugins/charts/public/mocks'; import { indexPatternFieldEditorPluginMock } from '../../../../../src/plugins/index_pattern_field_editor/public/mocks'; import { getFieldByNameFactory } from './pure_helpers'; +import { uiActionsPluginMock } from '../../../../../src/plugins/ui_actions/public/mocks'; const fieldsOne = [ { @@ -267,6 +268,7 @@ describe('IndexPattern Data Panel', () => { showNoDataPopover: jest.fn(), dropOntoWorkspace: jest.fn(), hasSuggestionForField: jest.fn(() => false), + uiActions: uiActionsPluginMock.createStartContract(), }; }); diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/datapanel.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/datapanel.tsx index 4839d9388253b..a0a6b30e541a7 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/datapanel.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/datapanel.tsx @@ -41,6 +41,7 @@ import { fieldExists } from './pure_helpers'; import { Loader } from '../loader'; import { esQuery, IIndexPattern } from '../../../../../src/plugins/data/public'; import { IndexPatternFieldEditorStart } from '../../../../../src/plugins/index_pattern_field_editor/public'; +import { VISUALIZE_GEO_FIELD_TRIGGER } from '../../../../../src/plugins/ui_actions/public'; export type Props = Omit, 'core'> & { data: DataPublicPluginStart; @@ -73,6 +74,8 @@ const supportedFieldTypes = new Set([ 'ip_range', 'histogram', 'document', + 'geo_point', + 'geo_shape', ]); const fieldTypeNames: Record = { @@ -83,6 +86,8 @@ const fieldTypeNames: Record = { date: i18n.translate('xpack.lens.datatypes.date', { defaultMessage: 'date' }), ip: i18n.translate('xpack.lens.datatypes.ipAddress', { defaultMessage: 'IP' }), histogram: i18n.translate('xpack.lens.datatypes.histogram', { defaultMessage: 'histogram' }), + geo_point: i18n.translate('xpack.lens.datatypes.geoPoint', { defaultMessage: 'geo_point' }), + geo_shape: i18n.translate('xpack.lens.datatypes.geoShape', { defaultMessage: 'geo_shape' }), }; // Wrapper around esQuery.buildEsQuery, handling errors (e.g. because a query can't be parsed) by @@ -121,6 +126,7 @@ export function IndexPatternDataPanel({ showNoDataPopover, dropOntoWorkspace, hasSuggestionForField, + uiActions, }: Props) { const { indexPatternRefs, indexPatterns, currentIndexPatternId } = state; const onChangeIndexPattern = useCallback( @@ -233,6 +239,7 @@ export function IndexPatternDataPanel({ existenceFetchTimeout={state.existenceFetchTimeout} dropOntoWorkspace={dropOntoWorkspace} hasSuggestionForField={hasSuggestionForField} + uiActions={uiActions} /> )} @@ -286,6 +293,7 @@ export const InnerIndexPatternDataPanel = function InnerIndexPatternDataPanel({ charts, dropOntoWorkspace, hasSuggestionForField, + uiActions, }: Omit & { data: DataPublicPluginStart; core: CoreStart; @@ -310,7 +318,10 @@ export const InnerIndexPatternDataPanel = function InnerIndexPatternDataPanel({ isMetaAccordionOpen: false, }); const currentIndexPattern = indexPatterns[currentIndexPatternId]; - const allFields = currentIndexPattern.fields; + const visualizeGeoFieldTrigger = uiActions.getTrigger(VISUALIZE_GEO_FIELD_TRIGGER); + const allFields = visualizeGeoFieldTrigger + ? currentIndexPattern.fields + : currentIndexPattern.fields.filter(({ type }) => type !== 'geo_point' && type !== 'geo_shape'); const clearLocalState = () => setLocalState((s) => ({ ...s, nameFilter: '', typeFilter: [] })); const hasSyncedExistingFields = existingFields[currentIndexPattern.title]; const availableFieldTypes = uniq(allFields.map(({ type }) => type)).filter( @@ -807,6 +818,7 @@ export const InnerIndexPatternDataPanel = function InnerIndexPatternDataPanel({ hasSuggestionForField={hasSuggestionForField} editField={editField} removeField={removeField} + uiActions={uiActions} />
diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/field_item.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/field_item.test.tsx index dcc11ea426117..cf9f7c0c559e4 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/field_item.test.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/field_item.test.tsx @@ -17,6 +17,7 @@ import { dataPluginMock } from '../../../../../src/plugins/data/public/mocks'; import { IndexPattern } from './types'; import { chartPluginMock } from '../../../../../src/plugins/charts/public/mocks'; import { documentField } from './document_field'; +import { uiActionsPluginMock } from '../../../../../src/plugins/ui_actions/public/mocks'; const chartsThemeService = chartPluginMock.createSetupContract().theme; @@ -109,6 +110,7 @@ describe('IndexPattern Field Item', () => { itemIndex: 0, dropOntoWorkspace: () => {}, hasSuggestionForField: () => false, + uiActions: uiActionsPluginMock.createStartContract(), }; data.fieldFormats = ({ diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/field_item.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/field_item.tsx index fce4fcda14cfc..013bb46500d0d 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/field_item.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/field_item.tsx @@ -54,6 +54,9 @@ import { BucketedAggregation, FieldStatsResponse } from '../../common'; import { IndexPattern, IndexPatternField, DraggedField } from './types'; import { LensFieldIcon } from './lens_field_icon'; import { trackUiEvent } from '../lens_ui_telemetry'; +import { UiActionsStart } from '../../../../../src/plugins/ui_actions/public'; +import { VisualizeGeoFieldButton } from './visualize_geo_field_button'; +import { getVisualizeGeoFieldMessage } from '../utils'; import { debouncedComponent } from '../debounced_component'; @@ -75,6 +78,7 @@ export interface FieldItemProps { editField?: (name: string) => void; removeField?: (name: string) => void; hasSuggestionForField: DatasourceDataPanelProps['hasSuggestionForField']; + uiActions: UiActionsStart; } interface State { @@ -149,7 +153,13 @@ export const InnerFieldItem = function InnerFieldItem(props: FieldItemProps) { function fetchData() { // Range types don't have any useful stats we can show - if (state.isLoading || field.type === 'document' || field.type.includes('range')) { + if ( + state.isLoading || + field.type === 'document' || + field.type.includes('range') || + field.type === 'geo_point' || + field.type === 'geo_shape' + ) { return; } @@ -392,6 +402,7 @@ function FieldItemPopoverContents(props: State & FieldItemProps) { removeField, hasSuggestionForField, hideDetails, + uiActions, } = props; const chartTheme = chartsThemeService.useChartsTheme(); @@ -467,6 +478,21 @@ function FieldItemPopoverContents(props: State & FieldItemProps) { ); + } else if (field.type === 'geo_point' || field.type === 'geo_shape') { + return ( + <> + {panelHeader} + + {getVisualizeGeoFieldMessage(field.type)} + + + + + ); } else if ( (!props.histogram || props.histogram.buckets.length === 0) && (!props.topValues || props.topValues.buckets.length === 0) diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/field_list.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/field_list.tsx index ee0011ad0390c..13d5d25bc2ea2 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/field_list.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/field_list.tsx @@ -14,6 +14,7 @@ import { NoFieldsCallout } from './no_fields_callout'; import { IndexPatternField } from './types'; import { FieldItemSharedProps, FieldsAccordion } from './fields_accordion'; import { DatasourceDataPanelProps } from '../types'; +import { UiActionsStart } from '../../../../../src/plugins/ui_actions/public'; const PAGINATION_SIZE = 50; export type FieldGroups = Record< @@ -55,6 +56,7 @@ export const FieldList = React.memo(function FieldList({ hasSuggestionForField, editField, removeField, + uiActions, }: { exists: (field: IndexPatternField) => boolean; fieldGroups: FieldGroups; @@ -72,6 +74,7 @@ export const FieldList = React.memo(function FieldList({ hasSuggestionForField: DatasourceDataPanelProps['hasSuggestionForField']; editField?: (name: string) => void; removeField?: (name: string) => void; + uiActions: UiActionsStart; }) { const [pageSize, setPageSize] = useState(PAGINATION_SIZE); const [scrollContainer, setScrollContainer] = useState(undefined); @@ -155,6 +158,7 @@ export const FieldList = React.memo(function FieldList({ groupIndex={0} dropOntoWorkspace={dropOntoWorkspace} hasSuggestionForField={hasSuggestionForField} + uiActions={uiActions} /> )) )} @@ -206,6 +210,7 @@ export const FieldList = React.memo(function FieldList({ defaultNoFieldsMessage={fieldGroup.defaultNoFieldsMessage} /> } + uiActions={uiActions} />
diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/fields_accordion.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/fields_accordion.test.tsx index c8c48e2accf9b..6270b94abf565 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/fields_accordion.test.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/fields_accordion.test.tsx @@ -15,6 +15,7 @@ import { IndexPattern } from './types'; import { FieldItem } from './field_item'; import { FieldsAccordion, FieldsAccordionProps, FieldItemSharedProps } from './fields_accordion'; import { chartPluginMock } from '../../../../../src/plugins/charts/public/mocks'; +import { uiActionsPluginMock } from '../../../../../src/plugins/ui_actions/public/mocks'; describe('Fields Accordion', () => { let defaultProps: FieldsAccordionProps; @@ -76,6 +77,7 @@ describe('Fields Accordion', () => { groupIndex: 0, dropOntoWorkspace: () => {}, hasSuggestionForField: () => false, + uiActions: uiActionsPluginMock.createStartContract(), }; }); diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/fields_accordion.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/fields_accordion.tsx index 42de7cb328b13..9f5409f9837f4 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/fields_accordion.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/fields_accordion.tsx @@ -24,6 +24,7 @@ import { Query, Filter } from '../../../../../src/plugins/data/public'; import { DatasourceDataPanelProps } from '../types'; import { IndexPattern } from './types'; import { ChartsPluginSetup } from '../../../../../src/plugins/charts/public'; +import { UiActionsStart } from '../../../../../src/plugins/ui_actions/public'; export interface FieldItemSharedProps { core: DatasourceDataPanelProps['core']; @@ -57,6 +58,7 @@ export interface FieldsAccordionProps { hasSuggestionForField: DatasourceDataPanelProps['hasSuggestionForField']; editField?: (name: string) => void; removeField?: (name: string) => void; + uiActions: UiActionsStart; } export const FieldsAccordion = memo(function InnerFieldsAccordion({ @@ -80,6 +82,7 @@ export const FieldsAccordion = memo(function InnerFieldsAccordion({ hasSuggestionForField, editField, removeField, + uiActions, }: FieldsAccordionProps) { const renderField = useCallback( (field: IndexPatternField, index) => ( @@ -95,6 +98,7 @@ export const FieldsAccordion = memo(function InnerFieldsAccordion({ hasSuggestionForField={hasSuggestionForField} editField={editField} removeField={removeField} + uiActions={uiActions} /> ), [ @@ -106,6 +110,7 @@ export const FieldsAccordion = memo(function InnerFieldsAccordion({ groupIndex, editField, removeField, + uiActions, ] ); diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/index.ts b/x-pack/plugins/lens/public/indexpattern_datasource/index.ts index a556c6ce0c095..f8bc84643bcab 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/index.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/index.ts @@ -15,6 +15,7 @@ import { DataPublicPluginStart, } from '../../../../../src/plugins/data/public'; import { Datasource, EditorFrameSetup } from '../types'; +import { UiActionsStart } from '../../../../../src/plugins/ui_actions/public'; export interface IndexPatternDatasourceSetupPlugins { expressions: ExpressionsSetup; @@ -26,6 +27,7 @@ export interface IndexPatternDatasourceSetupPlugins { export interface IndexPatternDatasourceStartPlugins { data: DataPublicPluginStart; indexPatternFieldEditor: IndexPatternFieldEditorStart; + uiActions: UiActionsStart; } export class IndexPatternDatasource { @@ -44,20 +46,23 @@ export class IndexPatternDatasource { getTimeScaleFunction, getSuffixFormatter, } = await import('../async_services'); - return core.getStartServices().then(([coreStart, { data, indexPatternFieldEditor }]) => { - data.fieldFormats.register([getSuffixFormatter(data.fieldFormats.deserialize)]); - expressions.registerFunction(getTimeScaleFunction(data)); - expressions.registerFunction(counterRate); - expressions.registerFunction(renameColumns); - expressions.registerFunction(formatColumn); - return getIndexPatternDatasource({ - core: coreStart, - storage: new Storage(localStorage), - data, - charts, - indexPatternFieldEditor, - }); - }) as Promise; + return core + .getStartServices() + .then(([coreStart, { data, indexPatternFieldEditor, uiActions }]) => { + data.fieldFormats.register([getSuffixFormatter(data.fieldFormats.deserialize)]); + expressions.registerFunction(getTimeScaleFunction(data)); + expressions.registerFunction(counterRate); + expressions.registerFunction(renameColumns); + expressions.registerFunction(formatColumn); + return getIndexPatternDatasource({ + core: coreStart, + storage: new Storage(localStorage), + data, + charts, + indexPatternFieldEditor, + uiActions, + }); + }) as Promise; }); } } diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.test.ts b/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.test.ts index c291c7ab3eac0..b1ff7b36b47a3 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.test.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.test.ts @@ -17,6 +17,7 @@ import { getFieldByNameFactory } from './pure_helpers'; import { operationDefinitionMap, getErrorMessages } from './operations'; import { createMockedReferenceOperation } from './operations/mocks'; import { indexPatternFieldEditorPluginMock } from 'src/plugins/index_pattern_field_editor/public/mocks'; +import { uiActionsPluginMock } from '../../../../../src/plugins/ui_actions/public/mocks'; jest.mock('./loader'); jest.mock('../id_generator'); @@ -172,6 +173,7 @@ describe('IndexPattern Data Source', () => { data: dataPluginMock.createStartContract(), charts: chartPluginMock.createSetupContract(), indexPatternFieldEditor: indexPatternFieldEditorPluginMock.createStartContract(), + uiActions: uiActionsPluginMock.createStartContract(), }); baseState = { diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.tsx index 81eb46e816715..8fb0994c42fb9 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.tsx @@ -45,7 +45,7 @@ import { import { isDraggedField, normalizeOperationDataType } from './utils'; import { LayerPanel } from './layerpanel'; import { IndexPatternColumn, getErrorMessages, IncompleteColumn } from './operations'; -import { IndexPatternPrivateState, IndexPatternPersistedState } from './types'; +import { IndexPatternField, IndexPatternPrivateState, IndexPatternPersistedState } from './types'; import { KibanaContextProvider } from '../../../../../src/plugins/kibana_react/public'; import { DataPublicPluginStart } from '../../../../../src/plugins/data/public'; import { VisualizeFieldContext } from '../../../../../src/plugins/ui_actions/public'; @@ -53,6 +53,9 @@ import { mergeLayer } from './state_helpers'; import { Datasource, StateSetter } from '../types'; import { ChartsPluginSetup } from '../../../../../src/plugins/charts/public'; import { deleteColumn, isReferenced } from './operations'; +import { UiActionsStart } from '../../../../../src/plugins/ui_actions/public'; +import { GeoFieldWorkspacePanel } from '../editor_frame_service/editor_frame/workspace_panel/geo_field_workspace_panel'; +import { DraggingIdentifier } from '../drag_drop'; export { OperationType, IndexPatternColumn, deleteColumn } from './operations'; @@ -78,12 +81,14 @@ export function getIndexPatternDatasource({ data, charts, indexPatternFieldEditor, + uiActions, }: { core: CoreStart; storage: IStorageWrapper; data: DataPublicPluginStart; charts: ChartsPluginSetup; indexPatternFieldEditor: IndexPatternFieldEditorStart; + uiActions: UiActionsStart; }) { const uiSettings = core.uiSettings; const onIndexPatternLoadError = (err: Error) => @@ -197,6 +202,7 @@ export function getIndexPatternDatasource({ indexPatternFieldEditor={indexPatternFieldEditor} {...props} core={core} + uiActions={uiActions} /> , domElement @@ -320,6 +326,34 @@ export function getIndexPatternDatasource({ getDropProps, onDrop, + getCustomWorkspaceRenderer: (state: IndexPatternPrivateState, dragging: DraggingIdentifier) => { + if (dragging.field === undefined || dragging.indexPatternId === undefined) { + return undefined; + } + + const draggedField = dragging as DraggingIdentifier & { + field: IndexPatternField; + indexPatternId: string; + }; + const geoFieldType = + draggedField.field.esTypes && + draggedField.field.esTypes.find((esType) => { + return ['geo_point', 'geo_shape'].includes(esType); + }); + return geoFieldType + ? () => { + return ( + + ); + } + : undefined; + }, + // Reset the temporary invalid state when closing the editor, but don't // update the state if it's not needed updateStateOnCloseDimension: ({ state, layerId, columnId }) => { diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/visualize_geo_field_button.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/visualize_geo_field_button.tsx new file mode 100644 index 0000000000000..3101fb12b933a --- /dev/null +++ b/x-pack/plugins/lens/public/indexpattern_datasource/visualize_geo_field_button.tsx @@ -0,0 +1,72 @@ +/* + * 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, { MouseEvent, useEffect, useState } from 'react'; +import { EuiButton } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { + visualizeGeoFieldTrigger, + VISUALIZE_GEO_FIELD_TRIGGER, + UiActionsStart, +} from '../../../../../src/plugins/ui_actions/public'; + +interface Props { + indexPatternId: string; + fieldName: string; + uiActions: UiActionsStart; +} + +export function VisualizeGeoFieldButton(props: Props) { + const [href, setHref] = useState(undefined); + + async function loadHref() { + const actions = await props.uiActions.getTriggerCompatibleActions(VISUALIZE_GEO_FIELD_TRIGGER, { + indexPatternId: props.indexPatternId, + fieldName: props.fieldName, + }); + const triggerOptions = { + indexPatternId: props.indexPatternId, + fieldName: props.fieldName, + trigger: visualizeGeoFieldTrigger, + }; + const loadedHref = actions.length ? await actions[0].getHref?.(triggerOptions) : undefined; + setHref(loadedHref); + } + + useEffect( + () => { + loadHref(); + }, + /* eslint-disable-next-line react-hooks/exhaustive-deps */ + [] + ); + + function onClick(event: MouseEvent) { + event.preventDefault(); + props.uiActions.getTrigger(VISUALIZE_GEO_FIELD_TRIGGER).exec({ + indexPatternId: props.indexPatternId, + fieldName: props.fieldName, + }); + } + + return ( + <> + {/* eslint-disable-next-line @elastic/eui/href-or-on-click */} + + + + + ); +} diff --git a/x-pack/plugins/lens/public/pie_visualization/render_function.test.tsx b/x-pack/plugins/lens/public/pie_visualization/render_function.test.tsx index 4dcd9772b61b4..7191da0af6bfe 100644 --- a/x-pack/plugins/lens/public/pie_visualization/render_function.test.tsx +++ b/x-pack/plugins/lens/public/pie_visualization/render_function.test.tsx @@ -13,6 +13,7 @@ import { NodeColorAccessor, ShapeTreeNode, HierarchyOfArrays, + Chart, } from '@elastic/charts'; import { shallow } from 'enzyme'; import { LensMultiTable } from '../types'; @@ -42,12 +43,12 @@ describe('PieVisualization component', () => { type: 'datatable', columns: [ { id: 'a', name: 'a', meta: { type: 'number' } }, - { id: 'b', name: 'b', meta: { type: 'number' } }, - { id: 'c', name: 'c', meta: { type: 'string' } }, + { id: 'b', name: 'b', meta: { type: 'string' } }, + { id: 'c', name: 'c', meta: { type: 'number' } }, ], rows: [ - { a: 6, b: 2, c: 'I', d: 'Row 1' }, - { a: 1, b: 5, c: 'J', d: 'Row 2' }, + { a: 6, b: 'I', c: 2, d: 'Row 1' }, + { a: 1, b: 'J', c: 5, d: 'Row 2' }, ], }, }, @@ -249,14 +250,14 @@ describe('PieVisualization component', () => { Object { "id": "b", "meta": Object { - "type": "number", + "type": "string", }, "name": "b", }, Object { "id": "c", "meta": Object { - "type": "string", + "type": "number", }, "name": "c", }, @@ -264,14 +265,14 @@ describe('PieVisualization component', () => { "rows": Array [ Object { "a": 6, - "b": 2, - "c": "I", + "b": "I", + "c": 2, "d": "Row 1", }, Object { "a": 1, - "b": 5, - "c": "J", + "b": "J", + "c": 5, "d": "Row 2", }, ], @@ -292,6 +293,48 @@ describe('PieVisualization component', () => { expect(component.find(Settings).first().prop('onElementClick')).toBeUndefined(); }); + test('it renders the empty placeholder when metric contains only falsy data', () => { + const defaultData = getDefaultArgs().data; + const emptyData: LensMultiTable = { + ...defaultData, + tables: { + first: { + ...defaultData.tables.first, + rows: [ + { a: 0, b: 'I', c: 0, d: 'Row 1' }, + { a: 0, b: 'J', c: null, d: 'Row 2' }, + ], + }, + }, + }; + + const component = shallow( + + ); + expect(component.find(EmptyPlaceholder)).toHaveLength(1); + }); + + test('it renders the chart when metric contains truthy data and buckets contain only falsy data', () => { + const defaultData = getDefaultArgs().data; + const emptyData: LensMultiTable = { + ...defaultData, + tables: { + first: { + ...defaultData.tables.first, + // a and b are buckets, c is a metric + rows: [{ a: 0, b: undefined, c: 12 }], + }, + }, + }; + + const component = shallow( + + ); + + expect(component.find(EmptyPlaceholder)).toHaveLength(0); + expect(component.find(Chart)).toHaveLength(1); + }); + test('it shows emptyPlaceholder for undefined grouped data', () => { const defaultData = getDefaultArgs().data; const emptyData: LensMultiTable = { @@ -300,8 +343,8 @@ describe('PieVisualization component', () => { first: { ...defaultData.tables.first, rows: [ - { a: undefined, b: undefined, c: 'I', d: 'Row 1' }, - { a: undefined, b: undefined, c: 'J', d: 'Row 2' }, + { a: undefined, b: 'I', c: undefined, d: 'Row 1' }, + { a: undefined, b: 'J', c: undefined, d: 'Row 2' }, ], }, }, diff --git a/x-pack/plugins/lens/public/pie_visualization/render_function.tsx b/x-pack/plugins/lens/public/pie_visualization/render_function.tsx index b6bb2908b9ed2..cc31222f6b9ab 100644 --- a/x-pack/plugins/lens/public/pie_visualization/render_function.tsx +++ b/x-pack/plugins/lens/public/pie_visualization/render_function.tsx @@ -222,11 +222,15 @@ export function PieComponent( const value = row[metricColumn.id]; return typeof value === 'number' && value < 0; }); + + const isMetricEmpty = firstTable.rows.every((row) => { + return !row[metricColumn.id]; + }); + const isEmpty = firstTable.rows.length === 0 || - firstTable.rows.every((row) => - groups.every((colId) => !row[colId] || typeof row[colId] === 'undefined') - ); + firstTable.rows.every((row) => groups.every((colId) => typeof row[colId] === 'undefined')) || + isMetricEmpty; if (isEmpty) { return ; diff --git a/x-pack/plugins/lens/public/types.ts b/x-pack/plugins/lens/public/types.ts index 94b4433a82551..51d679e7c40e5 100644 --- a/x-pack/plugins/lens/public/types.ts +++ b/x-pack/plugins/lens/public/types.ts @@ -33,6 +33,7 @@ import type { LensResizeActionData, LensToggleActionData, } from './datatable_visualization/components/types'; +import { UiActionsStart } from '../../../../src/plugins/ui_actions/public'; export type ErrorCallback = (e: { message: string }) => void; @@ -213,6 +214,10 @@ export interface Datasource { } ) => { dropTypes: DropType[]; nextLabel?: string } | undefined; onDrop: (props: DatasourceDimensionDropHandlerProps) => false | true | { deleted: string }; + getCustomWorkspaceRenderer?: ( + state: T, + dragging: DraggingIdentifier + ) => undefined | (() => JSX.Element); updateStateOnCloseDimension?: (props: { layerId: string; columnId: string; @@ -267,6 +272,7 @@ export interface DatasourceDataPanelProps { filters: Filter[]; dropOntoWorkspace: (field: DragDropIdentifier) => void; hasSuggestionForField: (field: DragDropIdentifier) => boolean; + uiActions: UiActionsStart; } interface SharedDimensionProps { @@ -343,7 +349,7 @@ export type DatasourceDimensionDropHandlerProps = DatasourceDimensionDropProp dropType: DropType; }; -export type FieldOnlyDataType = 'document' | 'ip' | 'histogram'; +export type FieldOnlyDataType = 'document' | 'ip' | 'histogram' | 'geo_point' | 'geo_shape'; export type DataType = 'string' | 'number' | 'date' | 'boolean' | FieldOnlyDataType; // An operation represents a column in a table, not any information diff --git a/x-pack/plugins/lens/public/utils.ts b/x-pack/plugins/lens/public/utils.ts index ab95141431541..2d8cfee2185fa 100644 --- a/x-pack/plugins/lens/public/utils.ts +++ b/x-pack/plugins/lens/public/utils.ts @@ -42,3 +42,10 @@ export const desanitizeFilterContext = ( } return result; }; + +export function getVisualizeGeoFieldMessage(fieldType: string) { + return i18n.translate('xpack.lens.visualizeGeoFieldMessage', { + defaultMessage: `Lens cannot visualize {fieldType} fields`, + values: { fieldType }, + }); +} diff --git a/x-pack/plugins/lens/public/xy_visualization/xy_suggestions.ts b/x-pack/plugins/lens/public/xy_visualization/xy_suggestions.ts index 1616a4c141476..d5120cfae973c 100644 --- a/x-pack/plugins/lens/public/xy_visualization/xy_suggestions.ts +++ b/x-pack/plugins/lens/public/xy_visualization/xy_suggestions.ts @@ -27,6 +27,8 @@ const columnSortOrder = { boolean: 4, number: 5, histogram: 6, + geo_point: 7, + geo_shape: 8, }; /** diff --git a/x-pack/plugins/lists/common/constants.mock.ts b/x-pack/plugins/lists/common/constants.mock.ts index 177f0a4b291d5..90f4825b97d43 100644 --- a/x-pack/plugins/lists/common/constants.mock.ts +++ b/x-pack/plugins/lists/common/constants.mock.ts @@ -6,10 +6,15 @@ */ import moment from 'moment'; +import { + EndpointEntriesArray, + EntriesArray, + Entry, + EntryMatch, + EntryNested, + OsTypeArray, +} from '@kbn/securitysolution-io-ts-utils'; -import { OsTypeArray } from './schemas/common'; -import { EntriesArray, Entry, EntryMatch, EntryNested } from './schemas/types'; -import { EndpointEntriesArray } from './schemas/types/endpoint'; export const DATE_NOW = '2020-04-20T15:25:31.830Z'; export const OLD_DATE_RELATIVE_TO_DATE_NOW = '2020-04-19T15:25:31.830Z'; export const USER = 'some user'; diff --git a/x-pack/plugins/lists/common/exceptions/build_exceptions_filter.test.ts b/x-pack/plugins/lists/common/exceptions/build_exceptions_filter.test.ts index 291831777e471..fa073b3b4cfb6 100644 --- a/x-pack/plugins/lists/common/exceptions/build_exceptions_filter.test.ts +++ b/x-pack/plugins/lists/common/exceptions/build_exceptions_filter.test.ts @@ -5,6 +5,8 @@ * 2.0. */ +import { EntryMatchAny } from '@kbn/securitysolution-io-ts-utils'; + import { getEntryMatchExcludeMock, getEntryMatchMock } from '../schemas/types/entry_match.mock'; import { getEntryMatchAnyExcludeMock, @@ -16,14 +18,10 @@ import { getEntryNestedMixedEntries, getEntryNestedMock, } from '../schemas/types/entry_nested.mock'; -import { - getExceptionListItemSchemaMock, - getExceptionListItemSchemaXMock, -} from '../schemas/response/exception_list_item_schema.mock'; -import { EntryMatchAny, ExceptionListItemSchema } from '../schemas'; +import { getExceptionListItemSchemaMock } from '../schemas/response/exception_list_item_schema.mock'; +import { ExceptionListItemSchema } from '../schemas'; import { - ExceptionItemSansLargeValueLists, buildExceptionFilter, buildExceptionItemFilter, buildExclusionClause, @@ -31,10 +29,8 @@ import { buildMatchAnyClause, buildMatchClause, buildNestedClause, - chunkExceptions, createOrClauses, } from './build_exceptions_filter'; -import { hasLargeValueList } from './utils'; const modifiedGetEntryMatchAnyMock = (): EntryMatchAny => ({ ...getEntryMatchAnyMock(), @@ -42,13 +38,6 @@ const modifiedGetEntryMatchAnyMock = (): EntryMatchAny => ({ value: ['some "host" name', 'some other host name'], }); -const getExceptionListItemsWoValueLists = (num: number): ExceptionItemSansLargeValueLists[] => { - const items = getExceptionListItemSchemaXMock(num); - return items.filter( - ({ entries }) => !hasLargeValueList(entries) - ) as ExceptionItemSansLargeValueLists[]; -}; - describe('build_exceptions_filter', () => { describe('buildExceptionFilter', () => { test('it should return undefined if no exception items', () => { @@ -431,55 +420,6 @@ describe('build_exceptions_filter', () => { }); }); - describe('chunkExceptions', () => { - test('it should NOT split a single should clause as there is nothing to split on with chunkSize 1', () => { - const exceptions = getExceptionListItemsWoValueLists(1); - const chunks = chunkExceptions(exceptions, 1); - expect(chunks).toHaveLength(1); - }); - - test('it should NOT split a single should clause as there is nothing to split on with chunkSize 2', () => { - const exceptions = getExceptionListItemsWoValueLists(1) as ExceptionItemSansLargeValueLists[]; - const chunks = chunkExceptions(exceptions, 2); - expect(chunks).toHaveLength(1); - }); - - test('it should return an empty array if no exception items passed in', () => { - const chunks = chunkExceptions([], 2); - expect(chunks).toEqual([]); - }); - - test('it should split an array of size 2 into a length 2 array with chunks on "chunkSize: 1"', () => { - const exceptions = getExceptionListItemsWoValueLists(2); - const chunks = chunkExceptions(exceptions, 1); - expect(chunks).toHaveLength(2); - }); - - test('it should split an array of size 2 into a length 4 array with chunks on "chunkSize: 1"', () => { - const exceptions = getExceptionListItemsWoValueLists(4); - const chunks = chunkExceptions(exceptions, 1); - expect(chunks).toHaveLength(4); - }); - - test('it should split an array of size 4 into a length 2 array with chunks on "chunkSize: 2"', () => { - const exceptions = getExceptionListItemsWoValueLists(4); - const chunks = chunkExceptions(exceptions, 2); - expect(chunks).toHaveLength(2); - }); - - test('it should NOT split an array of size 4 into any groups on "chunkSize: 5"', () => { - const exceptions = getExceptionListItemsWoValueLists(4); - const chunks = chunkExceptions(exceptions, 5); - expect(chunks).toHaveLength(1); - }); - - test('it should split an array of size 4 into 2 groups on "chunkSize: 3"', () => { - const exceptions = getExceptionListItemsWoValueLists(4); - const chunks = chunkExceptions(exceptions, 3); - expect(chunks).toHaveLength(2); - }); - }); - describe('createOrClauses', () => { test('it should create filter with one item if only one exception item exists', () => { const booleanFilter = createOrClauses([ diff --git a/x-pack/plugins/lists/common/exceptions/build_exceptions_filter.ts b/x-pack/plugins/lists/common/exceptions/build_exceptions_filter.ts index 0a9753b02a612..0fa069ba51013 100644 --- a/x-pack/plugins/lists/common/exceptions/build_exceptions_filter.ts +++ b/x-pack/plugins/lists/common/exceptions/build_exceptions_filter.ts @@ -6,20 +6,19 @@ */ import { chunk } from 'lodash/fp'; - -import { Filter } from '../../../../../src/plugins/data/common'; import { - CreateExceptionListItemSchema, EntryExists, EntryMatch, EntryMatchAny, EntryNested, - ExceptionListItemSchema, entriesExists, entriesMatch, entriesMatchAny, entriesNested, -} from '../schemas'; +} from '@kbn/securitysolution-io-ts-utils'; + +import { Filter } from '../../../../../src/plugins/data/common'; +import { CreateExceptionListItemSchema, ExceptionListItemSchema } from '../schemas'; import { BooleanFilter, NestedFilter } from './types'; import { hasLargeValueList } from './utils'; diff --git a/x-pack/plugins/lists/common/exceptions/utils.ts b/x-pack/plugins/lists/common/exceptions/utils.ts index d7dc706882cd5..689687e44256a 100644 --- a/x-pack/plugins/lists/common/exceptions/utils.ts +++ b/x-pack/plugins/lists/common/exceptions/utils.ts @@ -5,20 +5,9 @@ * 2.0. */ -import { CreateExceptionListItemSchema, EntriesArray, ExceptionListItemSchema } from '../schemas'; - -export const hasLargeValueItem = ( - exceptionItems: Array -): boolean => { - return exceptionItems.some((exceptionItem) => hasLargeValueList(exceptionItem.entries)); -}; +import { EntriesArray } from '@kbn/securitysolution-io-ts-utils'; export const hasLargeValueList = (entries: EntriesArray): boolean => { const found = entries.filter(({ type }) => type === 'list'); return found.length > 0; }; - -export const hasNestedEntry = (entries: EntriesArray): boolean => { - const found = entries.filter(({ type }) => type === 'nested'); - return found.length > 0; -}; diff --git a/x-pack/plugins/lists/common/format_errors.test.ts b/x-pack/plugins/lists/common/format_errors.test.ts deleted file mode 100644 index e95d9c1d5b461..0000000000000 --- a/x-pack/plugins/lists/common/format_errors.test.ts +++ /dev/null @@ -1,189 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import * as t from 'io-ts'; - -import { formatErrors } from './format_errors'; - -describe('utils', () => { - test('returns an empty error message string if there are no errors', () => { - const errors: t.Errors = []; - const output = formatErrors(errors); - expect(output).toEqual([]); - }); - - test('returns a single error message if given one', () => { - const validationError: t.ValidationError = { - context: [], - message: 'some error', - value: 'Some existing error', - }; - const errors: t.Errors = [validationError]; - const output = formatErrors(errors); - expect(output).toEqual(['some error']); - }); - - test('returns a two error messages if given two', () => { - const validationError1: t.ValidationError = { - context: [], - message: 'some error 1', - value: 'Some existing error 1', - }; - const validationError2: t.ValidationError = { - context: [], - message: 'some error 2', - value: 'Some existing error 2', - }; - const errors: t.Errors = [validationError1, validationError2]; - const output = formatErrors(errors); - expect(output).toEqual(['some error 1', 'some error 2']); - }); - - test('it filters out duplicate error messages', () => { - const validationError1: t.ValidationError = { - context: [], - message: 'some error 1', - value: 'Some existing error 1', - }; - const validationError2: t.ValidationError = { - context: [], - message: 'some error 1', - value: 'Some existing error 1', - }; - const errors: t.Errors = [validationError1, validationError2]; - const output = formatErrors(errors); - expect(output).toEqual(['some error 1']); - }); - - test('will use message before context if it is set', () => { - const context: t.Context = ([{ key: 'some string key' }] as unknown) as t.Context; - const validationError1: t.ValidationError = { - context, - message: 'I should be used first', - value: 'Some existing error 1', - }; - const errors: t.Errors = [validationError1]; - const output = formatErrors(errors); - expect(output).toEqual(['I should be used first']); - }); - - test('will use context entry of a single string', () => { - const context: t.Context = ([{ key: 'some string key' }] as unknown) as t.Context; - const validationError1: t.ValidationError = { - context, - value: 'Some existing error 1', - }; - const errors: t.Errors = [validationError1]; - const output = formatErrors(errors); - expect(output).toEqual(['Invalid value "Some existing error 1" supplied to "some string key"']); - }); - - test('will use two context entries of two strings', () => { - const context: t.Context = ([ - { key: 'some string key 1' }, - { key: 'some string key 2' }, - ] as unknown) as t.Context; - const validationError1: t.ValidationError = { - context, - value: 'Some existing error 1', - }; - const errors: t.Errors = [validationError1]; - const output = formatErrors(errors); - expect(output).toEqual([ - 'Invalid value "Some existing error 1" supplied to "some string key 1,some string key 2"', - ]); - }); - - test('will filter out and not use any strings of numbers', () => { - const context: t.Context = ([ - { key: '5' }, - { key: 'some string key 2' }, - ] as unknown) as t.Context; - const validationError1: t.ValidationError = { - context, - value: 'Some existing error 1', - }; - const errors: t.Errors = [validationError1]; - const output = formatErrors(errors); - expect(output).toEqual([ - 'Invalid value "Some existing error 1" supplied to "some string key 2"', - ]); - }); - - test('will filter out and not use null', () => { - const context: t.Context = ([ - { key: null }, - { key: 'some string key 2' }, - ] as unknown) as t.Context; - const validationError1: t.ValidationError = { - context, - value: 'Some existing error 1', - }; - const errors: t.Errors = [validationError1]; - const output = formatErrors(errors); - expect(output).toEqual([ - 'Invalid value "Some existing error 1" supplied to "some string key 2"', - ]); - }); - - test('will filter out and not use empty strings', () => { - const context: t.Context = ([ - { key: '' }, - { key: 'some string key 2' }, - ] as unknown) as t.Context; - const validationError1: t.ValidationError = { - context, - value: 'Some existing error 1', - }; - const errors: t.Errors = [validationError1]; - const output = formatErrors(errors); - expect(output).toEqual([ - 'Invalid value "Some existing error 1" supplied to "some string key 2"', - ]); - }); - - test('will use a name context if it cannot find a keyContext', () => { - const context: t.Context = ([ - { key: '' }, - { key: '', type: { name: 'someName' } }, - ] as unknown) as t.Context; - const validationError1: t.ValidationError = { - context, - value: 'Some existing error 1', - }; - const errors: t.Errors = [validationError1]; - const output = formatErrors(errors); - expect(output).toEqual(['Invalid value "Some existing error 1" supplied to "someName"']); - }); - - test('will return an empty string if name does not exist but type does', () => { - const context: t.Context = ([{ key: '' }, { key: '', type: {} }] as unknown) as t.Context; - const validationError1: t.ValidationError = { - context, - value: 'Some existing error 1', - }; - const errors: t.Errors = [validationError1]; - const output = formatErrors(errors); - expect(output).toEqual(['Invalid value "Some existing error 1" supplied to ""']); - }); - - test('will stringify an error value', () => { - const context: t.Context = ([ - { key: '' }, - { key: 'some string key 2' }, - ] as unknown) as t.Context; - const validationError1: t.ValidationError = { - context, - value: { foo: 'some error' }, - }; - const errors: t.Errors = [validationError1]; - const output = formatErrors(errors); - expect(output).toEqual([ - 'Invalid value "{"foo":"some error"}" supplied to "some string key 2"', - ]); - }); -}); diff --git a/x-pack/plugins/lists/common/format_errors.ts b/x-pack/plugins/lists/common/format_errors.ts deleted file mode 100644 index 16925699b0fcf..0000000000000 --- a/x-pack/plugins/lists/common/format_errors.ts +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import * as t from 'io-ts'; -import { isObject } from 'lodash/fp'; - -/** - * @deprecated Use packages/kbn-securitysolution-io-ts-utils/src/format_errors/index.ts - */ -export const formatErrors = (errors: t.Errors): string[] => { - const err = errors.map((error) => { - if (error.message != null) { - return error.message; - } else { - const keyContext = error.context - .filter( - (entry) => entry.key != null && !Number.isInteger(+entry.key) && entry.key.trim() !== '' - ) - .map((entry) => entry.key) - .join(','); - - const nameContext = error.context.find((entry) => entry.type?.name?.length > 0); - const suppliedValue = - keyContext !== '' ? keyContext : nameContext != null ? nameContext.type.name : ''; - const value = isObject(error.value) ? JSON.stringify(error.value) : error.value; - return `Invalid value "${value}" supplied to "${suppliedValue}"`; - } - }); - - return [...new Set(err)]; -}; diff --git a/x-pack/plugins/lists/common/schemas/common/schemas.test.ts b/x-pack/plugins/lists/common/schemas/common/schemas.test.ts index 9f3abb9259f6c..2b007f01b56eb 100644 --- a/x-pack/plugins/lists/common/schemas/common/schemas.test.ts +++ b/x-pack/plugins/lists/common/schemas/common/schemas.test.ts @@ -7,31 +7,19 @@ import { pipe } from 'fp-ts/lib/pipeable'; import { left } from 'fp-ts/lib/Either'; - -import { exactCheck, foldLeftRight, getPaths } from '../../shared_imports'; - import { - EsDataTypeGeoPoint, - EsDataTypeGeoPointRange, - EsDataTypeRange, - EsDataTypeRangeTerm, - EsDataTypeSingle, - EsDataTypeUnion, ExceptionListTypeEnum, - OperatorEnum, + ListOperatorEnum as OperatorEnum, Type, - esDataTypeGeoPoint, - esDataTypeGeoPointRange, - esDataTypeRange, - esDataTypeRangeTerm, - esDataTypeSingle, - esDataTypeUnion, + exactCheck, exceptionListType, - operator, + foldLeftRight, + getPaths, + listOperator as operator, osType, osTypeArrayOrUndefined, type, -} from './schemas'; +} from '@kbn/securitysolution-io-ts-utils'; describe('Common schemas', () => { describe('operator', () => { @@ -120,269 +108,6 @@ describe('Common schemas', () => { }); }); - describe('esDataTypeRange', () => { - test('it will work with a given gte, lte range', () => { - const payload: EsDataTypeRange = { gte: '127.0.0.1', lte: '127.0.0.1' }; - const decoded = esDataTypeRange.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); - }); - - test('it will give an error if given an extra madeup value', () => { - const payload: EsDataTypeRange & { madeupvalue: string } = { - gte: '127.0.0.1', - lte: '127.0.0.1', - madeupvalue: 'something', - }; - const decoded = esDataTypeRange.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual(['invalid keys "madeupvalue"']); - expect(message.schema).toEqual({}); - }); - }); - - describe('esDataTypeRangeTerm', () => { - test('it will work with a date_range', () => { - const payload: EsDataTypeRangeTerm = { date_range: { gte: '2015', lte: '2017' } }; - const decoded = esDataTypeRangeTerm.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); - }); - - test('it will give an error if given an extra madeup value for date_range', () => { - const payload: EsDataTypeRangeTerm & { madeupvalue: string } = { - date_range: { gte: '2015', lte: '2017' }, - madeupvalue: 'something', - }; - const decoded = esDataTypeRangeTerm.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual(['invalid keys "madeupvalue"']); - expect(message.schema).toEqual({}); - }); - - test('it will work with a double_range', () => { - const payload: EsDataTypeRangeTerm = { double_range: { gte: '2015', lte: '2017' } }; - const decoded = esDataTypeRangeTerm.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); - }); - - test('it will give an error if given an extra madeup value for double_range', () => { - const payload: EsDataTypeRangeTerm & { madeupvalue: string } = { - double_range: { gte: '2015', lte: '2017' }, - madeupvalue: 'something', - }; - const decoded = esDataTypeRangeTerm.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual(['invalid keys "madeupvalue"']); - expect(message.schema).toEqual({}); - }); - - test('it will work with a float_range', () => { - const payload: EsDataTypeRangeTerm = { float_range: { gte: '2015', lte: '2017' } }; - const decoded = esDataTypeRangeTerm.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); - }); - - test('it will give an error if given an extra madeup value for float_range', () => { - const payload: EsDataTypeRangeTerm & { madeupvalue: string } = { - float_range: { gte: '2015', lte: '2017' }, - madeupvalue: 'something', - }; - const decoded = esDataTypeRangeTerm.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual(['invalid keys "madeupvalue"']); - expect(message.schema).toEqual({}); - }); - - test('it will work with a integer_range', () => { - const payload: EsDataTypeRangeTerm = { integer_range: { gte: '2015', lte: '2017' } }; - const decoded = esDataTypeRangeTerm.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); - }); - - test('it will give an error if given an extra madeup value for integer_range', () => { - const payload: EsDataTypeRangeTerm & { madeupvalue: string } = { - integer_range: { gte: '2015', lte: '2017' }, - madeupvalue: 'something', - }; - const decoded = esDataTypeRangeTerm.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual(['invalid keys "madeupvalue"']); - expect(message.schema).toEqual({}); - }); - - test('it will work with a ip_range', () => { - const payload: EsDataTypeRangeTerm = { ip_range: { gte: '127.0.0.1', lte: '127.0.0.2' } }; - const decoded = esDataTypeRangeTerm.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); - }); - - test('it will work with a ip_range as a CIDR', () => { - const payload: EsDataTypeRangeTerm = { ip_range: '127.0.0.1/16' }; - const decoded = esDataTypeRangeTerm.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); - }); - - test('it will give an error if given an extra madeup value for ip_range', () => { - const payload: EsDataTypeRangeTerm & { madeupvalue: string } = { - ip_range: { gte: '127.0.0.1', lte: '127.0.0.2' }, - madeupvalue: 'something', - }; - const decoded = esDataTypeRangeTerm.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual(['invalid keys "madeupvalue"']); - expect(message.schema).toEqual({}); - }); - - test('it will work with a long_range', () => { - const payload: EsDataTypeRangeTerm = { long_range: { gte: '2015', lte: '2017' } }; - const decoded = esDataTypeRangeTerm.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); - }); - - test('it will give an error if given an extra madeup value for long_range', () => { - const payload: EsDataTypeRangeTerm & { madeupvalue: string } = { - long_range: { gte: '2015', lte: '2017' }, - madeupvalue: 'something', - }; - const decoded = esDataTypeRangeTerm.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual(['invalid keys "madeupvalue"']); - expect(message.schema).toEqual({}); - }); - }); - - describe('esDataTypeGeoPointRange', () => { - test('it will work with a given lat, lon range', () => { - const payload: EsDataTypeGeoPointRange = { lat: '20', lon: '30' }; - const decoded = esDataTypeGeoPointRange.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); - }); - - test('it will give an error if given an extra madeup value', () => { - const payload: EsDataTypeGeoPointRange & { madeupvalue: string } = { - lat: '20', - lon: '30', - madeupvalue: 'something', - }; - const decoded = esDataTypeGeoPointRange.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual(['invalid keys "madeupvalue"']); - expect(message.schema).toEqual({}); - }); - }); - - describe('esDataTypeGeoPoint', () => { - test('it will work with a given lat, lon range', () => { - const payload: EsDataTypeGeoPoint = { geo_point: { lat: '127.0.0.1', lon: '127.0.0.1' } }; - const decoded = esDataTypeGeoPoint.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); - }); - - test('it will work with a WKT (Well known text)', () => { - const payload: EsDataTypeGeoPoint = { geo_point: 'POINT (30 10)' }; - const decoded = esDataTypeGeoPoint.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); - }); - - test('it will give an error if given an extra madeup value', () => { - const payload: EsDataTypeGeoPoint & { madeupvalue: string } = { - geo_point: 'POINT (30 10)', - madeupvalue: 'something', - }; - const decoded = esDataTypeGeoPoint.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual(['invalid keys "madeupvalue"']); - expect(message.schema).toEqual({}); - }); - }); - - describe('esDataTypeSingle', () => { - test('it will work with single type', () => { - const payload: EsDataTypeSingle = { boolean: 'true' }; - const decoded = esDataTypeSingle.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); - }); - - test('it will not work with a madeup value', () => { - const payload: EsDataTypeSingle & { madeupValue: 'madeup' } = { - boolean: 'true', - madeupValue: 'madeup', - }; - const decoded = esDataTypeSingle.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual(['invalid keys "madeupValue"']); - expect(message.schema).toEqual({}); - }); - }); - - describe('esDataTypeUnion', () => { - test('it will work with a regular union', () => { - const payload: EsDataTypeUnion = { boolean: 'true' }; - const decoded = esDataTypeUnion.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); - }); - - test('it will not work with a madeup value', () => { - const payload: EsDataTypeUnion & { madeupValue: 'madeupValue' } = { - boolean: 'true', - madeupValue: 'madeupValue', - }; - const decoded = esDataTypeUnion.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual(['invalid keys "madeupValue"']); - expect(message.schema).toEqual({}); - }); - }); - describe('osType', () => { test('it will validate a correct osType', () => { const payload = 'windows'; diff --git a/x-pack/plugins/lists/common/schemas/common/schemas.ts b/x-pack/plugins/lists/common/schemas/common/schemas.ts index f223d56eb15cb..eb84ee07981f3 100644 --- a/x-pack/plugins/lists/common/schemas/common/schemas.ts +++ b/x-pack/plugins/lists/common/schemas/common/schemas.ts @@ -8,49 +8,18 @@ /* eslint-disable @typescript-eslint/naming-convention */ import * as t from 'io-ts'; - -import { DefaultNamespace } from '../types/default_namespace'; -import { DefaultArray, DefaultStringArray, NonEmptyString } from '../../shared_imports'; - -/** - * @deprecated Use packages/kbn-securitysolution-io-ts-utils - */ -export const name = t.string; - -/** - * @deprecated Use packages/kbn-securitysolution-io-ts-utils - */ -export type Name = t.TypeOf; - -/** - * @deprecated Use packages/kbn-securitysolution-io-ts-utils - */ -export const nameOrUndefined = t.union([name, t.undefined]); +import { DefaultNamespace, NonEmptyString } from '@kbn/securitysolution-io-ts-utils'; /** - * @deprecated Use packages/kbn-securitysolution-io-ts-utils + * @deprecated Directly use the type from the package and not from here */ -export type NameOrUndefined = t.TypeOf; - -/** - * @deprecated Use packages/kbn-securitysolution-io-ts-utils - */ -export const description = t.string; - -/** - * @deprecated Use packages/kbn-securitysolution-io-ts-utils - */ -export type Description = t.TypeOf; - -/** - * @deprecated Use packages/kbn-securitysolution-io-ts-utils - */ -export const descriptionOrUndefined = t.union([description, t.undefined]); - -/** - * @deprecated Use packages/kbn-securitysolution-io-ts-utils - */ -export type DescriptionOrUndefined = t.TypeOf; +export { + Type, + OsType, + OsTypeArray, + listOperator as operator, + NonEmptyEntriesArray, +} from '@kbn/securitysolution-io-ts-utils'; export const list_id = NonEmptyString; export type ListId = t.TypeOf; @@ -59,307 +28,7 @@ export type ListIdOrUndefined = t.TypeOf; export const item = t.string; -/** - * @deprecated Use packages/kbn-securitysolution-io-ts-utils - */ -export const created_at = t.string; // TODO: Make this into an ISO Date string check - -/** - * @deprecated Use packages/kbn-securitysolution-io-ts-utils - */ -export const updated_at = t.string; // TODO: Make this into an ISO Date string check - -/** - * @deprecated Use packages/kbn-securitysolution-io-ts-utils - */ -export const updated_by = t.string; - -/** - * @deprecated Use packages/kbn-securitysolution-io-ts-utils - */ -export const created_by = t.string; - export const file = t.object; - -/** - * @deprecated Use packages/kbn-securitysolution-io-ts-utils - */ -export const id = NonEmptyString; - -/** - * @deprecated Use packages/kbn-securitysolution-io-ts-utils - */ -export type Id = t.TypeOf; - -/** - * @deprecated Use packages/kbn-securitysolution-io-ts-utils - */ -export const idOrUndefined = t.union([id, t.undefined]); - -/** - * @deprecated Use packages/kbn-securitysolution-io-ts-utils - */ -export type IdOrUndefined = t.TypeOf; - -export const binary = t.string; -export const binaryOrUndefined = t.union([binary, t.undefined]); - -export const boolean = t.string; -export const booleanOrUndefined = t.union([boolean, t.undefined]); - -export const byte = t.string; -export const byteOrUndefined = t.union([byte, t.undefined]); - -export const date = t.string; -export const dateOrUndefined = t.union([date, t.undefined]); - -export const date_nanos = t.string; -export const dateNanosOrUndefined = t.union([date_nanos, t.undefined]); - -export const double = t.string; -export const doubleOrUndefined = t.union([double, t.undefined]); - -export const float = t.string; -export const floatOrUndefined = t.union([float, t.undefined]); - -export const geo_shape = t.string; -export const geoShapeOrUndefined = t.union([geo_shape, t.undefined]); - -export const half_float = t.string; -export const halfFloatOrUndefined = t.union([half_float, t.undefined]); - -export const integer = t.string; -export const integerOrUndefined = t.union([integer, t.undefined]); - -export const ip = t.string; -export const ipOrUndefined = t.union([ip, t.undefined]); - -export const keyword = t.string; -export const keywordOrUndefined = t.union([keyword, t.undefined]); - -export const text = t.string; -export const textOrUndefined = t.union([text, t.undefined]); - -export const long = t.string; -export const longOrUndefined = t.union([long, t.undefined]); - -export const shape = t.string; -export const shapeOrUndefined = t.union([shape, t.undefined]); - -export const short = t.string; -export const shortOrUndefined = t.union([short, t.undefined]); - -export const value = t.string; -export const valueOrUndefined = t.union([value, t.undefined]); - -export const tie_breaker_id = t.string; // TODO: Use UUID for this instead of a string for validation -export const _index = t.string; - -/** - * @deprecated Use packages/kbn-securitysolution-io-ts-utils - */ -export const type = t.keyof({ - binary: null, - boolean: null, - byte: null, - date: null, - date_nanos: null, - date_range: null, - double: null, - double_range: null, - float: null, - float_range: null, - geo_point: null, - geo_shape: null, - half_float: null, - integer: null, - integer_range: null, - ip: null, - ip_range: null, - keyword: null, - long: null, - long_range: null, - shape: null, - short: null, - text: null, -}); - -export const typeOrUndefined = t.union([type, t.undefined]); -export type Type = t.TypeOf; - -/** - * @deprecated Use packages/kbn-securitysolution-io-ts-utils - */ -export const meta = t.object; - -/** - * @deprecated Use packages/kbn-securitysolution-io-ts-utils - */ -export type Meta = t.TypeOf; - -/** - * @deprecated Use packages/kbn-securitysolution-io-ts-utils - */ -export const metaOrUndefined = t.union([meta, t.undefined]); - -/** - * @deprecated Use packages/kbn-securitysolution-io-ts-utils - */ -export type MetaOrUndefined = t.TypeOf; - -export const esDataTypeRange = t.exact(t.type({ gte: t.string, lte: t.string })); - -export const date_range = esDataTypeRange; -export const dateRangeOrUndefined = t.union([date_range, t.undefined]); - -export const double_range = esDataTypeRange; -export const doubleRangeOrUndefined = t.union([double_range, t.undefined]); - -export const float_range = esDataTypeRange; -export const floatRangeOrUndefined = t.union([float_range, t.undefined]); - -export const integer_range = esDataTypeRange; -export const integerRangeOrUndefined = t.union([integer_range, t.undefined]); - -// ip_range can be just a CIDR value as a range -export const ip_range = t.union([esDataTypeRange, t.string]); -export const ipRangeOrUndefined = t.union([ip_range, t.undefined]); - -export const long_range = esDataTypeRange; -export const longRangeOrUndefined = t.union([long_range, t.undefined]); - -export type EsDataTypeRange = t.TypeOf; - -export const esDataTypeRangeTerm = t.union([ - t.exact(t.type({ date_range })), - t.exact(t.type({ double_range })), - t.exact(t.type({ float_range })), - t.exact(t.type({ integer_range })), - t.exact(t.type({ ip_range })), - t.exact(t.type({ long_range })), -]); - -export type EsDataTypeRangeTerm = t.TypeOf; - -export const esDataTypeGeoPointRange = t.exact(t.type({ lat: t.string, lon: t.string })); -export type EsDataTypeGeoPointRange = t.TypeOf; - -export const geo_point = t.union([esDataTypeGeoPointRange, t.string]); -export type GeoPoint = t.TypeOf; - -export const geoPointOrUndefined = t.union([geo_point, t.undefined]); - -export const esDataTypeGeoPoint = t.exact(t.type({ geo_point })); -export type EsDataTypeGeoPoint = t.TypeOf; - -export const esDataTypeGeoShape = t.union([ - t.exact(t.type({ geo_shape: t.string })), - t.exact(t.type({ shape: t.string })), -]); - -export type EsDataTypeGeoShape = t.TypeOf; - -export const esDataTypeSingle = t.union([ - t.exact(t.type({ binary })), - t.exact(t.type({ boolean })), - t.exact(t.type({ byte })), - t.exact(t.type({ date })), - t.exact(t.type({ date_nanos })), - t.exact(t.type({ double })), - t.exact(t.type({ float })), - t.exact(t.type({ half_float })), - t.exact(t.type({ integer })), - t.exact(t.type({ ip })), - t.exact(t.type({ keyword })), - t.exact(t.type({ long })), - t.exact(t.type({ short })), - t.exact(t.type({ text })), -]); - -export type EsDataTypeSingle = t.TypeOf; - -export const esDataTypeUnion = t.union([ - esDataTypeRangeTerm, - esDataTypeGeoPoint, - esDataTypeGeoShape, - esDataTypeSingle, -]); - -export type EsDataTypeUnion = t.TypeOf; - -/** - * @deprecated Use packages/kbn-securitysolution-io-ts-utils - */ -export const tags = DefaultStringArray; - -/** - * @deprecated Use packages/kbn-securitysolution-io-ts-utils - */ -export type Tags = t.TypeOf; - -/** - * @deprecated Use packages/kbn-securitysolution-io-ts-utils - */ -export const tagsOrUndefined = t.union([tags, t.undefined]); - -/** - * @deprecated Use packages/kbn-securitysolution-io-ts-utils - */ -export type TagsOrUndefined = t.TypeOf; - -/** - * @deprecated Use packages/kbn-securitysolution-io-ts-utils - */ -export const exceptionListType = t.keyof({ - detection: null, - endpoint: null, - endpoint_events: null, -}); - -/** - * @deprecated Use packages/kbn-securitysolution-io-ts-utils - */ -export const exceptionListTypeOrUndefined = t.union([exceptionListType, t.undefined]); - -/** - * @deprecated Use packages/kbn-securitysolution-io-ts-utils - */ -export type ExceptionListType = t.TypeOf; - -/** - * @deprecated Use packages/kbn-securitysolution-io-ts-utils - */ -export type ExceptionListTypeOrUndefined = t.TypeOf; - -/** - * @deprecated Use packages/kbn-securitysolution-io-ts-utils - */ -export enum ExceptionListTypeEnum { - DETECTION = 'detection', - ENDPOINT = 'endpoint', - ENDPOINT_EVENTS = 'endpoint_events', -} - -/** - * @deprecated Use packages/kbn-securitysolution-io-ts-utils - */ -export const exceptionListItemType = t.keyof({ simple: null }); - -/** - * @deprecated Use packages/kbn-securitysolution-io-ts-utils - */ -export const exceptionListItemTypeOrUndefined = t.union([exceptionListItemType, t.undefined]); - -/** - * @deprecated Use packages/kbn-securitysolution-io-ts-utils - */ -export type ExceptionListItemType = t.TypeOf; - -/** - * @deprecated Use packages/kbn-securitysolution-io-ts-utils - */ -export type ExceptionListItemTypeOrUndefined = t.TypeOf; - export const list_type = t.keyof({ item: null, list: null }); export type ListType = t.TypeOf; @@ -404,41 +73,6 @@ export type CursorOrUndefined = t.TypeOf; export const namespace_type = DefaultNamespace; -/** - * @deprecated Use packages/kbn-securitysolution-io-ts-utils - */ -export const operatorIncluded = t.keyof({ included: null }); - -/** - * @deprecated Use packages/kbn-securitysolution-io-ts-utils - */ -export const operator = t.keyof({ excluded: null, included: null }); - -/** - * @deprecated Use packages/kbn-securitysolution-io-ts-utils - */ -export type Operator = t.TypeOf; - -/** - * @deprecated Use packages/kbn-securitysolution-io-ts-utils - */ -export enum OperatorEnum { - INCLUDED = 'included', - EXCLUDED = 'excluded', -} - -/** - * @deprecated Use packages/kbn-securitysolution-io-ts-utils - */ -export enum OperatorTypeEnum { - NESTED = 'nested', - MATCH = 'match', - MATCH_ANY = 'match_any', - WILDCARD = 'wildcard', - EXISTS = 'exists', - LIST = 'list', -} - export const serializer = t.string; export type Serializer = t.TypeOf; @@ -467,36 +101,7 @@ export type Immutable = t.TypeOf; export const immutableOrUndefined = t.union([immutable, t.undefined]); export type ImmutableOrUndefined = t.TypeOf; -/** - * @deprecated Use packages/kbn-securitysolution-io-ts-utils - */ -export const osType = t.keyof({ - linux: null, - macos: null, - windows: null, -}); - -/** - * @deprecated Use packages/kbn-securitysolution-io-ts-utils - */ -export type OsType = t.TypeOf; - -/** - * @deprecated Use packages/kbn-securitysolution-io-ts-utils - */ -export const osTypeArray = DefaultArray(osType); - -/** - * @deprecated Use packages/kbn-securitysolution-io-ts-utils - */ -export type OsTypeArray = t.TypeOf; - -/** - * @deprecated Use packages/kbn-securitysolution-io-ts-utils - */ -export const osTypeArrayOrUndefined = t.union([osTypeArray, t.undefined]); +export const value = t.string; +export const valueOrUndefined = t.union([value, t.undefined]); -/** - * @deprecated Use packages/kbn-securitysolution-io-ts-utils - */ -export type OsTypeArrayOrUndefined = t.OutputOf; +export const tie_breaker_id = t.string; // TODO: Use UUID for this instead of a string for validation diff --git a/x-pack/plugins/lists/common/schemas/index.ts b/x-pack/plugins/lists/common/schemas/index.ts index 0e6312e642c58..7731d555a5dd3 100644 --- a/x-pack/plugins/lists/common/schemas/index.ts +++ b/x-pack/plugins/lists/common/schemas/index.ts @@ -6,9 +6,5 @@ */ export * from './common'; -export * from './elastic_query'; -export * from './elastic_response'; export * from './request'; export * from './response'; -export * from './saved_objects'; -export * from './types'; diff --git a/x-pack/plugins/lists/common/schemas/request/create_endpoint_list_item_schema.test.ts b/x-pack/plugins/lists/common/schemas/request/create_endpoint_list_item_schema.test.ts index 728867b06bca5..30f3acc8a164a 100644 --- a/x-pack/plugins/lists/common/schemas/request/create_endpoint_list_item_schema.test.ts +++ b/x-pack/plugins/lists/common/schemas/request/create_endpoint_list_item_schema.test.ts @@ -7,11 +7,15 @@ import { left } from 'fp-ts/lib/Either'; import { pipe } from 'fp-ts/lib/pipeable'; +import { + CommentsArray, + exactCheck, + foldLeftRight, + getPaths, +} from '@kbn/securitysolution-io-ts-utils'; -import { exactCheck, foldLeftRight, getPaths } from '../../shared_imports'; import { getCreateCommentsArrayMock } from '../types/create_comment.mock'; import { getCommentsMock } from '../types/comment.mock'; -import { CommentsArray } from '../types'; import { CreateEndpointListItemSchema, diff --git a/x-pack/plugins/lists/common/schemas/request/create_endpoint_list_item_schema.ts b/x-pack/plugins/lists/common/schemas/request/create_endpoint_list_item_schema.ts index 6adffd362846d..af58c61dbaf9f 100644 --- a/x-pack/plugins/lists/common/schemas/request/create_endpoint_list_item_schema.ts +++ b/x-pack/plugins/lists/common/schemas/request/create_endpoint_list_item_schema.ts @@ -6,23 +6,24 @@ */ import * as t from 'io-ts'; - import { - ItemId, + CreateCommentsArray, + DefaultCreateCommentsArray, + DefaultUuid, + EntriesArray, OsTypeArray, Tags, description, exceptionListItemType, meta, name, + nonEmptyEndpointEntriesArray, osTypeArrayOrUndefined, tags, -} from '../common/schemas'; +} from '@kbn/securitysolution-io-ts-utils'; + +import { ItemId } from '../common/schemas'; import { RequiredKeepUndefined } from '../../types'; -import { CreateCommentsArray, DefaultCreateCommentsArray } from '../types'; -import { nonEmptyEndpointEntriesArray } from '../types/endpoint'; -import { EntriesArray } from '../types/entries'; -import { DefaultUuid } from '../../shared_imports'; export const createEndpointListItemSchema = t.intersection([ t.exact( diff --git a/x-pack/plugins/lists/common/schemas/request/create_exception_list_item_schema.test.ts b/x-pack/plugins/lists/common/schemas/request/create_exception_list_item_schema.test.ts index d21ed7039694c..1bb58d6195e7c 100644 --- a/x-pack/plugins/lists/common/schemas/request/create_exception_list_item_schema.test.ts +++ b/x-pack/plugins/lists/common/schemas/request/create_exception_list_item_schema.test.ts @@ -7,11 +7,15 @@ import { left } from 'fp-ts/lib/Either'; import { pipe } from 'fp-ts/lib/pipeable'; +import { + CommentsArray, + exactCheck, + foldLeftRight, + getPaths, +} from '@kbn/securitysolution-io-ts-utils'; -import { exactCheck, foldLeftRight, getPaths } from '../../shared_imports'; import { getCreateCommentsArrayMock } from '../types/create_comment.mock'; import { getCommentsMock } from '../types/comment.mock'; -import { CommentsArray } from '../types'; import { CreateExceptionListItemSchema, diff --git a/x-pack/plugins/lists/common/schemas/request/create_exception_list_item_schema.ts b/x-pack/plugins/lists/common/schemas/request/create_exception_list_item_schema.ts index 77f4f15d62038..da5630ef3f002 100644 --- a/x-pack/plugins/lists/common/schemas/request/create_exception_list_item_schema.ts +++ b/x-pack/plugins/lists/common/schemas/request/create_exception_list_item_schema.ts @@ -6,29 +6,25 @@ */ import * as t from 'io-ts'; - import { - ItemId, + CreateCommentsArray, + DefaultCreateCommentsArray, + DefaultUuid, + EntriesArray, + NamespaceType, OsTypeArray, Tags, description, exceptionListItemType, - list_id, meta, name, - namespace_type, + nonEmptyEntriesArray, osTypeArrayOrUndefined, tags, -} from '../common/schemas'; +} from '@kbn/securitysolution-io-ts-utils'; + +import { ItemId, list_id, namespace_type } from '../common/schemas'; import { RequiredKeepUndefined } from '../../types'; -import { - CreateCommentsArray, - DefaultCreateCommentsArray, - NamespaceType, - nonEmptyEntriesArray, -} from '../types'; -import { EntriesArray } from '../types/entries'; -import { DefaultUuid } from '../../shared_imports'; export const createExceptionListItemSchema = t.intersection([ t.exact( diff --git a/x-pack/plugins/lists/common/schemas/request/create_exception_list_schema.test.ts b/x-pack/plugins/lists/common/schemas/request/create_exception_list_schema.test.ts index ceefec34c0462..e6f29bc02702d 100644 --- a/x-pack/plugins/lists/common/schemas/request/create_exception_list_schema.test.ts +++ b/x-pack/plugins/lists/common/schemas/request/create_exception_list_schema.test.ts @@ -7,8 +7,7 @@ import { left } from 'fp-ts/lib/Either'; import { pipe } from 'fp-ts/lib/pipeable'; - -import { exactCheck, foldLeftRight, getPaths } from '../../shared_imports'; +import { exactCheck, foldLeftRight, getPaths } from '@kbn/securitysolution-io-ts-utils'; import { CreateExceptionListSchema, diff --git a/x-pack/plugins/lists/common/schemas/request/create_exception_list_schema.ts b/x-pack/plugins/lists/common/schemas/request/create_exception_list_schema.ts index 0f67f7690d4b1..42955ddbd7017 100644 --- a/x-pack/plugins/lists/common/schemas/request/create_exception_list_schema.ts +++ b/x-pack/plugins/lists/common/schemas/request/create_exception_list_schema.ts @@ -6,26 +6,23 @@ */ import * as t from 'io-ts'; - import { - ListId, + DefaultUuid, + DefaultVersionNumber, + DefaultVersionNumberDecoded, + NamespaceType, OsTypeArray, Tags, description, exceptionListType, meta, name, - namespace_type, osTypeArrayOrUndefined, tags, -} from '../common/schemas'; +} from '@kbn/securitysolution-io-ts-utils'; + +import { ListId, namespace_type } from '../common/schemas'; import { RequiredKeepUndefined } from '../../types'; -import { - DefaultUuid, - DefaultVersionNumber, - DefaultVersionNumberDecoded, -} from '../../shared_imports'; -import { NamespaceType } from '../types'; export const createExceptionListSchema = t.intersection([ t.exact( diff --git a/x-pack/plugins/lists/common/schemas/request/create_list_item_schema.test.ts b/x-pack/plugins/lists/common/schemas/request/create_list_item_schema.test.ts index 8ae65b32cfecb..99fd1f28dcae3 100644 --- a/x-pack/plugins/lists/common/schemas/request/create_list_item_schema.test.ts +++ b/x-pack/plugins/lists/common/schemas/request/create_list_item_schema.test.ts @@ -7,8 +7,7 @@ import { left } from 'fp-ts/lib/Either'; import { pipe } from 'fp-ts/lib/pipeable'; - -import { exactCheck, foldLeftRight, getPaths } from '../../shared_imports'; +import { exactCheck, foldLeftRight, getPaths } from '@kbn/securitysolution-io-ts-utils'; import { getCreateListItemSchemaMock } from './create_list_item_schema.mock'; import { CreateListItemSchema, createListItemSchema } from './create_list_item_schema'; diff --git a/x-pack/plugins/lists/common/schemas/request/create_list_item_schema.ts b/x-pack/plugins/lists/common/schemas/request/create_list_item_schema.ts index 0a23de9b103fa..867b441960a2c 100644 --- a/x-pack/plugins/lists/common/schemas/request/create_list_item_schema.ts +++ b/x-pack/plugins/lists/common/schemas/request/create_list_item_schema.ts @@ -6,8 +6,9 @@ */ import * as t from 'io-ts'; +import { id, meta } from '@kbn/securitysolution-io-ts-utils'; -import { id, list_id, meta, value } from '../common/schemas'; +import { list_id, value } from '../common/schemas'; import { RequiredKeepUndefined } from '../../types'; export const createListItemSchema = t.intersection([ diff --git a/x-pack/plugins/lists/common/schemas/request/create_list_schema.test.ts b/x-pack/plugins/lists/common/schemas/request/create_list_schema.test.ts index fbc9094ec7335..d183465a333af 100644 --- a/x-pack/plugins/lists/common/schemas/request/create_list_schema.test.ts +++ b/x-pack/plugins/lists/common/schemas/request/create_list_schema.test.ts @@ -7,8 +7,7 @@ import { left } from 'fp-ts/lib/Either'; import { pipe } from 'fp-ts/lib/pipeable'; - -import { exactCheck, foldLeftRight, getPaths } from '../../shared_imports'; +import { exactCheck, foldLeftRight, getPaths } from '@kbn/securitysolution-io-ts-utils'; import { CreateListSchema, createListSchema } from './create_list_schema'; import { getCreateListSchemaMock } from './create_list_schema.mock'; diff --git a/x-pack/plugins/lists/common/schemas/request/create_list_schema.ts b/x-pack/plugins/lists/common/schemas/request/create_list_schema.ts index dc77dcb8b91a3..8ac36cc3ad28e 100644 --- a/x-pack/plugins/lists/common/schemas/request/create_list_schema.ts +++ b/x-pack/plugins/lists/common/schemas/request/create_list_schema.ts @@ -6,10 +6,18 @@ */ import * as t from 'io-ts'; +import { + DefaultVersionNumber, + DefaultVersionNumberDecoded, + description, + id, + meta, + name, + type, +} from '@kbn/securitysolution-io-ts-utils'; -import { description, deserializer, id, meta, name, serializer, type } from '../common/schemas'; +import { deserializer, serializer } from '../common/schemas'; import { RequiredKeepUndefined } from '../../types'; -import { DefaultVersionNumber, DefaultVersionNumberDecoded } from '../../shared_imports'; export const createListSchema = t.intersection([ t.exact( diff --git a/x-pack/plugins/lists/common/schemas/request/delete_endpoint_list_item_schema.test.ts b/x-pack/plugins/lists/common/schemas/request/delete_endpoint_list_item_schema.test.ts index 8acb20905b616..11c3eaf866520 100644 --- a/x-pack/plugins/lists/common/schemas/request/delete_endpoint_list_item_schema.test.ts +++ b/x-pack/plugins/lists/common/schemas/request/delete_endpoint_list_item_schema.test.ts @@ -7,8 +7,7 @@ import { left } from 'fp-ts/lib/Either'; import { pipe } from 'fp-ts/lib/pipeable'; - -import { exactCheck, foldLeftRight, getPaths } from '../../shared_imports'; +import { exactCheck, foldLeftRight, getPaths } from '@kbn/securitysolution-io-ts-utils'; import { DeleteEndpointListItemSchema, diff --git a/x-pack/plugins/lists/common/schemas/request/delete_endpoint_list_item_schema.ts b/x-pack/plugins/lists/common/schemas/request/delete_endpoint_list_item_schema.ts index 12d983fe9954b..b8ff0834e8fb8 100644 --- a/x-pack/plugins/lists/common/schemas/request/delete_endpoint_list_item_schema.ts +++ b/x-pack/plugins/lists/common/schemas/request/delete_endpoint_list_item_schema.ts @@ -6,8 +6,9 @@ */ import * as t from 'io-ts'; +import { id } from '@kbn/securitysolution-io-ts-utils'; -import { id, item_id } from '../common/schemas'; +import { item_id } from '../common/schemas'; import { RequiredKeepUndefined } from '../../types'; export const deleteEndpointListItemSchema = t.exact( diff --git a/x-pack/plugins/lists/common/schemas/request/delete_exception_list_item_schema.test.ts b/x-pack/plugins/lists/common/schemas/request/delete_exception_list_item_schema.test.ts index fc500b8821232..63a1e29419760 100644 --- a/x-pack/plugins/lists/common/schemas/request/delete_exception_list_item_schema.test.ts +++ b/x-pack/plugins/lists/common/schemas/request/delete_exception_list_item_schema.test.ts @@ -7,8 +7,7 @@ import { left } from 'fp-ts/lib/Either'; import { pipe } from 'fp-ts/lib/pipeable'; - -import { exactCheck, foldLeftRight, getPaths } from '../../shared_imports'; +import { exactCheck, foldLeftRight, getPaths } from '@kbn/securitysolution-io-ts-utils'; import { DeleteExceptionListItemSchema, diff --git a/x-pack/plugins/lists/common/schemas/request/delete_exception_list_item_schema.ts b/x-pack/plugins/lists/common/schemas/request/delete_exception_list_item_schema.ts index 3a92823b319e8..cc188bf52d75c 100644 --- a/x-pack/plugins/lists/common/schemas/request/delete_exception_list_item_schema.ts +++ b/x-pack/plugins/lists/common/schemas/request/delete_exception_list_item_schema.ts @@ -6,9 +6,9 @@ */ import * as t from 'io-ts'; +import { NamespaceType, id } from '@kbn/securitysolution-io-ts-utils'; -import { id, item_id, namespace_type } from '../common/schemas'; -import { NamespaceType } from '../types'; +import { item_id, namespace_type } from '../common/schemas'; import { RequiredKeepUndefined } from '../../types'; export const deleteExceptionListItemSchema = t.exact( diff --git a/x-pack/plugins/lists/common/schemas/request/delete_exception_list_schema.test.ts b/x-pack/plugins/lists/common/schemas/request/delete_exception_list_schema.test.ts index 0ee6e392f1f5c..ea591f74b6b15 100644 --- a/x-pack/plugins/lists/common/schemas/request/delete_exception_list_schema.test.ts +++ b/x-pack/plugins/lists/common/schemas/request/delete_exception_list_schema.test.ts @@ -7,8 +7,7 @@ import { left } from 'fp-ts/lib/Either'; import { pipe } from 'fp-ts/lib/pipeable'; - -import { exactCheck, foldLeftRight, getPaths } from '../../shared_imports'; +import { exactCheck, foldLeftRight, getPaths } from '@kbn/securitysolution-io-ts-utils'; import { DeleteExceptionListSchema, diff --git a/x-pack/plugins/lists/common/schemas/request/delete_exception_list_schema.ts b/x-pack/plugins/lists/common/schemas/request/delete_exception_list_schema.ts index 8258b4480aec8..b816c08beb363 100644 --- a/x-pack/plugins/lists/common/schemas/request/delete_exception_list_schema.ts +++ b/x-pack/plugins/lists/common/schemas/request/delete_exception_list_schema.ts @@ -6,9 +6,9 @@ */ import * as t from 'io-ts'; +import { NamespaceType, id } from '@kbn/securitysolution-io-ts-utils'; -import { id, list_id, namespace_type } from '../common/schemas'; -import { NamespaceType } from '../types'; +import { list_id, namespace_type } from '../common/schemas'; import { RequiredKeepUndefined } from '../../types'; export const deleteExceptionListSchema = t.exact( diff --git a/x-pack/plugins/lists/common/schemas/request/delete_list_item_schema.test.ts b/x-pack/plugins/lists/common/schemas/request/delete_list_item_schema.test.ts index f2e05d3c3cb7b..350243e10e2b9 100644 --- a/x-pack/plugins/lists/common/schemas/request/delete_list_item_schema.test.ts +++ b/x-pack/plugins/lists/common/schemas/request/delete_list_item_schema.test.ts @@ -7,8 +7,7 @@ import { left } from 'fp-ts/lib/Either'; import { pipe } from 'fp-ts/lib/pipeable'; - -import { exactCheck, foldLeftRight, getPaths } from '../../shared_imports'; +import { exactCheck, foldLeftRight, getPaths } from '@kbn/securitysolution-io-ts-utils'; import { DeleteListItemSchema, deleteListItemSchema } from './delete_list_item_schema'; import { getDeleteListItemSchemaMock } from './delete_list_item_schema.mock'; diff --git a/x-pack/plugins/lists/common/schemas/request/delete_list_item_schema.ts b/x-pack/plugins/lists/common/schemas/request/delete_list_item_schema.ts index 2c5dd5bbeac08..5b4aa63d2d090 100644 --- a/x-pack/plugins/lists/common/schemas/request/delete_list_item_schema.ts +++ b/x-pack/plugins/lists/common/schemas/request/delete_list_item_schema.ts @@ -6,8 +6,9 @@ */ import * as t from 'io-ts'; +import { id } from '@kbn/securitysolution-io-ts-utils'; -import { id, list_id, valueOrUndefined } from '../common/schemas'; +import { list_id, valueOrUndefined } from '../common/schemas'; import { RequiredKeepUndefined } from '../../types'; export const deleteListItemSchema = t.intersection([ diff --git a/x-pack/plugins/lists/common/schemas/request/delete_list_schema.test.ts b/x-pack/plugins/lists/common/schemas/request/delete_list_schema.test.ts index 9efcffb771eb8..92a33c73ba3be 100644 --- a/x-pack/plugins/lists/common/schemas/request/delete_list_schema.test.ts +++ b/x-pack/plugins/lists/common/schemas/request/delete_list_schema.test.ts @@ -7,8 +7,7 @@ import { left } from 'fp-ts/lib/Either'; import { pipe } from 'fp-ts/lib/pipeable'; - -import { exactCheck, foldLeftRight, getPaths } from '../../shared_imports'; +import { exactCheck, foldLeftRight, getPaths } from '@kbn/securitysolution-io-ts-utils'; import { DeleteListSchema, deleteListSchema } from './delete_list_schema'; import { getDeleteListSchemaMock } from './delete_list_schema.mock'; diff --git a/x-pack/plugins/lists/common/schemas/request/delete_list_schema.ts b/x-pack/plugins/lists/common/schemas/request/delete_list_schema.ts index a4045e6c5a812..003dfdc6bd466 100644 --- a/x-pack/plugins/lists/common/schemas/request/delete_list_schema.ts +++ b/x-pack/plugins/lists/common/schemas/request/delete_list_schema.ts @@ -6,10 +6,9 @@ */ import * as t from 'io-ts'; +import { DefaultStringBooleanFalse, id } from '@kbn/securitysolution-io-ts-utils'; -import { id } from '../common/schemas'; import { RequiredKeepUndefined } from '../../types'; -import { DefaultStringBooleanFalse } from '../types/default_string_boolean_false'; export const deleteListSchema = t.intersection([ t.exact( diff --git a/x-pack/plugins/lists/common/schemas/request/export_exception_list_query_schema.test.ts b/x-pack/plugins/lists/common/schemas/request/export_exception_list_query_schema.test.ts index bb9698432f77a..06b432e74342d 100644 --- a/x-pack/plugins/lists/common/schemas/request/export_exception_list_query_schema.test.ts +++ b/x-pack/plugins/lists/common/schemas/request/export_exception_list_query_schema.test.ts @@ -7,8 +7,7 @@ import { left } from 'fp-ts/lib/Either'; import { pipe } from 'fp-ts/lib/pipeable'; - -import { exactCheck, foldLeftRight, getPaths } from '../../shared_imports'; +import { exactCheck, foldLeftRight, getPaths } from '@kbn/securitysolution-io-ts-utils'; import { ExportExceptionListQuerySchema, diff --git a/x-pack/plugins/lists/common/schemas/request/export_exception_list_query_schema.ts b/x-pack/plugins/lists/common/schemas/request/export_exception_list_query_schema.ts index 2c45f2804ba93..d3c18f0d1c485 100644 --- a/x-pack/plugins/lists/common/schemas/request/export_exception_list_query_schema.ts +++ b/x-pack/plugins/lists/common/schemas/request/export_exception_list_query_schema.ts @@ -6,8 +6,9 @@ */ import * as t from 'io-ts'; +import { id } from '@kbn/securitysolution-io-ts-utils'; -import { id, list_id, namespace_type } from '../common/schemas'; +import { list_id, namespace_type } from '../common/schemas'; export const exportExceptionListQuerySchema = t.exact( t.type({ diff --git a/x-pack/plugins/lists/common/schemas/request/export_list_item_query_schema.test.ts b/x-pack/plugins/lists/common/schemas/request/export_list_item_query_schema.test.ts index b45767ec20749..2ac69e0c281b3 100644 --- a/x-pack/plugins/lists/common/schemas/request/export_list_item_query_schema.test.ts +++ b/x-pack/plugins/lists/common/schemas/request/export_list_item_query_schema.test.ts @@ -7,8 +7,7 @@ import { left } from 'fp-ts/lib/Either'; import { pipe } from 'fp-ts/lib/pipeable'; - -import { exactCheck, foldLeftRight, getPaths } from '../../shared_imports'; +import { exactCheck, foldLeftRight, getPaths } from '@kbn/securitysolution-io-ts-utils'; import { ExportListItemQuerySchema, diff --git a/x-pack/plugins/lists/common/schemas/request/find_endpoint_list_item_schema.test.ts b/x-pack/plugins/lists/common/schemas/request/find_endpoint_list_item_schema.test.ts index 985311012eaf5..bd9a2a0bcb9e2 100644 --- a/x-pack/plugins/lists/common/schemas/request/find_endpoint_list_item_schema.test.ts +++ b/x-pack/plugins/lists/common/schemas/request/find_endpoint_list_item_schema.test.ts @@ -7,8 +7,7 @@ import { left } from 'fp-ts/lib/Either'; import { pipe } from 'fp-ts/lib/pipeable'; - -import { exactCheck, foldLeftRight, getPaths } from '../../shared_imports'; +import { exactCheck, foldLeftRight, getPaths } from '@kbn/securitysolution-io-ts-utils'; import { getFindEndpointListItemSchemaDecodedMock, diff --git a/x-pack/plugins/lists/common/schemas/request/find_endpoint_list_item_schema.ts b/x-pack/plugins/lists/common/schemas/request/find_endpoint_list_item_schema.ts index 978f16ed1acd9..13f45a070b2b7 100644 --- a/x-pack/plugins/lists/common/schemas/request/find_endpoint_list_item_schema.ts +++ b/x-pack/plugins/lists/common/schemas/request/find_endpoint_list_item_schema.ts @@ -6,10 +6,10 @@ */ import * as t from 'io-ts'; +import { StringToPositiveNumber } from '@kbn/securitysolution-io-ts-utils'; import { filter, sort_field, sort_order } from '../common/schemas'; import { RequiredKeepUndefined } from '../../types'; -import { StringToPositiveNumber } from '../types/string_to_positive_number'; export const findEndpointListItemSchema = t.exact( t.partial({ diff --git a/x-pack/plugins/lists/common/schemas/request/find_exception_list_item_schema.test.ts b/x-pack/plugins/lists/common/schemas/request/find_exception_list_item_schema.test.ts index 712209efe0ac9..d3a594e052c01 100644 --- a/x-pack/plugins/lists/common/schemas/request/find_exception_list_item_schema.test.ts +++ b/x-pack/plugins/lists/common/schemas/request/find_exception_list_item_schema.test.ts @@ -7,8 +7,8 @@ import { left } from 'fp-ts/lib/Either'; import { pipe } from 'fp-ts/lib/pipeable'; +import { exactCheck, foldLeftRight, getPaths } from '@kbn/securitysolution-io-ts-utils'; -import { exactCheck, foldLeftRight, getPaths } from '../../shared_imports'; import { LIST_ID } from '../../constants.mock'; import { diff --git a/x-pack/plugins/lists/common/schemas/request/find_exception_list_item_schema.ts b/x-pack/plugins/lists/common/schemas/request/find_exception_list_item_schema.ts index baaa3f571fb5b..4abceb4b3592d 100644 --- a/x-pack/plugins/lists/common/schemas/request/find_exception_list_item_schema.ts +++ b/x-pack/plugins/lists/common/schemas/request/find_exception_list_item_schema.ts @@ -6,16 +6,17 @@ */ import * as t from 'io-ts'; - -import { sort_field, sort_order } from '../common/schemas'; -import { RequiredKeepUndefined } from '../../types'; -import { StringToPositiveNumber } from '../types/string_to_positive_number'; import { DefaultNamespaceArray, DefaultNamespaceArrayTypeDecoded, -} from '../types/default_namespace_array'; -import { NonEmptyStringArray } from '../types/non_empty_string_array'; -import { EmptyStringArray, EmptyStringArrayDecoded } from '../types/empty_string_array'; + EmptyStringArray, + EmptyStringArrayDecoded, + NonEmptyStringArray, + StringToPositiveNumber, +} from '@kbn/securitysolution-io-ts-utils'; + +import { sort_field, sort_order } from '../common/schemas'; +import { RequiredKeepUndefined } from '../../types'; export const findExceptionListItemSchema = t.intersection([ t.exact( diff --git a/x-pack/plugins/lists/common/schemas/request/find_exception_list_schema.test.ts b/x-pack/plugins/lists/common/schemas/request/find_exception_list_schema.test.ts index ae07fecef1314..b1ec33878bd2a 100644 --- a/x-pack/plugins/lists/common/schemas/request/find_exception_list_schema.test.ts +++ b/x-pack/plugins/lists/common/schemas/request/find_exception_list_schema.test.ts @@ -7,8 +7,7 @@ import { left } from 'fp-ts/lib/Either'; import { pipe } from 'fp-ts/lib/pipeable'; - -import { exactCheck, foldLeftRight, getPaths } from '../../shared_imports'; +import { exactCheck, foldLeftRight, getPaths } from '@kbn/securitysolution-io-ts-utils'; import { getFindExceptionListSchemaDecodedMock, diff --git a/x-pack/plugins/lists/common/schemas/request/find_exception_list_schema.ts b/x-pack/plugins/lists/common/schemas/request/find_exception_list_schema.ts index 2d5eda1c2ea8e..ea5b5c5aafdb6 100644 --- a/x-pack/plugins/lists/common/schemas/request/find_exception_list_schema.ts +++ b/x-pack/plugins/lists/common/schemas/request/find_exception_list_schema.ts @@ -6,11 +6,14 @@ */ import * as t from 'io-ts'; +import { + DefaultNamespaceArray, + NamespaceTypeArray, + StringToPositiveNumber, +} from '@kbn/securitysolution-io-ts-utils'; import { filter, sort_field, sort_order } from '../common/schemas'; import { RequiredKeepUndefined } from '../../types'; -import { StringToPositiveNumber } from '../types/string_to_positive_number'; -import { DefaultNamespaceArray, NamespaceTypeArray } from '../types/default_namespace_array'; export const findExceptionListSchema = t.exact( t.partial({ diff --git a/x-pack/plugins/lists/common/schemas/request/find_list_item_schema.test.ts b/x-pack/plugins/lists/common/schemas/request/find_list_item_schema.test.ts index dc0d47c080850..7d298c3bdcb1e 100644 --- a/x-pack/plugins/lists/common/schemas/request/find_list_item_schema.test.ts +++ b/x-pack/plugins/lists/common/schemas/request/find_list_item_schema.test.ts @@ -7,8 +7,8 @@ import { left } from 'fp-ts/lib/Either'; import { pipe } from 'fp-ts/lib/pipeable'; +import { exactCheck, foldLeftRight, getPaths } from '@kbn/securitysolution-io-ts-utils'; -import { exactCheck, foldLeftRight, getPaths } from '../../shared_imports'; import { LIST_ID } from '../../constants.mock'; import { diff --git a/x-pack/plugins/lists/common/schemas/request/find_list_item_schema.ts b/x-pack/plugins/lists/common/schemas/request/find_list_item_schema.ts index 434efd55bbcf0..6adf53d0eda86 100644 --- a/x-pack/plugins/lists/common/schemas/request/find_list_item_schema.ts +++ b/x-pack/plugins/lists/common/schemas/request/find_list_item_schema.ts @@ -6,10 +6,10 @@ */ import * as t from 'io-ts'; +import { StringToPositiveNumber } from '@kbn/securitysolution-io-ts-utils'; import { cursor, filter, list_id, sort_field, sort_order } from '../common/schemas'; import { RequiredKeepUndefined } from '../../types'; -import { StringToPositiveNumber } from '../types/string_to_positive_number'; export const findListItemSchema = t.intersection([ t.exact(t.type({ list_id })), diff --git a/x-pack/plugins/lists/common/schemas/request/find_list_schema.test.ts b/x-pack/plugins/lists/common/schemas/request/find_list_schema.test.ts index 70c952d48335e..a700c88618d60 100644 --- a/x-pack/plugins/lists/common/schemas/request/find_list_schema.test.ts +++ b/x-pack/plugins/lists/common/schemas/request/find_list_schema.test.ts @@ -7,8 +7,7 @@ import { left } from 'fp-ts/lib/Either'; import { pipe } from 'fp-ts/lib/pipeable'; - -import { exactCheck, foldLeftRight, getPaths } from '../../shared_imports'; +import { exactCheck, foldLeftRight, getPaths } from '@kbn/securitysolution-io-ts-utils'; import { getFindListSchemaDecodedMock, getFindListSchemaMock } from './find_list_schema.mock'; import { FindListSchemaEncoded, findListSchema } from './find_list_schema'; diff --git a/x-pack/plugins/lists/common/schemas/request/find_list_schema.ts b/x-pack/plugins/lists/common/schemas/request/find_list_schema.ts index eae855a65bcef..bf6a68d97a58e 100644 --- a/x-pack/plugins/lists/common/schemas/request/find_list_schema.ts +++ b/x-pack/plugins/lists/common/schemas/request/find_list_schema.ts @@ -6,9 +6,9 @@ */ import * as t from 'io-ts'; +import { StringToPositiveNumber } from '@kbn/securitysolution-io-ts-utils'; import { cursor, filter, sort_field, sort_order } from '../common/schemas'; -import { StringToPositiveNumber } from '../types/string_to_positive_number'; import { RequiredKeepUndefined } from '../../types'; export const findListSchema = t.exact( diff --git a/x-pack/plugins/lists/common/schemas/request/import_list_item_query_schema.test.ts b/x-pack/plugins/lists/common/schemas/request/import_list_item_query_schema.test.ts index 5e043359f46a2..c00609e66af5b 100644 --- a/x-pack/plugins/lists/common/schemas/request/import_list_item_query_schema.test.ts +++ b/x-pack/plugins/lists/common/schemas/request/import_list_item_query_schema.test.ts @@ -7,8 +7,7 @@ import { left } from 'fp-ts/lib/Either'; import { pipe } from 'fp-ts/lib/pipeable'; - -import { exactCheck, foldLeftRight, getPaths } from '../../shared_imports'; +import { exactCheck, foldLeftRight, getPaths } from '@kbn/securitysolution-io-ts-utils'; import { ImportListItemQuerySchema, diff --git a/x-pack/plugins/lists/common/schemas/request/import_list_item_query_schema.ts b/x-pack/plugins/lists/common/schemas/request/import_list_item_query_schema.ts index a7dd312f1ed80..85644ff556443 100644 --- a/x-pack/plugins/lists/common/schemas/request/import_list_item_query_schema.ts +++ b/x-pack/plugins/lists/common/schemas/request/import_list_item_query_schema.ts @@ -6,9 +6,10 @@ */ import * as t from 'io-ts'; +import { type } from '@kbn/securitysolution-io-ts-utils'; import { RequiredKeepUndefined } from '../../types'; -import { deserializer, list_id, serializer, type } from '../common/schemas'; +import { deserializer, list_id, serializer } from '../common/schemas'; export const importListItemQuerySchema = t.exact( t.partial({ deserializer, list_id, serializer, type }) diff --git a/x-pack/plugins/lists/common/schemas/request/import_list_item_schema.test.ts b/x-pack/plugins/lists/common/schemas/request/import_list_item_schema.test.ts index 6837777ee5c8e..08298a505fa7c 100644 --- a/x-pack/plugins/lists/common/schemas/request/import_list_item_schema.test.ts +++ b/x-pack/plugins/lists/common/schemas/request/import_list_item_schema.test.ts @@ -7,8 +7,7 @@ import { left } from 'fp-ts/lib/Either'; import { pipe } from 'fp-ts/lib/pipeable'; - -import { exactCheck, foldLeftRight, getPaths } from '../../shared_imports'; +import { exactCheck, foldLeftRight, getPaths } from '@kbn/securitysolution-io-ts-utils'; import { ImportListItemSchema, importListItemSchema } from './import_list_item_schema'; import { getImportListItemSchemaMock } from './import_list_item_schema.mock'; diff --git a/x-pack/plugins/lists/common/schemas/request/patch_list_item_schema.test.ts b/x-pack/plugins/lists/common/schemas/request/patch_list_item_schema.test.ts index 2d272e0b5aab3..2ec903eef1a9d 100644 --- a/x-pack/plugins/lists/common/schemas/request/patch_list_item_schema.test.ts +++ b/x-pack/plugins/lists/common/schemas/request/patch_list_item_schema.test.ts @@ -7,8 +7,7 @@ import { left } from 'fp-ts/lib/Either'; import { pipe } from 'fp-ts/lib/pipeable'; - -import { exactCheck, foldLeftRight, getPaths } from '../../shared_imports'; +import { exactCheck, foldLeftRight, getPaths } from '@kbn/securitysolution-io-ts-utils'; import { getPathListItemSchemaMock } from './patch_list_item_schema.mock'; import { PatchListItemSchema, patchListItemSchema } from './patch_list_item_schema'; diff --git a/x-pack/plugins/lists/common/schemas/request/patch_list_item_schema.ts b/x-pack/plugins/lists/common/schemas/request/patch_list_item_schema.ts index 4c7615c5c0bce..edea4f161f248 100644 --- a/x-pack/plugins/lists/common/schemas/request/patch_list_item_schema.ts +++ b/x-pack/plugins/lists/common/schemas/request/patch_list_item_schema.ts @@ -6,8 +6,9 @@ */ import * as t from 'io-ts'; +import { id, meta } from '@kbn/securitysolution-io-ts-utils'; -import { _version, id, meta, value } from '../common/schemas'; +import { _version, value } from '../common/schemas'; import { RequiredKeepUndefined } from '../../types'; export const patchListItemSchema = t.intersection([ diff --git a/x-pack/plugins/lists/common/schemas/request/patch_list_schema.test.ts b/x-pack/plugins/lists/common/schemas/request/patch_list_schema.test.ts index 046741c525d2b..7c0e535aed2c2 100644 --- a/x-pack/plugins/lists/common/schemas/request/patch_list_schema.test.ts +++ b/x-pack/plugins/lists/common/schemas/request/patch_list_schema.test.ts @@ -7,8 +7,7 @@ import { left } from 'fp-ts/lib/Either'; import { pipe } from 'fp-ts/lib/pipeable'; - -import { exactCheck, foldLeftRight, getPaths } from '../../shared_imports'; +import { exactCheck, foldLeftRight, getPaths } from '@kbn/securitysolution-io-ts-utils'; import { getPathListSchemaMock } from './patch_list_schema.mock'; import { PatchListSchema, patchListSchema } from './patch_list_schema'; diff --git a/x-pack/plugins/lists/common/schemas/request/patch_list_schema.ts b/x-pack/plugins/lists/common/schemas/request/patch_list_schema.ts index 09fe5075b393d..144bf9c0f28a0 100644 --- a/x-pack/plugins/lists/common/schemas/request/patch_list_schema.ts +++ b/x-pack/plugins/lists/common/schemas/request/patch_list_schema.ts @@ -6,8 +6,9 @@ */ import * as t from 'io-ts'; +import { description, id, meta, name } from '@kbn/securitysolution-io-ts-utils'; -import { _version, description, id, meta, name, version } from '../common/schemas'; +import { _version, version } from '../common/schemas'; import { RequiredKeepUndefined } from '../../types'; export const patchListSchema = t.intersection([ diff --git a/x-pack/plugins/lists/common/schemas/request/read_endpoint_list_item_schema.test.ts b/x-pack/plugins/lists/common/schemas/request/read_endpoint_list_item_schema.test.ts index 9d3c6edd6a197..1c474db0d0bb7 100644 --- a/x-pack/plugins/lists/common/schemas/request/read_endpoint_list_item_schema.test.ts +++ b/x-pack/plugins/lists/common/schemas/request/read_endpoint_list_item_schema.test.ts @@ -7,8 +7,7 @@ import { left } from 'fp-ts/lib/Either'; import { pipe } from 'fp-ts/lib/pipeable'; - -import { exactCheck, foldLeftRight, getPaths } from '../../shared_imports'; +import { exactCheck, foldLeftRight, getPaths } from '@kbn/securitysolution-io-ts-utils'; import { getReadEndpointListItemSchemaMock } from './read_endpoint_list_item_schema.mock'; import { diff --git a/x-pack/plugins/lists/common/schemas/request/read_endpoint_list_item_schema.ts b/x-pack/plugins/lists/common/schemas/request/read_endpoint_list_item_schema.ts index a3d46fdc4eed9..116c70012c17e 100644 --- a/x-pack/plugins/lists/common/schemas/request/read_endpoint_list_item_schema.ts +++ b/x-pack/plugins/lists/common/schemas/request/read_endpoint_list_item_schema.ts @@ -6,8 +6,9 @@ */ import * as t from 'io-ts'; +import { id } from '@kbn/securitysolution-io-ts-utils'; -import { id, item_id } from '../common/schemas'; +import { item_id } from '../common/schemas'; import { RequiredKeepUndefined } from '../../types'; export const readEndpointListItemSchema = t.exact( diff --git a/x-pack/plugins/lists/common/schemas/request/read_exception_list_item_schema.test.ts b/x-pack/plugins/lists/common/schemas/request/read_exception_list_item_schema.test.ts index bef312fffa6ac..8b713dd38c4f2 100644 --- a/x-pack/plugins/lists/common/schemas/request/read_exception_list_item_schema.test.ts +++ b/x-pack/plugins/lists/common/schemas/request/read_exception_list_item_schema.test.ts @@ -7,8 +7,7 @@ import { left } from 'fp-ts/lib/Either'; import { pipe } from 'fp-ts/lib/pipeable'; - -import { exactCheck, foldLeftRight, getPaths } from '../../shared_imports'; +import { exactCheck, foldLeftRight, getPaths } from '@kbn/securitysolution-io-ts-utils'; import { getReadExceptionListItemSchemaMock } from './read_exception_list_item_schema.mock'; import { diff --git a/x-pack/plugins/lists/common/schemas/request/read_exception_list_item_schema.ts b/x-pack/plugins/lists/common/schemas/request/read_exception_list_item_schema.ts index 71e7de9feb81c..a0bd46b30d2f6 100644 --- a/x-pack/plugins/lists/common/schemas/request/read_exception_list_item_schema.ts +++ b/x-pack/plugins/lists/common/schemas/request/read_exception_list_item_schema.ts @@ -6,10 +6,10 @@ */ import * as t from 'io-ts'; +import { NamespaceType, id } from '@kbn/securitysolution-io-ts-utils'; -import { id, item_id, namespace_type } from '../common/schemas'; +import { item_id, namespace_type } from '../common/schemas'; import { RequiredKeepUndefined } from '../../types'; -import { NamespaceType } from '../types'; export const readExceptionListItemSchema = t.exact( t.partial({ diff --git a/x-pack/plugins/lists/common/schemas/request/read_exception_list_schema.test.ts b/x-pack/plugins/lists/common/schemas/request/read_exception_list_schema.test.ts index 5fd87ff24d3a7..031e3d6efb261 100644 --- a/x-pack/plugins/lists/common/schemas/request/read_exception_list_schema.test.ts +++ b/x-pack/plugins/lists/common/schemas/request/read_exception_list_schema.test.ts @@ -7,8 +7,7 @@ import { left } from 'fp-ts/lib/Either'; import { pipe } from 'fp-ts/lib/pipeable'; - -import { exactCheck, foldLeftRight, getPaths } from '../../shared_imports'; +import { exactCheck, foldLeftRight, getPaths } from '@kbn/securitysolution-io-ts-utils'; import { getReadExceptionListSchemaMock } from './read_exception_list_schema.mock'; import { ReadExceptionListSchema, readExceptionListSchema } from './read_exception_list_schema'; diff --git a/x-pack/plugins/lists/common/schemas/request/read_exception_list_schema.ts b/x-pack/plugins/lists/common/schemas/request/read_exception_list_schema.ts index d0064b6463159..fc8a6ee43a5a2 100644 --- a/x-pack/plugins/lists/common/schemas/request/read_exception_list_schema.ts +++ b/x-pack/plugins/lists/common/schemas/request/read_exception_list_schema.ts @@ -6,10 +6,10 @@ */ import * as t from 'io-ts'; +import { NamespaceType, id } from '@kbn/securitysolution-io-ts-utils'; -import { id, list_id, namespace_type } from '../common/schemas'; +import { list_id, namespace_type } from '../common/schemas'; import { RequiredKeepUndefined } from '../../types'; -import { NamespaceType } from '../types'; export const readExceptionListSchema = t.exact( t.partial({ diff --git a/x-pack/plugins/lists/common/schemas/request/read_list_item_schema.test.ts b/x-pack/plugins/lists/common/schemas/request/read_list_item_schema.test.ts index 4485ad24b8e3c..18af60f9d9d56 100644 --- a/x-pack/plugins/lists/common/schemas/request/read_list_item_schema.test.ts +++ b/x-pack/plugins/lists/common/schemas/request/read_list_item_schema.test.ts @@ -7,8 +7,7 @@ import { left } from 'fp-ts/lib/Either'; import { pipe } from 'fp-ts/lib/pipeable'; - -import { exactCheck, foldLeftRight, getPaths } from '../../shared_imports'; +import { exactCheck, foldLeftRight, getPaths } from '@kbn/securitysolution-io-ts-utils'; import { getReadListItemSchemaMock } from './read_list_item_schema.mock'; import { ReadListItemSchema, readListItemSchema } from './read_list_item_schema'; diff --git a/x-pack/plugins/lists/common/schemas/request/read_list_item_schema.ts b/x-pack/plugins/lists/common/schemas/request/read_list_item_schema.ts index 07bc1295a7b0c..450719f42ad4a 100644 --- a/x-pack/plugins/lists/common/schemas/request/read_list_item_schema.ts +++ b/x-pack/plugins/lists/common/schemas/request/read_list_item_schema.ts @@ -6,8 +6,9 @@ */ import * as t from 'io-ts'; +import { id } from '@kbn/securitysolution-io-ts-utils'; -import { id, list_id, value } from '../common/schemas'; +import { list_id, value } from '../common/schemas'; import { RequiredKeepUndefined } from '../../types'; export const readListItemSchema = t.exact(t.partial({ id, list_id, value })); diff --git a/x-pack/plugins/lists/common/schemas/request/read_list_schema.test.ts b/x-pack/plugins/lists/common/schemas/request/read_list_schema.test.ts index 92a45466b339e..e404e99f65218 100644 --- a/x-pack/plugins/lists/common/schemas/request/read_list_schema.test.ts +++ b/x-pack/plugins/lists/common/schemas/request/read_list_schema.test.ts @@ -7,8 +7,7 @@ import { left } from 'fp-ts/lib/Either'; import { pipe } from 'fp-ts/lib/pipeable'; - -import { exactCheck, foldLeftRight, getPaths } from '../../shared_imports'; +import { exactCheck, foldLeftRight, getPaths } from '@kbn/securitysolution-io-ts-utils'; import { getReadListSchemaMock } from './read_list_schema.mock'; import { ReadListSchema, readListSchema } from './read_list_schema'; diff --git a/x-pack/plugins/lists/common/schemas/request/read_list_schema.ts b/x-pack/plugins/lists/common/schemas/request/read_list_schema.ts index 989b35593422f..e07e2de1a4b80 100644 --- a/x-pack/plugins/lists/common/schemas/request/read_list_schema.ts +++ b/x-pack/plugins/lists/common/schemas/request/read_list_schema.ts @@ -6,8 +6,7 @@ */ import * as t from 'io-ts'; - -import { id } from '../common/schemas'; +import { id } from '@kbn/securitysolution-io-ts-utils'; export const readListSchema = t.exact( t.type({ diff --git a/x-pack/plugins/lists/common/schemas/request/update_endpoint_list_item_schema.test.ts b/x-pack/plugins/lists/common/schemas/request/update_endpoint_list_item_schema.test.ts index d39f8864cd68f..b5bd8caea8f85 100644 --- a/x-pack/plugins/lists/common/schemas/request/update_endpoint_list_item_schema.test.ts +++ b/x-pack/plugins/lists/common/schemas/request/update_endpoint_list_item_schema.test.ts @@ -7,8 +7,7 @@ import { left } from 'fp-ts/lib/Either'; import { pipe } from 'fp-ts/lib/pipeable'; - -import { exactCheck, foldLeftRight, getPaths } from '../../shared_imports'; +import { exactCheck, foldLeftRight, getPaths } from '@kbn/securitysolution-io-ts-utils'; import { UpdateEndpointListItemSchema, diff --git a/x-pack/plugins/lists/common/schemas/request/update_endpoint_list_item_schema.ts b/x-pack/plugins/lists/common/schemas/request/update_endpoint_list_item_schema.ts index 29ccb5e8e7831..d9e602419d61d 100644 --- a/x-pack/plugins/lists/common/schemas/request/update_endpoint_list_item_schema.ts +++ b/x-pack/plugins/lists/common/schemas/request/update_endpoint_list_item_schema.ts @@ -6,26 +6,24 @@ */ import * as t from 'io-ts'; - import { + DefaultUpdateCommentsArray, + EntriesArray, OsTypeArray, Tags, - _version, + UpdateCommentsArray, description, exceptionListItemType, id, meta, name, + nonEmptyEntriesArray, osTypeArrayOrUndefined, tags, -} from '../common/schemas'; +} from '@kbn/securitysolution-io-ts-utils'; + +import { _version } from '../common/schemas'; import { RequiredKeepUndefined } from '../../types'; -import { - DefaultUpdateCommentsArray, - EntriesArray, - UpdateCommentsArray, - nonEmptyEntriesArray, -} from '../types'; export const updateEndpointListItemSchema = t.intersection([ t.exact( diff --git a/x-pack/plugins/lists/common/schemas/request/update_exception_list_item_schema.test.ts b/x-pack/plugins/lists/common/schemas/request/update_exception_list_item_schema.test.ts index 80f700aa8a4e3..efcb4ecde1cbb 100644 --- a/x-pack/plugins/lists/common/schemas/request/update_exception_list_item_schema.test.ts +++ b/x-pack/plugins/lists/common/schemas/request/update_exception_list_item_schema.test.ts @@ -7,8 +7,7 @@ import { left } from 'fp-ts/lib/Either'; import { pipe } from 'fp-ts/lib/pipeable'; - -import { exactCheck, foldLeftRight, getPaths } from '../../shared_imports'; +import { exactCheck, foldLeftRight, getPaths } from '@kbn/securitysolution-io-ts-utils'; import { UpdateExceptionListItemSchema, diff --git a/x-pack/plugins/lists/common/schemas/request/update_exception_list_item_schema.ts b/x-pack/plugins/lists/common/schemas/request/update_exception_list_item_schema.ts index 9c892f109dcfd..f3b87c5ff5925 100644 --- a/x-pack/plugins/lists/common/schemas/request/update_exception_list_item_schema.ts +++ b/x-pack/plugins/lists/common/schemas/request/update_exception_list_item_schema.ts @@ -6,28 +6,25 @@ */ import * as t from 'io-ts'; - import { + DefaultUpdateCommentsArray, + EntriesArray, + NamespaceType, OsTypeArray, Tags, - _version, + UpdateCommentsArray, description, exceptionListItemType, id, meta, name, - namespace_type, + nonEmptyEntriesArray, osTypeArrayOrUndefined, tags, -} from '../common/schemas'; +} from '@kbn/securitysolution-io-ts-utils'; + +import { _version, namespace_type } from '../common/schemas'; import { RequiredKeepUndefined } from '../../types'; -import { - DefaultUpdateCommentsArray, - EntriesArray, - NamespaceType, - UpdateCommentsArray, - nonEmptyEntriesArray, -} from '../types'; export const updateExceptionListItemSchema = t.intersection([ t.exact( diff --git a/x-pack/plugins/lists/common/schemas/request/update_exception_list_schema.test.ts b/x-pack/plugins/lists/common/schemas/request/update_exception_list_schema.test.ts index b782de62d736c..30966f8eafef3 100644 --- a/x-pack/plugins/lists/common/schemas/request/update_exception_list_schema.test.ts +++ b/x-pack/plugins/lists/common/schemas/request/update_exception_list_schema.test.ts @@ -7,8 +7,7 @@ import { left } from 'fp-ts/lib/Either'; import { pipe } from 'fp-ts/lib/pipeable'; - -import { exactCheck, foldLeftRight, getPaths } from '../../shared_imports'; +import { exactCheck, foldLeftRight, getPaths } from '@kbn/securitysolution-io-ts-utils'; import { UpdateExceptionListSchema, diff --git a/x-pack/plugins/lists/common/schemas/request/update_exception_list_schema.ts b/x-pack/plugins/lists/common/schemas/request/update_exception_list_schema.ts index 28933d015cb4f..c8b354eff4d9e 100644 --- a/x-pack/plugins/lists/common/schemas/request/update_exception_list_schema.ts +++ b/x-pack/plugins/lists/common/schemas/request/update_exception_list_schema.ts @@ -6,24 +6,21 @@ */ import * as t from 'io-ts'; - import { + NamespaceType, OsTypeArray, Tags, - _version, description, exceptionListType, id, - list_id, meta, name, - namespace_type, osTypeArrayOrUndefined, tags, - version, -} from '../common/schemas'; +} from '@kbn/securitysolution-io-ts-utils'; + +import { _version, list_id, namespace_type, version } from '../common/schemas'; import { RequiredKeepUndefined } from '../../types'; -import { NamespaceType } from '../types'; export const updateExceptionListSchema = t.intersection([ t.exact( diff --git a/x-pack/plugins/lists/common/schemas/request/update_list_item_schema.test.ts b/x-pack/plugins/lists/common/schemas/request/update_list_item_schema.test.ts index c643719e3f2f3..2775abd1ee8d0 100644 --- a/x-pack/plugins/lists/common/schemas/request/update_list_item_schema.test.ts +++ b/x-pack/plugins/lists/common/schemas/request/update_list_item_schema.test.ts @@ -7,8 +7,7 @@ import { left } from 'fp-ts/lib/Either'; import { pipe } from 'fp-ts/lib/pipeable'; - -import { exactCheck, foldLeftRight, getPaths } from '../../shared_imports'; +import { exactCheck, foldLeftRight, getPaths } from '@kbn/securitysolution-io-ts-utils'; import { UpdateListItemSchema, updateListItemSchema } from './update_list_item_schema'; import { getUpdateListItemSchemaMock } from './update_list_item_schema.mock'; diff --git a/x-pack/plugins/lists/common/schemas/request/update_list_item_schema.ts b/x-pack/plugins/lists/common/schemas/request/update_list_item_schema.ts index 8266f6a2bed44..84916f15a59f6 100644 --- a/x-pack/plugins/lists/common/schemas/request/update_list_item_schema.ts +++ b/x-pack/plugins/lists/common/schemas/request/update_list_item_schema.ts @@ -6,8 +6,9 @@ */ import * as t from 'io-ts'; +import { id, meta } from '@kbn/securitysolution-io-ts-utils'; -import { _version, id, meta, value } from '../common/schemas'; +import { _version, value } from '../common/schemas'; import { RequiredKeepUndefined } from '../../types'; export const updateListItemSchema = t.intersection([ diff --git a/x-pack/plugins/lists/common/schemas/request/update_list_schema.test.ts b/x-pack/plugins/lists/common/schemas/request/update_list_schema.test.ts index 8cfe7c2944414..b20aa4d774938 100644 --- a/x-pack/plugins/lists/common/schemas/request/update_list_schema.test.ts +++ b/x-pack/plugins/lists/common/schemas/request/update_list_schema.test.ts @@ -7,8 +7,7 @@ import { left } from 'fp-ts/lib/Either'; import { pipe } from 'fp-ts/lib/pipeable'; - -import { exactCheck, foldLeftRight, getPaths } from '../../shared_imports'; +import { exactCheck, foldLeftRight, getPaths } from '@kbn/securitysolution-io-ts-utils'; import { UpdateListSchema, updateListSchema } from './update_list_schema'; import { getUpdateListSchemaMock } from './update_list_schema.mock'; diff --git a/x-pack/plugins/lists/common/schemas/request/update_list_schema.ts b/x-pack/plugins/lists/common/schemas/request/update_list_schema.ts index db23e53cb5792..6f520d399d577 100644 --- a/x-pack/plugins/lists/common/schemas/request/update_list_schema.ts +++ b/x-pack/plugins/lists/common/schemas/request/update_list_schema.ts @@ -6,8 +6,9 @@ */ import * as t from 'io-ts'; +import { description, id, meta, name, version } from '@kbn/securitysolution-io-ts-utils'; -import { _version, description, id, meta, name, version } from '../common/schemas'; +import { _version } from '../common/schemas'; import { RequiredKeepUndefined } from '../../types'; export const updateListSchema = t.intersection([ diff --git a/x-pack/plugins/lists/common/schemas/response/acknowledge_schema.test.ts b/x-pack/plugins/lists/common/schemas/response/acknowledge_schema.test.ts index 889231d3aa640..54b312fcfdb37 100644 --- a/x-pack/plugins/lists/common/schemas/response/acknowledge_schema.test.ts +++ b/x-pack/plugins/lists/common/schemas/response/acknowledge_schema.test.ts @@ -7,8 +7,7 @@ import { left } from 'fp-ts/lib/Either'; import { pipe } from 'fp-ts/lib/pipeable'; - -import { exactCheck, foldLeftRight, getPaths } from '../../shared_imports'; +import { exactCheck, foldLeftRight, getPaths } from '@kbn/securitysolution-io-ts-utils'; import { getAcknowledgeSchemaResponseMock } from './acknowledge_schema.mock'; import { AcknowledgeSchema, acknowledgeSchema } from './acknowledge_schema'; diff --git a/x-pack/plugins/lists/common/schemas/response/create_endpoint_list_schema.test.ts b/x-pack/plugins/lists/common/schemas/response/create_endpoint_list_schema.test.ts index 624687d4fc427..1f38044409b2c 100644 --- a/x-pack/plugins/lists/common/schemas/response/create_endpoint_list_schema.test.ts +++ b/x-pack/plugins/lists/common/schemas/response/create_endpoint_list_schema.test.ts @@ -7,8 +7,7 @@ import { left } from 'fp-ts/lib/Either'; import { pipe } from 'fp-ts/lib/pipeable'; - -import { exactCheck, foldLeftRight, getPaths } from '../../shared_imports'; +import { exactCheck, foldLeftRight, getPaths } from '@kbn/securitysolution-io-ts-utils'; import { getExceptionListSchemaMock } from './exception_list_schema.mock'; import { CreateEndpointListSchema, createEndpointListSchema } from './create_endpoint_list_schema'; diff --git a/x-pack/plugins/lists/common/schemas/response/exception_list_item_schema.mock.ts b/x-pack/plugins/lists/common/schemas/response/exception_list_item_schema.mock.ts index ab2aac39c19d2..c7d1459319eaf 100644 --- a/x-pack/plugins/lists/common/schemas/response/exception_list_item_schema.mock.ts +++ b/x-pack/plugins/lists/common/schemas/response/exception_list_item_schema.mock.ts @@ -48,10 +48,6 @@ export const getExceptionListItemSchemaMock = ( ...(overrides || {}), }); -export const getExceptionListItemSchemaXMock = (count = 1): ExceptionListItemSchema[] => { - return new Array(count).fill(null).map(() => getExceptionListItemSchemaMock()); -}; - /** * This is useful for end to end tests where we remove the auto generated parts for comparisons * such as created_at, updated_at, and id. diff --git a/x-pack/plugins/lists/common/schemas/response/exception_list_item_schema.test.ts b/x-pack/plugins/lists/common/schemas/response/exception_list_item_schema.test.ts index 50a12008e6579..b4809ee17b4bb 100644 --- a/x-pack/plugins/lists/common/schemas/response/exception_list_item_schema.test.ts +++ b/x-pack/plugins/lists/common/schemas/response/exception_list_item_schema.test.ts @@ -7,8 +7,7 @@ import { left } from 'fp-ts/lib/Either'; import { pipe } from 'fp-ts/lib/pipeable'; - -import { exactCheck, foldLeftRight, getPaths } from '../../shared_imports'; +import { exactCheck, foldLeftRight, getPaths } from '@kbn/securitysolution-io-ts-utils'; import { getExceptionListItemSchemaMock } from './exception_list_item_schema.mock'; import { ExceptionListItemSchema, exceptionListItemSchema } from './exception_list_item_schema'; diff --git a/x-pack/plugins/lists/common/schemas/response/exception_list_item_schema.ts b/x-pack/plugins/lists/common/schemas/response/exception_list_item_schema.ts index 0efda4316104d..769cfb3548ced 100644 --- a/x-pack/plugins/lists/common/schemas/response/exception_list_item_schema.ts +++ b/x-pack/plugins/lists/common/schemas/response/exception_list_item_schema.ts @@ -6,26 +6,29 @@ */ import * as t from 'io-ts'; - import { - _versionOrUndefined, + commentsArray, created_at, created_by, description, + entriesArray, exceptionListItemType, id, - item_id, - list_id, metaOrUndefined, name, - namespace_type, osTypeArray, tags, - tie_breaker_id, updated_at, updated_by, +} from '@kbn/securitysolution-io-ts-utils'; + +import { + _versionOrUndefined, + item_id, + list_id, + namespace_type, + tie_breaker_id, } from '../common/schemas'; -import { commentsArray, entriesArray } from '../types'; export const exceptionListItemSchema = t.exact( t.type({ diff --git a/x-pack/plugins/lists/common/schemas/response/exception_list_schema.test.ts b/x-pack/plugins/lists/common/schemas/response/exception_list_schema.test.ts index b0e8711c7acb6..ef2b639ba2f06 100644 --- a/x-pack/plugins/lists/common/schemas/response/exception_list_schema.test.ts +++ b/x-pack/plugins/lists/common/schemas/response/exception_list_schema.test.ts @@ -7,8 +7,7 @@ import { left } from 'fp-ts/lib/Either'; import { pipe } from 'fp-ts/lib/pipeable'; - -import { exactCheck, foldLeftRight, getPaths } from '../../shared_imports'; +import { exactCheck, foldLeftRight, getPaths } from '@kbn/securitysolution-io-ts-utils'; import { getExceptionListSchemaMock } from './exception_list_schema.mock'; import { ExceptionListSchema, exceptionListSchema } from './exception_list_schema'; diff --git a/x-pack/plugins/lists/common/schemas/response/exception_list_schema.ts b/x-pack/plugins/lists/common/schemas/response/exception_list_schema.ts index 578ced43db519..880c2d4f89e4f 100644 --- a/x-pack/plugins/lists/common/schemas/response/exception_list_schema.ts +++ b/x-pack/plugins/lists/common/schemas/response/exception_list_schema.ts @@ -6,24 +6,26 @@ */ import * as t from 'io-ts'; - import { - _versionOrUndefined, created_at, created_by, description, exceptionListType, id, - immutable, - list_id, metaOrUndefined, name, - namespace_type, osTypeArray, tags, - tie_breaker_id, updated_at, updated_by, +} from '@kbn/securitysolution-io-ts-utils'; + +import { + _versionOrUndefined, + immutable, + list_id, + namespace_type, + tie_breaker_id, version, } from '../common/schemas'; diff --git a/x-pack/plugins/lists/common/schemas/response/found_exception_list_item_schema.test.ts b/x-pack/plugins/lists/common/schemas/response/found_exception_list_item_schema.test.ts index e3e148b344dae..b04d9fbdad3b7 100644 --- a/x-pack/plugins/lists/common/schemas/response/found_exception_list_item_schema.test.ts +++ b/x-pack/plugins/lists/common/schemas/response/found_exception_list_item_schema.test.ts @@ -7,8 +7,7 @@ import { left } from 'fp-ts/lib/Either'; import { pipe } from 'fp-ts/lib/pipeable'; - -import { exactCheck, foldLeftRight, getPaths } from '../../shared_imports'; +import { exactCheck, foldLeftRight, getPaths } from '@kbn/securitysolution-io-ts-utils'; import { getExceptionListItemSchemaMock } from './exception_list_item_schema.mock'; import { getFoundExceptionListItemSchemaMock } from './found_exception_list_item_schema.mock'; diff --git a/x-pack/plugins/lists/common/schemas/response/found_exception_list_schema.test.ts b/x-pack/plugins/lists/common/schemas/response/found_exception_list_schema.test.ts index 1b8a600538cbd..cebf8ccc5d0d3 100644 --- a/x-pack/plugins/lists/common/schemas/response/found_exception_list_schema.test.ts +++ b/x-pack/plugins/lists/common/schemas/response/found_exception_list_schema.test.ts @@ -7,8 +7,7 @@ import { left } from 'fp-ts/lib/Either'; import { pipe } from 'fp-ts/lib/pipeable'; - -import { exactCheck, foldLeftRight, getPaths } from '../../shared_imports'; +import { exactCheck, foldLeftRight, getPaths } from '@kbn/securitysolution-io-ts-utils'; import { getExceptionListSchemaMock } from './exception_list_schema.mock'; import { getFoundExceptionListSchemaMock } from './found_exception_list_schema.mock'; diff --git a/x-pack/plugins/lists/common/schemas/response/list_item_index_exist_schema.test.ts b/x-pack/plugins/lists/common/schemas/response/list_item_index_exist_schema.test.ts index 8abd0a3c7d8b5..4a0592d49228e 100644 --- a/x-pack/plugins/lists/common/schemas/response/list_item_index_exist_schema.test.ts +++ b/x-pack/plugins/lists/common/schemas/response/list_item_index_exist_schema.test.ts @@ -7,8 +7,7 @@ import { left } from 'fp-ts/lib/Either'; import { pipe } from 'fp-ts/lib/pipeable'; - -import { exactCheck, foldLeftRight, getPaths } from '../../shared_imports'; +import { exactCheck, foldLeftRight, getPaths } from '@kbn/securitysolution-io-ts-utils'; import { getListItemIndexExistSchemaResponseMock } from './list_item_index_exist_schema.mock'; import { ListItemIndexExistSchema, listItemIndexExistSchema } from './list_item_index_exist_schema'; diff --git a/x-pack/plugins/lists/common/schemas/response/list_item_schema.test.ts b/x-pack/plugins/lists/common/schemas/response/list_item_schema.test.ts index 8cc82b43f1f8d..ffe49f305d484 100644 --- a/x-pack/plugins/lists/common/schemas/response/list_item_schema.test.ts +++ b/x-pack/plugins/lists/common/schemas/response/list_item_schema.test.ts @@ -7,8 +7,7 @@ import { left } from 'fp-ts/lib/Either'; import { pipe } from 'fp-ts/lib/pipeable'; - -import { exactCheck, foldLeftRight, getPaths } from '../../shared_imports'; +import { exactCheck, foldLeftRight, getPaths } from '@kbn/securitysolution-io-ts-utils'; import { getListItemResponseMock } from './list_item_schema.mock'; import { ListItemSchema, listItemSchema } from './list_item_schema'; diff --git a/x-pack/plugins/lists/common/schemas/response/list_item_schema.ts b/x-pack/plugins/lists/common/schemas/response/list_item_schema.ts index beedd50342018..1f105afac8b44 100644 --- a/x-pack/plugins/lists/common/schemas/response/list_item_schema.ts +++ b/x-pack/plugins/lists/common/schemas/response/list_item_schema.ts @@ -6,20 +6,22 @@ */ import * as t from 'io-ts'; - import { - _versionOrUndefined, created_at, created_by, - deserializerOrUndefined, id, - list_id, metaOrUndefined, - serializerOrUndefined, - tie_breaker_id, type, updated_at, updated_by, +} from '@kbn/securitysolution-io-ts-utils'; + +import { + _versionOrUndefined, + deserializerOrUndefined, + list_id, + serializerOrUndefined, + tie_breaker_id, value, } from '../common/schemas'; diff --git a/x-pack/plugins/lists/common/schemas/response/list_schema.test.ts b/x-pack/plugins/lists/common/schemas/response/list_schema.test.ts index bca42dd948bd3..294f7da9a098f 100644 --- a/x-pack/plugins/lists/common/schemas/response/list_schema.test.ts +++ b/x-pack/plugins/lists/common/schemas/response/list_schema.test.ts @@ -7,8 +7,7 @@ import { left } from 'fp-ts/lib/Either'; import { pipe } from 'fp-ts/lib/pipeable'; - -import { exactCheck, foldLeftRight, getPaths } from '../../shared_imports'; +import { exactCheck, foldLeftRight, getPaths } from '@kbn/securitysolution-io-ts-utils'; import { getListResponseMock } from './list_schema.mock'; import { ListSchema, listSchema } from './list_schema'; diff --git a/x-pack/plugins/lists/common/schemas/response/list_schema.ts b/x-pack/plugins/lists/common/schemas/response/list_schema.ts index 87d13a2071790..58abe94772ff6 100644 --- a/x-pack/plugins/lists/common/schemas/response/list_schema.ts +++ b/x-pack/plugins/lists/common/schemas/response/list_schema.ts @@ -6,22 +6,24 @@ */ import * as t from 'io-ts'; - import { - _versionOrUndefined, created_at, created_by, description, - deserializerOrUndefined, id, - immutable, metaOrUndefined, name, - serializerOrUndefined, - tie_breaker_id, type, updated_at, updated_by, +} from '@kbn/securitysolution-io-ts-utils'; + +import { + _versionOrUndefined, + deserializerOrUndefined, + immutable, + serializerOrUndefined, + tie_breaker_id, version, } from '../common/schemas'; diff --git a/x-pack/plugins/lists/common/schemas/response/search_list_item_schema.test.ts b/x-pack/plugins/lists/common/schemas/response/search_list_item_schema.test.ts index 2bebd7bafcd6c..4919e6a5ca73c 100644 --- a/x-pack/plugins/lists/common/schemas/response/search_list_item_schema.test.ts +++ b/x-pack/plugins/lists/common/schemas/response/search_list_item_schema.test.ts @@ -7,8 +7,7 @@ import { left } from 'fp-ts/lib/Either'; import { pipe } from 'fp-ts/lib/pipeable'; - -import { exactCheck, foldLeftRight, getPaths } from '../../shared_imports'; +import { exactCheck, foldLeftRight, getPaths } from '@kbn/securitysolution-io-ts-utils'; import { getSearchListItemResponseMock } from './search_list_item_schema.mock'; import { SearchListItemSchema, searchListItemSchema } from './search_list_item_schema'; diff --git a/x-pack/plugins/lists/common/schemas/types/comment.mock.ts b/x-pack/plugins/lists/common/schemas/types/comment.mock.ts index cad038344d941..75b4b6a431ac3 100644 --- a/x-pack/plugins/lists/common/schemas/types/comment.mock.ts +++ b/x-pack/plugins/lists/common/schemas/types/comment.mock.ts @@ -5,9 +5,9 @@ * 2.0. */ -import { DATE_NOW, ID, USER } from '../../constants.mock'; +import { Comment, CommentsArray } from '@kbn/securitysolution-io-ts-utils'; -import { Comment, CommentsArray } from './comment'; +import { DATE_NOW, ID, USER } from '../../constants.mock'; export const getCommentsMock = (): Comment => ({ comment: 'some old comment', diff --git a/x-pack/plugins/lists/common/schemas/types/comment.test.ts b/x-pack/plugins/lists/common/schemas/types/comment.test.ts deleted file mode 100644 index 3625d1ad7f1af..0000000000000 --- a/x-pack/plugins/lists/common/schemas/types/comment.test.ts +++ /dev/null @@ -1,238 +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 { pipe } from 'fp-ts/lib/pipeable'; -import { left } from 'fp-ts/lib/Either'; - -import { DATE_NOW } from '../../constants.mock'; -import { foldLeftRight, getPaths } from '../../shared_imports'; - -import { getCommentsArrayMock, getCommentsMock } from './comment.mock'; -import { - Comment, - CommentsArray, - CommentsArrayOrUndefined, - comment, - commentsArray, - commentsArrayOrUndefined, -} from './comment'; - -describe('Comment', () => { - describe('comment', () => { - test('it fails validation when "id" is undefined', () => { - const payload = { ...getCommentsMock(), id: undefined }; - const decoded = comment.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "undefined" supplied to "id"', - ]); - expect(message.schema).toEqual({}); - }); - - test('it passes validation with a typical comment', () => { - const payload = getCommentsMock(); - const decoded = comment.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); - }); - - test('it passes validation with "updated_at" and "updated_by" fields included', () => { - const payload = getCommentsMock(); - payload.updated_at = DATE_NOW; - payload.updated_by = 'someone'; - const decoded = comment.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); - }); - - test('it fails validation when undefined', () => { - const payload = undefined; - const decoded = comment.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "undefined" supplied to "({| comment: NonEmptyString, created_at: string, created_by: string, id: NonEmptyString |} & Partial<{| updated_at: string, updated_by: string |}>)"', - ]); - expect(message.schema).toEqual({}); - }); - - test('it fails validation when "comment" is an empty string', () => { - const payload: Omit & { comment: string } = { - ...getCommentsMock(), - comment: '', - }; - const decoded = comment.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual(['Invalid value "" supplied to "comment"']); - expect(message.schema).toEqual({}); - }); - - test('it fails validation when "comment" is not a string', () => { - const payload: Omit & { comment: string[] } = { - ...getCommentsMock(), - comment: ['some value'], - }; - const decoded = comment.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "["some value"]" supplied to "comment"', - ]); - expect(message.schema).toEqual({}); - }); - - test('it fails validation when "created_at" is not a string', () => { - const payload: Omit & { created_at: number } = { - ...getCommentsMock(), - created_at: 1, - }; - const decoded = comment.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "1" supplied to "created_at"', - ]); - expect(message.schema).toEqual({}); - }); - - test('it fails validation when "created_by" is not a string', () => { - const payload: Omit & { created_by: number } = { - ...getCommentsMock(), - created_by: 1, - }; - const decoded = comment.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "1" supplied to "created_by"', - ]); - expect(message.schema).toEqual({}); - }); - - test('it fails validation when "updated_at" is not a string', () => { - const payload: Omit & { updated_at: number } = { - ...getCommentsMock(), - updated_at: 1, - }; - const decoded = comment.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "1" supplied to "updated_at"', - ]); - expect(message.schema).toEqual({}); - }); - - test('it fails validation when "updated_by" is not a string', () => { - const payload: Omit & { updated_by: number } = { - ...getCommentsMock(), - updated_by: 1, - }; - const decoded = comment.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "1" supplied to "updated_by"', - ]); - expect(message.schema).toEqual({}); - }); - - test('it should strip out extra keys', () => { - const payload: Comment & { - extraKey?: string; - } = getCommentsMock(); - payload.extraKey = 'some value'; - const decoded = comment.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(getCommentsMock()); - }); - }); - - describe('commentsArray', () => { - test('it passes validation an array of Comment', () => { - const payload = getCommentsArrayMock(); - const decoded = commentsArray.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); - }); - - test('it passes validation when a Comment includes "updated_at" and "updated_by"', () => { - const commentsPayload = getCommentsMock(); - commentsPayload.updated_at = DATE_NOW; - commentsPayload.updated_by = 'someone'; - const payload = [{ ...commentsPayload }, ...getCommentsArrayMock()]; - const decoded = commentsArray.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); - }); - - test('it fails validation when undefined', () => { - const payload = undefined; - const decoded = commentsArray.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "undefined" supplied to "Array<({| comment: NonEmptyString, created_at: string, created_by: string, id: NonEmptyString |} & Partial<{| updated_at: string, updated_by: string |}>)>"', - ]); - expect(message.schema).toEqual({}); - }); - - test('it fails validation when array includes non Comment types', () => { - const payload = ([1] as unknown) as CommentsArray; - const decoded = commentsArray.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "1" supplied to "Array<({| comment: NonEmptyString, created_at: string, created_by: string, id: NonEmptyString |} & Partial<{| updated_at: string, updated_by: string |}>)>"', - ]); - expect(message.schema).toEqual({}); - }); - }); - - describe('commentsArrayOrUndefined', () => { - test('it passes validation an array of Comment', () => { - const payload = getCommentsArrayMock(); - const decoded = commentsArrayOrUndefined.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); - }); - - test('it passes validation when undefined', () => { - const payload = undefined; - const decoded = commentsArrayOrUndefined.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); - }); - - test('it fails validation when array includes non Comment types', () => { - const payload = ([1] as unknown) as CommentsArrayOrUndefined; - const decoded = commentsArray.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "1" supplied to "Array<({| comment: NonEmptyString, created_at: string, created_by: string, id: NonEmptyString |} & Partial<{| updated_at: string, updated_by: string |}>)>"', - ]); - expect(message.schema).toEqual({}); - }); - }); -}); diff --git a/x-pack/plugins/lists/common/schemas/types/comment.ts b/x-pack/plugins/lists/common/schemas/types/comment.ts deleted file mode 100644 index 016ef1b75edf8..0000000000000 --- a/x-pack/plugins/lists/common/schemas/types/comment.ts +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import * as t from 'io-ts'; - -import { NonEmptyString } from '../../shared_imports'; -import { created_at, created_by, id, updated_at, updated_by } from '../common/schemas'; - -/** - * @deprecated Use packages/kbn-securitysolution-io-ts-utils - */ -export const comment = t.intersection([ - t.exact( - t.type({ - comment: NonEmptyString, - created_at, - created_by, - id, - }) - ), - t.exact( - t.partial({ - updated_at, - updated_by, - }) - ), -]); - -/** - * @deprecated Use packages/kbn-securitysolution-io-ts-utils - */ -export const commentsArray = t.array(comment); - -/** - * @deprecated Use packages/kbn-securitysolution-io-ts-utils - */ -export type CommentsArray = t.TypeOf; - -/** - * @deprecated Use packages/kbn-securitysolution-io-ts-utils - */ -export type Comment = t.TypeOf; - -/** - * @deprecated Use packages/kbn-securitysolution-io-ts-utils - */ -export const commentsArrayOrUndefined = t.union([commentsArray, t.undefined]); - -/** - * @deprecated Use packages/kbn-securitysolution-io-ts-utils - */ -export type CommentsArrayOrUndefined = t.TypeOf; diff --git a/x-pack/plugins/lists/common/schemas/types/create_comment.mock.ts b/x-pack/plugins/lists/common/schemas/types/create_comment.mock.ts index 78d54d1d01616..2d8dd7b462258 100644 --- a/x-pack/plugins/lists/common/schemas/types/create_comment.mock.ts +++ b/x-pack/plugins/lists/common/schemas/types/create_comment.mock.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { CreateComment, CreateCommentsArray } from './create_comment'; +import { CreateComment, CreateCommentsArray } from '@kbn/securitysolution-io-ts-utils'; export const getCreateCommentsMock = (): CreateComment => ({ comment: 'some comments', diff --git a/x-pack/plugins/lists/common/schemas/types/create_comment.test.ts b/x-pack/plugins/lists/common/schemas/types/create_comment.test.ts deleted file mode 100644 index aed3860c0e2ef..0000000000000 --- a/x-pack/plugins/lists/common/schemas/types/create_comment.test.ts +++ /dev/null @@ -1,135 +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 { pipe } from 'fp-ts/lib/pipeable'; -import { left } from 'fp-ts/lib/Either'; - -import { foldLeftRight, getPaths } from '../../shared_imports'; - -import { getCreateCommentsArrayMock, getCreateCommentsMock } from './create_comment.mock'; -import { - CreateComment, - CreateCommentsArray, - CreateCommentsArrayOrUndefined, - createComment, - createCommentsArray, - createCommentsArrayOrUndefined, -} from './create_comment'; - -describe('CreateComment', () => { - describe('createComment', () => { - test('it passes validation with a default comment', () => { - const payload = getCreateCommentsMock(); - const decoded = createComment.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); - }); - - test('it fails validation when undefined', () => { - const payload = undefined; - const decoded = createComment.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "undefined" supplied to "{| comment: NonEmptyString |}"', - ]); - expect(message.schema).toEqual({}); - }); - - test('it fails validation when "comment" is not a string', () => { - const payload: Omit & { comment: string[] } = { - ...getCreateCommentsMock(), - comment: ['some value'], - }; - const decoded = createComment.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "["some value"]" supplied to "comment"', - ]); - expect(message.schema).toEqual({}); - }); - - test('it should strip out extra keys', () => { - const payload: CreateComment & { - extraKey?: string; - } = getCreateCommentsMock(); - payload.extraKey = 'some value'; - const decoded = createComment.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(getCreateCommentsMock()); - }); - }); - - describe('createCommentsArray', () => { - test('it passes validation an array of comments', () => { - const payload = getCreateCommentsArrayMock(); - const decoded = createCommentsArray.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); - }); - - test('it fails validation when undefined', () => { - const payload = undefined; - const decoded = createCommentsArray.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "undefined" supplied to "Array<{| comment: NonEmptyString |}>"', - ]); - expect(message.schema).toEqual({}); - }); - - test('it fails validation when array includes non comments types', () => { - const payload = ([1] as unknown) as CreateCommentsArray; - const decoded = createCommentsArray.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "1" supplied to "Array<{| comment: NonEmptyString |}>"', - ]); - expect(message.schema).toEqual({}); - }); - }); - - describe('createCommentsArrayOrUndefined', () => { - test('it passes validation an array of comments', () => { - const payload = getCreateCommentsArrayMock(); - const decoded = createCommentsArrayOrUndefined.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); - }); - - test('it passes validation when undefined', () => { - const payload = undefined; - const decoded = createCommentsArrayOrUndefined.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); - }); - - test('it fails validation when array includes non comments types', () => { - const payload = ([1] as unknown) as CreateCommentsArrayOrUndefined; - const decoded = createCommentsArray.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "1" supplied to "Array<{| comment: NonEmptyString |}>"', - ]); - expect(message.schema).toEqual({}); - }); - }); -}); diff --git a/x-pack/plugins/lists/common/schemas/types/create_comment.ts b/x-pack/plugins/lists/common/schemas/types/create_comment.ts deleted file mode 100644 index 070e860299f3d..0000000000000 --- a/x-pack/plugins/lists/common/schemas/types/create_comment.ts +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import * as t from 'io-ts'; - -import { NonEmptyString } from '../../shared_imports'; - -/** - * @deprecated Use packages/kbn-securitysolution-io-ts-utils - */ -export const createComment = t.exact( - t.type({ - comment: NonEmptyString, - }) -); - -/** - * @deprecated Use packages/kbn-securitysolution-io-ts-utils - */ -export type CreateComment = t.TypeOf; - -/** - * @deprecated Use packages/kbn-securitysolution-io-ts-utils - */ -export const createCommentsArray = t.array(createComment); - -/** - * @deprecated Use packages/kbn-securitysolution-io-ts-utils - */ -export type CreateCommentsArray = t.TypeOf; - -/** - * @deprecated Use packages/kbn-securitysolution-io-ts-utils - */ -export type CreateComments = t.TypeOf; - -/** - * @deprecated Use packages/kbn-securitysolution-io-ts-utils - */ -export const createCommentsArrayOrUndefined = t.union([createCommentsArray, t.undefined]); - -/** - * @deprecated Use packages/kbn-securitysolution-io-ts-utils - */ -export type CreateCommentsArrayOrUndefined = t.TypeOf; diff --git a/x-pack/plugins/lists/common/schemas/types/default_comments_array.test.ts b/x-pack/plugins/lists/common/schemas/types/default_comments_array.test.ts deleted file mode 100644 index 0ff1dad5cf515..0000000000000 --- a/x-pack/plugins/lists/common/schemas/types/default_comments_array.test.ts +++ /dev/null @@ -1,66 +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 { pipe } from 'fp-ts/lib/pipeable'; -import { left } from 'fp-ts/lib/Either'; - -import { foldLeftRight, getPaths } from '../../shared_imports'; - -import { DefaultCommentsArray } from './default_comments_array'; -import { CommentsArray } from './comment'; -import { getCommentsArrayMock } from './comment.mock'; - -describe('default_comments_array', () => { - test('it should pass validation when supplied an empty array', () => { - const payload: CommentsArray = []; - const decoded = DefaultCommentsArray.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); - }); - - test('it should pass validation when supplied an array of comments', () => { - const payload: CommentsArray = getCommentsArrayMock(); - const decoded = DefaultCommentsArray.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); - }); - - test('it should fail validation when supplied an array of numbers', () => { - const payload = [1]; - const decoded = DefaultCommentsArray.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "1" supplied to "Array<({| comment: NonEmptyString, created_at: string, created_by: string, id: NonEmptyString |} & Partial<{| updated_at: string, updated_by: string |}>)>"', - ]); - expect(message.schema).toEqual({}); - }); - - test('it should fail validation when supplied an array of strings', () => { - const payload = ['some string']; - const decoded = DefaultCommentsArray.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "some string" supplied to "Array<({| comment: NonEmptyString, created_at: string, created_by: string, id: NonEmptyString |} & Partial<{| updated_at: string, updated_by: string |}>)>"', - ]); - expect(message.schema).toEqual({}); - }); - - test('it should return a default array entry', () => { - const payload = null; - const decoded = DefaultCommentsArray.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual([]); - }); -}); diff --git a/x-pack/plugins/lists/common/schemas/types/default_comments_array.ts b/x-pack/plugins/lists/common/schemas/types/default_comments_array.ts deleted file mode 100644 index b190bfb649a9f..0000000000000 --- a/x-pack/plugins/lists/common/schemas/types/default_comments_array.ts +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import * as t from 'io-ts'; -import { Either } from 'fp-ts/lib/Either'; - -import { CommentsArray, comment } from './comment'; - -/** - * Types the DefaultCommentsArray as: - * - If null or undefined, then a default array of type entry will be set - * @deprecated Use packages/kbn-securitysolution-io-ts-utils - */ -export const DefaultCommentsArray = new t.Type( - 'DefaultCommentsArray', - t.array(comment).is, - (input): Either => - input == null ? t.success([]) : t.array(comment).decode(input), - t.identity -); diff --git a/x-pack/plugins/lists/common/schemas/types/default_create_comments_array.test.ts b/x-pack/plugins/lists/common/schemas/types/default_create_comments_array.test.ts deleted file mode 100644 index 089fb0a68f050..0000000000000 --- a/x-pack/plugins/lists/common/schemas/types/default_create_comments_array.test.ts +++ /dev/null @@ -1,81 +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 { pipe } from 'fp-ts/lib/pipeable'; -import { left } from 'fp-ts/lib/Either'; - -import { foldLeftRight, getPaths } from '../../shared_imports'; - -import { DefaultCreateCommentsArray } from './default_create_comments_array'; -import { CreateCommentsArray } from './create_comment'; -import { getCreateCommentsArrayMock } from './create_comment.mock'; -import { getCommentsArrayMock } from './comment.mock'; - -describe('default_create_comments_array', () => { - test('it should pass validation when an empty array', () => { - const payload: CreateCommentsArray = []; - const decoded = DefaultCreateCommentsArray.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); - }); - - test('it should pass validation when an array of comments', () => { - const payload: CreateCommentsArray = getCreateCommentsArrayMock(); - const decoded = DefaultCreateCommentsArray.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); - }); - - test('it should strip out "created_at" and "created_by" if they are passed in', () => { - const payload = getCommentsArrayMock(); - const decoded = DefaultCreateCommentsArray.decode(payload); - const message = pipe(decoded, foldLeftRight); - - // TODO: Known weird error formatting that is on our list to address - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual([ - { comment: 'some old comment' }, - { comment: 'some old comment' }, - ]); - }); - - test('it should not pass validation when an array of numbers', () => { - const payload = [1]; - const decoded = DefaultCreateCommentsArray.decode(payload); - const message = pipe(decoded, foldLeftRight); - - // TODO: Known weird error formatting that is on our list to address - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "1" supplied to "Array<{| comment: NonEmptyString |}>"', - ]); - expect(message.schema).toEqual({}); - }); - - test('it should not pass validation when an array of strings', () => { - const payload = ['some string']; - const decoded = DefaultCreateCommentsArray.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "some string" supplied to "Array<{| comment: NonEmptyString |}>"', - ]); - expect(message.schema).toEqual({}); - }); - - test('it should return a default array entry', () => { - const payload = null; - const decoded = DefaultCreateCommentsArray.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual([]); - }); -}); diff --git a/x-pack/plugins/lists/common/schemas/types/default_create_comments_array.ts b/x-pack/plugins/lists/common/schemas/types/default_create_comments_array.ts deleted file mode 100644 index 92121aaf05a6b..0000000000000 --- a/x-pack/plugins/lists/common/schemas/types/default_create_comments_array.ts +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import * as t from 'io-ts'; -import { Either } from 'fp-ts/lib/Either'; - -import { CreateCommentsArray, createComment } from './create_comment'; - -/** - * Types the DefaultCreateComments as: - * - If null or undefined, then a default array of type entry will be set - * @deprecated Use packages/kbn-securitysolution-io-ts-utils - */ -export const DefaultCreateCommentsArray = new t.Type< - CreateCommentsArray, - CreateCommentsArray, - unknown ->( - 'DefaultCreateComments', - t.array(createComment).is, - (input): Either => - input == null ? t.success([]) : t.array(createComment).decode(input), - t.identity -); diff --git a/x-pack/plugins/lists/common/schemas/types/default_namespace.test.ts b/x-pack/plugins/lists/common/schemas/types/default_namespace.test.ts deleted file mode 100644 index edb7f06d4505b..0000000000000 --- a/x-pack/plugins/lists/common/schemas/types/default_namespace.test.ts +++ /dev/null @@ -1,62 +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 { pipe } from 'fp-ts/lib/pipeable'; -import { left } from 'fp-ts/lib/Either'; - -import { foldLeftRight, getPaths } from '../../shared_imports'; - -import { DefaultNamespace } from './default_namespace'; - -describe('default_namespace', () => { - test('it should validate "single"', () => { - const payload = 'single'; - const decoded = DefaultNamespace.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); - }); - - test('it should validate "agnostic"', () => { - const payload = 'agnostic'; - const decoded = DefaultNamespace.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); - }); - - test('it defaults to "single" if "undefined"', () => { - const payload = undefined; - const decoded = DefaultNamespace.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual('single'); - }); - - test('it defaults to "single" if "null"', () => { - const payload = null; - const decoded = DefaultNamespace.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual('single'); - }); - - test('it should FAIL validation if not "single" or "agnostic"', () => { - const payload = 'something else'; - const decoded = DefaultNamespace.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([ - `Invalid value "something else" supplied to "DefaultNamespace"`, - ]); - expect(message.schema).toEqual({}); - }); -}); diff --git a/x-pack/plugins/lists/common/schemas/types/default_namespace.ts b/x-pack/plugins/lists/common/schemas/types/default_namespace.ts deleted file mode 100644 index c5ae93b0a11a5..0000000000000 --- a/x-pack/plugins/lists/common/schemas/types/default_namespace.ts +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import * as t from 'io-ts'; -import { Either } from 'fp-ts/lib/Either'; - -export const namespaceType = t.keyof({ agnostic: null, single: null }); -export type NamespaceType = t.TypeOf; - -/** - * Types the DefaultNamespace as: - * - If null or undefined, then a default string/enumeration of "single" will be used. - * @deprecated Use packages/kbn-securitysolution-io-ts-utils - */ -export const DefaultNamespace = new t.Type( - 'DefaultNamespace', - namespaceType.is, - (input, context): Either => - input == null ? t.success('single') : namespaceType.validate(input, context), - t.identity -); diff --git a/x-pack/plugins/lists/common/schemas/types/default_namespace_array.test.ts b/x-pack/plugins/lists/common/schemas/types/default_namespace_array.test.ts deleted file mode 100644 index d793296f88d53..0000000000000 --- a/x-pack/plugins/lists/common/schemas/types/default_namespace_array.test.ts +++ /dev/null @@ -1,100 +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 { pipe } from 'fp-ts/lib/pipeable'; -import { left } from 'fp-ts/lib/Either'; - -import { foldLeftRight, getPaths } from '../../shared_imports'; - -import { DefaultNamespaceArray, DefaultNamespaceArrayType } from './default_namespace_array'; - -describe('default_namespace_array', () => { - test('it should validate "null" single item as an array with a "single" value', () => { - const payload: DefaultNamespaceArrayType = null; - const decoded = DefaultNamespaceArray.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(['single']); - }); - - test('it should FAIL validation of numeric value', () => { - const payload = 5; - const decoded = DefaultNamespaceArray.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "5" supplied to "DefaultNamespaceArray"', - ]); - expect(message.schema).toEqual({}); - }); - - test('it should validate "undefined" item as an array with a "single" value', () => { - const payload: DefaultNamespaceArrayType = undefined; - const decoded = DefaultNamespaceArray.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(['single']); - }); - - test('it should validate "single" as an array of a "single" value', () => { - const payload: DefaultNamespaceArrayType = 'single'; - const decoded = DefaultNamespaceArray.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual([payload]); - }); - - test('it should validate "agnostic" as an array of a "agnostic" value', () => { - const payload: DefaultNamespaceArrayType = 'agnostic'; - const decoded = DefaultNamespaceArray.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual([payload]); - }); - - test('it should validate "single,agnostic" as an array of 2 values of ["single", "agnostic"] values', () => { - const payload: DefaultNamespaceArrayType = 'agnostic,single'; - const decoded = DefaultNamespaceArray.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(['agnostic', 'single']); - }); - - test('it should validate 3 elements of "single,agnostic,single" as an array of 3 values of ["single", "agnostic", "single"] values', () => { - const payload: DefaultNamespaceArrayType = 'single,agnostic,single'; - const decoded = DefaultNamespaceArray.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(['single', 'agnostic', 'single']); - }); - - test('it should validate 3 elements of "single,agnostic, single" as an array of 3 values of ["single", "agnostic", "single"] values when there are spaces', () => { - const payload: DefaultNamespaceArrayType = ' single, agnostic, single '; - const decoded = DefaultNamespaceArray.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(['single', 'agnostic', 'single']); - }); - - test('it should FAIL validation when given 3 elements of "single,agnostic,junk" since the 3rd value is junk', () => { - const payload: DefaultNamespaceArrayType = 'single,agnostic,junk'; - const decoded = DefaultNamespaceArray.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "junk" supplied to "DefaultNamespaceArray"', - ]); - expect(message.schema).toEqual({}); - }); -}); diff --git a/x-pack/plugins/lists/common/schemas/types/default_namespace_array.ts b/x-pack/plugins/lists/common/schemas/types/default_namespace_array.ts deleted file mode 100644 index c27e4eade4b38..0000000000000 --- a/x-pack/plugins/lists/common/schemas/types/default_namespace_array.ts +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import * as t from 'io-ts'; -import { Either } from 'fp-ts/lib/Either'; - -import { namespaceType } from './default_namespace'; - -export const namespaceTypeArray = t.array(namespaceType); -export type NamespaceTypeArray = t.TypeOf; - -/** - * Types the DefaultNamespaceArray as: - * - If null or undefined, then a default string array of "single" will be used. - * - If it contains a string, then it is split along the commas and puts them into an array and validates it - * @deprecated Use packages/kbn-securitysolution-io-ts-utils - */ -export const DefaultNamespaceArray = new t.Type< - NamespaceTypeArray, - string | undefined | null, - unknown ->( - 'DefaultNamespaceArray', - namespaceTypeArray.is, - (input, context): Either => { - if (input == null) { - return t.success(['single']); - } else if (typeof input === 'string') { - const commaSeparatedValues = input - .trim() - .split(',') - .map((value) => value.trim()); - return namespaceTypeArray.validate(commaSeparatedValues, context); - } - return t.failure(input, context); - }, - String -); - -/** - * @deprecated Use packages/kbn-securitysolution-io-ts-utils - */ -export type DefaultNamespaceArrayType = t.OutputOf; - -/** - * @deprecated Use packages/kbn-securitysolution-io-ts-utils - */ -export type DefaultNamespaceArrayTypeDecoded = t.TypeOf; diff --git a/x-pack/plugins/lists/common/schemas/types/default_string_boolean_false.test.ts b/x-pack/plugins/lists/common/schemas/types/default_string_boolean_false.test.ts deleted file mode 100644 index eccbb8bf9506a..0000000000000 --- a/x-pack/plugins/lists/common/schemas/types/default_string_boolean_false.test.ts +++ /dev/null @@ -1,102 +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 { pipe } from 'fp-ts/lib/pipeable'; -import { left } from 'fp-ts/lib/Either'; - -import { foldLeftRight, getPaths } from '../../test_utils'; - -import { DefaultStringBooleanFalse } from './default_string_boolean_false'; - -describe('default_string_boolean_false', () => { - test('it should validate a boolean false', () => { - const payload = false; - const decoded = DefaultStringBooleanFalse.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); - }); - - test('it should validate a boolean true', () => { - const payload = true; - const decoded = DefaultStringBooleanFalse.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); - }); - - test('it should not validate a number', () => { - const payload = 5; - const decoded = DefaultStringBooleanFalse.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "5" supplied to "DefaultStringBooleanFalse"', - ]); - expect(message.schema).toEqual({}); - }); - - test('it should return a default false', () => { - const payload = null; - const decoded = DefaultStringBooleanFalse.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(false); - }); - - test('it should return a default false when given a string of "false"', () => { - const payload = 'false'; - const decoded = DefaultStringBooleanFalse.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(false); - }); - - test('it should return a default true when given a string of "true"', () => { - const payload = 'true'; - const decoded = DefaultStringBooleanFalse.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(true); - }); - - test('it should return a default true when given a string of "TruE"', () => { - const payload = 'TruE'; - const decoded = DefaultStringBooleanFalse.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(true); - }); - - test('it should not work with a string of junk "junk"', () => { - const payload = 'junk'; - const decoded = DefaultStringBooleanFalse.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "junk" supplied to "DefaultStringBooleanFalse"', - ]); - expect(message.schema).toEqual({}); - }); - - test('it should not work with an empty string', () => { - const payload = ''; - const decoded = DefaultStringBooleanFalse.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "" supplied to "DefaultStringBooleanFalse"', - ]); - expect(message.schema).toEqual({}); - }); -}); diff --git a/x-pack/plugins/lists/common/schemas/types/default_string_boolean_false.ts b/x-pack/plugins/lists/common/schemas/types/default_string_boolean_false.ts deleted file mode 100644 index d409648b5435b..0000000000000 --- a/x-pack/plugins/lists/common/schemas/types/default_string_boolean_false.ts +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import * as t from 'io-ts'; -import { Either } from 'fp-ts/lib/Either'; - -/** - * Types the DefaultStringBooleanFalse as: - * - If a string this will convert the string to a boolean - * - If null or undefined, then a default false will be set - * @deprecated Use packages/kbn-securitysolution-io-ts-utils - */ -export const DefaultStringBooleanFalse = new t.Type( - 'DefaultStringBooleanFalse', - t.boolean.is, - (input, context): Either => { - if (input == null) { - return t.success(false); - } else if (typeof input === 'string' && input.toLowerCase() === 'true') { - return t.success(true); - } else if (typeof input === 'string' && input.toLowerCase() === 'false') { - return t.success(false); - } else { - return t.boolean.validate(input, context); - } - }, - t.identity -); - -/** - * @deprecated Use packages/kbn-securitysolution-io-ts-utils - */ -export type DefaultStringBooleanFalseC = typeof DefaultStringBooleanFalse; diff --git a/x-pack/plugins/lists/common/schemas/types/default_update_comments_array.test.ts b/x-pack/plugins/lists/common/schemas/types/default_update_comments_array.test.ts deleted file mode 100644 index 6f7fa678f0f20..0000000000000 --- a/x-pack/plugins/lists/common/schemas/types/default_update_comments_array.test.ts +++ /dev/null @@ -1,66 +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 { pipe } from 'fp-ts/lib/pipeable'; -import { left } from 'fp-ts/lib/Either'; - -import { foldLeftRight, getPaths } from '../../shared_imports'; - -import { DefaultUpdateCommentsArray } from './default_update_comments_array'; -import { UpdateCommentsArray } from './update_comment'; -import { getUpdateCommentsArrayMock } from './update_comment.mock'; - -describe('default_update_comments_array', () => { - test('it should pass validation when supplied an empty array', () => { - const payload: UpdateCommentsArray = []; - const decoded = DefaultUpdateCommentsArray.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); - }); - - test('it should pass validation when supplied an array of comments', () => { - const payload: UpdateCommentsArray = getUpdateCommentsArrayMock(); - const decoded = DefaultUpdateCommentsArray.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); - }); - - test('it should fail validation when supplied an array of numbers', () => { - const payload = [1]; - const decoded = DefaultUpdateCommentsArray.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "1" supplied to "Array<({| comment: NonEmptyString |} & Partial<{| id: NonEmptyString |}>)>"', - ]); - expect(message.schema).toEqual({}); - }); - - test('it should fail validation when supplied an array of strings', () => { - const payload = ['some string']; - const decoded = DefaultUpdateCommentsArray.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "some string" supplied to "Array<({| comment: NonEmptyString |} & Partial<{| id: NonEmptyString |}>)>"', - ]); - expect(message.schema).toEqual({}); - }); - - test('it should return a default array entry', () => { - const payload = null; - const decoded = DefaultUpdateCommentsArray.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual([]); - }); -}); diff --git a/x-pack/plugins/lists/common/schemas/types/default_update_comments_array.ts b/x-pack/plugins/lists/common/schemas/types/default_update_comments_array.ts deleted file mode 100644 index 43749b606e4ee..0000000000000 --- a/x-pack/plugins/lists/common/schemas/types/default_update_comments_array.ts +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import * as t from 'io-ts'; -import { Either } from 'fp-ts/lib/Either'; - -import { UpdateCommentsArray, updateCommentsArray } from './update_comment'; - -/** - * Types the DefaultCommentsUpdate as: - * - If null or undefined, then a default array of type entry will be set - * @deprecated Use packages/kbn-securitysolution-io-ts-utils - */ -export const DefaultUpdateCommentsArray = new t.Type< - UpdateCommentsArray, - UpdateCommentsArray, - unknown ->( - 'DefaultCreateComments', - updateCommentsArray.is, - (input): Either => - input == null ? t.success([]) : updateCommentsArray.decode(input), - t.identity -); diff --git a/x-pack/plugins/lists/common/schemas/types/empty_string_array.test.ts b/x-pack/plugins/lists/common/schemas/types/empty_string_array.test.ts deleted file mode 100644 index 7921416c35a58..0000000000000 --- a/x-pack/plugins/lists/common/schemas/types/empty_string_array.test.ts +++ /dev/null @@ -1,80 +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 { pipe } from 'fp-ts/lib/pipeable'; -import { left } from 'fp-ts/lib/Either'; - -import { foldLeftRight, getPaths } from '../../shared_imports'; - -import { EmptyStringArray, EmptyStringArrayEncoded } from './empty_string_array'; - -describe('empty_string_array', () => { - test('it should validate "null" and create an empty array', () => { - const payload: EmptyStringArrayEncoded = null; - const decoded = EmptyStringArray.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual([]); - }); - - test('it should validate "undefined" and create an empty array', () => { - const payload: EmptyStringArrayEncoded = undefined; - const decoded = EmptyStringArray.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual([]); - }); - - test('it should validate a single value of "a" into an array of size 1 of ["a"]', () => { - const payload: EmptyStringArrayEncoded = 'a'; - const decoded = EmptyStringArray.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(['a']); - }); - - test('it should validate 2 values of "a,b" into an array of size 2 of ["a", "b"]', () => { - const payload: EmptyStringArrayEncoded = 'a,b'; - const decoded = EmptyStringArray.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(['a', 'b']); - }); - - test('it should validate 3 values of "a,b,c" into an array of size 3 of ["a", "b", "c"]', () => { - const payload: EmptyStringArrayEncoded = 'a,b,c'; - const decoded = EmptyStringArray.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(['a', 'b', 'c']); - }); - - test('it should FAIL validation of number', () => { - const payload: number = 5; - const decoded = EmptyStringArray.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "5" supplied to "EmptyStringArray"', - ]); - expect(message.schema).toEqual({}); - }); - - test('it should validate 3 values of " a, b, c " into an array of size 3 of ["a", "b", "c"] even though they have spaces', () => { - const payload: EmptyStringArrayEncoded = ' a, b, c '; - const decoded = EmptyStringArray.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(['a', 'b', 'c']); - }); -}); diff --git a/x-pack/plugins/lists/common/schemas/types/empty_string_array.ts b/x-pack/plugins/lists/common/schemas/types/empty_string_array.ts deleted file mode 100644 index 6192417600870..0000000000000 --- a/x-pack/plugins/lists/common/schemas/types/empty_string_array.ts +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import * as t from 'io-ts'; -import { Either } from 'fp-ts/lib/Either'; - -/** - * Types the EmptyStringArray as: - * - A value that can be undefined, or null (which will be turned into an empty array) - * - A comma separated string that can turn into an array by splitting on it - * - Example input converted to output: undefined -> [] - * - Example input converted to output: null -> [] - * - Example input converted to output: "a,b,c" -> ["a", "b", "c"] - * @deprecated Use packages/kbn-securitysolution-io-ts-utils - */ -export const EmptyStringArray = new t.Type( - 'EmptyStringArray', - t.array(t.string).is, - (input, context): Either => { - if (input == null) { - return t.success([]); - } else if (typeof input === 'string' && input.trim() !== '') { - const arrayValues = input - .trim() - .split(',') - .map((value) => value.trim()); - const emptyValueFound = arrayValues.some((value) => value === ''); - if (emptyValueFound) { - return t.failure(input, context); - } else { - return t.success(arrayValues); - } - } else { - return t.failure(input, context); - } - }, - String -); - -/** - * @deprecated Use packages/kbn-securitysolution-io-ts-utils - */ -export type EmptyStringArrayEncoded = t.OutputOf; - -/** - * @deprecated Use packages/kbn-securitysolution-io-ts-utils - */ -export type EmptyStringArrayDecoded = t.TypeOf; diff --git a/x-pack/plugins/lists/common/schemas/types/endpoint/entries.mock.ts b/x-pack/plugins/lists/common/schemas/types/endpoint/entries.mock.ts deleted file mode 100644 index 8e7ede596871a..0000000000000 --- a/x-pack/plugins/lists/common/schemas/types/endpoint/entries.mock.ts +++ /dev/null @@ -1,17 +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 { EndpointEntriesArray } from './entries'; -import { getEndpointEntryMatchMock } from './entry_match.mock'; -import { getEndpointEntryMatchAnyMock } from './entry_match_any.mock'; -import { getEndpointEntryNestedMock } from './entry_nested.mock'; - -export const getEndpointEntriesArrayMock = (): EndpointEntriesArray => [ - getEndpointEntryMatchMock(), - getEndpointEntryMatchAnyMock(), - getEndpointEntryNestedMock(), -]; diff --git a/x-pack/plugins/lists/common/schemas/types/endpoint/entries.test.ts b/x-pack/plugins/lists/common/schemas/types/endpoint/entries.test.ts deleted file mode 100644 index 4835b607d681f..0000000000000 --- a/x-pack/plugins/lists/common/schemas/types/endpoint/entries.test.ts +++ /dev/null @@ -1,112 +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 { pipe } from 'fp-ts/lib/pipeable'; -import { left } from 'fp-ts/lib/Either'; - -import { foldLeftRight, getPaths } from '../../../shared_imports'; -import { getEntryExistsMock } from '../entry_exists.mock'; -import { getEntryListMock } from '../entry_list.mock'; - -import { getEndpointEntryMatchMock } from './entry_match.mock'; -import { getEndpointEntryMatchAnyMock } from './entry_match_any.mock'; -import { getEndpointEntryNestedMock } from './entry_nested.mock'; -import { getEndpointEntriesArrayMock } from './entries.mock'; -import { - NonEmptyEndpointEntriesArray, - endpointEntriesArray, - nonEmptyEndpointEntriesArray, -} from './entries'; - -describe('Endpoint', () => { - describe('entriesArray', () => { - test('it should validate an array with match entry', () => { - const payload = [getEndpointEntryMatchMock()]; - const decoded = endpointEntriesArray.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); - }); - - test('it should validate an array with match_any entry', () => { - const payload = [getEndpointEntryMatchAnyMock()]; - const decoded = endpointEntriesArray.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); - }); - - test('it should NOT validate an empty array', () => { - const payload: NonEmptyEndpointEntriesArray = []; - const decoded = nonEmptyEndpointEntriesArray.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "[]" supplied to "NonEmptyEndpointEntriesArray"', - ]); - expect(message.schema).toEqual({}); - }); - - test('type guard for nonEmptyEndpointNestedEntries should allow array of endpoint entries', () => { - const payload: NonEmptyEndpointEntriesArray = [getEndpointEntryMatchAnyMock()]; - const guarded = nonEmptyEndpointEntriesArray.is(payload); - expect(guarded).toBeTruthy(); - }); - - test('type guard for nonEmptyEndpointNestedEntries should disallow empty arrays', () => { - const payload: NonEmptyEndpointEntriesArray = []; - const guarded = nonEmptyEndpointEntriesArray.is(payload); - expect(guarded).toBeFalsy(); - }); - - test('it should NOT validate an array with exists entry', () => { - const payload = [getEntryExistsMock()]; - const decoded = endpointEntriesArray.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "exists" supplied to "type"', - 'Invalid value "undefined" supplied to "value"', - 'Invalid value "undefined" supplied to "entries"', - ]); - expect(message.schema).toEqual({}); - }); - - test('it should NOT validate an array with list entry', () => { - const payload = [getEntryListMock()]; - const decoded = endpointEntriesArray.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "list" supplied to "type"', - 'Invalid value "undefined" supplied to "value"', - 'Invalid value "undefined" supplied to "entries"', - ]); - expect(message.schema).toEqual({}); - }); - - test('it should validate an array with nested entry', () => { - const payload = [getEndpointEntryNestedMock()]; - const decoded = endpointEntriesArray.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); - }); - - test('it should validate an array with all types of entries', () => { - const payload = getEndpointEntriesArrayMock(); - const decoded = endpointEntriesArray.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); - }); - }); -}); diff --git a/x-pack/plugins/lists/common/schemas/types/endpoint/entries.ts b/x-pack/plugins/lists/common/schemas/types/endpoint/entries.ts deleted file mode 100644 index 4622a8a7d39b7..0000000000000 --- a/x-pack/plugins/lists/common/schemas/types/endpoint/entries.ts +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import * as t from 'io-ts'; -import { Either } from 'fp-ts/lib/Either'; - -import { endpointEntryMatchAny } from './entry_match_any'; -import { endpointEntryMatch } from './entry_match'; -import { endpointEntryNested } from './entry_nested'; - -/** - * @deprecated Use packages/kbn-securitysolution-io-ts-utils - */ -export const endpointEntriesArray = t.array( - t.union([endpointEntryMatch, endpointEntryMatchAny, endpointEntryNested]) -); - -/** - * @deprecated Use packages/kbn-securitysolution-io-ts-utils - */ -export type EndpointEntriesArray = t.TypeOf; - -/** - * Types the nonEmptyEndpointEntriesArray as: - * - An array of entries of length 1 or greater - * @deprecated Use packages/kbn-securitysolution-io-ts-utils - */ -export const nonEmptyEndpointEntriesArray = new t.Type< - EndpointEntriesArray, - EndpointEntriesArray, - unknown ->( - 'NonEmptyEndpointEntriesArray', - (u: unknown): u is EndpointEntriesArray => endpointEntriesArray.is(u) && u.length > 0, - (input, context): Either => { - if (Array.isArray(input) && input.length === 0) { - return t.failure(input, context); - } else { - return endpointEntriesArray.validate(input, context); - } - }, - t.identity -); - -/** - * @deprecated Use packages/kbn-securitysolution-io-ts-utils - */ -export type NonEmptyEndpointEntriesArray = t.OutputOf; - -/** - * @deprecated Use packages/kbn-securitysolution-io-ts-utils - */ -export type NonEmptyEndpointEntriesArrayDecoded = t.TypeOf; diff --git a/x-pack/plugins/lists/common/schemas/types/endpoint/entry_match.mock.ts b/x-pack/plugins/lists/common/schemas/types/endpoint/entry_match.mock.ts deleted file mode 100644 index ebdee20a6ebc8..0000000000000 --- a/x-pack/plugins/lists/common/schemas/types/endpoint/entry_match.mock.ts +++ /dev/null @@ -1,17 +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 { ENTRY_VALUE, FIELD, MATCH, OPERATOR } from '../../../constants.mock'; - -import { EndpointEntryMatch } from './entry_match'; - -export const getEndpointEntryMatchMock = (): EndpointEntryMatch => ({ - field: FIELD, - operator: OPERATOR, - type: MATCH, - value: ENTRY_VALUE, -}); diff --git a/x-pack/plugins/lists/common/schemas/types/endpoint/entry_match.test.ts b/x-pack/plugins/lists/common/schemas/types/endpoint/entry_match.test.ts deleted file mode 100644 index 7a970b873ba06..0000000000000 --- a/x-pack/plugins/lists/common/schemas/types/endpoint/entry_match.test.ts +++ /dev/null @@ -1,103 +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 { pipe } from 'fp-ts/lib/pipeable'; -import { left } from 'fp-ts/lib/Either'; - -import { foldLeftRight, getPaths } from '../../../shared_imports'; -import { getEntryMatchMock } from '../entry_match.mock'; - -import { getEndpointEntryMatchMock } from './entry_match.mock'; -import { EndpointEntryMatch, endpointEntryMatch } from './entry_match'; - -describe('endpointEntryMatch', () => { - test('it should validate an entry', () => { - const payload = getEndpointEntryMatchMock(); - const decoded = endpointEntryMatch.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); - }); - - test('it should NOT validate when "operator" is "excluded"', () => { - // Use the generic entry mock so we can test operator: excluded - const payload = getEntryMatchMock(); - payload.operator = 'excluded'; - const decoded = endpointEntryMatch.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "excluded" supplied to "operator"', - ]); - expect(message.schema).toEqual({}); - }); - - test('it should FAIL validation when "field" is empty string', () => { - const payload: Omit & { field: string } = { - ...getEndpointEntryMatchMock(), - field: '', - }; - const decoded = endpointEntryMatch.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual(['Invalid value "" supplied to "field"']); - expect(message.schema).toEqual({}); - }); - - test('it should FAIL validation when "value" is not string', () => { - const payload: Omit & { value: string[] } = { - ...getEndpointEntryMatchMock(), - value: ['some value'], - }; - const decoded = endpointEntryMatch.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "["some value"]" supplied to "value"', - ]); - expect(message.schema).toEqual({}); - }); - - test('it should FAIL validation when "value" is empty string', () => { - const payload: Omit & { value: string } = { - ...getEndpointEntryMatchMock(), - value: '', - }; - const decoded = endpointEntryMatch.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual(['Invalid value "" supplied to "value"']); - expect(message.schema).toEqual({}); - }); - - test('it should FAIL validation when "type" is not "match"', () => { - const payload: Omit & { type: string } = { - ...getEndpointEntryMatchMock(), - type: 'match_any', - }; - const decoded = endpointEntryMatch.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "match_any" supplied to "type"', - ]); - expect(message.schema).toEqual({}); - }); - - test('it should strip out extra keys', () => { - const payload: EndpointEntryMatch & { - extraKey?: string; - } = getEndpointEntryMatchMock(); - payload.extraKey = 'some value'; - const decoded = endpointEntryMatch.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(getEntryMatchMock()); - }); -}); diff --git a/x-pack/plugins/lists/common/schemas/types/endpoint/entry_match.ts b/x-pack/plugins/lists/common/schemas/types/endpoint/entry_match.ts deleted file mode 100644 index e4c24aa5e3560..0000000000000 --- a/x-pack/plugins/lists/common/schemas/types/endpoint/entry_match.ts +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import * as t from 'io-ts'; - -import { NonEmptyString } from '../../../shared_imports'; -import { operatorIncluded } from '../../common/schemas'; - -/** - * @deprecated Use packages/kbn-securitysolution-io-ts-utils - */ -export const endpointEntryMatch = t.exact( - t.type({ - field: NonEmptyString, - operator: operatorIncluded, - type: t.keyof({ match: null }), - value: NonEmptyString, - }) -); - -/** - * @deprecated Use packages/kbn-securitysolution-io-ts-utils - */ -export type EndpointEntryMatch = t.TypeOf; diff --git a/x-pack/plugins/lists/common/schemas/types/endpoint/entry_match_any.mock.ts b/x-pack/plugins/lists/common/schemas/types/endpoint/entry_match_any.mock.ts deleted file mode 100644 index dd884056b5c7c..0000000000000 --- a/x-pack/plugins/lists/common/schemas/types/endpoint/entry_match_any.mock.ts +++ /dev/null @@ -1,17 +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 { ENTRY_VALUE, FIELD, MATCH_ANY, OPERATOR } from '../../../constants.mock'; - -import { EndpointEntryMatchAny } from './entry_match_any'; - -export const getEndpointEntryMatchAnyMock = (): EndpointEntryMatchAny => ({ - field: FIELD, - operator: OPERATOR, - type: MATCH_ANY, - value: [ENTRY_VALUE], -}); diff --git a/x-pack/plugins/lists/common/schemas/types/endpoint/entry_match_any.test.ts b/x-pack/plugins/lists/common/schemas/types/endpoint/entry_match_any.test.ts deleted file mode 100644 index e460c8ceb86ff..0000000000000 --- a/x-pack/plugins/lists/common/schemas/types/endpoint/entry_match_any.test.ts +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { pipe } from 'fp-ts/lib/pipeable'; -import { left } from 'fp-ts/lib/Either'; - -import { foldLeftRight, getPaths } from '../../../shared_imports'; -import { getEntryMatchAnyMock } from '../entry_match_any.mock'; - -import { getEndpointEntryMatchAnyMock } from './entry_match_any.mock'; -import { EndpointEntryMatchAny, endpointEntryMatchAny } from './entry_match_any'; - -describe('endpointEntryMatchAny', () => { - test('it should validate an entry', () => { - const payload = getEndpointEntryMatchAnyMock(); - const decoded = endpointEntryMatchAny.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); - }); - - test('it should NOT validate when operator is "excluded"', () => { - // Use the generic entry mock so we can test operator: excluded - const payload = getEntryMatchAnyMock(); - payload.operator = 'excluded'; - const decoded = endpointEntryMatchAny.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "excluded" supplied to "operator"', - ]); - expect(message.schema).toEqual({}); - }); - - test('it should FAIL validation when field is empty string', () => { - const payload: Omit & { field: string } = { - ...getEndpointEntryMatchAnyMock(), - field: '', - }; - const decoded = endpointEntryMatchAny.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual(['Invalid value "" supplied to "field"']); - expect(message.schema).toEqual({}); - }); - - test('it should FAIL validation when value is empty array', () => { - const payload: Omit & { value: string[] } = { - ...getEndpointEntryMatchAnyMock(), - value: [], - }; - const decoded = endpointEntryMatchAny.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual(['Invalid value "[]" supplied to "value"']); - expect(message.schema).toEqual({}); - }); - - test('it should FAIL validation when value is not string array', () => { - const payload: Omit & { value: string } = { - ...getEndpointEntryMatchAnyMock(), - value: 'some string', - }; - const decoded = endpointEntryMatchAny.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "some string" supplied to "value"', - ]); - expect(message.schema).toEqual({}); - }); - - test('it should FAIL validation when "type" is not "match_any"', () => { - const payload: Omit & { type: string } = { - ...getEndpointEntryMatchAnyMock(), - type: 'match', - }; - const decoded = endpointEntryMatchAny.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual(['Invalid value "match" supplied to "type"']); - expect(message.schema).toEqual({}); - }); - - test('it should strip out extra keys', () => { - const payload: EndpointEntryMatchAny & { - extraKey?: string; - } = getEndpointEntryMatchAnyMock(); - payload.extraKey = 'some extra key'; - const decoded = endpointEntryMatchAny.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(getEntryMatchAnyMock()); - }); -}); diff --git a/x-pack/plugins/lists/common/schemas/types/endpoint/entry_match_any.ts b/x-pack/plugins/lists/common/schemas/types/endpoint/entry_match_any.ts deleted file mode 100644 index ffafd0f786547..0000000000000 --- a/x-pack/plugins/lists/common/schemas/types/endpoint/entry_match_any.ts +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import * as t from 'io-ts'; - -import { NonEmptyString } from '../../../shared_imports'; -import { operatorIncluded } from '../../common/schemas'; -import { nonEmptyOrNullableStringArray } from '../non_empty_or_nullable_string_array'; - -/** - * @deprecated Use packages/kbn-securitysolution-io-ts-utils - */ -export const endpointEntryMatchAny = t.exact( - t.type({ - field: NonEmptyString, - operator: operatorIncluded, - type: t.keyof({ match_any: null }), - value: nonEmptyOrNullableStringArray, - }) -); - -/** - * @deprecated Use packages/kbn-securitysolution-io-ts-utils - */ -export type EndpointEntryMatchAny = t.TypeOf; diff --git a/x-pack/plugins/lists/common/schemas/types/endpoint/entry_match_wildcard.ts b/x-pack/plugins/lists/common/schemas/types/endpoint/entry_match_wildcard.ts deleted file mode 100644 index ca4894991664c..0000000000000 --- a/x-pack/plugins/lists/common/schemas/types/endpoint/entry_match_wildcard.ts +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import * as t from 'io-ts'; - -import { NonEmptyString } from '../../../shared_imports'; -import { operatorIncluded } from '../../common/schemas'; - -/** - * @deprecated Use packages/kbn-securitysolution-io-ts-utils - */ -export const endpointEntryMatchWildcard = t.exact( - t.type({ - field: NonEmptyString, - operator: operatorIncluded, - type: t.keyof({ wildcard: null }), - value: NonEmptyString, - }) -); - -/** - * @deprecated Use packages/kbn-securitysolution-io-ts-utils - */ -export type EndpointEntryMatchWildcard = t.TypeOf; diff --git a/x-pack/plugins/lists/common/schemas/types/endpoint/entry_nested.mock.ts b/x-pack/plugins/lists/common/schemas/types/endpoint/entry_nested.mock.ts deleted file mode 100644 index 6a6aeb7a229df..0000000000000 --- a/x-pack/plugins/lists/common/schemas/types/endpoint/entry_nested.mock.ts +++ /dev/null @@ -1,18 +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 { FIELD, NESTED } from '../../../constants.mock'; - -import { EndpointEntryNested } from './entry_nested'; -import { getEndpointEntryMatchMock } from './entry_match.mock'; -import { getEndpointEntryMatchAnyMock } from './entry_match_any.mock'; - -export const getEndpointEntryNestedMock = (): EndpointEntryNested => ({ - entries: [getEndpointEntryMatchMock(), getEndpointEntryMatchAnyMock()], - field: FIELD, - type: NESTED, -}); diff --git a/x-pack/plugins/lists/common/schemas/types/endpoint/entry_nested.test.ts b/x-pack/plugins/lists/common/schemas/types/endpoint/entry_nested.test.ts deleted file mode 100644 index 75432db0da4e4..0000000000000 --- a/x-pack/plugins/lists/common/schemas/types/endpoint/entry_nested.test.ts +++ /dev/null @@ -1,138 +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 { pipe } from 'fp-ts/lib/pipeable'; -import { left } from 'fp-ts/lib/Either'; - -import { foldLeftRight, getPaths } from '../../../shared_imports'; -import { getEntryExistsMock } from '../entry_exists.mock'; - -import { getEndpointEntryNestedMock } from './entry_nested.mock'; -import { EndpointEntryNested, endpointEntryNested } from './entry_nested'; -import { getEndpointEntryMatchAnyMock } from './entry_match_any.mock'; -import { - NonEmptyEndpointNestedEntriesArray, - nonEmptyEndpointNestedEntriesArray, -} from './non_empty_nested_entries_array'; -import { getEndpointEntryMatchMock } from './entry_match.mock'; - -describe('endpointEntryNested', () => { - test('it should validate a nested entry', () => { - const payload = getEndpointEntryNestedMock(); - const decoded = endpointEntryNested.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); - }); - - test('it should FAIL validation when "type" is not "nested"', () => { - const payload: Omit & { type: 'match' } = { - ...getEndpointEntryNestedMock(), - type: 'match', - }; - const decoded = endpointEntryNested.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual(['Invalid value "match" supplied to "type"']); - expect(message.schema).toEqual({}); - }); - - test('it should FAIL validation when "field" is empty string', () => { - const payload: Omit & { - field: string; - } = { ...getEndpointEntryNestedMock(), field: '' }; - const decoded = endpointEntryNested.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual(['Invalid value "" supplied to "field"']); - expect(message.schema).toEqual({}); - }); - - test('it should FAIL validation when "field" is not a string', () => { - const payload: Omit & { - field: number; - } = { ...getEndpointEntryNestedMock(), field: 1 }; - const decoded = endpointEntryNested.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual(['Invalid value "1" supplied to "field"']); - expect(message.schema).toEqual({}); - }); - - test('it should FAIL validation when "entries" is not an array', () => { - const payload: Omit & { - entries: string; - } = { ...getEndpointEntryNestedMock(), entries: 'im a string' }; - const decoded = endpointEntryNested.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "im a string" supplied to "entries"', - ]); - expect(message.schema).toEqual({}); - }); - - test('it should validate when "entries" contains an entry item that is type "match"', () => { - const payload = { ...getEndpointEntryNestedMock(), entries: [getEndpointEntryMatchAnyMock()] }; - const decoded = endpointEntryNested.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual({ - entries: [ - { - field: 'host.name', - operator: 'included', - type: 'match_any', - value: ['some host name'], - }, - ], - field: 'host.name', - type: 'nested', - }); - }); - - test('it should NOT validate when "entries" contains an entry item that is type "exists"', () => { - const payload = { ...getEndpointEntryNestedMock(), entries: [getEntryExistsMock()] }; - const decoded = endpointEntryNested.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "exists" supplied to "entries,type"', - 'Invalid value "undefined" supplied to "entries,value"', - ]); - expect(message.schema).toEqual({}); - }); - - test('it should strip out extra keys', () => { - const payload: EndpointEntryNested & { - extraKey?: string; - } = getEndpointEntryNestedMock(); - payload.extraKey = 'some extra key'; - const decoded = endpointEntryNested.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(getEndpointEntryNestedMock()); - }); - - test('type guard for nonEmptyEndpointNestedEntries should allow array of endpoint entries', () => { - const payload: NonEmptyEndpointNestedEntriesArray = [ - getEndpointEntryMatchMock(), - getEndpointEntryMatchAnyMock(), - ]; - const guarded = nonEmptyEndpointNestedEntriesArray.is(payload); - expect(guarded).toBeTruthy(); - }); - - test('type guard for nonEmptyEndpointNestedEntries should disallow empty arrays', () => { - const payload: NonEmptyEndpointNestedEntriesArray = []; - const guarded = nonEmptyEndpointNestedEntriesArray.is(payload); - expect(guarded).toBeFalsy(); - }); -}); diff --git a/x-pack/plugins/lists/common/schemas/types/endpoint/entry_nested.ts b/x-pack/plugins/lists/common/schemas/types/endpoint/entry_nested.ts deleted file mode 100644 index 4304b7cd06c37..0000000000000 --- a/x-pack/plugins/lists/common/schemas/types/endpoint/entry_nested.ts +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import * as t from 'io-ts'; - -import { NonEmptyString } from '../../../shared_imports'; - -import { nonEmptyEndpointNestedEntriesArray } from './non_empty_nested_entries_array'; - -/** - * @deprecated Use packages/kbn-securitysolution-io-ts-utils - */ -export const endpointEntryNested = t.exact( - t.type({ - entries: nonEmptyEndpointNestedEntriesArray, - field: NonEmptyString, - type: t.keyof({ nested: null }), - }) -); - -/** - * @deprecated Use packages/kbn-securitysolution-io-ts-utils - */ -export type EndpointEntryNested = t.TypeOf; diff --git a/x-pack/plugins/lists/common/schemas/types/endpoint/non_empty_nested_entries_array.ts b/x-pack/plugins/lists/common/schemas/types/endpoint/non_empty_nested_entries_array.ts deleted file mode 100644 index e6bd3d61f7d78..0000000000000 --- a/x-pack/plugins/lists/common/schemas/types/endpoint/non_empty_nested_entries_array.ts +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import * as t from 'io-ts'; -import { Either } from 'fp-ts/lib/Either'; - -import { endpointEntryMatchAny } from './entry_match_any'; -import { endpointEntryMatch } from './entry_match'; - -/** - * @deprecated Use packages/kbn-securitysolution-io-ts-utils - */ -export const endpointNestedEntriesArray = t.array( - t.union([endpointEntryMatch, endpointEntryMatchAny]) -); - -/** - * @deprecated Use packages/kbn-securitysolution-io-ts-utils - */ -export type EndpointNestedEntriesArray = t.TypeOf; - -/** - * Types the nonEmptyNestedEntriesArray as: - * - An array of entries of length 1 or greater - * @deprecated Use packages/kbn-securitysolution-io-ts-utils - */ -export const nonEmptyEndpointNestedEntriesArray = new t.Type< - EndpointNestedEntriesArray, - EndpointNestedEntriesArray, - unknown ->( - 'NonEmptyEndpointNestedEntriesArray', - (u: unknown): u is EndpointNestedEntriesArray => endpointNestedEntriesArray.is(u) && u.length > 0, - (input, context): Either => { - if (Array.isArray(input) && input.length === 0) { - return t.failure(input, context); - } else { - return endpointNestedEntriesArray.validate(input, context); - } - }, - t.identity -); - -/** - * @deprecated Use packages/kbn-securitysolution-io-ts-utils - */ -export type NonEmptyEndpointNestedEntriesArray = t.OutputOf< - typeof nonEmptyEndpointNestedEntriesArray ->; - -/** - * @deprecated Use packages/kbn-securitysolution-io-ts-utils - */ -export type NonEmptyEndpointNestedEntriesArrayDecoded = t.TypeOf< - typeof nonEmptyEndpointNestedEntriesArray ->; diff --git a/x-pack/plugins/lists/common/schemas/types/entries.mock.ts b/x-pack/plugins/lists/common/schemas/types/entries.mock.ts index 319eb687c4ced..ee43a0b26ad54 100644 --- a/x-pack/plugins/lists/common/schemas/types/entries.mock.ts +++ b/x-pack/plugins/lists/common/schemas/types/entries.mock.ts @@ -5,7 +5,8 @@ * 2.0. */ -import { EntriesArray } from './entries'; +import { EntriesArray } from '@kbn/securitysolution-io-ts-utils'; + import { getEntryMatchMock } from './entry_match.mock'; import { getEntryMatchAnyMock } from './entry_match_any.mock'; import { getEntryListMock } from './entry_list.mock'; diff --git a/x-pack/plugins/lists/common/schemas/types/entries.test.ts b/x-pack/plugins/lists/common/schemas/types/entries.test.ts deleted file mode 100644 index 2e1dd7a3da7cc..0000000000000 --- a/x-pack/plugins/lists/common/schemas/types/entries.test.ts +++ /dev/null @@ -1,149 +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 { pipe } from 'fp-ts/lib/pipeable'; -import { left } from 'fp-ts/lib/Either'; - -import { foldLeftRight, getPaths } from '../../shared_imports'; - -import { getEntryMatchMock } from './entry_match.mock'; -import { getEntryMatchAnyMock } from './entry_match_any.mock'; -import { getEntryListMock } from './entry_list.mock'; -import { getEntryExistsMock } from './entry_exists.mock'; -import { getEntryNestedMock } from './entry_nested.mock'; -import { getEntriesArrayMock } from './entries.mock'; -import { entriesArray, entriesArrayOrUndefined, entry } from './entries'; - -describe('Entries', () => { - describe('entry', () => { - test('it should validate a match entry', () => { - const payload = getEntryMatchMock(); - const decoded = entry.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); - }); - - test('it should validate a match_any entry', () => { - const payload = getEntryMatchAnyMock(); - const decoded = entry.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); - }); - - test('it should validate a exists entry', () => { - const payload = getEntryExistsMock(); - const decoded = entry.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); - }); - - test('it should validate a list entry', () => { - const payload = getEntryListMock(); - const decoded = entry.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); - }); - - test('it should FAIL validation of nested entry', () => { - const payload = getEntryNestedMock(); - const decoded = entry.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "undefined" supplied to "operator"', - 'Invalid value "nested" supplied to "type"', - 'Invalid value "undefined" supplied to "value"', - 'Invalid value "undefined" supplied to "list"', - ]); - expect(message.schema).toEqual({}); - }); - }); - - describe('entriesArray', () => { - test('it should validate an array with match entry', () => { - const payload = [getEntryMatchMock()]; - const decoded = entriesArray.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); - }); - - test('it should validate an array with match_any entry', () => { - const payload = [getEntryMatchAnyMock()]; - const decoded = entriesArray.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); - }); - - test('it should validate an array with exists entry', () => { - const payload = [getEntryExistsMock()]; - const decoded = entriesArray.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); - }); - - test('it should validate an array with list entry', () => { - const payload = [getEntryListMock()]; - const decoded = entriesArray.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); - }); - - test('it should validate an array with nested entry', () => { - const payload = [getEntryNestedMock()]; - const decoded = entriesArray.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); - }); - - test('it should validate an array with all types of entries', () => { - const payload = [...getEntriesArrayMock()]; - const decoded = entriesArray.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); - }); - }); - - describe('entriesArrayOrUndefined', () => { - test('it should validate undefined', () => { - const payload = undefined; - const decoded = entriesArrayOrUndefined.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); - }); - - test('it should validate an array with nested entry', () => { - const payload = [getEntryNestedMock()]; - const decoded = entriesArrayOrUndefined.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); - }); - }); -}); diff --git a/x-pack/plugins/lists/common/schemas/types/entries.ts b/x-pack/plugins/lists/common/schemas/types/entries.ts deleted file mode 100644 index 043348070031b..0000000000000 --- a/x-pack/plugins/lists/common/schemas/types/entries.ts +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import * as t from 'io-ts'; - -import { entriesMatchAny } from './entry_match_any'; -import { entriesMatch } from './entry_match'; -import { entriesExists } from './entry_exists'; -import { entriesList } from './entry_list'; -import { entriesNested } from './entry_nested'; -import { entriesMatchWildcard } from './entry_match_wildcard'; - -// NOTE: Type nested is not included here to denote it's non-recursive nature. -// So a nested entry is really just a collection of `Entry` types. - -/** - * @deprecated Use packages/kbn-securitysolution-io-ts-utils - */ -export const entry = t.union([ - entriesMatch, - entriesMatchAny, - entriesList, - entriesExists, - entriesMatchWildcard, -]); - -/** - * @deprecated Use packages/kbn-securitysolution-io-ts-utils - */ -export type Entry = t.TypeOf; - -/** - * @deprecated Use packages/kbn-securitysolution-io-ts-utils - */ -export const entriesArray = t.array( - t.union([ - entriesMatch, - entriesMatchAny, - entriesList, - entriesExists, - entriesNested, - entriesMatchWildcard, - ]) -); - -/** - * @deprecated Use packages/kbn-securitysolution-io-ts-utils - */ -export type EntriesArray = t.TypeOf; - -/** - * @deprecated Use packages/kbn-securitysolution-io-ts-utils - */ -export const entriesArrayOrUndefined = t.union([entriesArray, t.undefined]); - -/** - * @deprecated Use packages/kbn-securitysolution-io-ts-utils - */ -export type EntriesArrayOrUndefined = t.TypeOf; diff --git a/x-pack/plugins/lists/common/schemas/types/entry_exists.mock.ts b/x-pack/plugins/lists/common/schemas/types/entry_exists.mock.ts index 9c8f199d3a3a1..3e26d261f44ca 100644 --- a/x-pack/plugins/lists/common/schemas/types/entry_exists.mock.ts +++ b/x-pack/plugins/lists/common/schemas/types/entry_exists.mock.ts @@ -5,9 +5,9 @@ * 2.0. */ -import { EXISTS, FIELD, OPERATOR } from '../../constants.mock'; +import { EntryExists } from '@kbn/securitysolution-io-ts-utils'; -import { EntryExists } from './entry_exists'; +import { EXISTS, FIELD, OPERATOR } from '../../constants.mock'; export const getEntryExistsMock = (): EntryExists => ({ field: FIELD, diff --git a/x-pack/plugins/lists/common/schemas/types/entry_exists.test.ts b/x-pack/plugins/lists/common/schemas/types/entry_exists.test.ts deleted file mode 100644 index dafae6a45995f..0000000000000 --- a/x-pack/plugins/lists/common/schemas/types/entry_exists.test.ts +++ /dev/null @@ -1,80 +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 { pipe } from 'fp-ts/lib/pipeable'; -import { left } from 'fp-ts/lib/Either'; - -import { foldLeftRight, getPaths } from '../../shared_imports'; - -import { getEntryExistsMock } from './entry_exists.mock'; -import { EntryExists, entriesExists } from './entry_exists'; - -describe('entriesExists', () => { - test('it should validate an entry', () => { - const payload = getEntryExistsMock(); - const decoded = entriesExists.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); - }); - - test('it should validate when "operator" is "included"', () => { - const payload = getEntryExistsMock(); - const decoded = entriesExists.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); - }); - - test('it should validate when "operator" is "excluded"', () => { - const payload = getEntryExistsMock(); - payload.operator = 'excluded'; - const decoded = entriesExists.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); - }); - - test('it should FAIL validation when "field" is empty string', () => { - const payload: Omit & { field: string } = { - ...getEntryExistsMock(), - field: '', - }; - const decoded = entriesExists.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual(['Invalid value "" supplied to "field"']); - expect(message.schema).toEqual({}); - }); - - test('it should strip out extra keys', () => { - const payload: EntryExists & { - extraKey?: string; - } = getEntryExistsMock(); - payload.extraKey = 'some extra key'; - const decoded = entriesExists.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(getEntryExistsMock()); - }); - - test('it should FAIL validation when "type" is not "exists"', () => { - const payload: Omit & { type: string } = { - ...getEntryExistsMock(), - type: 'match', - }; - const decoded = entriesExists.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual(['Invalid value "match" supplied to "type"']); - expect(message.schema).toEqual({}); - }); -}); diff --git a/x-pack/plugins/lists/common/schemas/types/entry_exists.ts b/x-pack/plugins/lists/common/schemas/types/entry_exists.ts deleted file mode 100644 index 32fb8888c88dc..0000000000000 --- a/x-pack/plugins/lists/common/schemas/types/entry_exists.ts +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import * as t from 'io-ts'; - -import { NonEmptyString } from '../../shared_imports'; -import { operator } from '../common/schemas'; - -/** - * @deprecated Use packages/kbn-securitysolution-io-ts-utils - */ -export const entriesExists = t.exact( - t.type({ - field: NonEmptyString, - operator, - type: t.keyof({ exists: null }), - }) -); - -/** - * @deprecated Use packages/kbn-securitysolution-io-ts-utils - */ -export type EntryExists = t.TypeOf; diff --git a/x-pack/plugins/lists/common/schemas/types/entry_list.mock.ts b/x-pack/plugins/lists/common/schemas/types/entry_list.mock.ts index d48d725a7c3f5..7eadfcdf3454c 100644 --- a/x-pack/plugins/lists/common/schemas/types/entry_list.mock.ts +++ b/x-pack/plugins/lists/common/schemas/types/entry_list.mock.ts @@ -5,9 +5,9 @@ * 2.0. */ -import { FIELD, LIST, LIST_ID, OPERATOR, TYPE } from '../../constants.mock'; +import { EntryList } from '@kbn/securitysolution-io-ts-utils'; -import { EntryList } from './entry_list'; +import { FIELD, LIST, LIST_ID, OPERATOR, TYPE } from '../../constants.mock'; export const getEntryListMock = (): EntryList => ({ field: FIELD, diff --git a/x-pack/plugins/lists/common/schemas/types/entry_list.test.ts b/x-pack/plugins/lists/common/schemas/types/entry_list.test.ts deleted file mode 100644 index 4a4caf9e6bc4c..0000000000000 --- a/x-pack/plugins/lists/common/schemas/types/entry_list.test.ts +++ /dev/null @@ -1,96 +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 { pipe } from 'fp-ts/lib/pipeable'; -import { left } from 'fp-ts/lib/Either'; - -import { foldLeftRight, getPaths } from '../../shared_imports'; - -import { getEntryListMock } from './entry_list.mock'; -import { EntryList, entriesList } from './entry_list'; - -describe('entriesList', () => { - test('it should validate an entry', () => { - const payload = getEntryListMock(); - const decoded = entriesList.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); - }); - - test('it should validate when operator is "included"', () => { - const payload = getEntryListMock(); - const decoded = entriesList.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); - }); - - test('it should validate when "operator" is "excluded"', () => { - const payload = getEntryListMock(); - payload.operator = 'excluded'; - const decoded = entriesList.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); - }); - - test('it should FAIL validation when "list" is not expected value', () => { - const payload: Omit & { list: string } = { - ...getEntryListMock(), - list: 'someListId', - }; - const decoded = entriesList.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "someListId" supplied to "list"', - ]); - expect(message.schema).toEqual({}); - }); - - test('it should FAIL validation when "list.id" is empty string', () => { - const payload: Omit & { list: { id: string; type: 'ip' } } = { - ...getEntryListMock(), - list: { id: '', type: 'ip' }, - }; - const decoded = entriesList.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual(['Invalid value "" supplied to "list,id"']); - expect(message.schema).toEqual({}); - }); - - test('it should FAIL validation when "type" is not "lists"', () => { - const payload: Omit & { type: 'match_any' } = { - ...getEntryListMock(), - type: 'match_any', - }; - const decoded = entriesList.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "match_any" supplied to "type"', - ]); - expect(message.schema).toEqual({}); - }); - - test('it should strip out extra keys', () => { - const payload: EntryList & { - extraKey?: string; - } = getEntryListMock(); - payload.extraKey = 'some extra key'; - const decoded = entriesList.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(getEntryListMock()); - }); -}); diff --git a/x-pack/plugins/lists/common/schemas/types/entry_list.ts b/x-pack/plugins/lists/common/schemas/types/entry_list.ts deleted file mode 100644 index 9b2b345244805..0000000000000 --- a/x-pack/plugins/lists/common/schemas/types/entry_list.ts +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import * as t from 'io-ts'; - -import { NonEmptyString } from '../../shared_imports'; -import { operator, type } from '../common/schemas'; - -/** - * @deprecated Use packages/kbn-securitysolution-io-ts-utils - */ -export const entriesList = t.exact( - t.type({ - field: NonEmptyString, - list: t.exact(t.type({ id: NonEmptyString, type })), - operator, - type: t.keyof({ list: null }), - }) -); - -/** - * @deprecated Use packages/kbn-securitysolution-io-ts-utils - */ -export type EntryList = t.TypeOf; diff --git a/x-pack/plugins/lists/common/schemas/types/entry_match.mock.ts b/x-pack/plugins/lists/common/schemas/types/entry_match.mock.ts index 7362d30ad52f1..bc0eb3b5c4f85 100644 --- a/x-pack/plugins/lists/common/schemas/types/entry_match.mock.ts +++ b/x-pack/plugins/lists/common/schemas/types/entry_match.mock.ts @@ -5,9 +5,9 @@ * 2.0. */ -import { ENTRY_VALUE, FIELD, MATCH, OPERATOR } from '../../constants.mock'; +import { EntryMatch } from '@kbn/securitysolution-io-ts-utils'; -import { EntryMatch } from './entry_match'; +import { ENTRY_VALUE, FIELD, MATCH, OPERATOR } from '../../constants.mock'; export const getEntryMatchMock = (): EntryMatch => ({ field: FIELD, diff --git a/x-pack/plugins/lists/common/schemas/types/entry_match.test.ts b/x-pack/plugins/lists/common/schemas/types/entry_match.test.ts deleted file mode 100644 index c2331ca2dbeb8..0000000000000 --- a/x-pack/plugins/lists/common/schemas/types/entry_match.test.ts +++ /dev/null @@ -1,108 +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 { pipe } from 'fp-ts/lib/pipeable'; -import { left } from 'fp-ts/lib/Either'; - -import { foldLeftRight, getPaths } from '../../shared_imports'; - -import { getEntryMatchMock } from './entry_match.mock'; -import { EntryMatch, entriesMatch } from './entry_match'; - -describe('entriesMatch', () => { - test('it should validate an entry', () => { - const payload = getEntryMatchMock(); - const decoded = entriesMatch.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); - }); - - test('it should validate when operator is "included"', () => { - const payload = getEntryMatchMock(); - const decoded = entriesMatch.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); - }); - - test('it should validate when "operator" is "excluded"', () => { - const payload = getEntryMatchMock(); - payload.operator = 'excluded'; - const decoded = entriesMatch.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); - }); - - test('it should FAIL validation when "field" is empty string', () => { - const payload: Omit & { field: string } = { - ...getEntryMatchMock(), - field: '', - }; - const decoded = entriesMatch.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual(['Invalid value "" supplied to "field"']); - expect(message.schema).toEqual({}); - }); - - test('it should FAIL validation when "value" is not string', () => { - const payload: Omit & { value: string[] } = { - ...getEntryMatchMock(), - value: ['some value'], - }; - const decoded = entriesMatch.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "["some value"]" supplied to "value"', - ]); - expect(message.schema).toEqual({}); - }); - - test('it should FAIL validation when "value" is empty string', () => { - const payload: Omit & { value: string } = { - ...getEntryMatchMock(), - value: '', - }; - const decoded = entriesMatch.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual(['Invalid value "" supplied to "value"']); - expect(message.schema).toEqual({}); - }); - - test('it should FAIL validation when "type" is not "match"', () => { - const payload: Omit & { type: string } = { - ...getEntryMatchMock(), - type: 'match_any', - }; - const decoded = entriesMatch.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "match_any" supplied to "type"', - ]); - expect(message.schema).toEqual({}); - }); - - test('it should strip out extra keys', () => { - const payload: EntryMatch & { - extraKey?: string; - } = getEntryMatchMock(); - payload.extraKey = 'some value'; - const decoded = entriesMatch.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(getEntryMatchMock()); - }); -}); diff --git a/x-pack/plugins/lists/common/schemas/types/entry_match.ts b/x-pack/plugins/lists/common/schemas/types/entry_match.ts deleted file mode 100644 index e3529f2d043be..0000000000000 --- a/x-pack/plugins/lists/common/schemas/types/entry_match.ts +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import * as t from 'io-ts'; - -import { NonEmptyString } from '../../shared_imports'; -import { operator } from '../common/schemas'; - -/** - * @deprecated Use packages/kbn-securitysolution-io-ts-utils - */ -export const entriesMatch = t.exact( - t.type({ - field: NonEmptyString, - operator, - type: t.keyof({ match: null }), - value: NonEmptyString, - }) -); - -/** - * @deprecated Use packages/kbn-securitysolution-io-ts-utils - */ -export type EntryMatch = t.TypeOf; diff --git a/x-pack/plugins/lists/common/schemas/types/entry_match_any.mock.ts b/x-pack/plugins/lists/common/schemas/types/entry_match_any.mock.ts index 2b92454fd1012..74c3abbaa5881 100644 --- a/x-pack/plugins/lists/common/schemas/types/entry_match_any.mock.ts +++ b/x-pack/plugins/lists/common/schemas/types/entry_match_any.mock.ts @@ -5,9 +5,9 @@ * 2.0. */ -import { ENTRY_VALUE, FIELD, MATCH_ANY, OPERATOR } from '../../constants.mock'; +import { EntryMatchAny } from '@kbn/securitysolution-io-ts-utils'; -import { EntryMatchAny } from './entry_match_any'; +import { ENTRY_VALUE, FIELD, MATCH_ANY, OPERATOR } from '../../constants.mock'; export const getEntryMatchAnyMock = (): EntryMatchAny => ({ field: FIELD, diff --git a/x-pack/plugins/lists/common/schemas/types/entry_match_any.test.ts b/x-pack/plugins/lists/common/schemas/types/entry_match_any.test.ts deleted file mode 100644 index 63e386290afb0..0000000000000 --- a/x-pack/plugins/lists/common/schemas/types/entry_match_any.test.ts +++ /dev/null @@ -1,106 +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 { pipe } from 'fp-ts/lib/pipeable'; -import { left } from 'fp-ts/lib/Either'; - -import { foldLeftRight, getPaths } from '../../shared_imports'; - -import { getEntryMatchAnyMock } from './entry_match_any.mock'; -import { EntryMatchAny, entriesMatchAny } from './entry_match_any'; - -describe('entriesMatchAny', () => { - test('it should validate an entry', () => { - const payload = getEntryMatchAnyMock(); - const decoded = entriesMatchAny.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); - }); - - test('it should validate when operator is "included"', () => { - const payload = getEntryMatchAnyMock(); - const decoded = entriesMatchAny.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); - }); - - test('it should validate when operator is "excluded"', () => { - const payload = getEntryMatchAnyMock(); - payload.operator = 'excluded'; - const decoded = entriesMatchAny.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); - }); - - test('it should FAIL validation when field is empty string', () => { - const payload: Omit & { field: string } = { - ...getEntryMatchAnyMock(), - field: '', - }; - const decoded = entriesMatchAny.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual(['Invalid value "" supplied to "field"']); - expect(message.schema).toEqual({}); - }); - - test('it should FAIL validation when value is empty array', () => { - const payload: Omit & { value: string[] } = { - ...getEntryMatchAnyMock(), - value: [], - }; - const decoded = entriesMatchAny.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual(['Invalid value "[]" supplied to "value"']); - expect(message.schema).toEqual({}); - }); - - test('it should FAIL validation when value is not string array', () => { - const payload: Omit & { value: string } = { - ...getEntryMatchAnyMock(), - value: 'some string', - }; - const decoded = entriesMatchAny.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "some string" supplied to "value"', - ]); - expect(message.schema).toEqual({}); - }); - - test('it should FAIL validation when "type" is not "match_any"', () => { - const payload: Omit & { type: string } = { - ...getEntryMatchAnyMock(), - type: 'match', - }; - const decoded = entriesMatchAny.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual(['Invalid value "match" supplied to "type"']); - expect(message.schema).toEqual({}); - }); - - test('it should strip out extra keys', () => { - const payload: EntryMatchAny & { - extraKey?: string; - } = getEntryMatchAnyMock(); - payload.extraKey = 'some extra key'; - const decoded = entriesMatchAny.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(getEntryMatchAnyMock()); - }); -}); diff --git a/x-pack/plugins/lists/common/schemas/types/entry_match_any.ts b/x-pack/plugins/lists/common/schemas/types/entry_match_any.ts deleted file mode 100644 index d4846c7949ebb..0000000000000 --- a/x-pack/plugins/lists/common/schemas/types/entry_match_any.ts +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import * as t from 'io-ts'; - -import { NonEmptyString } from '../../shared_imports'; -import { operator } from '../common/schemas'; - -import { nonEmptyOrNullableStringArray } from './non_empty_or_nullable_string_array'; - -/** - * @deprecated Use packages/kbn-securitysolution-io-ts-utils - */ -export const entriesMatchAny = t.exact( - t.type({ - field: NonEmptyString, - operator, - type: t.keyof({ match_any: null }), - value: nonEmptyOrNullableStringArray, - }) -); - -/** - * @deprecated Use packages/kbn-securitysolution-io-ts-utils - */ -export type EntryMatchAny = t.TypeOf; diff --git a/x-pack/plugins/lists/common/schemas/types/entry_match_wildcard.mock.ts b/x-pack/plugins/lists/common/schemas/types/entry_match_wildcard.mock.ts index 3204bbe064496..320664bd2f833 100644 --- a/x-pack/plugins/lists/common/schemas/types/entry_match_wildcard.mock.ts +++ b/x-pack/plugins/lists/common/schemas/types/entry_match_wildcard.mock.ts @@ -5,9 +5,9 @@ * 2.0. */ -import { ENTRY_VALUE, FIELD, OPERATOR, WILDCARD } from '../../constants.mock'; +import { EntryMatchWildcard } from '@kbn/securitysolution-io-ts-utils'; -import { EntryMatchWildcard } from './entry_match_wildcard'; +import { ENTRY_VALUE, FIELD, OPERATOR, WILDCARD } from '../../constants.mock'; export const getEntryMatchWildcardMock = (): EntryMatchWildcard => ({ field: FIELD, diff --git a/x-pack/plugins/lists/common/schemas/types/entry_match_wildcard.test.ts b/x-pack/plugins/lists/common/schemas/types/entry_match_wildcard.test.ts deleted file mode 100644 index 53cfc4fdff1f5..0000000000000 --- a/x-pack/plugins/lists/common/schemas/types/entry_match_wildcard.test.ts +++ /dev/null @@ -1,106 +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 { pipe } from 'fp-ts/lib/pipeable'; -import { left } from 'fp-ts/lib/Either'; - -import { foldLeftRight, getPaths } from '../../shared_imports'; - -import { getEntryMatchWildcardMock } from './entry_match_wildcard.mock'; -import { EntryMatchWildcard, entriesMatchWildcard } from './entry_match_wildcard'; - -describe('entriesMatchWildcard', () => { - test('it should validate an entry', () => { - const payload = getEntryMatchWildcardMock(); - const decoded = entriesMatchWildcard.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); - }); - - test('it should validate when operator is "included"', () => { - const payload = getEntryMatchWildcardMock(); - const decoded = entriesMatchWildcard.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); - }); - - test('it should validate when "operator" is "excluded"', () => { - const payload = getEntryMatchWildcardMock(); - payload.operator = 'excluded'; - const decoded = entriesMatchWildcard.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); - }); - - test('it should FAIL validation when "field" is empty string', () => { - const payload: Omit & { field: string } = { - ...getEntryMatchWildcardMock(), - field: '', - }; - const decoded = entriesMatchWildcard.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual(['Invalid value "" supplied to "field"']); - expect(message.schema).toEqual({}); - }); - - test('it should FAIL validation when "value" is not string', () => { - const payload: Omit & { value: string[] } = { - ...getEntryMatchWildcardMock(), - value: ['some value'], - }; - const decoded = entriesMatchWildcard.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "["some value"]" supplied to "value"', - ]); - expect(message.schema).toEqual({}); - }); - - test('it should FAIL validation when "value" is empty string', () => { - const payload: Omit & { value: string } = { - ...getEntryMatchWildcardMock(), - value: '', - }; - const decoded = entriesMatchWildcard.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual(['Invalid value "" supplied to "value"']); - expect(message.schema).toEqual({}); - }); - - test('it should FAIL validation when "type" is not "wildcard"', () => { - const payload: Omit & { type: string } = { - ...getEntryMatchWildcardMock(), - type: 'match', - }; - const decoded = entriesMatchWildcard.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual(['Invalid value "match" supplied to "type"']); - expect(message.schema).toEqual({}); - }); - - test('it should strip out extra keys', () => { - const payload: EntryMatchWildcard & { - extraKey?: string; - } = getEntryMatchWildcardMock(); - payload.extraKey = 'some value'; - const decoded = entriesMatchWildcard.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(getEntryMatchWildcardMock()); - }); -}); diff --git a/x-pack/plugins/lists/common/schemas/types/entry_match_wildcard.ts b/x-pack/plugins/lists/common/schemas/types/entry_match_wildcard.ts deleted file mode 100644 index 1c8cb4dcdd2d8..0000000000000 --- a/x-pack/plugins/lists/common/schemas/types/entry_match_wildcard.ts +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import * as t from 'io-ts'; - -import { NonEmptyString } from '../../shared_imports'; -import { operator } from '../common/schemas'; - -/** - * @deprecated Use packages/kbn-securitysolution-io-ts-utils - */ -export const entriesMatchWildcard = t.exact( - t.type({ - field: NonEmptyString, - operator, - type: t.keyof({ wildcard: null }), - value: NonEmptyString, - }) -); - -/** - * @deprecated Use packages/kbn-securitysolution-io-ts-utils - */ -export type EntryMatchWildcard = t.TypeOf; diff --git a/x-pack/plugins/lists/common/schemas/types/entry_nested.mock.ts b/x-pack/plugins/lists/common/schemas/types/entry_nested.mock.ts index 8966964a29223..f1d0a2bc76926 100644 --- a/x-pack/plugins/lists/common/schemas/types/entry_nested.mock.ts +++ b/x-pack/plugins/lists/common/schemas/types/entry_nested.mock.ts @@ -5,9 +5,10 @@ * 2.0. */ +import { EntryNested } from '@kbn/securitysolution-io-ts-utils'; + import { NESTED, NESTED_FIELD } from '../../constants.mock'; -import { EntryNested } from './entry_nested'; import { getEntryMatchExcludeMock, getEntryMatchMock } from './entry_match.mock'; import { getEntryMatchAnyExcludeMock, getEntryMatchAnyMock } from './entry_match_any.mock'; import { getEntryExistsMock } from './entry_exists.mock'; diff --git a/x-pack/plugins/lists/common/schemas/types/entry_nested.test.ts b/x-pack/plugins/lists/common/schemas/types/entry_nested.test.ts deleted file mode 100644 index a94b46c1d4a9e..0000000000000 --- a/x-pack/plugins/lists/common/schemas/types/entry_nested.test.ts +++ /dev/null @@ -1,125 +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 { pipe } from 'fp-ts/lib/pipeable'; -import { left } from 'fp-ts/lib/Either'; - -import { foldLeftRight, getPaths } from '../../shared_imports'; - -import { getEntryNestedMock } from './entry_nested.mock'; -import { EntryNested, entriesNested } from './entry_nested'; -import { getEntryMatchAnyMock } from './entry_match_any.mock'; -import { getEntryExistsMock } from './entry_exists.mock'; - -describe('entriesNested', () => { - test('it should validate a nested entry', () => { - const payload = getEntryNestedMock(); - const decoded = entriesNested.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); - }); - - test('it should FAIL validation when "type" is not "nested"', () => { - const payload: Omit & { type: 'match' } = { - ...getEntryNestedMock(), - type: 'match', - }; - const decoded = entriesNested.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual(['Invalid value "match" supplied to "type"']); - expect(message.schema).toEqual({}); - }); - - test('it should FAIL validation when "field" is empty string', () => { - const payload: Omit & { - field: string; - } = { ...getEntryNestedMock(), field: '' }; - const decoded = entriesNested.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual(['Invalid value "" supplied to "field"']); - expect(message.schema).toEqual({}); - }); - - test('it should FAIL validation when "field" is not a string', () => { - const payload: Omit & { - field: number; - } = { ...getEntryNestedMock(), field: 1 }; - const decoded = entriesNested.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual(['Invalid value "1" supplied to "field"']); - expect(message.schema).toEqual({}); - }); - - test('it should FAIL validation when "entries" is not a an array', () => { - const payload: Omit & { - entries: string; - } = { ...getEntryNestedMock(), entries: 'im a string' }; - const decoded = entriesNested.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "im a string" supplied to "entries"', - ]); - expect(message.schema).toEqual({}); - }); - - test('it should validate when "entries" contains an entry item that is type "match"', () => { - const payload = { ...getEntryNestedMock(), entries: [getEntryMatchAnyMock()] }; - const decoded = entriesNested.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual({ - entries: [ - { - field: 'host.name', - operator: 'included', - type: 'match_any', - value: ['some host name'], - }, - ], - field: 'parent.field', - type: 'nested', - }); - }); - - test('it should validate when "entries" contains an entry item that is type "exists"', () => { - const payload = { ...getEntryNestedMock(), entries: [getEntryExistsMock()] }; - const decoded = entriesNested.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual({ - entries: [ - { - field: 'host.name', - operator: 'included', - type: 'exists', - }, - ], - field: 'parent.field', - type: 'nested', - }); - }); - - test('it should strip out extra keys', () => { - const payload: EntryNested & { - extraKey?: string; - } = getEntryNestedMock(); - payload.extraKey = 'some extra key'; - const decoded = entriesNested.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(getEntryNestedMock()); - }); -}); diff --git a/x-pack/plugins/lists/common/schemas/types/entry_nested.ts b/x-pack/plugins/lists/common/schemas/types/entry_nested.ts deleted file mode 100644 index e0027dab7e071..0000000000000 --- a/x-pack/plugins/lists/common/schemas/types/entry_nested.ts +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import * as t from 'io-ts'; - -import { NonEmptyString } from '../../shared_imports'; - -import { nonEmptyNestedEntriesArray } from './non_empty_nested_entries_array'; - -/** - * @deprecated Use packages/kbn-securitysolution-io-ts-utils - */ -export const entriesNested = t.exact( - t.type({ - entries: nonEmptyNestedEntriesArray, - field: NonEmptyString, - type: t.keyof({ nested: null }), - }) -); - -/** - * @deprecated Use packages/kbn-securitysolution-io-ts-utils - */ -export type EntryNested = t.TypeOf; diff --git a/x-pack/plugins/lists/common/schemas/types/index.ts b/x-pack/plugins/lists/common/schemas/types/index.ts deleted file mode 100644 index ebe21174570cb..0000000000000 --- a/x-pack/plugins/lists/common/schemas/types/index.ts +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -export * from './comment'; -export * from './create_comment'; -export * from './update_comment'; -export * from './default_comments_array'; -export * from './default_create_comments_array'; -export * from './default_update_comments_array'; -export * from './default_namespace'; -export * from './entries'; -export * from './entry_match'; -export * from './entry_match_any'; -export * from './entry_match_wildcard'; -export * from './entry_list'; -export * from './entry_exists'; -export * from './entry_nested'; -export * from './non_empty_entries_array'; -export * from './non_empty_or_nullable_string_array'; -export * from './non_empty_nested_entries_array'; diff --git a/x-pack/plugins/lists/common/schemas/types/non_empty_entries_array.test.ts b/x-pack/plugins/lists/common/schemas/types/non_empty_entries_array.test.ts deleted file mode 100644 index ecd1125589f54..0000000000000 --- a/x-pack/plugins/lists/common/schemas/types/non_empty_entries_array.test.ts +++ /dev/null @@ -1,132 +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 { pipe } from 'fp-ts/lib/pipeable'; -import { left } from 'fp-ts/lib/Either'; - -import { foldLeftRight, getPaths } from '../../shared_imports'; - -import { getEntryMatchMock } from './entry_match.mock'; -import { getEntryMatchAnyMock } from './entry_match_any.mock'; -import { getEntryExistsMock } from './entry_exists.mock'; -import { getEntryNestedMock } from './entry_nested.mock'; -import { - getEntriesArrayMock, - getListAndNonListEntriesArrayMock, - getListEntriesArrayMock, -} from './entries.mock'; -import { nonEmptyEntriesArray } from './non_empty_entries_array'; -import { EntriesArray } from './entries'; - -describe('non_empty_entries_array', () => { - test('it should FAIL validation when given an empty array', () => { - const payload: EntriesArray = []; - const decoded = nonEmptyEntriesArray.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "[]" supplied to "NonEmptyEntriesArray"', - ]); - expect(message.schema).toEqual({}); - }); - - test('it should FAIL validation when given "undefined"', () => { - const payload = undefined; - const decoded = nonEmptyEntriesArray.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "undefined" supplied to "NonEmptyEntriesArray"', - ]); - expect(message.schema).toEqual({}); - }); - - test('it should FAIL validation when given "null"', () => { - const payload = null; - const decoded = nonEmptyEntriesArray.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "null" supplied to "NonEmptyEntriesArray"', - ]); - expect(message.schema).toEqual({}); - }); - - test('it should validate an array of "match" entries', () => { - const payload: EntriesArray = [getEntryMatchMock(), getEntryMatchMock()]; - const decoded = nonEmptyEntriesArray.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); - }); - - test('it should validate an array of "match_any" entries', () => { - const payload: EntriesArray = [getEntryMatchAnyMock(), getEntryMatchAnyMock()]; - const decoded = nonEmptyEntriesArray.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); - }); - - test('it should validate an array of "exists" entries', () => { - const payload: EntriesArray = [getEntryExistsMock(), getEntryExistsMock()]; - const decoded = nonEmptyEntriesArray.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); - }); - - test('it should validate an array of "list" entries', () => { - const payload: EntriesArray = [...getListEntriesArrayMock()]; - const decoded = nonEmptyEntriesArray.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); - }); - - test('it should validate an array of "nested" entries', () => { - const payload: EntriesArray = [getEntryNestedMock(), getEntryNestedMock()]; - const decoded = nonEmptyEntriesArray.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); - }); - - test('it should validate an array of entries', () => { - const payload: EntriesArray = [...getEntriesArrayMock()]; - const decoded = nonEmptyEntriesArray.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); - }); - - test('it should FAIL validation when given an array of entries of value list and non-value list entries', () => { - const payload: EntriesArray = [...getListAndNonListEntriesArrayMock()]; - const decoded = nonEmptyEntriesArray.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual(['Cannot have entry of type list and other']); - expect(message.schema).toEqual({}); - }); - - test('it should FAIL validation when given an array of non entries', () => { - const payload = [1]; - const decoded = nonEmptyEntriesArray.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "1" supplied to "NonEmptyEntriesArray"', - ]); - expect(message.schema).toEqual({}); - }); -}); diff --git a/x-pack/plugins/lists/common/schemas/types/non_empty_entries_array.ts b/x-pack/plugins/lists/common/schemas/types/non_empty_entries_array.ts deleted file mode 100644 index 89d47c5742b08..0000000000000 --- a/x-pack/plugins/lists/common/schemas/types/non_empty_entries_array.ts +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import * as t from 'io-ts'; -import { Either } from 'fp-ts/lib/Either'; - -import { EntriesArray, entriesArray } from './entries'; -import { entriesList } from './entry_list'; - -/** - * Types the nonEmptyEntriesArray as: - * - An array of entries of length 1 or greater - * @deprecated Use packages/kbn-securitysolution-io-ts-utils - */ -export const nonEmptyEntriesArray = new t.Type( - 'NonEmptyEntriesArray', - entriesArray.is, - (input, context): Either => { - if (Array.isArray(input) && input.length === 0) { - return t.failure(input, context); - } else { - if ( - Array.isArray(input) && - input.some((entry) => entriesList.is(entry)) && - input.some((entry) => !entriesList.is(entry)) - ) { - // fail when an exception item contains both a value list entry and a non-value list entry - return t.failure(input, context, 'Cannot have entry of type list and other'); - } - return entriesArray.validate(input, context); - } - }, - t.identity -); - -/** - * @deprecated Use packages/kbn-securitysolution-io-ts-utils - */ -export type NonEmptyEntriesArray = t.OutputOf; - -/** - * @deprecated Use packages/kbn-securitysolution-io-ts-utils - */ -export type NonEmptyEntriesArrayDecoded = t.TypeOf; diff --git a/x-pack/plugins/lists/common/schemas/types/non_empty_nested_entries_array.test.ts b/x-pack/plugins/lists/common/schemas/types/non_empty_nested_entries_array.test.ts deleted file mode 100644 index 20761c7ccdd8a..0000000000000 --- a/x-pack/plugins/lists/common/schemas/types/non_empty_nested_entries_array.test.ts +++ /dev/null @@ -1,117 +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 { pipe } from 'fp-ts/lib/pipeable'; -import { left } from 'fp-ts/lib/Either'; - -import { foldLeftRight, getPaths } from '../../shared_imports'; - -import { getEntryMatchMock } from './entry_match.mock'; -import { getEntryMatchAnyMock } from './entry_match_any.mock'; -import { getEntryExistsMock } from './entry_exists.mock'; -import { getEntryNestedMock } from './entry_nested.mock'; -import { nonEmptyNestedEntriesArray } from './non_empty_nested_entries_array'; -import { EntriesArray } from './entries'; - -describe('non_empty_nested_entries_array', () => { - test('it should FAIL validation when given an empty array', () => { - const payload: EntriesArray = []; - const decoded = nonEmptyNestedEntriesArray.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "[]" supplied to "NonEmptyNestedEntriesArray"', - ]); - expect(message.schema).toEqual({}); - }); - - test('it should FAIL validation when given "undefined"', () => { - const payload = undefined; - const decoded = nonEmptyNestedEntriesArray.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "undefined" supplied to "NonEmptyNestedEntriesArray"', - ]); - expect(message.schema).toEqual({}); - }); - - test('it should FAIL validation when given "null"', () => { - const payload = null; - const decoded = nonEmptyNestedEntriesArray.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "null" supplied to "NonEmptyNestedEntriesArray"', - ]); - expect(message.schema).toEqual({}); - }); - - test('it should validate an array of "match" entries', () => { - const payload: EntriesArray = [getEntryMatchMock(), getEntryMatchMock()]; - const decoded = nonEmptyNestedEntriesArray.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); - }); - - test('it should validate an array of "match_any" entries', () => { - const payload: EntriesArray = [getEntryMatchAnyMock(), getEntryMatchAnyMock()]; - const decoded = nonEmptyNestedEntriesArray.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); - }); - - test('it should validate an array of "exists" entries', () => { - const payload: EntriesArray = [getEntryExistsMock(), getEntryExistsMock()]; - const decoded = nonEmptyNestedEntriesArray.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); - }); - - test('it should FAIL validation when given an array of "nested" entries', () => { - const payload: EntriesArray = [getEntryNestedMock(), getEntryNestedMock()]; - const decoded = nonEmptyNestedEntriesArray.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "undefined" supplied to "operator"', - 'Invalid value "nested" supplied to "type"', - 'Invalid value "undefined" supplied to "value"', - ]); - expect(message.schema).toEqual({}); - }); - - test('it should validate an array of entries', () => { - const payload: EntriesArray = [ - getEntryExistsMock(), - getEntryMatchAnyMock(), - getEntryMatchMock(), - ]; - const decoded = nonEmptyNestedEntriesArray.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); - }); - - test('it should FAIL validation when given an array of non entries', () => { - const payload = [1]; - const decoded = nonEmptyNestedEntriesArray.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "1" supplied to "NonEmptyNestedEntriesArray"', - ]); - expect(message.schema).toEqual({}); - }); -}); diff --git a/x-pack/plugins/lists/common/schemas/types/non_empty_nested_entries_array.ts b/x-pack/plugins/lists/common/schemas/types/non_empty_nested_entries_array.ts deleted file mode 100644 index 475183695a559..0000000000000 --- a/x-pack/plugins/lists/common/schemas/types/non_empty_nested_entries_array.ts +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import * as t from 'io-ts'; -import { Either } from 'fp-ts/lib/Either'; - -import { entriesMatchAny } from './entry_match_any'; -import { entriesMatch } from './entry_match'; -import { entriesExists } from './entry_exists'; - -export const nestedEntryItem = t.union([entriesMatch, entriesMatchAny, entriesExists]); -export const nestedEntriesArray = t.array(nestedEntryItem); -export type NestedEntriesArray = t.TypeOf; - -/** - * Types the nonEmptyNestedEntriesArray as: - * - An array of entries of length 1 or greater - * @deprecated Use packages/kbn-securitysolution-io-ts-utils - */ -export const nonEmptyNestedEntriesArray = new t.Type< - NestedEntriesArray, - NestedEntriesArray, - unknown ->( - 'NonEmptyNestedEntriesArray', - nestedEntriesArray.is, - (input, context): Either => { - if (Array.isArray(input) && input.length === 0) { - return t.failure(input, context); - } else { - return nestedEntriesArray.validate(input, context); - } - }, - t.identity -); - -/** - * @deprecated Use packages/kbn-securitysolution-io-ts-utils - */ -export type NonEmptyNestedEntriesArray = t.OutputOf; - -/** - * @deprecated Use packages/kbn-securitysolution-io-ts-utils - */ -export type NonEmptyNestedEntriesArrayDecoded = t.TypeOf; diff --git a/x-pack/plugins/lists/common/schemas/types/non_empty_or_nullable_string_array.test.ts b/x-pack/plugins/lists/common/schemas/types/non_empty_or_nullable_string_array.test.ts deleted file mode 100644 index d98f5a2a8f82b..0000000000000 --- a/x-pack/plugins/lists/common/schemas/types/non_empty_or_nullable_string_array.test.ts +++ /dev/null @@ -1,70 +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 { pipe } from 'fp-ts/lib/pipeable'; -import { left } from 'fp-ts/lib/Either'; - -import { foldLeftRight, getPaths } from '../../shared_imports'; - -import { nonEmptyOrNullableStringArray } from './non_empty_or_nullable_string_array'; - -describe('nonEmptyOrNullableStringArray', () => { - test('it should FAIL validation when given an empty array', () => { - const payload: string[] = []; - const decoded = nonEmptyOrNullableStringArray.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "[]" supplied to "NonEmptyOrNullableStringArray"', - ]); - expect(message.schema).toEqual({}); - }); - - test('it should FAIL validation when given "undefined"', () => { - const payload = undefined; - const decoded = nonEmptyOrNullableStringArray.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "undefined" supplied to "NonEmptyOrNullableStringArray"', - ]); - expect(message.schema).toEqual({}); - }); - - test('it should FAIL validation when given "null"', () => { - const payload = null; - const decoded = nonEmptyOrNullableStringArray.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "null" supplied to "NonEmptyOrNullableStringArray"', - ]); - expect(message.schema).toEqual({}); - }); - - test('it should FAIL validation when given an array of with an empty string', () => { - const payload: string[] = ['im good', '']; - const decoded = nonEmptyOrNullableStringArray.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "["im good",""]" supplied to "NonEmptyOrNullableStringArray"', - ]); - expect(message.schema).toEqual({}); - }); - - test('it should FAIL validation when given an array of non strings', () => { - const payload = [1]; - const decoded = nonEmptyOrNullableStringArray.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "[1]" supplied to "NonEmptyOrNullableStringArray"', - ]); - expect(message.schema).toEqual({}); - }); -}); diff --git a/x-pack/plugins/lists/common/schemas/types/non_empty_or_nullable_string_array.ts b/x-pack/plugins/lists/common/schemas/types/non_empty_or_nullable_string_array.ts deleted file mode 100644 index ae5a24a250f3a..0000000000000 --- a/x-pack/plugins/lists/common/schemas/types/non_empty_or_nullable_string_array.ts +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import * as t from 'io-ts'; -import { Either } from 'fp-ts/lib/Either'; - -/** - * Types the nonEmptyOrNullableStringArray as: - * - An array of non empty strings of length 1 or greater - * - This differs from NonEmptyStringArray in that both input and output are type array - * @deprecated Use packages/kbn-securitysolution-io-ts-utils - */ -export const nonEmptyOrNullableStringArray = new t.Type( - 'NonEmptyOrNullableStringArray', - t.array(t.string).is, - (input, context): Either => { - const emptyValueFound = Array.isArray(input) && input.some((value) => value === ''); - const nonStringValueFound = - Array.isArray(input) && input.some((value) => typeof value !== 'string'); - - if (Array.isArray(input) && (emptyValueFound || nonStringValueFound || input.length === 0)) { - return t.failure(input, context); - } else { - return t.array(t.string).validate(input, context); - } - }, - t.identity -); - -/** - * @deprecated Use packages/kbn-securitysolution-io-ts-utils - */ -export type NonEmptyOrNullableStringArray = t.OutputOf; - -/** - * @deprecated Use packages/kbn-securitysolution-io-ts-utils - */ -export type NonEmptyOrNullableStringArrayDecoded = t.TypeOf; diff --git a/x-pack/plugins/lists/common/schemas/types/string_to_positive_number.ts b/x-pack/plugins/lists/common/schemas/types/string_to_positive_number.ts deleted file mode 100644 index 21acc88118039..0000000000000 --- a/x-pack/plugins/lists/common/schemas/types/string_to_positive_number.ts +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import * as t from 'io-ts'; -import { Either, either } from 'fp-ts/lib/Either'; - -export type StringToPositiveNumberC = t.Type; - -/** - * Types the StrongToPositiveNumber as: - * - If a string this converts the string into a number - * - Ensures it is a number (and not NaN) - * - Ensures it is positive number - * @deprecated Use packages/kbn-securitysolution-io-ts-utils - */ -export const StringToPositiveNumber: StringToPositiveNumberC = new t.Type( - 'StringToPositiveNumber', - t.number.is, - (input, context): Either => { - return either.chain( - t.string.validate(input, context), - (numberAsString): Either => { - const stringAsNumber = +numberAsString; - if (numberAsString.trim().length === 0 || isNaN(stringAsNumber) || stringAsNumber <= 0) { - return t.failure(input, context); - } else { - return t.success(stringAsNumber); - } - } - ); - }, - String -); diff --git a/x-pack/plugins/lists/common/schemas/types/update_comment.mock.ts b/x-pack/plugins/lists/common/schemas/types/update_comment.mock.ts index b4f3069fb17f9..dea0f9a08fc4c 100644 --- a/x-pack/plugins/lists/common/schemas/types/update_comment.mock.ts +++ b/x-pack/plugins/lists/common/schemas/types/update_comment.mock.ts @@ -5,9 +5,9 @@ * 2.0. */ -import { ID } from '../../constants.mock'; +import { UpdateComment, UpdateCommentsArray } from '@kbn/securitysolution-io-ts-utils'; -import { UpdateComment, UpdateCommentsArray } from './update_comment'; +import { ID } from '../../constants.mock'; export const getUpdateCommentMock = (): UpdateComment => ({ comment: 'some comment', diff --git a/x-pack/plugins/lists/common/schemas/types/update_comment.test.ts b/x-pack/plugins/lists/common/schemas/types/update_comment.test.ts deleted file mode 100644 index e0256035ba6e0..0000000000000 --- a/x-pack/plugins/lists/common/schemas/types/update_comment.test.ts +++ /dev/null @@ -1,150 +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 { pipe } from 'fp-ts/lib/pipeable'; -import { left } from 'fp-ts/lib/Either'; - -import { foldLeftRight, getPaths } from '../../shared_imports'; - -import { getUpdateCommentMock, getUpdateCommentsArrayMock } from './update_comment.mock'; -import { - UpdateComment, - UpdateCommentsArray, - UpdateCommentsArrayOrUndefined, - updateComment, - updateCommentsArray, - updateCommentsArrayOrUndefined, -} from './update_comment'; - -describe('CommentsUpdate', () => { - describe('updateComment', () => { - test('it should pass validation when supplied typical comment update', () => { - const payload = getUpdateCommentMock(); - const decoded = updateComment.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); - }); - - test('it should fail validation when supplied an undefined for "comment"', () => { - const payload = getUpdateCommentMock(); - // @ts-expect-error - delete payload.comment; - const decoded = updateComment.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "undefined" supplied to "comment"', - ]); - expect(message.schema).toEqual({}); - }); - - test('it should fail validation when supplied an empty string for "comment"', () => { - const payload = { ...getUpdateCommentMock(), comment: '' }; - const decoded = updateComment.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual(['Invalid value "" supplied to "comment"']); - expect(message.schema).toEqual({}); - }); - - test('it should pass validation when supplied an undefined for "id"', () => { - const payload = getUpdateCommentMock(); - delete payload.id; - const decoded = updateComment.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); - }); - - test('it should fail validation when supplied an empty string for "id"', () => { - const payload = { ...getUpdateCommentMock(), id: '' }; - const decoded = updateComment.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual(['Invalid value "" supplied to "id"']); - expect(message.schema).toEqual({}); - }); - - test('it should strip out extra key passed in', () => { - const payload: UpdateComment & { - extraKey?: string; - } = { ...getUpdateCommentMock(), extraKey: 'some new value' }; - const decoded = updateComment.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(getUpdateCommentMock()); - }); - }); - - describe('updateCommentsArray', () => { - test('it should pass validation when supplied an array of comments', () => { - const payload = getUpdateCommentsArrayMock(); - const decoded = updateCommentsArray.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); - }); - - test('it should fail validation when undefined', () => { - const payload = undefined; - const decoded = updateCommentsArray.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "undefined" supplied to "Array<({| comment: NonEmptyString |} & Partial<{| id: NonEmptyString |}>)>"', - ]); - expect(message.schema).toEqual({}); - }); - - test('it should fail validation when array includes non comments types', () => { - const payload = ([1] as unknown) as UpdateCommentsArray; - const decoded = updateCommentsArray.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "1" supplied to "Array<({| comment: NonEmptyString |} & Partial<{| id: NonEmptyString |}>)>"', - ]); - expect(message.schema).toEqual({}); - }); - }); - - describe('updateCommentsArrayOrUndefined', () => { - test('it should pass validation when supplied an array of comments', () => { - const payload = getUpdateCommentsArrayMock(); - const decoded = updateCommentsArrayOrUndefined.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); - }); - - test('it should pass validation when supplied when undefined', () => { - const payload = undefined; - const decoded = updateCommentsArrayOrUndefined.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); - }); - - test('it should fail validation when array includes non comments types', () => { - const payload = ([1] as unknown) as UpdateCommentsArrayOrUndefined; - const decoded = updateCommentsArray.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "1" supplied to "Array<({| comment: NonEmptyString |} & Partial<{| id: NonEmptyString |}>)>"', - ]); - expect(message.schema).toEqual({}); - }); - }); -}); diff --git a/x-pack/plugins/lists/common/schemas/types/update_comment.ts b/x-pack/plugins/lists/common/schemas/types/update_comment.ts deleted file mode 100644 index 88d9d38688391..0000000000000 --- a/x-pack/plugins/lists/common/schemas/types/update_comment.ts +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import * as t from 'io-ts'; - -import { NonEmptyString } from '../../shared_imports'; -import { id } from '../common/schemas'; - -/** - * @deprecated Use packages/kbn-securitysolution-io-ts-utils - */ -export const updateComment = t.intersection([ - t.exact( - t.type({ - comment: NonEmptyString, - }) - ), - t.exact( - t.partial({ - id, - }) - ), -]); - -/** - * @deprecated Use packages/kbn-securitysolution-io-ts-utils - */ -export type UpdateComment = t.TypeOf; - -/** - * @deprecated Use packages/kbn-securitysolution-io-ts-utils - */ -export const updateCommentsArray = t.array(updateComment); - -/** - * @deprecated Use packages/kbn-securitysolution-io-ts-utils - */ -export type UpdateCommentsArray = t.TypeOf; - -/** - * @deprecated Use packages/kbn-securitysolution-io-ts-utils - */ -export const updateCommentsArrayOrUndefined = t.union([updateCommentsArray, t.undefined]); - -/** - * @deprecated Use packages/kbn-securitysolution-io-ts-utils - */ -export type UpdateCommentsArrayOrUndefined = t.TypeOf; diff --git a/x-pack/plugins/lists/common/shared_exports.ts b/x-pack/plugins/lists/common/shared_exports.ts index 8be53cb8cddbc..38eb5aeee8cd2 100644 --- a/x-pack/plugins/lists/common/shared_exports.ts +++ b/x-pack/plugins/lists/common/shared_exports.ts @@ -5,17 +5,12 @@ * 2.0. */ +// TODO: We should remove these and instead directly import them in the security_solution project. This is to get my PR across the line without too many conflicts. export { - ListSchema, CommentsArray, - CreateCommentsArray, Comment, CreateComment, - ExceptionListSchema, - ExceptionListItemSchema, - CreateExceptionListSchema, - CreateExceptionListItemSchema, - UpdateExceptionListItemSchema, + CreateCommentsArray, Entry, EntryExists, EntryMatch, @@ -26,15 +21,14 @@ export { EntriesArray, NamespaceType, NestedEntriesArray, - Operator, - OperatorEnum, - OperatorTypeEnum, + ListOperator as Operator, + ListOperatorEnum as OperatorEnum, + ListOperatorTypeEnum as OperatorTypeEnum, + listOperator as operator, ExceptionListTypeEnum, + ExceptionListType, comment, - exceptionListItemSchema, exceptionListType, - createExceptionListItemSchema, - listSchema, entry, entriesNested, nestedEntryItem, @@ -44,11 +38,22 @@ export { entriesExists, entriesList, namespaceType, - ExceptionListType, - Type, osType, osTypeArray, OsTypeArray, + Type, +} from '@kbn/securitysolution-io-ts-utils'; + +export { + ListSchema, + ExceptionListSchema, + ExceptionListItemSchema, + CreateExceptionListSchema, + CreateExceptionListItemSchema, + UpdateExceptionListItemSchema, + exceptionListItemSchema, + createExceptionListItemSchema, + listSchema, } from './schemas'; export { buildExceptionFilter } from './exceptions'; diff --git a/x-pack/plugins/lists/common/shared_imports.ts b/x-pack/plugins/lists/common/shared_imports.ts deleted file mode 100644 index 2483c1f7dd992..0000000000000 --- a/x-pack/plugins/lists/common/shared_imports.ts +++ /dev/null @@ -1,23 +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. - */ - -export { - NonEmptyString, - DefaultArray, - DefaultUuid, - DefaultStringArray, - DefaultVersionNumber, - DefaultVersionNumberDecoded, - addIdToItem, - removeIdFromItem, - exactCheck, - getPaths, - foldLeftRight, - validate, - validateEither, - formatErrors, -} from '../../security_solution/common'; diff --git a/x-pack/plugins/lists/common/test_utils.ts b/x-pack/plugins/lists/common/test_utils.ts deleted file mode 100644 index dcf6a2747c3de..0000000000000 --- a/x-pack/plugins/lists/common/test_utils.ts +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import * as t from 'io-ts'; -import { fold } from 'fp-ts/lib/Either'; -import { pipe } from 'fp-ts/lib/pipeable'; - -import { formatErrors } from './format_errors'; - -interface Message { - errors: t.Errors; - schema: T | {}; -} - -/** - * @deprecated Use packages/kbn-securitysolution-io-ts-utils/src/test_utils/index.ts - */ -const onLeft = (errors: t.Errors): Message => { - return { errors, schema: {} }; -}; - -/** - * @deprecated Use packages/kbn-securitysolution-io-ts-utils/src/test_utils/index.ts - */ -const onRight = (schema: T): Message => { - return { - errors: [], - schema, - }; -}; - -/** - * @deprecated Use packages/kbn-securitysolution-io-ts-utils/src/test_utils/index.ts - */ -export const foldLeftRight = fold(onLeft, onRight); - -/** - * Convenience utility to keep the error message handling within tests to be - * very concise. - * @param validation The validation to get the errors from - * @deprecated Use packages/kbn-securitysolution-io-ts-utils/src/test_utils/index.ts - */ -export const getPaths = (validation: t.Validation): string[] => { - return pipe( - validation, - fold( - (errors) => formatErrors(errors), - () => ['no errors'] - ) - ); -}; - -/** - * Convenience utility to remove text appended to links by EUI - * @deprecated Use packages/kbn-securitysolution-io-ts-utils/src/test_utils/index.ts - */ -export const removeExternalLinkText = (str: string): string => - str.replace(/\(opens in a new tab or window\)/g, ''); diff --git a/x-pack/plugins/lists/kibana.json b/x-pack/plugins/lists/kibana.json index 1e25fd987552d..ae7b3e7679e0b 100644 --- a/x-pack/plugins/lists/kibana.json +++ b/x-pack/plugins/lists/kibana.json @@ -5,7 +5,7 @@ "kibanaVersion": "kibana", "requiredPlugins": [], "optionalPlugins": ["spaces", "security"], - "requiredBundles": ["securitySolution"], + "requiredBundles": [], "server": true, "ui": true, "version": "8.0.0" diff --git a/x-pack/plugins/lists/public/exceptions/api.ts b/x-pack/plugins/lists/public/exceptions/api.ts index ccd917e5ddd8d..e97530da7904a 100644 --- a/x-pack/plugins/lists/public/exceptions/api.ts +++ b/x-pack/plugins/lists/public/exceptions/api.ts @@ -7,8 +7,8 @@ import { chain, fromEither, tryCatch } from 'fp-ts/lib/TaskEither'; import { flow } from 'fp-ts/lib/function'; +import { validateEither } from '@kbn/securitysolution-io-ts-utils'; -import { validateEither } from '../../common/shared_imports'; import { toError, toPromise } from '../common/fp_utils'; import { ENDPOINT_LIST_URL, diff --git a/x-pack/plugins/lists/public/exceptions/components/builder/entry_renderer.tsx b/x-pack/plugins/lists/public/exceptions/components/builder/entry_renderer.tsx index f0ab7e940f9d8..28d7469d18910 100644 --- a/x-pack/plugins/lists/public/exceptions/components/builder/entry_renderer.tsx +++ b/x-pack/plugins/lists/public/exceptions/components/builder/entry_renderer.tsx @@ -8,6 +8,7 @@ import React, { useCallback, useMemo } from 'react'; import { EuiFlexGroup, EuiFlexItem, EuiFormRow } from '@elastic/eui'; import styled from 'styled-components'; +import { OsTypeArray } from '@kbn/securitysolution-io-ts-utils'; import { AutocompleteStart } from '../../../../../../../src/plugins/data/public'; import { IFieldType, IIndexPattern } from '../../../../../../../src/plugins/data/common'; @@ -22,7 +23,6 @@ import { AutocompleteFieldMatchAnyComponent } from '../autocomplete/field_value_ import { AutocompleteFieldListsComponent } from '../autocomplete/field_value_lists'; import { ExceptionListType, ListSchema, OperatorTypeEnum } from '../../../../common'; import { getEmptyValue } from '../../../common/empty_value'; -import { OsTypeArray } from '../../../../common/schemas/common'; import { getEntryOnFieldChange, diff --git a/x-pack/plugins/lists/public/exceptions/components/builder/exception_item_renderer.tsx b/x-pack/plugins/lists/public/exceptions/components/builder/exception_item_renderer.tsx index 11e64630b242d..5f094a64c3660 100644 --- a/x-pack/plugins/lists/public/exceptions/components/builder/exception_item_renderer.tsx +++ b/x-pack/plugins/lists/public/exceptions/components/builder/exception_item_renderer.tsx @@ -10,10 +10,10 @@ import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import styled from 'styled-components'; import { HttpStart } from 'kibana/public'; import { AutocompleteStart } from 'src/plugins/data/public'; +import { OsTypeArray } from '@kbn/securitysolution-io-ts-utils'; import { ExceptionListType } from '../../../../common'; import { IIndexPattern } from '../../../../../../../src/plugins/data/common'; -import { OsTypeArray } from '../../../../common/schemas'; import { BuilderEntry, ExceptionsBuilderExceptionItem, FormattedBuilderEntry } from './types'; import { BuilderAndBadgeComponent } from './and_badge'; diff --git a/x-pack/plugins/lists/public/exceptions/components/builder/exception_items_renderer.tsx b/x-pack/plugins/lists/public/exceptions/components/builder/exception_items_renderer.tsx index 990831dac6dd9..a698feb93722c 100644 --- a/x-pack/plugins/lists/public/exceptions/components/builder/exception_items_renderer.tsx +++ b/x-pack/plugins/lists/public/exceptions/components/builder/exception_items_renderer.tsx @@ -9,8 +9,9 @@ import React, { useCallback, useEffect, useReducer } from 'react'; import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import styled from 'styled-components'; import { HttpStart } from 'kibana/public'; +import { addIdToItem } from '@kbn/securitysolution-utils'; +import { OsTypeArray } from '@kbn/securitysolution-io-ts-utils'; -import { addIdToItem } from '../../../../common/shared_imports'; import { AutocompleteStart, IIndexPattern } from '../../../../../../../src/plugins/data/public'; import { CreateExceptionListItemSchema, @@ -23,7 +24,6 @@ import { exceptionListItemSchema, } from '../../../../common'; import { AndOrBadge } from '../and_or_badge'; -import { OsTypeArray } from '../../../../common/schemas'; import { CreateExceptionListItemBuilderSchema, ExceptionsBuilderExceptionItem } from './types'; import { BuilderExceptionListItemComponent } from './exception_item_renderer'; diff --git a/x-pack/plugins/lists/public/exceptions/components/builder/helpers.ts b/x-pack/plugins/lists/public/exceptions/components/builder/helpers.ts index 209f96742aaf0..6d3bdd09c93ea 100644 --- a/x-pack/plugins/lists/public/exceptions/components/builder/helpers.ts +++ b/x-pack/plugins/lists/public/exceptions/components/builder/helpers.ts @@ -6,9 +6,10 @@ */ import uuid from 'uuid'; +import { addIdToItem, removeIdFromItem } from '@kbn/securitysolution-utils'; +import { OsTypeArray, validate } from '@kbn/securitysolution-io-ts-utils'; import { IFieldType, IIndexPattern } from '../../../../../../../src/plugins/data/public'; -import { addIdToItem, removeIdFromItem, validate } from '../../../../common/shared_imports'; import { CreateExceptionListItemSchema, EntriesArray, @@ -37,7 +38,6 @@ import { isOperator, } from '../autocomplete/operators'; import { OperatorOption } from '../autocomplete/types'; -import { OsTypeArray } from '../../../../common/schemas'; import { BuilderEntry, diff --git a/x-pack/plugins/lists/public/exceptions/transforms.test.ts b/x-pack/plugins/lists/public/exceptions/transforms.test.ts index 12b0f0bd8624a..c5c43b16d6428 100644 --- a/x-pack/plugins/lists/public/exceptions/transforms.test.ts +++ b/x-pack/plugins/lists/public/exceptions/transforms.test.ts @@ -5,6 +5,8 @@ * 2.0. */ +import { Entry, EntryMatch, EntryNested } from '@kbn/securitysolution-io-ts-utils'; + import { ExceptionListItemSchema } from '../../common/schemas/response/exception_list_item_schema'; import { UpdateExceptionListItemSchema } from '../../common/schemas/request/update_exception_list_item_schema'; import { CreateExceptionListItemSchema } from '../../common/schemas/request/create_exception_list_item_schema'; @@ -12,7 +14,6 @@ import { getCreateExceptionListItemSchemaMock } from '../../common/schemas/reque import { getUpdateExceptionListItemSchemaMock } from '../../common/schemas/request/update_exception_list_item_schema.mock'; import { getExceptionListItemSchemaMock } from '../../common/schemas/response/exception_list_item_schema.mock'; import { ENTRIES_WITH_IDS } from '../../common/constants.mock'; -import { Entry, EntryMatch, EntryNested } from '../../common/schemas'; import { addIdToExceptionItemEntries, diff --git a/x-pack/plugins/lists/public/exceptions/transforms.ts b/x-pack/plugins/lists/public/exceptions/transforms.ts index 0791760611bf5..468dfc00ca852 100644 --- a/x-pack/plugins/lists/public/exceptions/transforms.ts +++ b/x-pack/plugins/lists/public/exceptions/transforms.ts @@ -6,6 +6,7 @@ */ import { flow } from 'fp-ts/lib/function'; +import { addIdToItem, removeIdFromItem } from '@kbn/securitysolution-utils'; import { CreateExceptionListItemSchema, @@ -14,7 +15,6 @@ import { ExceptionListItemSchema, UpdateExceptionListItemSchema, } from '../../common'; -import { addIdToItem, removeIdFromItem } from '../../common/shared_imports'; // These are a collection of transforms that are UI specific and useful for UI concerns // that are inserted between the API and the actual user interface. In some ways these diff --git a/x-pack/plugins/lists/public/exceptions/types.ts b/x-pack/plugins/lists/public/exceptions/types.ts index 03cae387711f8..a2842d81a7292 100644 --- a/x-pack/plugins/lists/public/exceptions/types.ts +++ b/x-pack/plugins/lists/public/exceptions/types.ts @@ -5,13 +5,13 @@ * 2.0. */ +import { ExceptionListType, NamespaceType } from '@kbn/securitysolution-io-ts-utils'; + import { CreateExceptionListItemSchema, CreateExceptionListSchema, ExceptionListItemSchema, ExceptionListSchema, - ExceptionListType, - NamespaceType, Page, PerPage, TotalOrUndefined, diff --git a/x-pack/plugins/lists/public/exceptions/utils.ts b/x-pack/plugins/lists/public/exceptions/utils.ts index 009d6e56dc022..6324fdf1df420 100644 --- a/x-pack/plugins/lists/public/exceptions/utils.ts +++ b/x-pack/plugins/lists/public/exceptions/utils.ts @@ -6,10 +6,9 @@ */ import { get } from 'lodash/fp'; +import { NamespaceType, NamespaceTypeArray } from '@kbn/securitysolution-io-ts-utils'; import { ENDPOINT_TRUSTED_APPS_LIST_ID } from '../../common/constants'; -import { NamespaceType } from '../../common/schemas'; -import { NamespaceTypeArray } from '../../common/schemas/types/default_namespace_array'; import { SavedObjectType, exceptionListAgnosticSavedObjectType, diff --git a/x-pack/plugins/lists/public/lists/api.ts b/x-pack/plugins/lists/public/lists/api.ts index bfb688250475c..09baa83519fc4 100644 --- a/x-pack/plugins/lists/public/lists/api.ts +++ b/x-pack/plugins/lists/public/lists/api.ts @@ -8,6 +8,7 @@ import { chain, fromEither, map, tryCatch } from 'fp-ts/lib/TaskEither'; import { flow } from 'fp-ts/lib/function'; import { pipe } from 'fp-ts/lib/pipeable'; +import { validateEither } from '@kbn/securitysolution-io-ts-utils'; import { AcknowledgeSchema, @@ -30,7 +31,6 @@ import { listSchema, } from '../../common/schemas'; import { LIST_INDEX, LIST_ITEM_URL, LIST_PRIVILEGES_URL, LIST_URL } from '../../common/constants'; -import { validateEither } from '../../common/shared_imports'; import { toError, toPromise } from '../common/fp_utils'; import { diff --git a/x-pack/plugins/lists/public/lists/types.ts b/x-pack/plugins/lists/public/lists/types.ts index 38df9fbf2e37a..6708620439803 100644 --- a/x-pack/plugins/lists/public/lists/types.ts +++ b/x-pack/plugins/lists/public/lists/types.ts @@ -5,8 +5,9 @@ * 2.0. */ +import { Type } from '@kbn/securitysolution-io-ts-utils'; + import { HttpStart } from '../../../../../src/core/public'; -import { Type } from '../../common/schemas'; export interface ApiParams { http: HttpStart; diff --git a/x-pack/plugins/lists/server/routes/create_endpoint_list_item_route.ts b/x-pack/plugins/lists/server/routes/create_endpoint_list_item_route.ts index f972489716641..76ff8b1728922 100644 --- a/x-pack/plugins/lists/server/routes/create_endpoint_list_item_route.ts +++ b/x-pack/plugins/lists/server/routes/create_endpoint_list_item_route.ts @@ -5,10 +5,11 @@ * 2.0. */ +import { validate } from '@kbn/securitysolution-io-ts-utils'; + import type { ListsPluginRouter } from '../types'; import { ENDPOINT_LIST_ID, ENDPOINT_LIST_ITEM_URL } from '../../common/constants'; import { buildRouteValidation, buildSiemResponse, transformError } from '../siem_server_deps'; -import { validate } from '../../common/shared_imports'; import { CreateEndpointListItemSchemaDecoded, createEndpointListItemSchema, diff --git a/x-pack/plugins/lists/server/routes/create_endpoint_list_route.ts b/x-pack/plugins/lists/server/routes/create_endpoint_list_route.ts index ab295977e98af..23f098f7e9457 100644 --- a/x-pack/plugins/lists/server/routes/create_endpoint_list_route.ts +++ b/x-pack/plugins/lists/server/routes/create_endpoint_list_route.ts @@ -5,10 +5,11 @@ * 2.0. */ +import { validate } from '@kbn/securitysolution-io-ts-utils'; + import type { ListsPluginRouter } from '../types'; import { ENDPOINT_LIST_URL } from '../../common/constants'; import { buildSiemResponse, transformError } from '../siem_server_deps'; -import { validate } from '../../common/shared_imports'; import { createEndpointListSchema } from '../../common/schemas'; import { getExceptionListClient } from './utils/get_exception_list_client'; diff --git a/x-pack/plugins/lists/server/routes/create_exception_list_item_route.ts b/x-pack/plugins/lists/server/routes/create_exception_list_item_route.ts index 1f6ff5a493f77..4bcb41c666f56 100644 --- a/x-pack/plugins/lists/server/routes/create_exception_list_item_route.ts +++ b/x-pack/plugins/lists/server/routes/create_exception_list_item_route.ts @@ -5,10 +5,11 @@ * 2.0. */ +import { validate } from '@kbn/securitysolution-io-ts-utils'; + import type { ListsPluginRouter } from '../types'; import { EXCEPTION_LIST_ITEM_URL } from '../../common/constants'; import { buildRouteValidation, buildSiemResponse, transformError } from '../siem_server_deps'; -import { validate } from '../../common/shared_imports'; import { CreateExceptionListItemSchemaDecoded, createExceptionListItemSchema, diff --git a/x-pack/plugins/lists/server/routes/create_exception_list_route.ts b/x-pack/plugins/lists/server/routes/create_exception_list_route.ts index c8201dcedbea4..12c887a16c318 100644 --- a/x-pack/plugins/lists/server/routes/create_exception_list_route.ts +++ b/x-pack/plugins/lists/server/routes/create_exception_list_route.ts @@ -5,10 +5,11 @@ * 2.0. */ +import { validate } from '@kbn/securitysolution-io-ts-utils'; + import type { ListsPluginRouter } from '../types'; import { EXCEPTION_LIST_URL } from '../../common/constants'; import { buildRouteValidation, buildSiemResponse, transformError } from '../siem_server_deps'; -import { validate } from '../../common/shared_imports'; import { CreateExceptionListSchemaDecoded, createExceptionListSchema, diff --git a/x-pack/plugins/lists/server/routes/create_list_index_route.ts b/x-pack/plugins/lists/server/routes/create_list_index_route.ts index db172679d2143..12fe586a07cc0 100644 --- a/x-pack/plugins/lists/server/routes/create_list_index_route.ts +++ b/x-pack/plugins/lists/server/routes/create_list_index_route.ts @@ -5,9 +5,10 @@ * 2.0. */ +import { validate } from '@kbn/securitysolution-io-ts-utils'; + import type { ListsPluginRouter } from '../types'; import { buildSiemResponse, transformError } from '../siem_server_deps'; -import { validate } from '../../common/shared_imports'; import { LIST_INDEX } from '../../common/constants'; import { acknowledgeSchema } from '../../common/schemas'; diff --git a/x-pack/plugins/lists/server/routes/create_list_item_route.ts b/x-pack/plugins/lists/server/routes/create_list_item_route.ts index 0491f3511a4cb..2e3c944af0df8 100644 --- a/x-pack/plugins/lists/server/routes/create_list_item_route.ts +++ b/x-pack/plugins/lists/server/routes/create_list_item_route.ts @@ -5,11 +5,12 @@ * 2.0. */ +import { validate } from '@kbn/securitysolution-io-ts-utils'; + import type { ListsPluginRouter } from '../types'; import { LIST_ITEM_URL } from '../../common/constants'; import { buildRouteValidation, buildSiemResponse, transformError } from '../siem_server_deps'; import { createListItemSchema, listItemSchema } from '../../common/schemas'; -import { validate } from '../../common/shared_imports'; import { getListClient } from '.'; diff --git a/x-pack/plugins/lists/server/routes/create_list_route.ts b/x-pack/plugins/lists/server/routes/create_list_route.ts index 480b9d789708e..4346d519c9003 100644 --- a/x-pack/plugins/lists/server/routes/create_list_route.ts +++ b/x-pack/plugins/lists/server/routes/create_list_route.ts @@ -5,10 +5,11 @@ * 2.0. */ +import { validate } from '@kbn/securitysolution-io-ts-utils'; + import type { ListsPluginRouter } from '../types'; import { LIST_URL } from '../../common/constants'; import { buildRouteValidation, buildSiemResponse, transformError } from '../siem_server_deps'; -import { validate } from '../../common/shared_imports'; import { CreateListSchemaDecoded, createListSchema, listSchema } from '../../common/schemas'; import { getListClient } from '.'; diff --git a/x-pack/plugins/lists/server/routes/delete_endpoint_list_item_route.ts b/x-pack/plugins/lists/server/routes/delete_endpoint_list_item_route.ts index 19c0b6c54bb40..195384356f40b 100644 --- a/x-pack/plugins/lists/server/routes/delete_endpoint_list_item_route.ts +++ b/x-pack/plugins/lists/server/routes/delete_endpoint_list_item_route.ts @@ -5,10 +5,11 @@ * 2.0. */ +import { validate } from '@kbn/securitysolution-io-ts-utils'; + import type { ListsPluginRouter } from '../types'; import { ENDPOINT_LIST_ITEM_URL } from '../../common/constants'; import { buildRouteValidation, buildSiemResponse, transformError } from '../siem_server_deps'; -import { validate } from '../../common/shared_imports'; import { DeleteEndpointListItemSchemaDecoded, deleteEndpointListItemSchema, diff --git a/x-pack/plugins/lists/server/routes/delete_exception_list_item_route.ts b/x-pack/plugins/lists/server/routes/delete_exception_list_item_route.ts index bfcaf6046fb6d..ddcd1cf9b7180 100644 --- a/x-pack/plugins/lists/server/routes/delete_exception_list_item_route.ts +++ b/x-pack/plugins/lists/server/routes/delete_exception_list_item_route.ts @@ -5,10 +5,11 @@ * 2.0. */ +import { validate } from '@kbn/securitysolution-io-ts-utils'; + import type { ListsPluginRouter } from '../types'; import { EXCEPTION_LIST_ITEM_URL } from '../../common/constants'; import { buildRouteValidation, buildSiemResponse, transformError } from '../siem_server_deps'; -import { validate } from '../../common/shared_imports'; import { DeleteExceptionListItemSchemaDecoded, deleteExceptionListItemSchema, diff --git a/x-pack/plugins/lists/server/routes/delete_exception_list_route.ts b/x-pack/plugins/lists/server/routes/delete_exception_list_route.ts index 90bf6a66f3d6e..f11deef5cb0c8 100644 --- a/x-pack/plugins/lists/server/routes/delete_exception_list_route.ts +++ b/x-pack/plugins/lists/server/routes/delete_exception_list_route.ts @@ -5,10 +5,11 @@ * 2.0. */ +import { validate } from '@kbn/securitysolution-io-ts-utils'; + import type { ListsPluginRouter } from '../types'; import { EXCEPTION_LIST_URL } from '../../common/constants'; import { buildRouteValidation, buildSiemResponse, transformError } from '../siem_server_deps'; -import { validate } from '../../common/shared_imports'; import { DeleteExceptionListSchemaDecoded, deleteExceptionListSchema, diff --git a/x-pack/plugins/lists/server/routes/delete_list_index_route.ts b/x-pack/plugins/lists/server/routes/delete_list_index_route.ts index b6a9b76fd8c94..efad16c37a2dc 100644 --- a/x-pack/plugins/lists/server/routes/delete_list_index_route.ts +++ b/x-pack/plugins/lists/server/routes/delete_list_index_route.ts @@ -5,10 +5,11 @@ * 2.0. */ +import { validate } from '@kbn/securitysolution-io-ts-utils'; + import type { ListsPluginRouter } from '../types'; import { LIST_INDEX } from '../../common/constants'; import { buildSiemResponse, transformError } from '../siem_server_deps'; -import { validate } from '../../common/shared_imports'; import { acknowledgeSchema } from '../../common/schemas'; import { getListClient } from '.'; diff --git a/x-pack/plugins/lists/server/routes/delete_list_item_route.ts b/x-pack/plugins/lists/server/routes/delete_list_item_route.ts index 7345829a8e9b4..a07035fc50d9c 100644 --- a/x-pack/plugins/lists/server/routes/delete_list_item_route.ts +++ b/x-pack/plugins/lists/server/routes/delete_list_item_route.ts @@ -5,10 +5,11 @@ * 2.0. */ +import { validate } from '@kbn/securitysolution-io-ts-utils'; + import type { ListsPluginRouter } from '../types'; import { LIST_ITEM_URL } from '../../common/constants'; import { buildRouteValidation, buildSiemResponse, transformError } from '../siem_server_deps'; -import { validate } from '../../common/shared_imports'; import { deleteListItemSchema, listItemArraySchema, listItemSchema } from '../../common/schemas'; import { getListClient } from '.'; diff --git a/x-pack/plugins/lists/server/routes/delete_list_route.ts b/x-pack/plugins/lists/server/routes/delete_list_route.ts index 3e9b76a1b330a..65faa54b20cc7 100644 --- a/x-pack/plugins/lists/server/routes/delete_list_route.ts +++ b/x-pack/plugins/lists/server/routes/delete_list_route.ts @@ -5,12 +5,12 @@ * 2.0. */ +import { EntriesArray, validate } from '@kbn/securitysolution-io-ts-utils'; + import type { ListsPluginRouter } from '../types'; import { LIST_URL } from '../../common/constants'; import { buildRouteValidation, buildSiemResponse, transformError } from '../siem_server_deps'; -import { validate } from '../../common/shared_imports'; import { - EntriesArray, ExceptionListItemSchema, FoundExceptionListSchema, deleteListSchema, diff --git a/x-pack/plugins/lists/server/routes/find_endpoint_list_item_route.ts b/x-pack/plugins/lists/server/routes/find_endpoint_list_item_route.ts index 67dc65fcffa43..ee5245982dc0b 100644 --- a/x-pack/plugins/lists/server/routes/find_endpoint_list_item_route.ts +++ b/x-pack/plugins/lists/server/routes/find_endpoint_list_item_route.ts @@ -5,10 +5,11 @@ * 2.0. */ +import { validate } from '@kbn/securitysolution-io-ts-utils'; + import type { ListsPluginRouter } from '../types'; import { ENDPOINT_LIST_ID, ENDPOINT_LIST_ITEM_URL } from '../../common/constants'; import { buildRouteValidation, buildSiemResponse, transformError } from '../siem_server_deps'; -import { validate } from '../../common/shared_imports'; import { FindEndpointListItemSchemaDecoded, findEndpointListItemSchema, diff --git a/x-pack/plugins/lists/server/routes/find_exception_list_item_route.ts b/x-pack/plugins/lists/server/routes/find_exception_list_item_route.ts index 3505b892cf007..82988a7cbeb76 100644 --- a/x-pack/plugins/lists/server/routes/find_exception_list_item_route.ts +++ b/x-pack/plugins/lists/server/routes/find_exception_list_item_route.ts @@ -5,10 +5,11 @@ * 2.0. */ +import { validate } from '@kbn/securitysolution-io-ts-utils'; + import type { ListsPluginRouter } from '../types'; import { EXCEPTION_LIST_ITEM_URL } from '../../common/constants'; import { buildRouteValidation, buildSiemResponse, transformError } from '../siem_server_deps'; -import { validate } from '../../common/shared_imports'; import { FindExceptionListItemSchemaDecoded, findExceptionListItemSchema, diff --git a/x-pack/plugins/lists/server/routes/find_exception_list_route.ts b/x-pack/plugins/lists/server/routes/find_exception_list_route.ts index fc7d58ad9c4bb..4b188b4dca4e2 100644 --- a/x-pack/plugins/lists/server/routes/find_exception_list_route.ts +++ b/x-pack/plugins/lists/server/routes/find_exception_list_route.ts @@ -5,10 +5,11 @@ * 2.0. */ +import { validate } from '@kbn/securitysolution-io-ts-utils'; + import type { ListsPluginRouter } from '../types'; import { EXCEPTION_LIST_URL } from '../../common/constants'; import { buildRouteValidation, buildSiemResponse, transformError } from '../siem_server_deps'; -import { validate } from '../../common/shared_imports'; import { FindExceptionListSchemaDecoded, findExceptionListSchema, diff --git a/x-pack/plugins/lists/server/routes/find_list_item_route.ts b/x-pack/plugins/lists/server/routes/find_list_item_route.ts index d8138792225aa..a904d7f84733d 100644 --- a/x-pack/plugins/lists/server/routes/find_list_item_route.ts +++ b/x-pack/plugins/lists/server/routes/find_list_item_route.ts @@ -5,10 +5,11 @@ * 2.0. */ +import { validate } from '@kbn/securitysolution-io-ts-utils'; + import type { ListsPluginRouter } from '../types'; import { LIST_ITEM_URL } from '../../common/constants'; import { buildRouteValidation, buildSiemResponse, transformError } from '../siem_server_deps'; -import { validate } from '../../common/shared_imports'; import { FindListItemSchemaDecoded, findListItemSchema, diff --git a/x-pack/plugins/lists/server/routes/find_list_route.ts b/x-pack/plugins/lists/server/routes/find_list_route.ts index e66a5dd5f6678..c5f1b58c1e957 100644 --- a/x-pack/plugins/lists/server/routes/find_list_route.ts +++ b/x-pack/plugins/lists/server/routes/find_list_route.ts @@ -5,10 +5,11 @@ * 2.0. */ +import { validate } from '@kbn/securitysolution-io-ts-utils'; + import type { ListsPluginRouter } from '../types'; import { LIST_URL } from '../../common/constants'; import { buildRouteValidation, buildSiemResponse, transformError } from '../siem_server_deps'; -import { validate } from '../../common/shared_imports'; import { findListSchema, foundListSchema } from '../../common/schemas'; import { decodeCursor } from '../services/utils'; diff --git a/x-pack/plugins/lists/server/routes/import_list_item_route.ts b/x-pack/plugins/lists/server/routes/import_list_item_route.ts index 3049f853692ee..070764b0e1e77 100644 --- a/x-pack/plugins/lists/server/routes/import_list_item_route.ts +++ b/x-pack/plugins/lists/server/routes/import_list_item_route.ts @@ -6,11 +6,11 @@ */ import { schema } from '@kbn/config-schema'; +import { validate } from '@kbn/securitysolution-io-ts-utils'; import type { ListsPluginRouter } from '../types'; import { LIST_ITEM_URL } from '../../common/constants'; import { buildRouteValidation, buildSiemResponse, transformError } from '../siem_server_deps'; -import { validate } from '../../common/shared_imports'; import { importListItemQuerySchema, listSchema } from '../../common/schemas'; import { ConfigType } from '../config'; diff --git a/x-pack/plugins/lists/server/routes/patch_list_item_route.ts b/x-pack/plugins/lists/server/routes/patch_list_item_route.ts index b00e00ba1cdb1..53fd7c65c8ab8 100644 --- a/x-pack/plugins/lists/server/routes/patch_list_item_route.ts +++ b/x-pack/plugins/lists/server/routes/patch_list_item_route.ts @@ -5,10 +5,11 @@ * 2.0. */ +import { validate } from '@kbn/securitysolution-io-ts-utils'; + import type { ListsPluginRouter } from '../types'; import { LIST_ITEM_URL } from '../../common/constants'; import { buildRouteValidation, buildSiemResponse, transformError } from '../siem_server_deps'; -import { validate } from '../../common/shared_imports'; import { listItemSchema, patchListItemSchema } from '../../common/schemas'; import { getListClient } from '.'; diff --git a/x-pack/plugins/lists/server/routes/patch_list_route.ts b/x-pack/plugins/lists/server/routes/patch_list_route.ts index d8f40474893f4..f139fb72c3066 100644 --- a/x-pack/plugins/lists/server/routes/patch_list_route.ts +++ b/x-pack/plugins/lists/server/routes/patch_list_route.ts @@ -5,10 +5,11 @@ * 2.0. */ +import { validate } from '@kbn/securitysolution-io-ts-utils'; + import type { ListsPluginRouter } from '../types'; import { LIST_URL } from '../../common/constants'; import { buildRouteValidation, buildSiemResponse, transformError } from '../siem_server_deps'; -import { validate } from '../../common/shared_imports'; import { listSchema, patchListSchema } from '../../common/schemas'; import { getListClient } from '.'; diff --git a/x-pack/plugins/lists/server/routes/read_endpoint_list_item_route.ts b/x-pack/plugins/lists/server/routes/read_endpoint_list_item_route.ts index dc08dfa30a84c..c78a4a435e5b4 100644 --- a/x-pack/plugins/lists/server/routes/read_endpoint_list_item_route.ts +++ b/x-pack/plugins/lists/server/routes/read_endpoint_list_item_route.ts @@ -5,10 +5,11 @@ * 2.0. */ +import { validate } from '@kbn/securitysolution-io-ts-utils'; + import type { ListsPluginRouter } from '../types'; import { ENDPOINT_LIST_ITEM_URL } from '../../common/constants'; import { buildRouteValidation, buildSiemResponse, transformError } from '../siem_server_deps'; -import { validate } from '../../common/shared_imports'; import { ReadEndpointListItemSchemaDecoded, exceptionListItemSchema, diff --git a/x-pack/plugins/lists/server/routes/read_exception_list_item_route.ts b/x-pack/plugins/lists/server/routes/read_exception_list_item_route.ts index c41dffcaf98f9..fd92543fa85a7 100644 --- a/x-pack/plugins/lists/server/routes/read_exception_list_item_route.ts +++ b/x-pack/plugins/lists/server/routes/read_exception_list_item_route.ts @@ -5,10 +5,11 @@ * 2.0. */ +import { validate } from '@kbn/securitysolution-io-ts-utils'; + import type { ListsPluginRouter } from '../types'; import { EXCEPTION_LIST_ITEM_URL } from '../../common/constants'; import { buildRouteValidation, buildSiemResponse, transformError } from '../siem_server_deps'; -import { validate } from '../../common/shared_imports'; import { ReadExceptionListItemSchemaDecoded, exceptionListItemSchema, diff --git a/x-pack/plugins/lists/server/routes/read_exception_list_route.ts b/x-pack/plugins/lists/server/routes/read_exception_list_route.ts index 5fc1cdffcd1b1..3d4e831f4a2da 100644 --- a/x-pack/plugins/lists/server/routes/read_exception_list_route.ts +++ b/x-pack/plugins/lists/server/routes/read_exception_list_route.ts @@ -5,10 +5,11 @@ * 2.0. */ +import { validate } from '@kbn/securitysolution-io-ts-utils'; + import type { ListsPluginRouter } from '../types'; import { EXCEPTION_LIST_URL } from '../../common/constants'; import { buildRouteValidation, buildSiemResponse, transformError } from '../siem_server_deps'; -import { validate } from '../../common/shared_imports'; import { ReadExceptionListSchemaDecoded, exceptionListSchema, diff --git a/x-pack/plugins/lists/server/routes/read_list_index_route.ts b/x-pack/plugins/lists/server/routes/read_list_index_route.ts index c0bf76c127016..467348669bc0b 100644 --- a/x-pack/plugins/lists/server/routes/read_list_index_route.ts +++ b/x-pack/plugins/lists/server/routes/read_list_index_route.ts @@ -5,10 +5,11 @@ * 2.0. */ +import { validate } from '@kbn/securitysolution-io-ts-utils'; + import type { ListsPluginRouter } from '../types'; import { LIST_INDEX } from '../../common/constants'; import { buildSiemResponse, transformError } from '../siem_server_deps'; -import { validate } from '../../common/shared_imports'; import { listItemIndexExistSchema } from '../../common/schemas'; import { getListClient } from '.'; diff --git a/x-pack/plugins/lists/server/routes/read_list_item_route.ts b/x-pack/plugins/lists/server/routes/read_list_item_route.ts index 5d32b7878b1c2..fd216197f91b5 100644 --- a/x-pack/plugins/lists/server/routes/read_list_item_route.ts +++ b/x-pack/plugins/lists/server/routes/read_list_item_route.ts @@ -5,11 +5,12 @@ * 2.0. */ +import { validate } from '@kbn/securitysolution-io-ts-utils'; + import type { ListsPluginRouter } from '../types'; import { LIST_ITEM_URL } from '../../common/constants'; import { buildRouteValidation, buildSiemResponse, transformError } from '../siem_server_deps'; import { listItemArraySchema, listItemSchema, readListItemSchema } from '../../common/schemas'; -import { validate } from '../../common/shared_imports'; import { getListClient } from '.'; diff --git a/x-pack/plugins/lists/server/routes/read_list_route.ts b/x-pack/plugins/lists/server/routes/read_list_route.ts index 91ffeb6f0489e..56acb1e043bd5 100644 --- a/x-pack/plugins/lists/server/routes/read_list_route.ts +++ b/x-pack/plugins/lists/server/routes/read_list_route.ts @@ -5,10 +5,11 @@ * 2.0. */ +import { validate } from '@kbn/securitysolution-io-ts-utils'; + import type { ListsPluginRouter } from '../types'; import { LIST_URL } from '../../common/constants'; import { buildRouteValidation, buildSiemResponse, transformError } from '../siem_server_deps'; -import { validate } from '../../common/shared_imports'; import { listSchema, readListSchema } from '../../common/schemas'; import { getListClient } from '.'; diff --git a/x-pack/plugins/lists/server/routes/update_endpoint_list_item_route.ts b/x-pack/plugins/lists/server/routes/update_endpoint_list_item_route.ts index 101e3acf1c3d0..9f445f4e3c114 100644 --- a/x-pack/plugins/lists/server/routes/update_endpoint_list_item_route.ts +++ b/x-pack/plugins/lists/server/routes/update_endpoint_list_item_route.ts @@ -5,10 +5,11 @@ * 2.0. */ +import { validate } from '@kbn/securitysolution-io-ts-utils'; + import type { ListsPluginRouter } from '../types'; import { ENDPOINT_LIST_ITEM_URL } from '../../common/constants'; import { buildRouteValidation, buildSiemResponse, transformError } from '../siem_server_deps'; -import { validate } from '../../common/shared_imports'; import { UpdateEndpointListItemSchemaDecoded, exceptionListItemSchema, diff --git a/x-pack/plugins/lists/server/routes/update_exception_list_item_route.ts b/x-pack/plugins/lists/server/routes/update_exception_list_item_route.ts index bb2687ff3a575..6a87af6c666bb 100644 --- a/x-pack/plugins/lists/server/routes/update_exception_list_item_route.ts +++ b/x-pack/plugins/lists/server/routes/update_exception_list_item_route.ts @@ -5,10 +5,11 @@ * 2.0. */ +import { validate } from '@kbn/securitysolution-io-ts-utils'; + import type { ListsPluginRouter } from '../types'; import { EXCEPTION_LIST_ITEM_URL } from '../../common/constants'; import { buildRouteValidation, buildSiemResponse, transformError } from '../siem_server_deps'; -import { validate } from '../../common/shared_imports'; import { UpdateExceptionListItemSchemaDecoded, exceptionListItemSchema, diff --git a/x-pack/plugins/lists/server/routes/update_exception_list_route.ts b/x-pack/plugins/lists/server/routes/update_exception_list_route.ts index c674ab612a737..a6b99579d87ad 100644 --- a/x-pack/plugins/lists/server/routes/update_exception_list_route.ts +++ b/x-pack/plugins/lists/server/routes/update_exception_list_route.ts @@ -5,10 +5,11 @@ * 2.0. */ +import { validate } from '@kbn/securitysolution-io-ts-utils'; + import type { ListsPluginRouter } from '../types'; import { EXCEPTION_LIST_URL } from '../../common/constants'; import { buildRouteValidation, buildSiemResponse, transformError } from '../siem_server_deps'; -import { validate } from '../../common/shared_imports'; import { UpdateExceptionListSchemaDecoded, exceptionListSchema, diff --git a/x-pack/plugins/lists/server/routes/update_list_item_route.ts b/x-pack/plugins/lists/server/routes/update_list_item_route.ts index 1fe20a71d46ab..e2905c1a00a11 100644 --- a/x-pack/plugins/lists/server/routes/update_list_item_route.ts +++ b/x-pack/plugins/lists/server/routes/update_list_item_route.ts @@ -5,10 +5,11 @@ * 2.0. */ +import { validate } from '@kbn/securitysolution-io-ts-utils'; + import type { ListsPluginRouter } from '../types'; import { LIST_ITEM_URL } from '../../common/constants'; import { buildRouteValidation, buildSiemResponse, transformError } from '../siem_server_deps'; -import { validate } from '../../common/shared_imports'; import { listItemSchema, updateListItemSchema } from '../../common/schemas'; import { getListClient } from '.'; diff --git a/x-pack/plugins/lists/server/routes/update_list_route.ts b/x-pack/plugins/lists/server/routes/update_list_route.ts index 0354350ea4574..d69c110aa129b 100644 --- a/x-pack/plugins/lists/server/routes/update_list_route.ts +++ b/x-pack/plugins/lists/server/routes/update_list_route.ts @@ -5,10 +5,11 @@ * 2.0. */ +import { validate } from '@kbn/securitysolution-io-ts-utils'; + import type { ListsPluginRouter } from '../types'; import { LIST_URL } from '../../common/constants'; import { buildRouteValidation, buildSiemResponse, transformError } from '../siem_server_deps'; -import { validate } from '../../common/shared_imports'; import { listSchema, updateListSchema } from '../../common/schemas'; import { getListClient } from '.'; diff --git a/x-pack/plugins/lists/server/routes/validate.ts b/x-pack/plugins/lists/server/routes/validate.ts index 6d03fec7581f8..005d9e85f4853 100644 --- a/x-pack/plugins/lists/server/routes/validate.ts +++ b/x-pack/plugins/lists/server/routes/validate.ts @@ -8,14 +8,18 @@ import { pipe } from 'fp-ts/lib/pipeable'; import * as t from 'io-ts'; import { fold } from 'fp-ts/lib/Either'; +import { + NamespaceType, + NonEmptyEntriesArray, + exactCheck, + formatErrors, + nonEmptyEndpointEntriesArray, + validate, +} from '@kbn/securitysolution-io-ts-utils'; import { ExceptionListClient } from '../services/exception_lists/exception_list_client'; import { MAX_EXCEPTION_LIST_SIZE } from '../../common/constants'; import { foundExceptionListItemSchema } from '../../common/schemas'; -import { NamespaceType, NonEmptyEntriesArray } from '../../common/schemas/types'; -import { nonEmptyEndpointEntriesArray } from '../../common/schemas/types/endpoint'; -import { exactCheck, validate } from '../../common/shared_imports'; -import { formatErrors } from '../siem_server_deps'; export const validateExceptionListSize = async ( exceptionLists: ExceptionListClient, diff --git a/x-pack/plugins/lists/server/saved_objects/migrations.test.ts b/x-pack/plugins/lists/server/saved_objects/migrations.test.ts index f71109b9bb85d..27c883d8b9674 100644 --- a/x-pack/plugins/lists/server/saved_objects/migrations.test.ts +++ b/x-pack/plugins/lists/server/saved_objects/migrations.test.ts @@ -9,7 +9,7 @@ import { SavedObjectUnsanitizedDoc } from 'kibana/server'; import uuid from 'uuid'; import { ENDPOINT_LIST_ID, ENDPOINT_TRUSTED_APPS_LIST_ID } from '../../common/constants'; -import { ExceptionListSoSchema } from '../../common/schemas/saved_objects'; +import { ExceptionListSoSchema } from '../schemas/saved_objects'; import { OldExceptionListSoSchema, migrations } from './migrations'; diff --git a/x-pack/plugins/lists/server/saved_objects/migrations.ts b/x-pack/plugins/lists/server/saved_objects/migrations.ts index 2fa19a6810a8a..316c5f1311774 100644 --- a/x-pack/plugins/lists/server/saved_objects/migrations.ts +++ b/x-pack/plugins/lists/server/saved_objects/migrations.ts @@ -7,16 +7,16 @@ import * as t from 'io-ts'; import { SavedObjectSanitizedDoc, SavedObjectUnsanitizedDoc } from 'kibana/server'; - -import { ENDPOINT_LIST_ID, ENDPOINT_TRUSTED_APPS_LIST_ID } from '../../common/constants'; import { EntriesArray, - ExceptionListSoSchema, NonEmptyNestedEntriesArray, OsTypeArray, entriesNested, entry, -} from '../../common/schemas'; +} from '@kbn/securitysolution-io-ts-utils'; + +import { ENDPOINT_LIST_ID, ENDPOINT_TRUSTED_APPS_LIST_ID } from '../../common/constants'; +import { ExceptionListSoSchema } from '../schemas/saved_objects'; const entryType = t.union([entry, entriesNested]); type EntryType = t.TypeOf; diff --git a/x-pack/plugins/lists/common/get_call_cluster.mock.ts b/x-pack/plugins/lists/server/schemas/common/get_call_cluster.mock.ts similarity index 94% rename from x-pack/plugins/lists/common/get_call_cluster.mock.ts rename to x-pack/plugins/lists/server/schemas/common/get_call_cluster.mock.ts index eca73eb305ba7..5ed745c3dbc90 100644 --- a/x-pack/plugins/lists/common/get_call_cluster.mock.ts +++ b/x-pack/plugins/lists/server/schemas/common/get_call_cluster.mock.ts @@ -8,7 +8,8 @@ import { CreateDocumentResponse } from 'elasticsearch'; import { LegacyAPICaller } from 'kibana/server'; -import { LIST_INDEX } from './constants.mock'; +import { LIST_INDEX } from '../../../common/constants.mock'; + import { getShardMock } from './get_shard.mock'; export const getEmptyCreateDocumentResponseMock = (): CreateDocumentResponse => ({ diff --git a/x-pack/plugins/lists/common/get_shard.mock.ts b/x-pack/plugins/lists/server/schemas/common/get_shard.mock.ts similarity index 100% rename from x-pack/plugins/lists/common/get_shard.mock.ts rename to x-pack/plugins/lists/server/schemas/common/get_shard.mock.ts diff --git a/x-pack/plugins/lists/server/schemas/common/schemas.test.ts b/x-pack/plugins/lists/server/schemas/common/schemas.test.ts new file mode 100644 index 0000000000000..75cbc846fbcac --- /dev/null +++ b/x-pack/plugins/lists/server/schemas/common/schemas.test.ts @@ -0,0 +1,288 @@ +/* + * 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 { exactCheck, foldLeftRight, getPaths } from '@kbn/securitysolution-io-ts-utils'; +import { pipe } from 'fp-ts/lib/pipeable'; +import { left } from 'fp-ts/lib/Either'; + +import { + EsDataTypeGeoPoint, + EsDataTypeGeoPointRange, + EsDataTypeRange, + EsDataTypeRangeTerm, + EsDataTypeSingle, + EsDataTypeUnion, + esDataTypeGeoPoint, + esDataTypeGeoPointRange, + esDataTypeRange, + esDataTypeRangeTerm, + esDataTypeSingle, + esDataTypeUnion, +} from './schemas'; + +describe('esDataTypeUnion', () => { + test('it will work with a regular union', () => { + const payload: EsDataTypeUnion = { boolean: 'true' }; + const decoded = esDataTypeUnion.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + expect(getPaths(left(message.errors))).toEqual([]); + expect(message.schema).toEqual(payload); + }); + + test('it will not work with a madeup value', () => { + const payload: EsDataTypeUnion & { madeupValue: 'madeupValue' } = { + boolean: 'true', + madeupValue: 'madeupValue', + }; + const decoded = esDataTypeUnion.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + expect(getPaths(left(message.errors))).toEqual(['invalid keys "madeupValue"']); + expect(message.schema).toEqual({}); + }); +}); + +describe('esDataTypeRange', () => { + test('it will work with a given gte, lte range', () => { + const payload: EsDataTypeRange = { gte: '127.0.0.1', lte: '127.0.0.1' }; + const decoded = esDataTypeRange.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + expect(getPaths(left(message.errors))).toEqual([]); + expect(message.schema).toEqual(payload); + }); + + test('it will give an error if given an extra madeup value', () => { + const payload: EsDataTypeRange & { madeupvalue: string } = { + gte: '127.0.0.1', + lte: '127.0.0.1', + madeupvalue: 'something', + }; + const decoded = esDataTypeRange.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + expect(getPaths(left(message.errors))).toEqual(['invalid keys "madeupvalue"']); + expect(message.schema).toEqual({}); + }); +}); + +describe('esDataTypeRangeTerm', () => { + test('it will work with a date_range', () => { + const payload: EsDataTypeRangeTerm = { date_range: { gte: '2015', lte: '2017' } }; + const decoded = esDataTypeRangeTerm.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + expect(getPaths(left(message.errors))).toEqual([]); + expect(message.schema).toEqual(payload); + }); + + test('it will give an error if given an extra madeup value for date_range', () => { + const payload: EsDataTypeRangeTerm & { madeupvalue: string } = { + date_range: { gte: '2015', lte: '2017' }, + madeupvalue: 'something', + }; + const decoded = esDataTypeRangeTerm.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + expect(getPaths(left(message.errors))).toEqual(['invalid keys "madeupvalue"']); + expect(message.schema).toEqual({}); + }); + + test('it will work with a double_range', () => { + const payload: EsDataTypeRangeTerm = { double_range: { gte: '2015', lte: '2017' } }; + const decoded = esDataTypeRangeTerm.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + expect(getPaths(left(message.errors))).toEqual([]); + expect(message.schema).toEqual(payload); + }); + + test('it will give an error if given an extra madeup value for double_range', () => { + const payload: EsDataTypeRangeTerm & { madeupvalue: string } = { + double_range: { gte: '2015', lte: '2017' }, + madeupvalue: 'something', + }; + const decoded = esDataTypeRangeTerm.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + expect(getPaths(left(message.errors))).toEqual(['invalid keys "madeupvalue"']); + expect(message.schema).toEqual({}); + }); + + test('it will work with a float_range', () => { + const payload: EsDataTypeRangeTerm = { float_range: { gte: '2015', lte: '2017' } }; + const decoded = esDataTypeRangeTerm.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + expect(getPaths(left(message.errors))).toEqual([]); + expect(message.schema).toEqual(payload); + }); + + test('it will give an error if given an extra madeup value for float_range', () => { + const payload: EsDataTypeRangeTerm & { madeupvalue: string } = { + float_range: { gte: '2015', lte: '2017' }, + madeupvalue: 'something', + }; + const decoded = esDataTypeRangeTerm.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + expect(getPaths(left(message.errors))).toEqual(['invalid keys "madeupvalue"']); + expect(message.schema).toEqual({}); + }); + + test('it will work with a integer_range', () => { + const payload: EsDataTypeRangeTerm = { integer_range: { gte: '2015', lte: '2017' } }; + const decoded = esDataTypeRangeTerm.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + expect(getPaths(left(message.errors))).toEqual([]); + expect(message.schema).toEqual(payload); + }); + + test('it will give an error if given an extra madeup value for integer_range', () => { + const payload: EsDataTypeRangeTerm & { madeupvalue: string } = { + integer_range: { gte: '2015', lte: '2017' }, + madeupvalue: 'something', + }; + const decoded = esDataTypeRangeTerm.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + expect(getPaths(left(message.errors))).toEqual(['invalid keys "madeupvalue"']); + expect(message.schema).toEqual({}); + }); + + test('it will work with a ip_range', () => { + const payload: EsDataTypeRangeTerm = { ip_range: { gte: '127.0.0.1', lte: '127.0.0.2' } }; + const decoded = esDataTypeRangeTerm.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + expect(getPaths(left(message.errors))).toEqual([]); + expect(message.schema).toEqual(payload); + }); + + test('it will work with a ip_range as a CIDR', () => { + const payload: EsDataTypeRangeTerm = { ip_range: '127.0.0.1/16' }; + const decoded = esDataTypeRangeTerm.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + expect(getPaths(left(message.errors))).toEqual([]); + expect(message.schema).toEqual(payload); + }); + + test('it will give an error if given an extra madeup value for ip_range', () => { + const payload: EsDataTypeRangeTerm & { madeupvalue: string } = { + ip_range: { gte: '127.0.0.1', lte: '127.0.0.2' }, + madeupvalue: 'something', + }; + const decoded = esDataTypeRangeTerm.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + expect(getPaths(left(message.errors))).toEqual(['invalid keys "madeupvalue"']); + expect(message.schema).toEqual({}); + }); + + test('it will work with a long_range', () => { + const payload: EsDataTypeRangeTerm = { long_range: { gte: '2015', lte: '2017' } }; + const decoded = esDataTypeRangeTerm.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + expect(getPaths(left(message.errors))).toEqual([]); + expect(message.schema).toEqual(payload); + }); + + test('it will give an error if given an extra madeup value for long_range', () => { + const payload: EsDataTypeRangeTerm & { madeupvalue: string } = { + long_range: { gte: '2015', lte: '2017' }, + madeupvalue: 'something', + }; + const decoded = esDataTypeRangeTerm.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + expect(getPaths(left(message.errors))).toEqual(['invalid keys "madeupvalue"']); + expect(message.schema).toEqual({}); + }); +}); + +describe('esDataTypeGeoPointRange', () => { + test('it will work with a given lat, lon range', () => { + const payload: EsDataTypeGeoPointRange = { lat: '20', lon: '30' }; + const decoded = esDataTypeGeoPointRange.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + expect(getPaths(left(message.errors))).toEqual([]); + expect(message.schema).toEqual(payload); + }); + + test('it will give an error if given an extra madeup value', () => { + const payload: EsDataTypeGeoPointRange & { madeupvalue: string } = { + lat: '20', + lon: '30', + madeupvalue: 'something', + }; + const decoded = esDataTypeGeoPointRange.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + expect(getPaths(left(message.errors))).toEqual(['invalid keys "madeupvalue"']); + expect(message.schema).toEqual({}); + }); +}); + +describe('esDataTypeGeoPoint', () => { + test('it will work with a given lat, lon range', () => { + const payload: EsDataTypeGeoPoint = { geo_point: { lat: '127.0.0.1', lon: '127.0.0.1' } }; + const decoded = esDataTypeGeoPoint.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + expect(getPaths(left(message.errors))).toEqual([]); + expect(message.schema).toEqual(payload); + }); + + test('it will work with a WKT (Well known text)', () => { + const payload: EsDataTypeGeoPoint = { geo_point: 'POINT (30 10)' }; + const decoded = esDataTypeGeoPoint.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + expect(getPaths(left(message.errors))).toEqual([]); + expect(message.schema).toEqual(payload); + }); + + test('it will give an error if given an extra madeup value', () => { + const payload: EsDataTypeGeoPoint & { madeupvalue: string } = { + geo_point: 'POINT (30 10)', + madeupvalue: 'something', + }; + const decoded = esDataTypeGeoPoint.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + expect(getPaths(left(message.errors))).toEqual(['invalid keys "madeupvalue"']); + expect(message.schema).toEqual({}); + }); +}); + +describe('esDataTypeSingle', () => { + test('it will work with single type', () => { + const payload: EsDataTypeSingle = { boolean: 'true' }; + const decoded = esDataTypeSingle.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + expect(getPaths(left(message.errors))).toEqual([]); + expect(message.schema).toEqual(payload); + }); + + test('it will not work with a madeup value', () => { + const payload: EsDataTypeSingle & { madeupValue: 'madeup' } = { + boolean: 'true', + madeupValue: 'madeup', + }; + const decoded = esDataTypeSingle.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + expect(getPaths(left(message.errors))).toEqual(['invalid keys "madeupValue"']); + expect(message.schema).toEqual({}); + }); +}); diff --git a/x-pack/plugins/lists/server/schemas/common/schemas.ts b/x-pack/plugins/lists/server/schemas/common/schemas.ts new file mode 100644 index 0000000000000..86d29f96f2d53 --- /dev/null +++ b/x-pack/plugins/lists/server/schemas/common/schemas.ts @@ -0,0 +1,140 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +/* eslint-disable @typescript-eslint/naming-convention */ + +import * as t from 'io-ts'; + +export const binary = t.string; +export const binaryOrUndefined = t.union([binary, t.undefined]); + +export const boolean = t.string; +export const booleanOrUndefined = t.union([boolean, t.undefined]); + +export const byte = t.string; +export const byteOrUndefined = t.union([byte, t.undefined]); + +export const date = t.string; +export const dateOrUndefined = t.union([date, t.undefined]); + +export const date_nanos = t.string; +export const dateNanosOrUndefined = t.union([date_nanos, t.undefined]); + +export const double = t.string; +export const doubleOrUndefined = t.union([double, t.undefined]); + +export const float = t.string; +export const floatOrUndefined = t.union([float, t.undefined]); + +export const geo_shape = t.string; +export const geoShapeOrUndefined = t.union([geo_shape, t.undefined]); + +export const half_float = t.string; +export const halfFloatOrUndefined = t.union([half_float, t.undefined]); + +export const integer = t.string; +export const integerOrUndefined = t.union([integer, t.undefined]); + +export const ip = t.string; +export const ipOrUndefined = t.union([ip, t.undefined]); + +export const keyword = t.string; +export const keywordOrUndefined = t.union([keyword, t.undefined]); + +export const text = t.string; +export const textOrUndefined = t.union([text, t.undefined]); + +export const long = t.string; +export const longOrUndefined = t.union([long, t.undefined]); + +export const shape = t.string; +export const shapeOrUndefined = t.union([shape, t.undefined]); + +export const short = t.string; +export const shortOrUndefined = t.union([short, t.undefined]); + +export const esDataTypeRange = t.exact(t.type({ gte: t.string, lte: t.string })); + +export const date_range = esDataTypeRange; +export const dateRangeOrUndefined = t.union([date_range, t.undefined]); + +export const double_range = esDataTypeRange; +export const doubleRangeOrUndefined = t.union([double_range, t.undefined]); + +export const float_range = esDataTypeRange; +export const floatRangeOrUndefined = t.union([float_range, t.undefined]); + +export const integer_range = esDataTypeRange; +export const integerRangeOrUndefined = t.union([integer_range, t.undefined]); + +// ip_range can be just a CIDR value as a range +export const ip_range = t.union([esDataTypeRange, t.string]); +export const ipRangeOrUndefined = t.union([ip_range, t.undefined]); + +export const long_range = esDataTypeRange; +export const longRangeOrUndefined = t.union([long_range, t.undefined]); + +export type EsDataTypeRange = t.TypeOf; + +export const esDataTypeRangeTerm = t.union([ + t.exact(t.type({ date_range })), + t.exact(t.type({ double_range })), + t.exact(t.type({ float_range })), + t.exact(t.type({ integer_range })), + t.exact(t.type({ ip_range })), + t.exact(t.type({ long_range })), +]); + +export type EsDataTypeRangeTerm = t.TypeOf; + +export const esDataTypeGeoPointRange = t.exact(t.type({ lat: t.string, lon: t.string })); +export type EsDataTypeGeoPointRange = t.TypeOf; + +export const geo_point = t.union([esDataTypeGeoPointRange, t.string]); +export type GeoPoint = t.TypeOf; + +export const geoPointOrUndefined = t.union([geo_point, t.undefined]); + +export const esDataTypeGeoPoint = t.exact(t.type({ geo_point })); +export type EsDataTypeGeoPoint = t.TypeOf; + +export const esDataTypeGeoShape = t.union([ + t.exact(t.type({ geo_shape: t.string })), + t.exact(t.type({ shape: t.string })), +]); + +export type EsDataTypeGeoShape = t.TypeOf; + +export const esDataTypeSingle = t.union([ + t.exact(t.type({ binary })), + t.exact(t.type({ boolean })), + t.exact(t.type({ byte })), + t.exact(t.type({ date })), + t.exact(t.type({ date_nanos })), + t.exact(t.type({ double })), + t.exact(t.type({ float })), + t.exact(t.type({ half_float })), + t.exact(t.type({ integer })), + t.exact(t.type({ ip })), + t.exact(t.type({ keyword })), + t.exact(t.type({ long })), + t.exact(t.type({ short })), + t.exact(t.type({ text })), +]); + +export type EsDataTypeSingle = t.TypeOf; + +export const esDataTypeUnion = t.union([ + esDataTypeRangeTerm, + esDataTypeGeoPoint, + esDataTypeGeoShape, + esDataTypeSingle, +]); + +export type EsDataTypeUnion = t.TypeOf; + +export const _index = t.string; diff --git a/x-pack/plugins/lists/common/schemas/elastic_query/create_es_bulk_type.ts b/x-pack/plugins/lists/server/schemas/elastic_query/create_es_bulk_type.ts similarity index 100% rename from x-pack/plugins/lists/common/schemas/elastic_query/create_es_bulk_type.ts rename to x-pack/plugins/lists/server/schemas/elastic_query/create_es_bulk_type.ts diff --git a/x-pack/plugins/lists/common/schemas/elastic_query/index.ts b/x-pack/plugins/lists/server/schemas/elastic_query/index.ts similarity index 100% rename from x-pack/plugins/lists/common/schemas/elastic_query/index.ts rename to x-pack/plugins/lists/server/schemas/elastic_query/index.ts index 3d7fcadd42d01..23f74859a6577 100644 --- a/x-pack/plugins/lists/common/schemas/elastic_query/index.ts +++ b/x-pack/plugins/lists/server/schemas/elastic_query/index.ts @@ -5,8 +5,8 @@ * 2.0. */ -export * from './update_es_list_schema'; +export * from './create_es_bulk_type'; +export * from './index_es_list_item_schema'; export * from './index_es_list_schema'; export * from './update_es_list_item_schema'; -export * from './index_es_list_item_schema'; -export * from './create_es_bulk_type'; +export * from './update_es_list_schema'; diff --git a/x-pack/plugins/lists/common/schemas/elastic_query/index_es_list_item_schema.mock.ts b/x-pack/plugins/lists/server/schemas/elastic_query/index_es_list_item_schema.mock.ts similarity index 90% rename from x-pack/plugins/lists/common/schemas/elastic_query/index_es_list_item_schema.mock.ts rename to x-pack/plugins/lists/server/schemas/elastic_query/index_es_list_item_schema.mock.ts index 938af6b6c5e0c..9a41946c6677c 100644 --- a/x-pack/plugins/lists/common/schemas/elastic_query/index_es_list_item_schema.mock.ts +++ b/x-pack/plugins/lists/server/schemas/elastic_query/index_es_list_item_schema.mock.ts @@ -5,9 +5,10 @@ * 2.0. */ -import { IndexEsListItemSchema } from '../../../common/schemas'; import { DATE_NOW, LIST_ID, META, TIE_BREAKER, USER, VALUE } from '../../../common/constants.mock'; +import { IndexEsListItemSchema } from './index_es_list_item_schema'; + export const getIndexESListItemMock = (ip = VALUE): IndexEsListItemSchema => ({ created_at: DATE_NOW, created_by: USER, diff --git a/x-pack/plugins/lists/common/schemas/elastic_query/index_es_list_item_schema.ts b/x-pack/plugins/lists/server/schemas/elastic_query/index_es_list_item_schema.ts similarity index 86% rename from x-pack/plugins/lists/common/schemas/elastic_query/index_es_list_item_schema.ts rename to x-pack/plugins/lists/server/schemas/elastic_query/index_es_list_item_schema.ts index da8a490e444ab..696434a616c53 100644 --- a/x-pack/plugins/lists/common/schemas/elastic_query/index_es_list_item_schema.ts +++ b/x-pack/plugins/lists/server/schemas/elastic_query/index_es_list_item_schema.ts @@ -6,19 +6,21 @@ */ import * as t from 'io-ts'; - import { created_at, created_by, + metaOrUndefined, + updated_at, + updated_by, +} from '@kbn/securitysolution-io-ts-utils'; + +import { esDataTypeUnion } from '../common/schemas'; +import { deserializerOrUndefined, - esDataTypeUnion, list_id, - metaOrUndefined, serializerOrUndefined, tie_breaker_id, - updated_at, - updated_by, -} from '../common/schemas'; +} from '../../../common/schemas'; export const indexEsListItemSchema = t.intersection([ t.exact( diff --git a/x-pack/plugins/lists/common/schemas/elastic_query/index_es_list_schema.mock.ts b/x-pack/plugins/lists/server/schemas/elastic_query/index_es_list_schema.mock.ts similarity index 92% rename from x-pack/plugins/lists/common/schemas/elastic_query/index_es_list_schema.mock.ts rename to x-pack/plugins/lists/server/schemas/elastic_query/index_es_list_schema.mock.ts index bd7171a6552c2..9e72c83223bab 100644 --- a/x-pack/plugins/lists/common/schemas/elastic_query/index_es_list_schema.mock.ts +++ b/x-pack/plugins/lists/server/schemas/elastic_query/index_es_list_schema.mock.ts @@ -5,7 +5,6 @@ * 2.0. */ -import { IndexEsListSchema } from '../../../common/schemas'; import { DATE_NOW, DESCRIPTION, @@ -18,6 +17,8 @@ import { VERSION, } from '../../../common/constants.mock'; +import { IndexEsListSchema } from './index_es_list_schema'; + export const getIndexESListMock = (): IndexEsListSchema => ({ created_at: DATE_NOW, created_by: USER, diff --git a/x-pack/plugins/lists/common/schemas/elastic_query/index_es_list_schema.ts b/x-pack/plugins/lists/server/schemas/elastic_query/index_es_list_schema.ts similarity index 91% rename from x-pack/plugins/lists/common/schemas/elastic_query/index_es_list_schema.ts rename to x-pack/plugins/lists/server/schemas/elastic_query/index_es_list_schema.ts index 4fd89ca731368..c69abaf785dec 100644 --- a/x-pack/plugins/lists/common/schemas/elastic_query/index_es_list_schema.ts +++ b/x-pack/plugins/lists/server/schemas/elastic_query/index_es_list_schema.ts @@ -6,22 +6,24 @@ */ import * as t from 'io-ts'; - import { created_at, created_by, description, - deserializerOrUndefined, - immutable, metaOrUndefined, name, - serializerOrUndefined, - tie_breaker_id, type, updated_at, updated_by, +} from '@kbn/securitysolution-io-ts-utils'; + +import { + deserializerOrUndefined, + immutable, + serializerOrUndefined, + tie_breaker_id, version, -} from '../common/schemas'; +} from '../../../common/schemas'; export const indexEsListSchema = t.exact( t.type({ diff --git a/x-pack/plugins/lists/common/schemas/elastic_query/update_es_list_item_schema.ts b/x-pack/plugins/lists/server/schemas/elastic_query/update_es_list_item_schema.ts similarity index 78% rename from x-pack/plugins/lists/common/schemas/elastic_query/update_es_list_item_schema.ts rename to x-pack/plugins/lists/server/schemas/elastic_query/update_es_list_item_schema.ts index 2808873dec67f..1f49943a910bc 100644 --- a/x-pack/plugins/lists/common/schemas/elastic_query/update_es_list_item_schema.ts +++ b/x-pack/plugins/lists/server/schemas/elastic_query/update_es_list_item_schema.ts @@ -6,8 +6,9 @@ */ import * as t from 'io-ts'; +import { metaOrUndefined, updated_at, updated_by } from '@kbn/securitysolution-io-ts-utils'; -import { esDataTypeUnion, metaOrUndefined, updated_at, updated_by } from '../common/schemas'; +import { esDataTypeUnion } from '../common/schemas'; export const updateEsListItemSchema = t.intersection([ t.exact( diff --git a/x-pack/plugins/lists/common/schemas/elastic_query/update_es_list_schema.ts b/x-pack/plugins/lists/server/schemas/elastic_query/update_es_list_schema.ts similarity index 93% rename from x-pack/plugins/lists/common/schemas/elastic_query/update_es_list_schema.ts rename to x-pack/plugins/lists/server/schemas/elastic_query/update_es_list_schema.ts index 5612aaddf18b0..fbeac92c66bdd 100644 --- a/x-pack/plugins/lists/common/schemas/elastic_query/update_es_list_schema.ts +++ b/x-pack/plugins/lists/server/schemas/elastic_query/update_es_list_schema.ts @@ -6,14 +6,13 @@ */ import * as t from 'io-ts'; - import { descriptionOrUndefined, metaOrUndefined, nameOrUndefined, updated_at, updated_by, -} from '../common/schemas'; +} from '@kbn/securitysolution-io-ts-utils'; export const updateEsListSchema = t.exact( t.type({ diff --git a/x-pack/plugins/lists/common/schemas/elastic_response/index.ts b/x-pack/plugins/lists/server/schemas/elastic_response/index.ts similarity index 100% rename from x-pack/plugins/lists/common/schemas/elastic_response/index.ts rename to x-pack/plugins/lists/server/schemas/elastic_response/index.ts diff --git a/x-pack/plugins/lists/common/schemas/elastic_response/search_es_list_item_schema.mock.ts b/x-pack/plugins/lists/server/schemas/elastic_response/search_es_list_item_schema.mock.ts similarity index 93% rename from x-pack/plugins/lists/common/schemas/elastic_response/search_es_list_item_schema.mock.ts rename to x-pack/plugins/lists/server/schemas/elastic_response/search_es_list_item_schema.mock.ts index 4a1f9dab17540..de49e822f7dc8 100644 --- a/x-pack/plugins/lists/common/schemas/elastic_response/search_es_list_item_schema.mock.ts +++ b/x-pack/plugins/lists/server/schemas/elastic_response/search_es_list_item_schema.mock.ts @@ -7,7 +7,6 @@ import { SearchResponse } from 'elasticsearch'; -import { SearchEsListItemSchema } from '../../../common/schemas'; import { DATE_NOW, LIST_ID, @@ -18,7 +17,9 @@ import { USER, VALUE, } from '../../../common/constants.mock'; -import { getShardMock } from '../../get_shard.mock'; +import { getShardMock } from '../common/get_shard.mock'; + +import { SearchEsListItemSchema } from './search_es_list_item_schema'; export const getSearchEsListItemsAsAllUndefinedMock = (): SearchEsListItemSchema => ({ binary: undefined, diff --git a/x-pack/plugins/lists/common/schemas/elastic_response/search_es_list_item_schema.test.ts b/x-pack/plugins/lists/server/schemas/elastic_response/search_es_list_item_schema.test.ts similarity index 94% rename from x-pack/plugins/lists/common/schemas/elastic_response/search_es_list_item_schema.test.ts rename to x-pack/plugins/lists/server/schemas/elastic_response/search_es_list_item_schema.test.ts index 26cb66735bb0b..5206335554a6b 100644 --- a/x-pack/plugins/lists/common/schemas/elastic_response/search_es_list_item_schema.test.ts +++ b/x-pack/plugins/lists/server/schemas/elastic_response/search_es_list_item_schema.test.ts @@ -7,8 +7,7 @@ import { pipe } from 'fp-ts/lib/pipeable'; import { left } from 'fp-ts/lib/Either'; - -import { exactCheck, foldLeftRight, getPaths } from '../../shared_imports'; +import { exactCheck, foldLeftRight, getPaths } from '@kbn/securitysolution-io-ts-utils'; import { SearchEsListItemSchema, searchEsListItemSchema } from './search_es_list_item_schema'; import { getSearchEsListItemMock } from './search_es_list_item_schema.mock'; diff --git a/x-pack/plugins/lists/common/schemas/elastic_response/search_es_list_item_schema.ts b/x-pack/plugins/lists/server/schemas/elastic_response/search_es_list_item_schema.ts similarity index 95% rename from x-pack/plugins/lists/common/schemas/elastic_response/search_es_list_item_schema.ts rename to x-pack/plugins/lists/server/schemas/elastic_response/search_es_list_item_schema.ts index 05dc175303759..8ac88a1610ea7 100644 --- a/x-pack/plugins/lists/common/schemas/elastic_response/search_es_list_item_schema.ts +++ b/x-pack/plugins/lists/server/schemas/elastic_response/search_es_list_item_schema.ts @@ -6,17 +6,21 @@ */ import * as t from 'io-ts'; +import { + created_at, + created_by, + metaOrUndefined, + updated_at, + updated_by, +} from '@kbn/securitysolution-io-ts-utils'; import { binaryOrUndefined, booleanOrUndefined, byteOrUndefined, - created_at, - created_by, dateNanosOrUndefined, dateOrUndefined, dateRangeOrUndefined, - deserializerOrUndefined, doubleOrUndefined, doubleRangeOrUndefined, floatOrUndefined, @@ -29,18 +33,18 @@ import { ipOrUndefined, ipRangeOrUndefined, keywordOrUndefined, - list_id, longOrUndefined, longRangeOrUndefined, - metaOrUndefined, - serializerOrUndefined, shapeOrUndefined, shortOrUndefined, textOrUndefined, - tie_breaker_id, - updated_at, - updated_by, } from '../common/schemas'; +import { + deserializerOrUndefined, + list_id, + serializerOrUndefined, + tie_breaker_id, +} from '../../../common/schemas'; export const searchEsListItemSchema = t.exact( t.type({ diff --git a/x-pack/plugins/lists/common/schemas/elastic_response/search_es_list_schema.mock.ts b/x-pack/plugins/lists/server/schemas/elastic_response/search_es_list_schema.mock.ts similarity index 92% rename from x-pack/plugins/lists/common/schemas/elastic_response/search_es_list_schema.mock.ts rename to x-pack/plugins/lists/server/schemas/elastic_response/search_es_list_schema.mock.ts index 05ab914f5238d..07d8c92f79932 100644 --- a/x-pack/plugins/lists/common/schemas/elastic_response/search_es_list_schema.mock.ts +++ b/x-pack/plugins/lists/server/schemas/elastic_response/search_es_list_schema.mock.ts @@ -7,7 +7,6 @@ import { SearchResponse } from 'elasticsearch'; -import { SearchEsListSchema } from '../../../common/schemas'; import { DATE_NOW, DESCRIPTION, @@ -21,7 +20,9 @@ import { USER, VERSION, } from '../../../common/constants.mock'; -import { getShardMock } from '../../get_shard.mock'; +import { getShardMock } from '../common/get_shard.mock'; + +import { SearchEsListSchema } from './search_es_list_schema'; export const getSearchEsListMock = (): SearchEsListSchema => ({ created_at: DATE_NOW, diff --git a/x-pack/plugins/lists/common/schemas/elastic_response/search_es_list_schema.test.ts b/x-pack/plugins/lists/server/schemas/elastic_response/search_es_list_schema.test.ts similarity index 94% rename from x-pack/plugins/lists/common/schemas/elastic_response/search_es_list_schema.test.ts rename to x-pack/plugins/lists/server/schemas/elastic_response/search_es_list_schema.test.ts index 80d4d885b87bc..4500c0ed68dcc 100644 --- a/x-pack/plugins/lists/common/schemas/elastic_response/search_es_list_schema.test.ts +++ b/x-pack/plugins/lists/server/schemas/elastic_response/search_es_list_schema.test.ts @@ -7,8 +7,7 @@ import { pipe } from 'fp-ts/lib/pipeable'; import { left } from 'fp-ts/lib/Either'; - -import { exactCheck, foldLeftRight, getPaths } from '../../shared_imports'; +import { exactCheck, foldLeftRight, getPaths } from '@kbn/securitysolution-io-ts-utils'; import { SearchEsListSchema, searchEsListSchema } from './search_es_list_schema'; import { getSearchEsListMock } from './search_es_list_schema.mock'; diff --git a/x-pack/plugins/lists/common/schemas/elastic_response/search_es_list_schema.ts b/x-pack/plugins/lists/server/schemas/elastic_response/search_es_list_schema.ts similarity index 91% rename from x-pack/plugins/lists/common/schemas/elastic_response/search_es_list_schema.ts rename to x-pack/plugins/lists/server/schemas/elastic_response/search_es_list_schema.ts index 37fe3147b57df..a060ebda04a46 100644 --- a/x-pack/plugins/lists/common/schemas/elastic_response/search_es_list_schema.ts +++ b/x-pack/plugins/lists/server/schemas/elastic_response/search_es_list_schema.ts @@ -6,22 +6,24 @@ */ import * as t from 'io-ts'; - import { created_at, created_by, description, - deserializerOrUndefined, - immutable, metaOrUndefined, name, - serializerOrUndefined, - tie_breaker_id, type, updated_at, updated_by, +} from '@kbn/securitysolution-io-ts-utils'; + +import { + deserializerOrUndefined, + immutable, + serializerOrUndefined, + tie_breaker_id, version, -} from '../common/schemas'; +} from '../../../common/schemas'; export const searchEsListSchema = t.exact( t.type({ diff --git a/x-pack/plugins/lists/common/schemas/saved_objects/exceptions_list_so_schema.ts b/x-pack/plugins/lists/server/schemas/saved_objects/exceptions_list_so_schema.ts similarity index 89% rename from x-pack/plugins/lists/common/schemas/saved_objects/exceptions_list_so_schema.ts rename to x-pack/plugins/lists/server/schemas/saved_objects/exceptions_list_so_schema.ts index 01865e04bfe45..f6d2e891a60d0 100644 --- a/x-pack/plugins/lists/common/schemas/saved_objects/exceptions_list_so_schema.ts +++ b/x-pack/plugins/lists/server/schemas/saved_objects/exceptions_list_so_schema.ts @@ -6,26 +6,29 @@ */ import * as t from 'io-ts'; - -import { commentsArrayOrUndefined, entriesArrayOrUndefined } from '../types'; import { + commentsArrayOrUndefined, created_at, created_by, description, + entriesArrayOrUndefined, exceptionListItemType, exceptionListType, - immutableOrUndefined, - itemIdOrUndefined, - list_id, - list_type, metaOrUndefined, name, osTypeArray, tags, - tie_breaker_id, updated_by, +} from '@kbn/securitysolution-io-ts-utils'; + +import { + immutableOrUndefined, + itemIdOrUndefined, + list_id, + list_type, + tie_breaker_id, versionOrUndefined, -} from '../common/schemas'; +} from '../../../common/schemas'; /** * Superset saved object of both lists and list items since they share the same saved object type. diff --git a/x-pack/plugins/lists/common/schemas/saved_objects/index.ts b/x-pack/plugins/lists/server/schemas/saved_objects/index.ts similarity index 100% rename from x-pack/plugins/lists/common/schemas/saved_objects/index.ts rename to x-pack/plugins/lists/server/schemas/saved_objects/index.ts diff --git a/x-pack/plugins/lists/server/services/exception_lists/create_endoint_event_filters_list.ts b/x-pack/plugins/lists/server/services/exception_lists/create_endoint_event_filters_list.ts index 95e9df03400af..dba729437b814 100644 --- a/x-pack/plugins/lists/server/services/exception_lists/create_endoint_event_filters_list.ts +++ b/x-pack/plugins/lists/server/services/exception_lists/create_endoint_event_filters_list.ts @@ -13,7 +13,8 @@ import { ENDPOINT_EVENT_FILTERS_LIST_ID, ENDPOINT_EVENT_FILTERS_LIST_NAME, } from '../../../common/constants'; -import { ExceptionListSchema, ExceptionListSoSchema, Version } from '../../../common/schemas'; +import { ExceptionListSchema, Version } from '../../../common/schemas'; +import { ExceptionListSoSchema } from '../../schemas/saved_objects'; import { getSavedObjectType, transformSavedObjectToExceptionList } from './utils'; diff --git a/x-pack/plugins/lists/server/services/exception_lists/create_endpoint_list.ts b/x-pack/plugins/lists/server/services/exception_lists/create_endpoint_list.ts index bf573f7527614..de2be0cb72735 100644 --- a/x-pack/plugins/lists/server/services/exception_lists/create_endpoint_list.ts +++ b/x-pack/plugins/lists/server/services/exception_lists/create_endpoint_list.ts @@ -13,7 +13,8 @@ import { ENDPOINT_LIST_ID, ENDPOINT_LIST_NAME, } from '../../../common/constants'; -import { ExceptionListSchema, ExceptionListSoSchema, Version } from '../../../common/schemas'; +import { ExceptionListSchema, Version } from '../../../common/schemas'; +import { ExceptionListSoSchema } from '../../schemas/saved_objects'; import { getSavedObjectType, transformSavedObjectToExceptionList } from './utils'; diff --git a/x-pack/plugins/lists/server/services/exception_lists/create_endpoint_trusted_apps_list.ts b/x-pack/plugins/lists/server/services/exception_lists/create_endpoint_trusted_apps_list.ts index 2e56c6edfed0e..6f6ad7c357f14 100644 --- a/x-pack/plugins/lists/server/services/exception_lists/create_endpoint_trusted_apps_list.ts +++ b/x-pack/plugins/lists/server/services/exception_lists/create_endpoint_trusted_apps_list.ts @@ -13,7 +13,8 @@ import { ENDPOINT_TRUSTED_APPS_LIST_ID, ENDPOINT_TRUSTED_APPS_LIST_NAME, } from '../../../common/constants'; -import { ExceptionListSchema, ExceptionListSoSchema, Version } from '../../../common/schemas'; +import { ExceptionListSchema, Version } from '../../../common/schemas'; +import { ExceptionListSoSchema } from '../../schemas/saved_objects'; import { getSavedObjectType, transformSavedObjectToExceptionList } from './utils'; diff --git a/x-pack/plugins/lists/server/services/exception_lists/create_exception_list.ts b/x-pack/plugins/lists/server/services/exception_lists/create_exception_list.ts index f7dd57dfba55a..ef4ceb2f12922 100644 --- a/x-pack/plugins/lists/server/services/exception_lists/create_exception_list.ts +++ b/x-pack/plugins/lists/server/services/exception_lists/create_exception_list.ts @@ -7,20 +7,17 @@ import { SavedObjectsClientContract } from 'kibana/server'; import uuid from 'uuid'; - import { Description, - ExceptionListSchema, - ExceptionListSoSchema, ExceptionListType, - Immutable, - ListId, MetaOrUndefined, Name, NamespaceType, Tags, - Version, -} from '../../../common/schemas'; +} from '@kbn/securitysolution-io-ts-utils'; + +import { ExceptionListSchema, Immutable, ListId, Version } from '../../../common/schemas'; +import { ExceptionListSoSchema } from '../../schemas/saved_objects'; import { getSavedObjectType, transformSavedObjectToExceptionList } from './utils'; diff --git a/x-pack/plugins/lists/server/services/exception_lists/create_exception_list_item.ts b/x-pack/plugins/lists/server/services/exception_lists/create_exception_list_item.ts index 219686617d493..5f88244171f6a 100644 --- a/x-pack/plugins/lists/server/services/exception_lists/create_exception_list_item.ts +++ b/x-pack/plugins/lists/server/services/exception_lists/create_exception_list_item.ts @@ -7,22 +7,20 @@ import { SavedObjectsClientContract } from 'kibana/server'; import uuid from 'uuid'; - import { CreateCommentsArray, Description, EntriesArray, - ExceptionListItemSchema, ExceptionListItemType, - ExceptionListSoSchema, - ItemId, - ListId, MetaOrUndefined, Name, NamespaceType, OsTypeArray, Tags, -} from '../../../common/schemas'; +} from '@kbn/securitysolution-io-ts-utils'; + +import { ExceptionListItemSchema, ItemId, ListId } from '../../../common/schemas'; +import { ExceptionListSoSchema } from '../../schemas/saved_objects'; import { getSavedObjectType, diff --git a/x-pack/plugins/lists/server/services/exception_lists/delete_exception_list.ts b/x-pack/plugins/lists/server/services/exception_lists/delete_exception_list.ts index eb9509d631c4f..afe9106e28d82 100644 --- a/x-pack/plugins/lists/server/services/exception_lists/delete_exception_list.ts +++ b/x-pack/plugins/lists/server/services/exception_lists/delete_exception_list.ts @@ -6,13 +6,9 @@ */ import { SavedObjectsClientContract } from 'kibana/server'; +import { IdOrUndefined, NamespaceType } from '@kbn/securitysolution-io-ts-utils'; -import { - ExceptionListSchema, - IdOrUndefined, - ListIdOrUndefined, - NamespaceType, -} from '../../../common/schemas'; +import { ExceptionListSchema, ListIdOrUndefined } from '../../../common/schemas'; import { getSavedObjectType } from './utils'; import { getExceptionList } from './get_exception_list'; diff --git a/x-pack/plugins/lists/server/services/exception_lists/delete_exception_list_item.ts b/x-pack/plugins/lists/server/services/exception_lists/delete_exception_list_item.ts index d89ff4d2a9ff0..d0e1d2283cc6f 100644 --- a/x-pack/plugins/lists/server/services/exception_lists/delete_exception_list_item.ts +++ b/x-pack/plugins/lists/server/services/exception_lists/delete_exception_list_item.ts @@ -6,14 +6,9 @@ */ import { SavedObjectsClientContract } from 'kibana/server'; +import { Id, IdOrUndefined, NamespaceType } from '@kbn/securitysolution-io-ts-utils'; -import { - ExceptionListItemSchema, - Id, - IdOrUndefined, - ItemIdOrUndefined, - NamespaceType, -} from '../../../common/schemas'; +import { ExceptionListItemSchema, ItemIdOrUndefined } from '../../../common/schemas'; import { getSavedObjectType } from './utils'; import { getExceptionListItem } from './get_exception_list_item'; diff --git a/x-pack/plugins/lists/server/services/exception_lists/delete_exception_list_items_by_list.ts b/x-pack/plugins/lists/server/services/exception_lists/delete_exception_list_items_by_list.ts index 43ef45d326699..d9ec08b818f2d 100644 --- a/x-pack/plugins/lists/server/services/exception_lists/delete_exception_list_items_by_list.ts +++ b/x-pack/plugins/lists/server/services/exception_lists/delete_exception_list_items_by_list.ts @@ -5,8 +5,10 @@ * 2.0. */ +import { NamespaceType } from '@kbn/securitysolution-io-ts-utils'; + import { SavedObjectsClientContract } from '../../../../../../src/core/server/'; -import { ListId, NamespaceType } from '../../../common/schemas'; +import { ListId } from '../../../common/schemas'; import { findExceptionListItem } from './find_exception_list_item'; import { getSavedObjectType } from './utils'; diff --git a/x-pack/plugins/lists/server/services/exception_lists/exception_list_client_types.ts b/x-pack/plugins/lists/server/services/exception_lists/exception_list_client_types.ts index 86587baee5fb3..0954a55d44dcc 100644 --- a/x-pack/plugins/lists/server/services/exception_lists/exception_list_client_types.ts +++ b/x-pack/plugins/lists/server/services/exception_lists/exception_list_client_types.ts @@ -6,39 +6,41 @@ */ import { SavedObjectsClientContract } from 'kibana/server'; - -import { NamespaceTypeArray } from '../../../common/schemas/types/default_namespace_array'; -import { NonEmptyStringArrayDecoded } from '../../../common/schemas/types/non_empty_string_array'; -import { EmptyStringArrayDecoded } from '../../../common/schemas/types/empty_string_array'; import { CreateCommentsArray, Description, DescriptionOrUndefined, + EmptyStringArrayDecoded, EntriesArray, ExceptionListItemType, ExceptionListItemTypeOrUndefined, ExceptionListType, ExceptionListTypeOrUndefined, - FilterOrUndefined, Id, IdOrUndefined, - Immutable, - ItemId, - ItemIdOrUndefined, - ListId, - ListIdOrUndefined, MetaOrUndefined, Name, NameOrUndefined, NamespaceType, + NamespaceTypeArray, + NonEmptyStringArrayDecoded, OsTypeArray, + Tags, + TagsOrUndefined, + UpdateCommentsArray, +} from '@kbn/securitysolution-io-ts-utils'; + +import { + FilterOrUndefined, + Immutable, + ItemId, + ItemIdOrUndefined, + ListId, + ListIdOrUndefined, PageOrUndefined, PerPageOrUndefined, SortFieldOrUndefined, SortOrderOrUndefined, - Tags, - TagsOrUndefined, - UpdateCommentsArray, Version, VersionOrUndefined, _VersionOrUndefined, diff --git a/x-pack/plugins/lists/server/services/exception_lists/find_exception_list.ts b/x-pack/plugins/lists/server/services/exception_lists/find_exception_list.ts index 794457c80f047..82ea5a4f104c5 100644 --- a/x-pack/plugins/lists/server/services/exception_lists/find_exception_list.ts +++ b/x-pack/plugins/lists/server/services/exception_lists/find_exception_list.ts @@ -6,11 +6,10 @@ */ import { SavedObjectsClientContract } from 'kibana/server'; +import { NamespaceTypeArray } from '@kbn/securitysolution-io-ts-utils'; -import { NamespaceTypeArray } from '../../../common/schemas/types/default_namespace_array'; import { SavedObjectType } from '../../../common/types'; import { - ExceptionListSoSchema, FilterOrUndefined, FoundExceptionListSchema, PageOrUndefined, @@ -18,6 +17,7 @@ import { SortFieldOrUndefined, SortOrderOrUndefined, } from '../../../common/schemas'; +import { ExceptionListSoSchema } from '../../schemas/saved_objects'; import { getSavedObjectTypes, transformSavedObjectsToFoundExceptionList } from './utils'; diff --git a/x-pack/plugins/lists/server/services/exception_lists/find_exception_list_item.ts b/x-pack/plugins/lists/server/services/exception_lists/find_exception_list_item.ts index ec51ae089d961..cb9c16ffe3c7b 100644 --- a/x-pack/plugins/lists/server/services/exception_lists/find_exception_list_item.ts +++ b/x-pack/plugins/lists/server/services/exception_lists/find_exception_list_item.ts @@ -6,12 +6,12 @@ */ import { SavedObjectsClientContract } from 'kibana/server'; +import { NamespaceType } from '@kbn/securitysolution-io-ts-utils'; import { FilterOrUndefined, FoundExceptionListItemSchema, ListId, - NamespaceType, PageOrUndefined, PerPageOrUndefined, SortFieldOrUndefined, diff --git a/x-pack/plugins/lists/server/services/exception_lists/find_exception_list_items.ts b/x-pack/plugins/lists/server/services/exception_lists/find_exception_list_items.ts index 155408dafc79d..6721aff5b0c1e 100644 --- a/x-pack/plugins/lists/server/services/exception_lists/find_exception_list_items.ts +++ b/x-pack/plugins/lists/server/services/exception_lists/find_exception_list_items.ts @@ -6,25 +6,27 @@ */ import { SavedObjectsClientContract } from 'kibana/server'; +import { + EmptyStringArrayDecoded, + Id, + NamespaceTypeArray, + NonEmptyStringArrayDecoded, +} from '@kbn/securitysolution-io-ts-utils'; import { SavedObjectType, exceptionListAgnosticSavedObjectType, exceptionListSavedObjectType, } from '../../../common/types'; -import { EmptyStringArrayDecoded } from '../../../common/schemas/types/empty_string_array'; -import { NamespaceTypeArray } from '../../../common/schemas/types/default_namespace_array'; -import { NonEmptyStringArrayDecoded } from '../../../common/schemas/types/non_empty_string_array'; import { - ExceptionListSoSchema, FoundExceptionListItemSchema, - Id, PageOrUndefined, PerPageOrUndefined, SortFieldOrUndefined, SortOrderOrUndefined, } from '../../../common/schemas'; import { escapeQuotes } from '../utils/escape_query'; +import { ExceptionListSoSchema } from '../../schemas/saved_objects'; import { getSavedObjectTypes, transformSavedObjectsToFoundExceptionListItem } from './utils'; import { getExceptionList } from './get_exception_list'; diff --git a/x-pack/plugins/lists/server/services/exception_lists/get_exception_list.ts b/x-pack/plugins/lists/server/services/exception_lists/get_exception_list.ts index 16fa4fd30fd37..342e03160b45b 100644 --- a/x-pack/plugins/lists/server/services/exception_lists/get_exception_list.ts +++ b/x-pack/plugins/lists/server/services/exception_lists/get_exception_list.ts @@ -5,17 +5,14 @@ * 2.0. */ +import { IdOrUndefined, NamespaceType } from '@kbn/securitysolution-io-ts-utils'; + import { SavedObjectsClientContract, SavedObjectsErrorHelpers, } from '../../../../../../src/core/server/'; -import { - ExceptionListSchema, - ExceptionListSoSchema, - IdOrUndefined, - ListIdOrUndefined, - NamespaceType, -} from '../../../common/schemas'; +import { ExceptionListSchema, ListIdOrUndefined } from '../../../common/schemas'; +import { ExceptionListSoSchema } from '../../schemas/saved_objects'; import { getSavedObjectType, transformSavedObjectToExceptionList } from './utils'; diff --git a/x-pack/plugins/lists/server/services/exception_lists/get_exception_list_item.ts b/x-pack/plugins/lists/server/services/exception_lists/get_exception_list_item.ts index 99fdd23ccf9c4..cf469baa46370 100644 --- a/x-pack/plugins/lists/server/services/exception_lists/get_exception_list_item.ts +++ b/x-pack/plugins/lists/server/services/exception_lists/get_exception_list_item.ts @@ -5,17 +5,14 @@ * 2.0. */ +import { IdOrUndefined, NamespaceType } from '@kbn/securitysolution-io-ts-utils'; + import { SavedObjectsClientContract, SavedObjectsErrorHelpers, } from '../../../../../../src/core/server/'; -import { - ExceptionListItemSchema, - ExceptionListSoSchema, - IdOrUndefined, - ItemIdOrUndefined, - NamespaceType, -} from '../../../common/schemas'; +import { ExceptionListItemSchema, ItemIdOrUndefined } from '../../../common/schemas'; +import { ExceptionListSoSchema } from '../../schemas/saved_objects'; import { getSavedObjectType, transformSavedObjectToExceptionListItem } from './utils'; diff --git a/x-pack/plugins/lists/server/services/exception_lists/update_exception_list.ts b/x-pack/plugins/lists/server/services/exception_lists/update_exception_list.ts index e81e119b0cf17..69d9b87227bca 100644 --- a/x-pack/plugins/lists/server/services/exception_lists/update_exception_list.ts +++ b/x-pack/plugins/lists/server/services/exception_lists/update_exception_list.ts @@ -6,22 +6,24 @@ */ import { SavedObjectsClientContract } from 'kibana/server'; - import { DescriptionOrUndefined, - ExceptionListSchema, - ExceptionListSoSchema, ExceptionListTypeOrUndefined, IdOrUndefined, - ListIdOrUndefined, MetaOrUndefined, NameOrUndefined, NamespaceType, OsTypeArray, TagsOrUndefined, +} from '@kbn/securitysolution-io-ts-utils'; + +import { + ExceptionListSchema, + ListIdOrUndefined, VersionOrUndefined, _VersionOrUndefined, } from '../../../common/schemas'; +import { ExceptionListSoSchema } from '../../schemas/saved_objects'; import { getSavedObjectType, transformSavedObjectUpdateToExceptionList } from './utils'; import { getExceptionList } from './get_exception_list'; diff --git a/x-pack/plugins/lists/server/services/exception_lists/update_exception_list_item.ts b/x-pack/plugins/lists/server/services/exception_lists/update_exception_list_item.ts index e1bed459006cc..041008a06f3df 100644 --- a/x-pack/plugins/lists/server/services/exception_lists/update_exception_list_item.ts +++ b/x-pack/plugins/lists/server/services/exception_lists/update_exception_list_item.ts @@ -6,23 +6,25 @@ */ import { SavedObjectsClientContract } from 'kibana/server'; - import { DescriptionOrUndefined, EntriesArray, - ExceptionListItemSchema, ExceptionListItemTypeOrUndefined, - ExceptionListSoSchema, IdOrUndefined, - ItemIdOrUndefined, MetaOrUndefined, NameOrUndefined, NamespaceType, OsTypeArray, TagsOrUndefined, UpdateCommentsArrayOrUndefined, +} from '@kbn/securitysolution-io-ts-utils'; + +import { + ExceptionListItemSchema, + ItemIdOrUndefined, _VersionOrUndefined, } from '../../../common/schemas'; +import { ExceptionListSoSchema } from '../../schemas/saved_objects'; import { getSavedObjectType, diff --git a/x-pack/plugins/lists/server/services/exception_lists/utils.ts b/x-pack/plugins/lists/server/services/exception_lists/utils.ts index 5d9525ae1030f..1322f153bf3bd 100644 --- a/x-pack/plugins/lists/server/services/exception_lists/utils.ts +++ b/x-pack/plugins/lists/server/services/exception_lists/utils.ts @@ -7,27 +7,29 @@ import uuid from 'uuid'; import { SavedObject, SavedObjectsFindResponse, SavedObjectsUpdateResponse } from 'kibana/server'; +import { + CommentsArray, + CreateComment, + CreateCommentsArray, + NamespaceType, + NamespaceTypeArray, + UpdateCommentsArrayOrUndefined, + exceptionListItemType, + exceptionListType, +} from '@kbn/securitysolution-io-ts-utils'; import { SavedObjectType, exceptionListAgnosticSavedObjectType, exceptionListSavedObjectType, } from '../../../common/types'; -import { NamespaceTypeArray } from '../../../common/schemas/types/default_namespace_array'; import { - CommentsArray, - CreateComment, - CreateCommentsArray, ExceptionListItemSchema, ExceptionListSchema, - ExceptionListSoSchema, FoundExceptionListItemSchema, FoundExceptionListSchema, - NamespaceType, - UpdateCommentsArrayOrUndefined, - exceptionListItemType, - exceptionListType, } from '../../../common/schemas'; +import { ExceptionListSoSchema } from '../../schemas/saved_objects'; export const getSavedObjectType = ({ namespaceType, diff --git a/x-pack/plugins/lists/server/services/items/create_list_item.test.ts b/x-pack/plugins/lists/server/services/items/create_list_item.test.ts index acfed44c5259e..d601f7f3eff45 100644 --- a/x-pack/plugins/lists/server/services/items/create_list_item.test.ts +++ b/x-pack/plugins/lists/server/services/items/create_list_item.test.ts @@ -9,8 +9,8 @@ import { elasticsearchClientMock } from 'src/core/server/elasticsearch/client/mocks'; import { getListItemResponseMock } from '../../../common/schemas/response/list_item_schema.mock'; -import { getIndexESListItemMock } from '../../../common/schemas/elastic_query/index_es_list_item_schema.mock'; import { LIST_ITEM_ID, LIST_ITEM_INDEX } from '../../../common/constants.mock'; +import { getIndexESListItemMock } from '../../schemas/elastic_query/index_es_list_item_schema.mock'; import { CreateListItemOptions, createListItem } from './create_list_item'; import { getCreateListItemOptionsMock } from './create_list_item.mock'; diff --git a/x-pack/plugins/lists/server/services/items/create_list_item.ts b/x-pack/plugins/lists/server/services/items/create_list_item.ts index cf8a43be796df..3c51f56c7916a 100644 --- a/x-pack/plugins/lists/server/services/items/create_list_item.ts +++ b/x-pack/plugins/lists/server/services/items/create_list_item.ts @@ -7,18 +7,16 @@ import uuid from 'uuid'; import { ElasticsearchClient } from 'kibana/server'; +import { IdOrUndefined, MetaOrUndefined, Type } from '@kbn/securitysolution-io-ts-utils'; import { DeserializerOrUndefined, - IdOrUndefined, - IndexEsListItemSchema, ListItemSchema, - MetaOrUndefined, SerializerOrUndefined, - Type, } from '../../../common/schemas'; import { transformListItemToElasticQuery } from '../utils'; import { encodeHitVersion } from '../utils/encode_hit_version'; +import { IndexEsListItemSchema } from '../../schemas/elastic_query'; export interface CreateListItemOptions { deserializer: DeserializerOrUndefined; diff --git a/x-pack/plugins/lists/server/services/items/create_list_items_bulk.test.ts b/x-pack/plugins/lists/server/services/items/create_list_items_bulk.test.ts index f9f9728798a0b..ea2ff697c7d37 100644 --- a/x-pack/plugins/lists/server/services/items/create_list_items_bulk.test.ts +++ b/x-pack/plugins/lists/server/services/items/create_list_items_bulk.test.ts @@ -5,8 +5,8 @@ * 2.0. */ -import { getIndexESListItemMock } from '../../../common/schemas/elastic_query/index_es_list_item_schema.mock'; import { LIST_ITEM_INDEX, TIE_BREAKERS, VALUE_2 } from '../../../common/constants.mock'; +import { getIndexESListItemMock } from '../../schemas/elastic_query/index_es_list_item_schema.mock'; import { CreateListItemsBulkOptions, createListItemsBulk } from './create_list_items_bulk'; import { getCreateListItemBulkOptionsMock } from './create_list_items_bulk.mock'; diff --git a/x-pack/plugins/lists/server/services/items/create_list_items_bulk.ts b/x-pack/plugins/lists/server/services/items/create_list_items_bulk.ts index 86d8d9a698b1f..5928260ab94ac 100644 --- a/x-pack/plugins/lists/server/services/items/create_list_items_bulk.ts +++ b/x-pack/plugins/lists/server/services/items/create_list_items_bulk.ts @@ -7,16 +7,11 @@ import uuid from 'uuid'; import { ElasticsearchClient } from 'kibana/server'; +import { MetaOrUndefined, Type } from '@kbn/securitysolution-io-ts-utils'; import { transformListItemToElasticQuery } from '../utils'; -import { - CreateEsBulkTypeSchema, - DeserializerOrUndefined, - IndexEsListItemSchema, - MetaOrUndefined, - SerializerOrUndefined, - Type, -} from '../../../common/schemas'; +import { DeserializerOrUndefined, SerializerOrUndefined } from '../../../common/schemas'; +import { CreateEsBulkTypeSchema, IndexEsListItemSchema } from '../../schemas/elastic_query'; export interface CreateListItemsBulkOptions { deserializer: DeserializerOrUndefined; diff --git a/x-pack/plugins/lists/server/services/items/delete_list_item.ts b/x-pack/plugins/lists/server/services/items/delete_list_item.ts index f2e9949c82c3e..4fcb2656d2ba7 100644 --- a/x-pack/plugins/lists/server/services/items/delete_list_item.ts +++ b/x-pack/plugins/lists/server/services/items/delete_list_item.ts @@ -6,8 +6,9 @@ */ import { ElasticsearchClient } from 'kibana/server'; +import { Id } from '@kbn/securitysolution-io-ts-utils'; -import { Id, ListItemSchema } from '../../../common/schemas'; +import { ListItemSchema } from '../../../common/schemas'; import { getListItem } from '.'; diff --git a/x-pack/plugins/lists/server/services/items/delete_list_item_by_value.ts b/x-pack/plugins/lists/server/services/items/delete_list_item_by_value.ts index 1c7ac3afb3ee3..ccbe8d6fe7925 100644 --- a/x-pack/plugins/lists/server/services/items/delete_list_item_by_value.ts +++ b/x-pack/plugins/lists/server/services/items/delete_list_item_by_value.ts @@ -6,8 +6,9 @@ */ import { ElasticsearchClient } from 'kibana/server'; +import { Type } from '@kbn/securitysolution-io-ts-utils'; -import { ListItemArraySchema, Type } from '../../../common/schemas'; +import { ListItemArraySchema } from '../../../common/schemas'; import { getQueryFilterFromTypeValue } from '../utils'; import { getListItemByValues } from './get_list_item_by_values'; diff --git a/x-pack/plugins/lists/server/services/items/find_list_item.mock.ts b/x-pack/plugins/lists/server/services/items/find_list_item.mock.ts index 4bf62982b2a9f..c00da8ab2496b 100644 --- a/x-pack/plugins/lists/server/services/items/find_list_item.mock.ts +++ b/x-pack/plugins/lists/server/services/items/find_list_item.mock.ts @@ -9,8 +9,8 @@ import { Client } from 'elasticsearch'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { elasticsearchClientMock } from 'src/core/server/elasticsearch/client/mocks'; -import { getShardMock } from '../../../common/get_shard.mock'; import { LIST_ID, LIST_INDEX, LIST_ITEM_INDEX } from '../../../common/constants.mock'; +import { getShardMock } from '../../schemas/common/get_shard.mock'; import { FindListItemOptions } from './find_list_item'; diff --git a/x-pack/plugins/lists/server/services/items/find_list_item.test.ts b/x-pack/plugins/lists/server/services/items/find_list_item.test.ts index c76d1c505df0c..098c8d20ae5ce 100644 --- a/x-pack/plugins/lists/server/services/items/find_list_item.test.ts +++ b/x-pack/plugins/lists/server/services/items/find_list_item.test.ts @@ -8,9 +8,9 @@ // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { elasticsearchClientMock } from 'src/core/server/elasticsearch/client/mocks'; -import { getShardMock } from '../../../common/get_shard.mock'; import { getFoundListItemSchemaMock } from '../../../common/schemas/response/found_list_item_schema.mock'; -import { getEmptySearchListMock } from '../../../common/schemas/elastic_response/search_es_list_schema.mock'; +import { getShardMock } from '../../schemas/common/get_shard.mock'; +import { getEmptySearchListMock } from '../../schemas/elastic_response/search_es_list_schema.mock'; import { getFindListItemOptionsMock } from './find_list_item.mock'; import { findListItem } from './find_list_item'; diff --git a/x-pack/plugins/lists/server/services/items/find_list_item.ts b/x-pack/plugins/lists/server/services/items/find_list_item.ts index 3e37ccb0cfb1f..e1586daf1cbb1 100644 --- a/x-pack/plugins/lists/server/services/items/find_list_item.ts +++ b/x-pack/plugins/lists/server/services/items/find_list_item.ts @@ -13,10 +13,10 @@ import { ListId, Page, PerPage, - SearchEsListItemSchema, SortFieldOrUndefined, SortOrderOrUndefined, } from '../../../common/schemas'; +import { SearchEsListItemSchema } from '../../schemas/elastic_response'; import { getList } from '../lists'; import { encodeCursor, diff --git a/x-pack/plugins/lists/server/services/items/get_list_item.test.ts b/x-pack/plugins/lists/server/services/items/get_list_item.test.ts index f92031cae02ca..f6ec534a39ebb 100644 --- a/x-pack/plugins/lists/server/services/items/get_list_item.test.ts +++ b/x-pack/plugins/lists/server/services/items/get_list_item.test.ts @@ -8,7 +8,6 @@ // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { elasticsearchClientMock } from 'src/core/server/elasticsearch/client/mocks'; -import { getSearchListItemMock } from '../../../common/schemas/elastic_response/search_es_list_item_schema.mock'; import { getListItemResponseMock } from '../../../common/schemas/response/list_item_schema.mock'; import { DATE_NOW, @@ -18,6 +17,7 @@ import { TIE_BREAKER, USER, } from '../../../common/constants.mock'; +import { getSearchListItemMock } from '../../schemas/elastic_response/search_es_list_item_schema.mock'; import { getListItem } from './get_list_item'; diff --git a/x-pack/plugins/lists/server/services/items/get_list_item.ts b/x-pack/plugins/lists/server/services/items/get_list_item.ts index 519ebaedfddbc..aca8deac24817 100644 --- a/x-pack/plugins/lists/server/services/items/get_list_item.ts +++ b/x-pack/plugins/lists/server/services/items/get_list_item.ts @@ -6,10 +6,12 @@ */ import { ElasticsearchClient } from 'kibana/server'; +import { Id } from '@kbn/securitysolution-io-ts-utils'; -import { Id, ListItemSchema, SearchEsListItemSchema } from '../../../common/schemas'; +import { ListItemSchema } from '../../../common/schemas'; import { transformElasticToListItem } from '../utils'; import { findSourceType } from '../utils/find_source_type'; +import { SearchEsListItemSchema } from '../../schemas/elastic_response'; interface GetListItemOptions { id: Id; diff --git a/x-pack/plugins/lists/server/services/items/get_list_item_by_value.ts b/x-pack/plugins/lists/server/services/items/get_list_item_by_value.ts index 7d3fe81babe59..083dca2ea9410 100644 --- a/x-pack/plugins/lists/server/services/items/get_list_item_by_value.ts +++ b/x-pack/plugins/lists/server/services/items/get_list_item_by_value.ts @@ -6,8 +6,9 @@ */ import { ElasticsearchClient } from 'kibana/server'; +import { Type } from '@kbn/securitysolution-io-ts-utils'; -import { ListItemArraySchema, Type } from '../../../common/schemas'; +import { ListItemArraySchema } from '../../../common/schemas'; import { getListItemByValues } from '.'; diff --git a/x-pack/plugins/lists/server/services/items/get_list_item_by_values.test.ts b/x-pack/plugins/lists/server/services/items/get_list_item_by_values.test.ts index aa22049ce6fe4..f3c72794b6dd9 100644 --- a/x-pack/plugins/lists/server/services/items/get_list_item_by_values.test.ts +++ b/x-pack/plugins/lists/server/services/items/get_list_item_by_values.test.ts @@ -8,7 +8,6 @@ // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { elasticsearchClientMock } from 'src/core/server/elasticsearch/client/mocks'; -import { getSearchListItemMock } from '../../../common/schemas/elastic_response/search_es_list_item_schema.mock'; import { DATE_NOW, LIST_ID, @@ -21,6 +20,7 @@ import { VALUE, VALUE_2, } from '../../../common/constants.mock'; +import { getSearchListItemMock } from '../../schemas/elastic_response/search_es_list_item_schema.mock'; import { getListItemByValues } from './get_list_item_by_values'; diff --git a/x-pack/plugins/lists/server/services/items/get_list_item_by_values.ts b/x-pack/plugins/lists/server/services/items/get_list_item_by_values.ts index c00ee2b13426a..5a4d55172af23 100644 --- a/x-pack/plugins/lists/server/services/items/get_list_item_by_values.ts +++ b/x-pack/plugins/lists/server/services/items/get_list_item_by_values.ts @@ -6,13 +6,15 @@ */ import { ElasticsearchClient } from 'kibana/server'; +import { Type } from '@kbn/securitysolution-io-ts-utils'; -import { ListItemArraySchema, SearchEsListItemSchema, Type } from '../../../common/schemas'; +import { ListItemArraySchema } from '../../../common/schemas'; import { TransformElasticToListItemOptions, getQueryFilterFromTypeValue, transformElasticToListItem, } from '../utils'; +import { SearchEsListItemSchema } from '../../schemas/elastic_response'; export interface GetListItemByValuesOptions { listId: string; diff --git a/x-pack/plugins/lists/server/services/items/index.ts b/x-pack/plugins/lists/server/services/items/index.ts index 01df8b4a7d9f1..346c64ba68fc0 100644 --- a/x-pack/plugins/lists/server/services/items/index.ts +++ b/x-pack/plugins/lists/server/services/items/index.ts @@ -21,3 +21,5 @@ export * from './update_list_item'; export * from './write_lines_to_bulk_list_items'; export * from './write_list_items_to_stream'; export * from './search_list_item_by_values'; +export * from './update_list_item'; +export * from './write_list_items_to_stream'; diff --git a/x-pack/plugins/lists/server/services/items/search_list_item_by_values.test.ts b/x-pack/plugins/lists/server/services/items/search_list_item_by_values.test.ts index 0d084c50b5745..817432495926b 100644 --- a/x-pack/plugins/lists/server/services/items/search_list_item_by_values.test.ts +++ b/x-pack/plugins/lists/server/services/items/search_list_item_by_values.test.ts @@ -9,8 +9,8 @@ import { elasticsearchClientMock } from 'src/core/server/elasticsearch/client/mocks'; import { SearchListItemArraySchema } from '../../../common/schemas'; -import { getSearchListItemMock } from '../../../common/schemas/elastic_response/search_es_list_item_schema.mock'; import { LIST_ID, LIST_ITEM_INDEX, TYPE, VALUE, VALUE_2 } from '../../../common/constants.mock'; +import { getSearchListItemMock } from '../../schemas/elastic_response/search_es_list_item_schema.mock'; import { searchListItemByValues } from './search_list_item_by_values'; diff --git a/x-pack/plugins/lists/server/services/items/search_list_item_by_values.ts b/x-pack/plugins/lists/server/services/items/search_list_item_by_values.ts index 4f8808d06e425..d6d8f66770653 100644 --- a/x-pack/plugins/lists/server/services/items/search_list_item_by_values.ts +++ b/x-pack/plugins/lists/server/services/items/search_list_item_by_values.ts @@ -6,13 +6,15 @@ */ import { ElasticsearchClient } from 'kibana/server'; +import { Type } from '@kbn/securitysolution-io-ts-utils'; -import { SearchEsListItemSchema, SearchListItemArraySchema, Type } from '../../../common/schemas'; +import { SearchListItemArraySchema } from '../../../common/schemas'; import { TransformElasticMSearchToListItemOptions, getQueryFilterFromTypeValue, transformElasticNamedSearchToListItem, } from '../utils'; +import { SearchEsListItemSchema } from '../../schemas/elastic_response'; export interface SearchListItemByValuesOptions { listId: string; diff --git a/x-pack/plugins/lists/server/services/items/update_list_item.ts b/x-pack/plugins/lists/server/services/items/update_list_item.ts index 89c7e77707d8f..91c38dd3f331c 100644 --- a/x-pack/plugins/lists/server/services/items/update_list_item.ts +++ b/x-pack/plugins/lists/server/services/items/update_list_item.ts @@ -6,17 +6,13 @@ */ import { ElasticsearchClient } from 'kibana/server'; +import { Id, MetaOrUndefined } from '@kbn/securitysolution-io-ts-utils'; -import { - Id, - ListItemSchema, - MetaOrUndefined, - UpdateEsListItemSchema, - _VersionOrUndefined, -} from '../../../common/schemas'; +import { ListItemSchema, _VersionOrUndefined } from '../../../common/schemas'; import { transformListItemToElasticQuery } from '../utils'; import { decodeVersion } from '../utils/decode_version'; import { encodeHitVersion } from '../utils/encode_hit_version'; +import { UpdateEsListItemSchema } from '../../schemas/elastic_query'; import { getListItem } from './get_list_item'; diff --git a/x-pack/plugins/lists/server/services/items/write_lines_to_bulk_list_items.ts b/x-pack/plugins/lists/server/services/items/write_lines_to_bulk_list_items.ts index 8450890cfa355..8a05e4667a290 100644 --- a/x-pack/plugins/lists/server/services/items/write_lines_to_bulk_list_items.ts +++ b/x-pack/plugins/lists/server/services/items/write_lines_to_bulk_list_items.ts @@ -8,15 +8,14 @@ import { Readable } from 'stream'; import { ElasticsearchClient } from 'kibana/server'; +import { MetaOrUndefined, Type } from '@kbn/securitysolution-io-ts-utils'; import { createListIfItDoesNotExist } from '../lists/create_list_if_it_does_not_exist'; import { DeserializerOrUndefined, ListIdOrUndefined, ListSchema, - MetaOrUndefined, SerializerOrUndefined, - Type, Version, } from '../../../common/schemas'; import { ConfigType } from '../../config'; diff --git a/x-pack/plugins/lists/server/services/items/write_list_items_to_stream.test.ts b/x-pack/plugins/lists/server/services/items/write_list_items_to_stream.test.ts index ee4f3af9cdd5c..e6d0b21ab0517 100644 --- a/x-pack/plugins/lists/server/services/items/write_list_items_to_stream.test.ts +++ b/x-pack/plugins/lists/server/services/items/write_list_items_to_stream.test.ts @@ -8,8 +8,8 @@ // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { elasticsearchClientMock } from 'src/core/server/elasticsearch/client/mocks'; -import { getSearchListItemMock } from '../../../common/schemas/elastic_response/search_es_list_item_schema.mock'; import { LIST_ID, LIST_ITEM_INDEX } from '../../../common/constants.mock'; +import { getSearchListItemMock } from '../../schemas/elastic_response/search_es_list_item_schema.mock'; import { getExportListItemsToStreamOptionsMock, diff --git a/x-pack/plugins/lists/server/services/items/write_list_items_to_stream.ts b/x-pack/plugins/lists/server/services/items/write_list_items_to_stream.ts index 3679680ad79bd..a70db9aba3880 100644 --- a/x-pack/plugins/lists/server/services/items/write_list_items_to_stream.ts +++ b/x-pack/plugins/lists/server/services/items/write_list_items_to_stream.ts @@ -10,9 +10,9 @@ import { PassThrough } from 'stream'; import type { estypes } from '@elastic/elasticsearch'; import { ElasticsearchClient } from 'kibana/server'; -import { SearchEsListItemSchema } from '../../../common/schemas'; import { ErrorWithStatusCode } from '../../error_with_status_code'; import { findSourceValue } from '../utils/find_source_value'; +import { SearchEsListItemSchema } from '../../schemas/elastic_response'; /** * How many results to page through from the network at a time diff --git a/x-pack/plugins/lists/server/services/items/write_list_items_to_streams.mock.ts b/x-pack/plugins/lists/server/services/items/write_list_items_to_streams.mock.ts index 3de8fdb0c9df6..8961d7527ad0b 100644 --- a/x-pack/plugins/lists/server/services/items/write_list_items_to_streams.mock.ts +++ b/x-pack/plugins/lists/server/services/items/write_list_items_to_streams.mock.ts @@ -10,7 +10,6 @@ import { Stream } from 'stream'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { elasticsearchClientMock } from 'src/core/server/elasticsearch/client/mocks'; -import { getSearchListItemMock } from '../../../common/schemas/elastic_response/search_es_list_item_schema.mock'; import { ExportListItemsToStreamOptions, GetResponseOptions, @@ -18,6 +17,7 @@ import { WriteResponseHitsToStreamOptions, } from '../items'; import { LIST_ID, LIST_ITEM_INDEX } from '../../../common/constants.mock'; +import { getSearchListItemMock } from '../../schemas/elastic_response/search_es_list_item_schema.mock'; export const getExportListItemsToStreamOptionsMock = (): ExportListItemsToStreamOptions => ({ esClient: elasticsearchClientMock.createScopedClusterClient().asCurrentUser, diff --git a/x-pack/plugins/lists/server/services/lists/create_list.test.ts b/x-pack/plugins/lists/server/services/lists/create_list.test.ts index e6213a1c6eabe..600d148d77b95 100644 --- a/x-pack/plugins/lists/server/services/lists/create_list.test.ts +++ b/x-pack/plugins/lists/server/services/lists/create_list.test.ts @@ -10,8 +10,8 @@ import { elasticsearchClientMock } from 'src/core/server/elasticsearch/client/mo import { ListSchema } from '../../../common/schemas'; import { getListResponseMock } from '../../../common/schemas/response/list_schema.mock'; -import { getIndexESListMock } from '../../../common/schemas/elastic_query/index_es_list_schema.mock'; import { LIST_ID, LIST_INDEX } from '../../../common/constants.mock'; +import { getIndexESListMock } from '../../schemas/elastic_query/index_es_list_schema.mock'; import { CreateListOptions, createList } from './create_list'; import { getCreateListOptionsMock } from './create_list.mock'; diff --git a/x-pack/plugins/lists/server/services/lists/create_list.ts b/x-pack/plugins/lists/server/services/lists/create_list.ts index baed699dc992f..6b0954f3fcc9d 100644 --- a/x-pack/plugins/lists/server/services/lists/create_list.ts +++ b/x-pack/plugins/lists/server/services/lists/create_list.ts @@ -7,21 +7,23 @@ import uuid from 'uuid'; import { ElasticsearchClient } from 'kibana/server'; +import { + Description, + IdOrUndefined, + MetaOrUndefined, + Name, + Type, +} from '@kbn/securitysolution-io-ts-utils'; import { encodeHitVersion } from '../utils/encode_hit_version'; import { - Description, DeserializerOrUndefined, - IdOrUndefined, Immutable, - IndexEsListSchema, ListSchema, - MetaOrUndefined, - Name, SerializerOrUndefined, - Type, Version, } from '../../../common/schemas'; +import { IndexEsListSchema } from '../../schemas/elastic_query'; export interface CreateListOptions { id: IdOrUndefined; diff --git a/x-pack/plugins/lists/server/services/lists/create_list_if_it_does_not_exist.ts b/x-pack/plugins/lists/server/services/lists/create_list_if_it_does_not_exist.ts index 5325d951626c7..483810a9b1c43 100644 --- a/x-pack/plugins/lists/server/services/lists/create_list_if_it_does_not_exist.ts +++ b/x-pack/plugins/lists/server/services/lists/create_list_if_it_does_not_exist.ts @@ -6,17 +6,13 @@ */ import { ElasticsearchClient } from 'kibana/server'; +import { Description, Id, MetaOrUndefined, Name, Type } from '@kbn/securitysolution-io-ts-utils'; import { - Description, DeserializerOrUndefined, - Id, Immutable, ListSchema, - MetaOrUndefined, - Name, SerializerOrUndefined, - Type, Version, } from '../../../common/schemas'; diff --git a/x-pack/plugins/lists/server/services/lists/delete_list.ts b/x-pack/plugins/lists/server/services/lists/delete_list.ts index 4fe200bff436f..0e140544fa47d 100644 --- a/x-pack/plugins/lists/server/services/lists/delete_list.ts +++ b/x-pack/plugins/lists/server/services/lists/delete_list.ts @@ -6,8 +6,9 @@ */ import { ElasticsearchClient } from 'kibana/server'; +import { Id } from '@kbn/securitysolution-io-ts-utils'; -import { Id, ListSchema } from '../../../common/schemas'; +import { ListSchema } from '../../../common/schemas'; import { getList } from './get_list'; diff --git a/x-pack/plugins/lists/server/services/lists/find_list.ts b/x-pack/plugins/lists/server/services/lists/find_list.ts index 9c61d36dc0cd3..92d7262c19543 100644 --- a/x-pack/plugins/lists/server/services/lists/find_list.ts +++ b/x-pack/plugins/lists/server/services/lists/find_list.ts @@ -12,10 +12,10 @@ import { FoundListSchema, Page, PerPage, - SearchEsListSchema, SortFieldOrUndefined, SortOrderOrUndefined, } from '../../../common/schemas'; +import { SearchEsListSchema } from '../../schemas/elastic_response'; import { encodeCursor, getQueryFilter, diff --git a/x-pack/plugins/lists/server/services/lists/get_list.test.ts b/x-pack/plugins/lists/server/services/lists/get_list.test.ts index 930a52266ba41..f599e5ef8b6c5 100644 --- a/x-pack/plugins/lists/server/services/lists/get_list.test.ts +++ b/x-pack/plugins/lists/server/services/lists/get_list.test.ts @@ -8,9 +8,9 @@ // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { elasticsearchClientMock } from 'src/core/server/elasticsearch/client/mocks'; -import { getSearchListMock } from '../../../common/schemas/elastic_response/search_es_list_schema.mock'; import { getListResponseMock } from '../../../common/schemas/response/list_schema.mock'; import { LIST_ID, LIST_INDEX } from '../../../common/constants.mock'; +import { getSearchListMock } from '../../schemas/elastic_response/search_es_list_schema.mock'; import { getList } from './get_list'; diff --git a/x-pack/plugins/lists/server/services/lists/get_list.ts b/x-pack/plugins/lists/server/services/lists/get_list.ts index 6f18d143df00b..a248f81449bfc 100644 --- a/x-pack/plugins/lists/server/services/lists/get_list.ts +++ b/x-pack/plugins/lists/server/services/lists/get_list.ts @@ -6,9 +6,11 @@ */ import { ElasticsearchClient } from 'kibana/server'; +import { Id } from '@kbn/securitysolution-io-ts-utils'; -import { Id, ListSchema, SearchEsListSchema } from '../../../common/schemas'; +import { ListSchema } from '../../../common/schemas'; import { transformElasticToList } from '../utils/transform_elastic_to_list'; +import { SearchEsListSchema } from '../../schemas/elastic_response'; interface GetListOptions { id: Id; diff --git a/x-pack/plugins/lists/server/services/lists/index.ts b/x-pack/plugins/lists/server/services/lists/index.ts index f19103a558c1c..c12868816d91c 100644 --- a/x-pack/plugins/lists/server/services/lists/index.ts +++ b/x-pack/plugins/lists/server/services/lists/index.ts @@ -8,7 +8,9 @@ export * from './create_list'; export * from './delete_list'; export * from './find_list'; -export * from './get_list'; +export * from './get_list_index'; export * from './get_list_template'; +export * from './get_list'; +export * from './list_client'; +export * from './types'; export * from './update_list'; -export * from './get_list_index'; diff --git a/x-pack/plugins/lists/server/services/lists/list_client_types.ts b/x-pack/plugins/lists/server/services/lists/list_client_types.ts index 1efcd2af5420e..b684511ff679c 100644 --- a/x-pack/plugins/lists/server/services/lists/list_client_types.ts +++ b/x-pack/plugins/lists/server/services/lists/list_client_types.ts @@ -8,26 +8,28 @@ import { PassThrough, Readable } from 'stream'; import { ElasticsearchClient } from 'kibana/server'; - import { Description, DescriptionOrUndefined, - DeserializerOrUndefined, - Filter, Id, IdOrUndefined, - Immutable, - ListId, - ListIdOrUndefined, MetaOrUndefined, Name, NameOrUndefined, + Type, +} from '@kbn/securitysolution-io-ts-utils'; + +import { + DeserializerOrUndefined, + Filter, + Immutable, + ListId, + ListIdOrUndefined, Page, PerPage, SerializerOrUndefined, SortFieldOrUndefined, SortOrderOrUndefined, - Type, Version, VersionOrUndefined, _VersionOrUndefined, diff --git a/x-pack/plugins/lists/server/services/lists/update_list.ts b/x-pack/plugins/lists/server/services/lists/update_list.ts index f98e40b04b6d7..4917fec7397ea 100644 --- a/x-pack/plugins/lists/server/services/lists/update_list.ts +++ b/x-pack/plugins/lists/server/services/lists/update_list.ts @@ -6,19 +6,17 @@ */ import { ElasticsearchClient } from 'kibana/server'; - -import { decodeVersion } from '../utils/decode_version'; -import { encodeHitVersion } from '../utils/encode_hit_version'; import { DescriptionOrUndefined, Id, - ListSchema, MetaOrUndefined, NameOrUndefined, - UpdateEsListSchema, - VersionOrUndefined, - _VersionOrUndefined, -} from '../../../common/schemas'; +} from '@kbn/securitysolution-io-ts-utils'; + +import { decodeVersion } from '../utils/decode_version'; +import { encodeHitVersion } from '../utils/encode_hit_version'; +import { ListSchema, VersionOrUndefined, _VersionOrUndefined } from '../../../common/schemas'; +import { UpdateEsListSchema } from '../../schemas/elastic_query'; import { getList } from '.'; diff --git a/x-pack/plugins/lists/server/services/utils/encode_decode_cursor.ts b/x-pack/plugins/lists/server/services/utils/encode_decode_cursor.ts index 1692f76727fac..a1a349d5e38da 100644 --- a/x-pack/plugins/lists/server/services/utils/encode_decode_cursor.ts +++ b/x-pack/plugins/lists/server/services/utils/encode_decode_cursor.ts @@ -8,9 +8,9 @@ import * as t from 'io-ts'; import { fold } from 'fp-ts/lib/Either'; import { pipe } from 'fp-ts/lib/pipeable'; +import { exactCheck } from '@kbn/securitysolution-io-ts-utils'; import { CursorOrUndefined, SortFieldOrUndefined } from '../../../common/schemas'; -import { exactCheck } from '../../../common/shared_imports'; /** * Used only internally for this ad-hoc opaque cursor structure to keep track of the diff --git a/x-pack/plugins/lists/server/services/utils/find_source_type.test.ts b/x-pack/plugins/lists/server/services/utils/find_source_type.test.ts index 5cba4928420cb..e408f7d33b548 100644 --- a/x-pack/plugins/lists/server/services/utils/find_source_type.test.ts +++ b/x-pack/plugins/lists/server/services/utils/find_source_type.test.ts @@ -5,8 +5,10 @@ * 2.0. */ -import { getSearchEsListItemMock } from '../../../common/schemas/elastic_response/search_es_list_item_schema.mock'; -import { SearchEsListItemSchema, Type } from '../../../common/schemas'; +import { Type } from '@kbn/securitysolution-io-ts-utils'; + +import { getSearchEsListItemMock } from '../../schemas/elastic_response/search_es_list_item_schema.mock'; +import { SearchEsListItemSchema } from '../../schemas/elastic_response'; import { findSourceType } from './find_source_type'; diff --git a/x-pack/plugins/lists/server/services/utils/find_source_type.ts b/x-pack/plugins/lists/server/services/utils/find_source_type.ts index 904b2982ea757..00a6985b2c751 100644 --- a/x-pack/plugins/lists/server/services/utils/find_source_type.ts +++ b/x-pack/plugins/lists/server/services/utils/find_source_type.ts @@ -5,7 +5,9 @@ * 2.0. */ -import { SearchEsListItemSchema, Type, type } from '../../../common/schemas'; +import { Type, type } from '@kbn/securitysolution-io-ts-utils'; + +import { SearchEsListItemSchema } from '../../schemas/elastic_response'; export const findSourceType = ( listItem: SearchEsListItemSchema, diff --git a/x-pack/plugins/lists/server/services/utils/find_source_value.test.ts b/x-pack/plugins/lists/server/services/utils/find_source_value.test.ts index f071be81dbae1..59226a2643140 100644 --- a/x-pack/plugins/lists/server/services/utils/find_source_value.test.ts +++ b/x-pack/plugins/lists/server/services/utils/find_source_value.test.ts @@ -5,8 +5,8 @@ * 2.0. */ -import { SearchEsListItemSchema } from '../../../common/schemas'; -import { getSearchEsListItemsAsAllUndefinedMock } from '../../../common/schemas/elastic_response/search_es_list_item_schema.mock'; +import { SearchEsListItemSchema } from '../../schemas/elastic_response'; +import { getSearchEsListItemsAsAllUndefinedMock } from '../../schemas/elastic_response/search_es_list_item_schema.mock'; import { DEFAULT_DATE_RANGE, diff --git a/x-pack/plugins/lists/server/services/utils/find_source_value.ts b/x-pack/plugins/lists/server/services/utils/find_source_value.ts index 4f882d590780f..c12f4bdfcdb9f 100644 --- a/x-pack/plugins/lists/server/services/utils/find_source_value.ts +++ b/x-pack/plugins/lists/server/services/utils/find_source_value.ts @@ -6,14 +6,11 @@ */ import Mustache from 'mustache'; +import { type } from '@kbn/securitysolution-io-ts-utils'; -import { - DeserializerOrUndefined, - SearchEsListItemSchema, - esDataTypeGeoPointRange, - esDataTypeRange, - type, -} from '../../../common/schemas'; +import { DeserializerOrUndefined } from '../../../common/schemas'; +import { SearchEsListItemSchema } from '../../schemas/elastic_response'; +import { esDataTypeGeoPointRange, esDataTypeRange } from '../../schemas/common/schemas'; export const DEFAULT_GEO_POINT = '{{{lat}}},{{{lon}}}'; export const DEFAULT_DATE_RANGE = '{{{gte}}},{{{lte}}}'; diff --git a/x-pack/plugins/lists/server/services/utils/get_query_filter_from_type_value.ts b/x-pack/plugins/lists/server/services/utils/get_query_filter_from_type_value.ts index 46524565770c6..0ece97b21d5b7 100644 --- a/x-pack/plugins/lists/server/services/utils/get_query_filter_from_type_value.ts +++ b/x-pack/plugins/lists/server/services/utils/get_query_filter_from_type_value.ts @@ -6,8 +6,7 @@ */ import { isEmpty, isObject } from 'lodash/fp'; - -import { Type } from '../../../common/schemas'; +import { Type } from '@kbn/securitysolution-io-ts-utils'; export type QueryFilterType = [ { term: Record }, diff --git a/x-pack/plugins/lists/server/services/utils/index.ts b/x-pack/plugins/lists/server/services/utils/index.ts index 78beb5cc8e37d..0cd2720bd199b 100644 --- a/x-pack/plugins/lists/server/services/utils/index.ts +++ b/x-pack/plugins/lists/server/services/utils/index.ts @@ -6,17 +6,20 @@ */ export * from './calculate_scroll_math'; +export * from './decode_version'; export * from './encode_decode_cursor'; +export * from './encode_hit_version'; +export * from './escape_query'; export * from './find_source_type'; export * from './find_source_value'; -export * from './get_query_filter'; export * from './get_query_filter_from_type_value'; +export * from './get_query_filter'; export * from './get_search_after_scroll'; export * from './get_search_after_with_tie_breaker'; export * from './get_sort_with_tie_breaker'; export * from './get_source_with_tie_breaker'; export * from './scroll_to_start_page'; export * from './transform_elastic_named_search_to_list_item'; -export * from './transform_elastic_to_list'; export * from './transform_elastic_to_list_item'; +export * from './transform_elastic_to_list'; export * from './transform_list_item_to_elastic_query'; diff --git a/x-pack/plugins/lists/server/services/utils/transform_elastic_named_search_to_list_item.test.ts b/x-pack/plugins/lists/server/services/utils/transform_elastic_named_search_to_list_item.test.ts index df9e6c57cecc8..1846f1b7909fb 100644 --- a/x-pack/plugins/lists/server/services/utils/transform_elastic_named_search_to_list_item.test.ts +++ b/x-pack/plugins/lists/server/services/utils/transform_elastic_named_search_to_list_item.test.ts @@ -7,11 +7,11 @@ import { getSearchListItemResponseMock } from '../../../common/schemas/response/search_list_item_schema.mock'; import { LIST_INDEX, LIST_ITEM_ID, TYPE, VALUE } from '../../../common/constants.mock'; +import { SearchListItemArraySchema } from '../../../common/schemas'; import { getSearchEsListItemMock, getSearchListItemMock, -} from '../../../common/schemas/elastic_response/search_es_list_item_schema.mock'; -import { SearchListItemArraySchema } from '../../../common/schemas'; +} from '../../schemas/elastic_response/search_es_list_item_schema.mock'; import { transformElasticNamedSearchToListItem } from './transform_elastic_named_search_to_list_item'; diff --git a/x-pack/plugins/lists/server/services/utils/transform_elastic_named_search_to_list_item.ts b/x-pack/plugins/lists/server/services/utils/transform_elastic_named_search_to_list_item.ts index 4f0f8fe49a9d0..f02ae17fa0293 100644 --- a/x-pack/plugins/lists/server/services/utils/transform_elastic_named_search_to_list_item.ts +++ b/x-pack/plugins/lists/server/services/utils/transform_elastic_named_search_to_list_item.ts @@ -6,8 +6,10 @@ */ import type { estypes } from '@elastic/elasticsearch'; +import { Type } from '@kbn/securitysolution-io-ts-utils'; -import { SearchEsListItemSchema, SearchListItemArraySchema, Type } from '../../../common/schemas'; +import { SearchListItemArraySchema } from '../../../common/schemas'; +import { SearchEsListItemSchema } from '../../schemas/elastic_response'; import { transformElasticHitsToListItem } from './transform_elastic_to_list_item'; diff --git a/x-pack/plugins/lists/server/services/utils/transform_elastic_to_list.ts b/x-pack/plugins/lists/server/services/utils/transform_elastic_to_list.ts index 4ed08f70219af..8d8c076f6e219 100644 --- a/x-pack/plugins/lists/server/services/utils/transform_elastic_to_list.ts +++ b/x-pack/plugins/lists/server/services/utils/transform_elastic_to_list.ts @@ -7,7 +7,8 @@ import type { estypes } from '@elastic/elasticsearch'; -import { ListArraySchema, SearchEsListSchema } from '../../../common/schemas'; +import { ListArraySchema } from '../../../common/schemas'; +import { SearchEsListSchema } from '../../schemas/elastic_response'; import { encodeHitVersion } from './encode_hit_version'; diff --git a/x-pack/plugins/lists/server/services/utils/transform_elastic_to_list_item.test.ts b/x-pack/plugins/lists/server/services/utils/transform_elastic_to_list_item.test.ts index 5ca9a26844207..3629881f61d5a 100644 --- a/x-pack/plugins/lists/server/services/utils/transform_elastic_to_list_item.test.ts +++ b/x-pack/plugins/lists/server/services/utils/transform_elastic_to_list_item.test.ts @@ -5,9 +5,9 @@ * 2.0. */ -import { getSearchListItemMock } from '../../../common/schemas/elastic_response/search_es_list_item_schema.mock'; import { getListItemResponseMock } from '../../../common/schemas/response/list_item_schema.mock'; import { ListItemArraySchema } from '../../../common/schemas'; +import { getSearchListItemMock } from '../../schemas/elastic_response/search_es_list_item_schema.mock'; import { transformElasticHitsToListItem, diff --git a/x-pack/plugins/lists/server/services/utils/transform_elastic_to_list_item.ts b/x-pack/plugins/lists/server/services/utils/transform_elastic_to_list_item.ts index 436987e71dd22..3e27bd24517e4 100644 --- a/x-pack/plugins/lists/server/services/utils/transform_elastic_to_list_item.ts +++ b/x-pack/plugins/lists/server/services/utils/transform_elastic_to_list_item.ts @@ -6,9 +6,11 @@ */ import type { estypes } from '@elastic/elasticsearch'; +import { Type } from '@kbn/securitysolution-io-ts-utils'; -import { ListItemArraySchema, SearchEsListItemSchema, Type } from '../../../common/schemas'; +import { ListItemArraySchema } from '../../../common/schemas'; import { ErrorWithStatusCode } from '../../error_with_status_code'; +import { SearchEsListItemSchema } from '../../schemas/elastic_response'; import { encodeHitVersion } from './encode_hit_version'; import { findSourceValue } from './find_source_value'; diff --git a/x-pack/plugins/lists/server/services/utils/transform_list_item_to_elastic_query.test.ts b/x-pack/plugins/lists/server/services/utils/transform_list_item_to_elastic_query.test.ts index fae857484dcf5..f697d93f28561 100644 --- a/x-pack/plugins/lists/server/services/utils/transform_list_item_to_elastic_query.test.ts +++ b/x-pack/plugins/lists/server/services/utils/transform_list_item_to_elastic_query.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { EsDataTypeUnion } from '../../../common/schemas'; +import { EsDataTypeUnion } from '../../schemas/common/schemas'; import { DEFAULT_DATE_REGEX, diff --git a/x-pack/plugins/lists/server/services/utils/transform_list_item_to_elastic_query.ts b/x-pack/plugins/lists/server/services/utils/transform_list_item_to_elastic_query.ts index 957c5311bc8e2..32eb885871cb1 100644 --- a/x-pack/plugins/lists/server/services/utils/transform_list_item_to_elastic_query.ts +++ b/x-pack/plugins/lists/server/services/utils/transform_list_item_to_elastic_query.ts @@ -5,18 +5,19 @@ * 2.0. */ +import { Type } from '@kbn/securitysolution-io-ts-utils'; + +import { SerializerOrUndefined } from '../../../common/schemas'; import { EsDataTypeGeoPoint, EsDataTypeGeoShape, EsDataTypeRangeTerm, EsDataTypeSingle, EsDataTypeUnion, - SerializerOrUndefined, - Type, esDataTypeGeoShape, esDataTypeRangeTerm, esDataTypeSingle, -} from '../../../common/schemas'; +} from '../../schemas/common/schemas'; export const DEFAULT_DATE_REGEX = RegExp('(?.+),(?.+)|(?.+)'); export const DEFAULT_LTE_GTE_REGEX = RegExp('(?.+)-(?.+)|(?.+)'); diff --git a/x-pack/plugins/maps/common/descriptor_types/map_descriptor.ts b/x-pack/plugins/maps/common/descriptor_types/map_descriptor.ts index acb0d9329e065..0bb9c7cc6f02b 100644 --- a/x-pack/plugins/maps/common/descriptor_types/map_descriptor.ts +++ b/x-pack/plugins/maps/common/descriptor_types/map_descriptor.ts @@ -7,7 +7,9 @@ /* eslint-disable @typescript-eslint/consistent-type-definitions */ +import { ReactNode } from 'react'; import { GeoJsonProperties } from 'geojson'; +import { Geometry } from 'geojson'; import { Query } from '../../../../../src/plugins/data/common'; import { DRAW_TYPE, ES_GEO_FIELD_TYPE, ES_SPATIAL_RELATIONS } from '../constants'; @@ -41,10 +43,20 @@ export type Goto = { center?: MapCenterAndZoom; }; +export const GEOMETRY_FILTER_ACTION = 'GEOMETRY_FILTER_ACTION'; + +export type TooltipFeatureAction = { + label: string; + id: typeof GEOMETRY_FILTER_ACTION; + form: ReactNode; +}; + export type TooltipFeature = { id?: number | string; layerId: string; + geometry?: Geometry; mbProperties: GeoJsonProperties; + actions: TooltipFeatureAction[]; }; export type TooltipState = { diff --git a/x-pack/plugins/maps/public/actions/tooltip_actions.ts b/x-pack/plugins/maps/public/actions/tooltip_actions.ts index 365a906739991..c1b5f8190a73a 100644 --- a/x-pack/plugins/maps/public/actions/tooltip_actions.ts +++ b/x-pack/plugins/maps/public/actions/tooltip_actions.ts @@ -6,7 +6,6 @@ */ import _ from 'lodash'; -import uuid from 'uuid/v4'; import { Dispatch } from 'redux'; import { Feature } from 'geojson'; import { getOpenTooltips } from '../selectors/map_selectors'; @@ -36,11 +35,7 @@ export function openOnClickTooltip(tooltipState: TooltipState) { ); }); - openTooltips.push({ - ...tooltipState, - isLocked: true, - id: uuid(), - }); + openTooltips.push(tooltipState); dispatch({ type: SET_OPEN_TOOLTIPS, @@ -63,13 +58,7 @@ export function closeOnHoverTooltip() { export function openOnHoverTooltip(tooltipState: TooltipState) { return { type: SET_OPEN_TOOLTIPS, - openTooltips: [ - { - ...tooltipState, - isLocked: false, - id: uuid(), - }, - ], + openTooltips: [tooltipState], }; } diff --git a/x-pack/plugins/maps/public/classes/layers/layer.tsx b/x-pack/plugins/maps/public/classes/layers/layer.tsx index dee1a26efef42..4167ed4775219 100644 --- a/x-pack/plugins/maps/public/classes/layers/layer.tsx +++ b/x-pack/plugins/maps/public/classes/layers/layer.tsx @@ -77,7 +77,6 @@ export interface ILayer { getMbLayerIds(): string[]; ownsMbLayerId(mbLayerId: string): boolean; ownsMbSourceId(mbSourceId: string): boolean; - canShowTooltip(): boolean; syncLayerWithMB(mbMap: MbMap): void; getLayerTypeIconName(): string; isInitialDataLoadComplete(): boolean; @@ -452,10 +451,6 @@ export class AbstractLayer implements ILayer { throw new Error('Should implement AbstractLayer#ownsMbSourceId'); } - canShowTooltip() { - return false; - } - syncLayerWithMB(mbMap: MbMap) { throw new Error('Should implement AbstractLayer#syncLayerWithMB'); } diff --git a/x-pack/plugins/maps/public/classes/layers/vector_layer/vector_layer.tsx b/x-pack/plugins/maps/public/classes/layers/vector_layer/vector_layer.tsx index 104d0b56578d1..a1474237f6c9c 100644 --- a/x-pack/plugins/maps/public/classes/layers/vector_layer/vector_layer.tsx +++ b/x-pack/plugins/maps/public/classes/layers/vector_layer/vector_layer.tsx @@ -88,6 +88,7 @@ export interface IVectorLayer extends ILayer { getFeatureById(id: string | number): Feature | null; getPropertiesForTooltip(properties: GeoJsonProperties): Promise; hasJoins(): boolean; + canShowTooltip(): boolean; } export class VectorLayer extends AbstractLayer implements IVectorLayer { @@ -781,7 +782,7 @@ export class VectorLayer extends AbstractLayer implements IVectorLayer { } const filterExpr = getPointFilterExpression(this.hasJoins()); - if (filterExpr !== mbMap.getFilter(pointLayerId)) { + if (!_.isEqual(filterExpr, mbMap.getFilter(pointLayerId))) { mbMap.setFilter(pointLayerId, filterExpr); mbMap.setFilter(textLayerId, filterExpr); } @@ -817,7 +818,7 @@ export class VectorLayer extends AbstractLayer implements IVectorLayer { } const filterExpr = getPointFilterExpression(this.hasJoins()); - if (filterExpr !== mbMap.getFilter(symbolLayerId)) { + if (!_.isEqual(filterExpr, mbMap.getFilter(symbolLayerId))) { mbMap.setFilter(symbolLayerId, filterExpr); } @@ -899,14 +900,14 @@ export class VectorLayer extends AbstractLayer implements IVectorLayer { this.syncVisibilityWithMb(mbMap, fillLayerId); mbMap.setLayerZoomRange(fillLayerId, this.getMinZoom(), this.getMaxZoom()); const fillFilterExpr = getFillFilterExpression(hasJoins); - if (fillFilterExpr !== mbMap.getFilter(fillLayerId)) { + if (!_.isEqual(fillFilterExpr, mbMap.getFilter(fillLayerId))) { mbMap.setFilter(fillLayerId, fillFilterExpr); } this.syncVisibilityWithMb(mbMap, lineLayerId); mbMap.setLayerZoomRange(lineLayerId, this.getMinZoom(), this.getMaxZoom()); const lineFilterExpr = getLineFilterExpression(hasJoins); - if (lineFilterExpr !== mbMap.getFilter(lineLayerId)) { + if (!_.isEqual(lineFilterExpr, mbMap.getFilter(lineLayerId))) { mbMap.setFilter(lineLayerId, lineFilterExpr); } @@ -930,7 +931,7 @@ export class VectorLayer extends AbstractLayer implements IVectorLayer { } const filterExpr = getCentroidFilterExpression(this.hasJoins()); - if (filterExpr !== mbMap.getFilter(centroidLayerId)) { + if (!_.isEqual(filterExpr, mbMap.getFilter(centroidLayerId))) { mbMap.setFilter(centroidLayerId, filterExpr); } @@ -1033,10 +1034,7 @@ export class VectorLayer extends AbstractLayer implements IVectorLayer { } canShowTooltip() { - return ( - this.isVisible() && - (this.getSource().canFormatFeatureProperties() || this.getJoins().length > 0) - ); + return this.getSource().hasTooltipProperties() || this.getJoins().length > 0; } getFeatureById(id: string | number) { diff --git a/x-pack/plugins/maps/public/classes/sources/ems_file_source/ems_file_source.tsx b/x-pack/plugins/maps/public/classes/sources/ems_file_source/ems_file_source.tsx index a61ae85c89ac6..209dc43f504d1 100644 --- a/x-pack/plugins/maps/public/classes/sources/ems_file_source/ems_file_source.tsx +++ b/x-pack/plugins/maps/public/classes/sources/ems_file_source/ems_file_source.tsx @@ -196,7 +196,7 @@ export class EMSFileSource extends AbstractVectorSource implements IEmsFileSourc return fields.map((f) => this.createField({ fieldName: f.name })); } - canFormatFeatureProperties() { + hasTooltipProperties() { return this._tooltipFields.length > 0; } diff --git a/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/es_geo_grid_source.tsx b/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/es_geo_grid_source.tsx index 7bca22df9b870..69ec0740948fc 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/es_geo_grid_source.tsx +++ b/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/es_geo_grid_source.tsx @@ -471,7 +471,7 @@ export class ESGeoGridSource extends AbstractESAggSource implements ITiledSingle } } - canFormatFeatureProperties(): boolean { + hasTooltipProperties(): boolean { return true; } diff --git a/x-pack/plugins/maps/public/classes/sources/es_geo_line_source/es_geo_line_source.tsx b/x-pack/plugins/maps/public/classes/sources/es_geo_line_source/es_geo_line_source.tsx index 9a1f23e055af1..460c1228e50a8 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_geo_line_source/es_geo_line_source.tsx +++ b/x-pack/plugins/maps/public/classes/sources/es_geo_line_source/es_geo_line_source.tsx @@ -359,7 +359,7 @@ export class ESGeoLineSource extends AbstractESAggSource { return true; } - canFormatFeatureProperties() { + hasTooltipProperties() { return true; } diff --git a/x-pack/plugins/maps/public/classes/sources/es_pew_pew_source/es_pew_pew_source.js b/x-pack/plugins/maps/public/classes/sources/es_pew_pew_source/es_pew_pew_source.js index 781cc7f8c36b0..7ed24b4805997 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_pew_pew_source/es_pew_pew_source.js +++ b/x-pack/plugins/maps/public/classes/sources/es_pew_pew_source/es_pew_pew_source.js @@ -226,7 +226,7 @@ export class ESPewPewSource extends AbstractESAggSource { return turfBboxToBounds(turfBbox(multiPoint(corners))); } - canFormatFeatureProperties() { + hasTooltipProperties() { return true; } } diff --git a/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx b/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx index 3de98fd545827..8a6e97bf2a1af 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx +++ b/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx @@ -464,7 +464,7 @@ export class ESSearchSource extends AbstractESSource implements ITiledSingleLaye }; } - canFormatFeatureProperties(): boolean { + hasTooltipProperties(): boolean { return this._tooltipFields.length > 0; } diff --git a/x-pack/plugins/maps/public/classes/sources/geojson_file_source/geojson_file_source.ts b/x-pack/plugins/maps/public/classes/sources/geojson_file_source/geojson_file_source.ts index 363e19fdb1587..592c2f852f0e7 100644 --- a/x-pack/plugins/maps/public/classes/sources/geojson_file_source/geojson_file_source.ts +++ b/x-pack/plugins/maps/public/classes/sources/geojson_file_source/geojson_file_source.ts @@ -121,7 +121,7 @@ export class GeoJsonFileSource extends AbstractVectorSource { return (this._descriptor as GeojsonFileSourceDescriptor).name; } - canFormatFeatureProperties() { + hasTooltipProperties() { return true; } diff --git a/x-pack/plugins/maps/public/classes/sources/kibana_regionmap_source/kibana_regionmap_source.ts b/x-pack/plugins/maps/public/classes/sources/kibana_regionmap_source/kibana_regionmap_source.ts index 12e4b00c3c7b9..b0241876e5728 100644 --- a/x-pack/plugins/maps/public/classes/sources/kibana_regionmap_source/kibana_regionmap_source.ts +++ b/x-pack/plugins/maps/public/classes/sources/kibana_regionmap_source/kibana_regionmap_source.ts @@ -104,7 +104,7 @@ export class KibanaRegionmapSource extends AbstractVectorSource { return this._descriptor.name; } - canFormatFeatureProperties() { + hasTooltipProperties() { return true; } } diff --git a/x-pack/plugins/maps/public/classes/sources/mvt_single_layer_vector_source/mvt_single_layer_vector_source.test.tsx b/x-pack/plugins/maps/public/classes/sources/mvt_single_layer_vector_source/mvt_single_layer_vector_source.test.tsx index b65536c2307d8..b265c4883323e 100644 --- a/x-pack/plugins/maps/public/classes/sources/mvt_single_layer_vector_source/mvt_single_layer_vector_source.test.tsx +++ b/x-pack/plugins/maps/public/classes/sources/mvt_single_layer_vector_source/mvt_single_layer_vector_source.test.tsx @@ -30,10 +30,10 @@ describe('getUrlTemplateWithMeta', () => { }); }); -describe('canFormatFeatureProperties', () => { +describe('hasTooltipProperties', () => { it('false if no tooltips', async () => { const source = new MVTSingleLayerVectorSource(descriptor); - expect(source.canFormatFeatureProperties()).toEqual(false); + expect(source.hasTooltipProperties()).toEqual(false); }); it('true if tooltip', async () => { const descriptorWithTooltips = { @@ -42,7 +42,7 @@ describe('canFormatFeatureProperties', () => { tooltipProperties: ['foobar'], }; const source = new MVTSingleLayerVectorSource(descriptorWithTooltips); - expect(source.canFormatFeatureProperties()).toEqual(true); + expect(source.hasTooltipProperties()).toEqual(true); }); }); diff --git a/x-pack/plugins/maps/public/classes/sources/mvt_single_layer_vector_source/mvt_single_layer_vector_source.tsx b/x-pack/plugins/maps/public/classes/sources/mvt_single_layer_vector_source/mvt_single_layer_vector_source.tsx index 92b643643ba2a..692e1fd18efaf 100644 --- a/x-pack/plugins/maps/public/classes/sources/mvt_single_layer_vector_source/mvt_single_layer_vector_source.tsx +++ b/x-pack/plugins/maps/public/classes/sources/mvt_single_layer_vector_source/mvt_single_layer_vector_source.tsx @@ -169,7 +169,7 @@ export class MVTSingleLayerVectorSource return [VECTOR_SHAPE_TYPE.POINT, VECTOR_SHAPE_TYPE.LINE, VECTOR_SHAPE_TYPE.POLYGON]; } - canFormatFeatureProperties(): boolean { + hasTooltipProperties(): boolean { return !!this._tooltipFields.length; } diff --git a/x-pack/plugins/maps/public/classes/sources/table_source/table_source.ts b/x-pack/plugins/maps/public/classes/sources/table_source/table_source.ts index d4c7a7474c57c..372fb4983d7cc 100644 --- a/x-pack/plugins/maps/public/classes/sources/table_source/table_source.ts +++ b/x-pack/plugins/maps/public/classes/sources/table_source/table_source.ts @@ -143,7 +143,7 @@ export class TableSource extends AbstractVectorSource implements ITermJoinSource }); } - canFormatFeatureProperties(): boolean { + hasTooltipProperties(): boolean { return false; } diff --git a/x-pack/plugins/maps/public/classes/sources/vector_source/vector_source.tsx b/x-pack/plugins/maps/public/classes/sources/vector_source/vector_source.tsx index b28cd7365d69e..da5a236a20936 100644 --- a/x-pack/plugins/maps/public/classes/sources/vector_source/vector_source.tsx +++ b/x-pack/plugins/maps/public/classes/sources/vector_source/vector_source.tsx @@ -60,7 +60,7 @@ export interface IVectorSource extends ISource { getSyncMeta(): VectorSourceSyncMeta | null; getFieldNames(): string[]; createField({ fieldName }: { fieldName: string }): IField; - canFormatFeatureProperties(): boolean; + hasTooltipProperties(): boolean; getSupportedShapeTypes(): Promise; isBoundsAware(): boolean; getSourceTooltipContent(sourceDataRequest?: DataRequest): SourceTooltipConfig; @@ -115,7 +115,7 @@ export class AbstractVectorSource extends AbstractSource implements IVectorSourc throw new Error('Should implement VectorSource#getGeoJson'); } - canFormatFeatureProperties() { + hasTooltipProperties() { return false; } diff --git a/x-pack/plugins/maps/public/classes/util/mb_filter_expressions.ts b/x-pack/plugins/maps/public/classes/util/mb_filter_expressions.ts index 4526340d3d865..c798f05df9813 100644 --- a/x-pack/plugins/maps/public/classes/util/mb_filter_expressions.ts +++ b/x-pack/plugins/maps/public/classes/util/mb_filter_expressions.ts @@ -15,61 +15,55 @@ import { export const EXCLUDE_TOO_MANY_FEATURES_BOX = ['!=', ['get', KBN_TOO_MANY_FEATURES_PROPERTY], true]; const EXCLUDE_CENTROID_FEATURES = ['!=', ['get', KBN_IS_CENTROID_FEATURE], true]; -const VISIBILITY_FILTER_CLAUSE = ['all', ['==', ['get', FEATURE_VISIBLE_PROPERTY_NAME], true]]; -// Kibana features are features added by kibana that do not exist in real data -const EXCLUDE_KBN_FEATURES = ['all', EXCLUDE_TOO_MANY_FEATURES_BOX, EXCLUDE_CENTROID_FEATURES]; +function getFilterExpression(geometryFilter: unknown[], hasJoins: boolean) { + const filters: unknown[] = [ + EXCLUDE_TOO_MANY_FEATURES_BOX, + EXCLUDE_CENTROID_FEATURES, + geometryFilter, + ]; -const CLOSED_SHAPE_MB_FILTER = [ - ...EXCLUDE_KBN_FEATURES, - [ - 'any', - ['==', ['geometry-type'], GEO_JSON_TYPE.POLYGON], - ['==', ['geometry-type'], GEO_JSON_TYPE.MULTI_POLYGON], - ], -]; + if (hasJoins) { + filters.push(['==', ['get', FEATURE_VISIBLE_PROPERTY_NAME], true]); + } -const VISIBLE_CLOSED_SHAPE_MB_FILTER = [...VISIBILITY_FILTER_CLAUSE, CLOSED_SHAPE_MB_FILTER]; - -const ALL_SHAPE_MB_FILTER = [ - ...EXCLUDE_KBN_FEATURES, - [ - 'any', - ['==', ['geometry-type'], GEO_JSON_TYPE.POLYGON], - ['==', ['geometry-type'], GEO_JSON_TYPE.MULTI_POLYGON], - ['==', ['geometry-type'], GEO_JSON_TYPE.LINE_STRING], - ['==', ['geometry-type'], GEO_JSON_TYPE.MULTI_LINE_STRING], - ], -]; - -const VISIBLE_ALL_SHAPE_MB_FILTER = [...VISIBILITY_FILTER_CLAUSE, ALL_SHAPE_MB_FILTER]; - -const POINT_MB_FILTER = [ - ...EXCLUDE_KBN_FEATURES, - [ - 'any', - ['==', ['geometry-type'], GEO_JSON_TYPE.POINT], - ['==', ['geometry-type'], GEO_JSON_TYPE.MULTI_POINT], - ], -]; - -const VISIBLE_POINT_MB_FILTER = [...VISIBILITY_FILTER_CLAUSE, POINT_MB_FILTER]; - -const CENTROID_MB_FILTER = ['all', ['==', ['get', KBN_IS_CENTROID_FEATURE], true]]; - -const VISIBLE_CENTROID_MB_FILTER = [...VISIBILITY_FILTER_CLAUSE, CENTROID_MB_FILTER]; + return ['all', ...filters]; +} export function getFillFilterExpression(hasJoins: boolean): unknown[] { - return hasJoins ? VISIBLE_CLOSED_SHAPE_MB_FILTER : CLOSED_SHAPE_MB_FILTER; + return getFilterExpression( + [ + 'any', + ['==', ['geometry-type'], GEO_JSON_TYPE.POLYGON], + ['==', ['geometry-type'], GEO_JSON_TYPE.MULTI_POLYGON], + ], + hasJoins + ); } export function getLineFilterExpression(hasJoins: boolean): unknown[] { - return hasJoins ? VISIBLE_ALL_SHAPE_MB_FILTER : ALL_SHAPE_MB_FILTER; + return getFilterExpression( + [ + 'any', + ['==', ['geometry-type'], GEO_JSON_TYPE.POLYGON], + ['==', ['geometry-type'], GEO_JSON_TYPE.MULTI_POLYGON], + ['==', ['geometry-type'], GEO_JSON_TYPE.LINE_STRING], + ['==', ['geometry-type'], GEO_JSON_TYPE.MULTI_LINE_STRING], + ], + hasJoins + ); } export function getPointFilterExpression(hasJoins: boolean): unknown[] { - return hasJoins ? VISIBLE_POINT_MB_FILTER : POINT_MB_FILTER; + return getFilterExpression( + [ + 'any', + ['==', ['geometry-type'], GEO_JSON_TYPE.POINT], + ['==', ['geometry-type'], GEO_JSON_TYPE.MULTI_POINT], + ], + hasJoins + ); } export function getCentroidFilterExpression(hasJoins: boolean): unknown[] { - return hasJoins ? VISIBLE_CENTROID_MB_FILTER : CENTROID_MB_FILTER; + return getFilterExpression(['==', ['get', KBN_IS_CENTROID_FEATURE], true], hasJoins); } diff --git a/x-pack/plugins/maps/public/connected_components/mb_map/features_tooltip/features_tooltip.tsx b/x-pack/plugins/maps/public/connected_components/mb_map/features_tooltip/features_tooltip.tsx index 41a2b98ab4b28..f85b1c5de3619 100644 --- a/x-pack/plugins/maps/public/connected_components/mb_map/features_tooltip/features_tooltip.tsx +++ b/x-pack/plugins/maps/public/connected_components/mb_map/features_tooltip/features_tooltip.tsx @@ -8,26 +8,21 @@ import React, { Component, Fragment, ReactNode } from 'react'; import { EuiIcon, EuiLink } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { FormattedMessage } from '@kbn/i18n/react'; import { ActionExecutionContext, Action } from 'src/plugins/ui_actions/public'; import { GeoJsonProperties, Geometry } from 'geojson'; import { Filter } from 'src/plugins/data/public'; import { FeatureProperties } from './feature_properties'; -import { GEO_JSON_TYPE, ES_GEO_FIELD_TYPE, RawValue } from '../../../../common/constants'; -import { FeatureGeometryFilterForm } from './feature_geometry_filter_form'; +import { RawValue } from '../../../../common/constants'; import { Footer } from './footer'; import { Header } from './header'; -import { PreIndexedShape } from '../../../../common/elasticsearch_util'; -import { GeoFieldWithIndex } from '../../../components/geo_field_with_index'; -import { TooltipFeature } from '../../../../common/descriptor_types'; +import { GEOMETRY_FILTER_ACTION, TooltipFeature } from '../../../../common/descriptor_types'; import { ITooltipProperty } from '../../../classes/tooltips/tooltip_property'; import { ILayer } from '../../../classes/layers/layer'; -enum VIEWS { - PROPERTIES_VIEW = 'PROPERTIES_VIEW', - GEOMETRY_FILTER_VIEW = 'GEOMETRY_FILTER_VIEW', - FILTER_ACTIONS_VIEW = 'FILTER_ACTIONS_VIEW', -} +const PROPERTIES_VIEW = 'PROPERTIES_VIEW'; +const FILTER_ACTIONS_VIEW = 'FILTER_ACTIONS_VIEW'; + +type VIEWS = typeof PROPERTIES_VIEW | typeof FILTER_ACTIONS_VIEW | typeof GEOMETRY_FILTER_ACTION; interface Props { addFilters: ((filters: Filter[], actionId: string) => Promise) | null; @@ -55,14 +50,6 @@ interface Props { }) => Geometry | null; getLayerName: (layerId: string) => Promise; findLayerById: (layerId: string) => ILayer | undefined; - geoFields: GeoFieldWithIndex[]; - loadPreIndexedShape: ({ - layerId, - featureId, - }: { - layerId: string; - featureId?: string | number; - }) => Promise; } interface State { @@ -77,14 +64,14 @@ export class FeaturesTooltip extends Component { currentFeature: null, filterView: null, prevFeatures: [], - view: VIEWS.PROPERTIES_VIEW, + view: PROPERTIES_VIEW, }; static getDerivedStateFromProps(nextProps: Props, prevState: State) { if (nextProps.features !== prevState.prevFeatures) { return { currentFeature: nextProps.features ? nextProps.features[0] : null, - view: VIEWS.PROPERTIES_VIEW, + view: PROPERTIES_VIEW, prevFeatures: nextProps.features, }; } @@ -96,69 +83,37 @@ export class FeaturesTooltip extends Component { this.setState({ currentFeature: feature }); }; - _showGeometryFilterView = () => { - this.setState({ view: VIEWS.GEOMETRY_FILTER_VIEW }); - }; - _showPropertiesView = () => { - this.setState({ view: VIEWS.PROPERTIES_VIEW, filterView: null }); + this.setState({ view: PROPERTIES_VIEW, filterView: null }); }; _showFilterActionsView = (filterView: ReactNode) => { - this.setState({ view: VIEWS.FILTER_ACTIONS_VIEW, filterView }); + this.setState({ view: FILTER_ACTIONS_VIEW, filterView }); }; - _renderActions(geoFields: GeoFieldWithIndex[]) { - if (!this.props.isLocked || geoFields.length === 0) { - return null; - } - - return ( - - - - ); - } - - _filterGeoFields(featureGeometry: Geometry | null) { - if (!featureGeometry) { - return []; - } - - // line geometry can only create filters for geo_shape fields. + _renderActions() { if ( - featureGeometry.type === GEO_JSON_TYPE.LINE_STRING || - featureGeometry.type === GEO_JSON_TYPE.MULTI_LINE_STRING + !this.props.isLocked || + !this.state.currentFeature || + this.state.currentFeature.actions.length === 0 ) { - return this.props.geoFields.filter(({ geoFieldType }) => { - return geoFieldType === ES_GEO_FIELD_TYPE.GEO_SHAPE; - }); - } - - // TODO support geo distance filters for points - if ( - featureGeometry.type === GEO_JSON_TYPE.POINT || - featureGeometry.type === GEO_JSON_TYPE.MULTI_POINT - ) { - return []; - } - - return this.props.geoFields; - } - - _loadCurrentFeaturePreIndexedShape = async () => { - if (!this.state.currentFeature) { return null; } - return this.props.loadPreIndexedShape({ - layerId: this.state.currentFeature.layerId, - featureId: this.state.currentFeature.id, + return this.state.currentFeature.actions.map((action) => { + return ( + { + this.setState({ view: action.id }); + }} + key={action.id} + > + {action.label} + + ); }); - }; + } _renderBackButton(label: string) { return ( @@ -181,38 +136,20 @@ export class FeaturesTooltip extends Component { return null; } - const currentFeatureGeometry = this.props.loadFeatureGeometry({ - layerId: this.state.currentFeature.layerId, - featureId: this.state.currentFeature.id, + const action = this.state.currentFeature.actions.find(({ id }) => { + return id === this.state.view; }); - const geoFields = this._filterGeoFields(currentFeatureGeometry); - if ( - this.state.view === VIEWS.GEOMETRY_FILTER_VIEW && - currentFeatureGeometry && - this.props.addFilters - ) { + if (action) { return ( - {this._renderBackButton( - i18n.translate('xpack.maps.tooltip.showGeometryFilterViewLinkLabel', { - defaultMessage: 'Filter by geometry', - }) - )} - + {this._renderBackButton(action.label)} + {action.form} ); } - if (this.state.view === VIEWS.FILTER_ACTIONS_VIEW) { + if (this.state.view === FILTER_ACTIONS_VIEW) { return ( {this._renderBackButton( @@ -247,7 +184,7 @@ export class FeaturesTooltip extends Component { onSingleValueTrigger={this.props.onSingleValueTrigger} showFilterActions={this._showFilterActionsView} /> - {this._renderActions(geoFields)} + {this._renderActions()}