diff --git a/.buildkite/scripts/post_build_kibana.sh b/.buildkite/scripts/post_build_kibana.sh index a4f8d71b77105..ad22a224f7c55 100755 --- a/.buildkite/scripts/post_build_kibana.sh +++ b/.buildkite/scripts/post_build_kibana.sh @@ -6,7 +6,7 @@ if [[ ! "${DISABLE_CI_STATS_SHIPPING:-}" ]]; then echo "--- Ship Kibana Distribution Metrics to CI Stats" node scripts/ship_ci_stats \ --metrics target/optimizer_bundle_metrics.json \ - --metrics packages/kbn-ui-shared-deps/target/metrics.json + --metrics node_modules/@kbn/ui-shared-deps/shared_built_assets/metrics.json fi echo "--- Upload Build Artifacts" diff --git a/.eslintignore b/.eslintignore index ce21d5bb31264..63cd01d6e90db 100644 --- a/.eslintignore +++ b/.eslintignore @@ -37,7 +37,7 @@ snapshots.js /packages/kbn-test/src/functional_test_runner/__tests__/fixtures/ /packages/kbn-test/src/functional_test_runner/lib/config/__tests__/fixtures/ /packages/kbn-ui-framework/dist -/packages/kbn-ui-shared-deps/flot_charts +/packages/kbn-ui-shared-deps/src/flot_charts /packages/kbn-monaco/src/painless/antlr # Bazel diff --git a/.eslintrc.js b/.eslintrc.js index c4883feff9b3c..40dd6a55a2a3f 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1471,7 +1471,7 @@ module.exports = { }, }, { - files: ['packages/kbn-ui-shared-deps/flot_charts/**/*.js'], + files: ['packages/kbn-ui-shared-deps/src/flot_charts/**/*.js'], env: { jquery: true, }, diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index a017c79309a56..f2d6749813013 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -76,6 +76,7 @@ /src/plugins/apm_oss/ @elastic/apm-ui /src/apm.js @elastic/kibana-core @vigneshshanmugam /packages/kbn-apm-config-loader/ @elastic/kibana-core @vigneshshanmugam +/src/core/types/elasticsearch @elastic/apm-ui #CC# /src/plugins/apm_oss/ @elastic/apm-ui #CC# /x-pack/plugins/observability/ @elastic/apm-ui diff --git a/.i18nrc.json b/.i18nrc.json index ad91042a2172d..0926f73722731 100644 --- a/.i18nrc.json +++ b/.i18nrc.json @@ -11,7 +11,7 @@ "uiActionsExamples": "examples/ui_action_examples", "share": "src/plugins/share", "home": "src/plugins/home", - "flot": "packages/kbn-ui-shared-deps/flot_charts", + "flot": "packages/kbn-ui-shared-deps/src/flot_charts", "charts": "src/plugins/charts", "esUi": "src/plugins/es_ui_shared", "devTools": "src/plugins/dev_tools", diff --git a/docs/developer/getting-started/monorepo-packages.asciidoc b/docs/developer/getting-started/monorepo-packages.asciidoc index ba484c9c2884f..c211751c09b49 100644 --- a/docs/developer/getting-started/monorepo-packages.asciidoc +++ b/docs/developer/getting-started/monorepo-packages.asciidoc @@ -102,5 +102,6 @@ yarn kbn watch-bazel - @kbn/std - @kbn/telemetry-utils - @kbn/tinymath +- @kbn/ui-shared-deps - @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 9930ab7319f65..d3d76079cdc2a 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 @@ -185,5 +185,18 @@ readonly links: { readonly plugins: Record; readonly snapshotRestore: Record; readonly ingest: Record; + readonly fleet: Readonly<{ + guide: string; + fleetServer: string; + fleetServerAddFleetServer: string; + settings: string; + settingsFleetServerHostSettings: string; + troubleshooting: string; + elasticAgent: string; + datastreams: string; + datastreamsNamingScheme: string; + upgradeElasticAgent: string; + upgradeElasticAgent712lower: string; + }>; }; ``` 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 ab8cdea5e4d86..34279cef198bf 100644 --- a/docs/development/core/public/kibana-plugin-core-public.doclinksstart.md +++ b/docs/development/core/public/kibana-plugin-core-public.doclinksstart.md @@ -17,5 +17,5 @@ export interface DocLinksStart | --- | --- | --- | | [DOC\_LINK\_VERSION](./kibana-plugin-core-public.doclinksstart.doc_link_version.md) | string | | | [ELASTIC\_WEBSITE\_URL](./kibana-plugin-core-public.doclinksstart.elastic_website_url.md) | string | | -| [links](./kibana-plugin-core-public.doclinksstart.links.md) | {
readonly canvas: {
readonly guide: string;
};
readonly dashboard: {
readonly guide: string;
readonly drilldowns: string;
readonly drilldownsTriggerPicker: string;
readonly urlDrilldownTemplateSyntax: string;
readonly urlDrilldownVariables: string;
};
readonly discover: Record<string, string>;
readonly filebeat: {
readonly base: string;
readonly installation: string;
readonly configuration: string;
readonly elasticsearchOutput: string;
readonly elasticsearchModule: string;
readonly startup: string;
readonly exportedFields: string;
};
readonly auditbeat: {
readonly base: string;
};
readonly metricbeat: {
readonly base: string;
readonly configure: string;
readonly httpEndpoint: string;
readonly install: string;
readonly start: string;
};
readonly enterpriseSearch: {
readonly base: string;
readonly appSearchBase: string;
readonly workplaceSearchBase: string;
};
readonly heartbeat: {
readonly base: string;
};
readonly logstash: {
readonly base: string;
};
readonly functionbeat: {
readonly base: string;
};
readonly winlogbeat: {
readonly base: string;
};
readonly aggs: {
readonly composite: string;
readonly composite_missing_bucket: string;
readonly date_histogram: string;
readonly date_range: string;
readonly date_format_pattern: string;
readonly filter: string;
readonly filters: string;
readonly geohash_grid: string;
readonly histogram: string;
readonly ip_range: string;
readonly range: string;
readonly significant_terms: string;
readonly terms: string;
readonly avg: string;
readonly avg_bucket: string;
readonly max_bucket: string;
readonly min_bucket: string;
readonly sum_bucket: string;
readonly cardinality: string;
readonly count: string;
readonly cumulative_sum: string;
readonly derivative: string;
readonly geo_bounds: string;
readonly geo_centroid: string;
readonly max: string;
readonly median: string;
readonly min: string;
readonly moving_avg: string;
readonly percentile_ranks: string;
readonly serial_diff: string;
readonly std_dev: string;
readonly sum: string;
readonly top_hits: string;
};
readonly runtimeFields: {
readonly overview: string;
readonly mapping: string;
};
readonly scriptedFields: {
readonly scriptFields: string;
readonly scriptAggs: string;
readonly painless: string;
readonly painlessApi: string;
readonly painlessLangSpec: string;
readonly painlessSyntax: string;
readonly painlessWalkthrough: string;
readonly luceneExpressions: string;
};
readonly search: {
readonly sessions: string;
};
readonly indexPatterns: {
readonly introduction: string;
readonly fieldFormattersNumber: string;
readonly fieldFormattersString: string;
readonly runtimeFields: string;
};
readonly addData: string;
readonly kibana: string;
readonly upgradeAssistant: string;
readonly 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>;
} | | +| [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 runtimeFields: 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>;
readonly fleet: Readonly<{
guide: string;
fleetServer: string;
fleetServerAddFleetServer: string;
settings: string;
settingsFleetServerHostSettings: string;
troubleshooting: string;
elasticAgent: string;
datastreams: string;
datastreamsNamingScheme: string;
upgradeElasticAgent: string;
upgradeElasticAgent712lower: string;
}>;
} | | diff --git a/docs/development/core/server/kibana-plugin-core-server.md b/docs/development/core/server/kibana-plugin-core-server.md index 604cdea1a7fbe..ac8930c52ac5c 100644 --- a/docs/development/core/server/kibana-plugin-core-server.md +++ b/docs/development/core/server/kibana-plugin-core-server.md @@ -165,6 +165,7 @@ The plugin integrates with the core system via lifecycle events: `setup` | [SavedObjectsDeleteOptions](./kibana-plugin-core-server.savedobjectsdeleteoptions.md) | | | [SavedObjectsExportByObjectOptions](./kibana-plugin-core-server.savedobjectsexportbyobjectoptions.md) | Options for the [export by objects API](./kibana-plugin-core-server.savedobjectsexporter.exportbyobjects.md) | | [SavedObjectsExportByTypeOptions](./kibana-plugin-core-server.savedobjectsexportbytypeoptions.md) | Options for the [export by type API](./kibana-plugin-core-server.savedobjectsexporter.exportbytypes.md) | +| [SavedObjectsExportExcludedObject](./kibana-plugin-core-server.savedobjectsexportexcludedobject.md) | | | [SavedObjectsExportResultDetails](./kibana-plugin-core-server.savedobjectsexportresultdetails.md) | Structure of the export result details entry | | [SavedObjectsExportTransformContext](./kibana-plugin-core-server.savedobjectsexporttransformcontext.md) | Context passed down to a [export transform function](./kibana-plugin-core-server.savedobjectsexporttransform.md) | | [SavedObjectsFindOptions](./kibana-plugin-core-server.savedobjectsfindoptions.md) | | diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectsexportexcludedobject.id.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectsexportexcludedobject.id.md new file mode 100644 index 0000000000000..f7b96e71c8e53 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectsexportexcludedobject.id.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [SavedObjectsExportExcludedObject](./kibana-plugin-core-server.savedobjectsexportexcludedobject.md) > [id](./kibana-plugin-core-server.savedobjectsexportexcludedobject.id.md) + +## SavedObjectsExportExcludedObject.id property + +id of the excluded object + +Signature: + +```typescript +id: string; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectsexportexcludedobject.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectsexportexcludedobject.md new file mode 100644 index 0000000000000..4766ae25a936d --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectsexportexcludedobject.md @@ -0,0 +1,21 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [SavedObjectsExportExcludedObject](./kibana-plugin-core-server.savedobjectsexportexcludedobject.md) + +## SavedObjectsExportExcludedObject interface + + +Signature: + +```typescript +export interface SavedObjectsExportExcludedObject +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [id](./kibana-plugin-core-server.savedobjectsexportexcludedobject.id.md) | string | id of the excluded object | +| [reason](./kibana-plugin-core-server.savedobjectsexportexcludedobject.reason.md) | string | optional cause of the exclusion | +| [type](./kibana-plugin-core-server.savedobjectsexportexcludedobject.type.md) | string | type of the excluded object | + diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectsexportexcludedobject.reason.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectsexportexcludedobject.reason.md new file mode 100644 index 0000000000000..0adb1ba35e696 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectsexportexcludedobject.reason.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [SavedObjectsExportExcludedObject](./kibana-plugin-core-server.savedobjectsexportexcludedobject.md) > [reason](./kibana-plugin-core-server.savedobjectsexportexcludedobject.reason.md) + +## SavedObjectsExportExcludedObject.reason property + +optional cause of the exclusion + +Signature: + +```typescript +reason?: string; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectsexportexcludedobject.type.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectsexportexcludedobject.type.md new file mode 100644 index 0000000000000..be28ac2d0ffb6 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectsexportexcludedobject.type.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [SavedObjectsExportExcludedObject](./kibana-plugin-core-server.savedobjectsexportexcludedobject.md) > [type](./kibana-plugin-core-server.savedobjectsexportexcludedobject.type.md) + +## SavedObjectsExportExcludedObject.type property + +type of the excluded object + +Signature: + +```typescript +type: string; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectsexportresultdetails.excludedobjects.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectsexportresultdetails.excludedobjects.md new file mode 100644 index 0000000000000..90432bf6d6705 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectsexportresultdetails.excludedobjects.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [SavedObjectsExportResultDetails](./kibana-plugin-core-server.savedobjectsexportresultdetails.md) > [excludedObjects](./kibana-plugin-core-server.savedobjectsexportresultdetails.excludedobjects.md) + +## SavedObjectsExportResultDetails.excludedObjects property + +excluded objects details + +Signature: + +```typescript +excludedObjects: SavedObjectsExportExcludedObject[]; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectsexportresultdetails.excludedobjectscount.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectsexportresultdetails.excludedobjectscount.md new file mode 100644 index 0000000000000..05846e28b9cab --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectsexportresultdetails.excludedobjectscount.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [SavedObjectsExportResultDetails](./kibana-plugin-core-server.savedobjectsexportresultdetails.md) > [excludedObjectsCount](./kibana-plugin-core-server.savedobjectsexportresultdetails.excludedobjectscount.md) + +## SavedObjectsExportResultDetails.excludedObjectsCount property + +number of objects that were excluded from the export + +Signature: + +```typescript +excludedObjectsCount: number; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectsexportresultdetails.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectsexportresultdetails.md index d98088c5f45be..f017f2329170b 100644 --- a/docs/development/core/server/kibana-plugin-core-server.savedobjectsexportresultdetails.md +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectsexportresultdetails.md @@ -16,6 +16,8 @@ export interface SavedObjectsExportResultDetails | Property | Type | Description | | --- | --- | --- | +| [excludedObjects](./kibana-plugin-core-server.savedobjectsexportresultdetails.excludedobjects.md) | SavedObjectsExportExcludedObject[] | excluded objects details | +| [excludedObjectsCount](./kibana-plugin-core-server.savedobjectsexportresultdetails.excludedobjectscount.md) | number | number of objects that were excluded from the export | | [exportedCount](./kibana-plugin-core-server.savedobjectsexportresultdetails.exportedcount.md) | number | number of successfully exported objects | | [missingRefCount](./kibana-plugin-core-server.savedobjectsexportresultdetails.missingrefcount.md) | number | number of missing references | | [missingReferences](./kibana-plugin-core-server.savedobjectsexportresultdetails.missingreferences.md) | Array<{
id: string;
type: string;
}> | missing references details | diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectsexporttransform.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectsexporttransform.md index 50d4c5425e8fd..2effed1ae9d70 100644 --- a/docs/development/core/server/kibana-plugin-core-server.savedobjectsexporttransform.md +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectsexporttransform.md @@ -11,7 +11,7 @@ A type's export transform function will be executed once per user-initiated expo Signature: ```typescript -export declare type SavedObjectsExportTransform = (context: SavedObjectsExportTransformContext, objects: Array>) => SavedObject[] | Promise; +export declare type SavedObjectsExportTransform = (context: SavedObjectsExportTransformContext, objects: Array>) => SavedObject[] | Promise; ``` ## Remarks diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectsservicesetup.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectsservicesetup.md index 56ebb48707f59..a1bc99ce8d13d 100644 --- a/docs/development/core/server/kibana-plugin-core-server.savedobjectsservicesetup.md +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectsservicesetup.md @@ -52,6 +52,6 @@ export class Plugin() { | Property | Type | Description | | --- | --- | --- | | [addClientWrapper](./kibana-plugin-core-server.savedobjectsservicesetup.addclientwrapper.md) | (priority: number, id: string, factory: SavedObjectsClientWrapperFactory) => void | Add a [client wrapper factory](./kibana-plugin-core-server.savedobjectsclientwrapperfactory.md) with the given priority. | -| [registerType](./kibana-plugin-core-server.savedobjectsservicesetup.registertype.md) | (type: SavedObjectsType) => void | Register a [savedObjects type](./kibana-plugin-core-server.savedobjectstype.md) definition.See the [mappings format](./kibana-plugin-core-server.savedobjectstypemappingdefinition.md) and [migration format](./kibana-plugin-core-server.savedobjectmigrationmap.md) for more details about these. | +| [registerType](./kibana-plugin-core-server.savedobjectsservicesetup.registertype.md) | <Attributes = any>(type: SavedObjectsType<Attributes>) => void | Register a [savedObjects type](./kibana-plugin-core-server.savedobjectstype.md) definition.See the [mappings format](./kibana-plugin-core-server.savedobjectstypemappingdefinition.md) and [migration format](./kibana-plugin-core-server.savedobjectmigrationmap.md) for more details about these. | | [setClientFactoryProvider](./kibana-plugin-core-server.savedobjectsservicesetup.setclientfactoryprovider.md) | (clientFactoryProvider: SavedObjectsClientFactoryProvider) => void | Set the default [factory provider](./kibana-plugin-core-server.savedobjectsclientfactoryprovider.md) for creating Saved Objects clients. Only one provider can be set, subsequent calls to this method will fail. | diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectsservicesetup.registertype.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectsservicesetup.registertype.md index 54e01d3110a2d..7f74ce4d7bea7 100644 --- a/docs/development/core/server/kibana-plugin-core-server.savedobjectsservicesetup.registertype.md +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectsservicesetup.registertype.md @@ -11,7 +11,7 @@ See the [mappings format](./kibana-plugin-core-server.savedobjectstypemappingdef Signature: ```typescript -registerType: (type: SavedObjectsType) => void; +registerType: (type: SavedObjectsType) => void; ``` ## Example diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectstype.management.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectstype.management.md index fbaf58f959075..d98c553656b1f 100644 --- a/docs/development/core/server/kibana-plugin-core-server.savedobjectstype.management.md +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectstype.management.md @@ -9,5 +9,5 @@ An optional [saved objects management section](./kibana-plugin-core-server.saved Signature: ```typescript -management?: SavedObjectsTypeManagementDefinition; +management?: SavedObjectsTypeManagementDefinition; ``` diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectstype.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectstype.md index d882938d731c8..c3aba5261561f 100644 --- a/docs/development/core/server/kibana-plugin-core-server.savedobjectstype.md +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectstype.md @@ -7,7 +7,7 @@ Signature: ```typescript -export interface SavedObjectsType +export interface SavedObjectsType ``` ## Remarks @@ -54,7 +54,7 @@ Example after converting to a multi-namespace (shareable) type in 8.1: Note: migration function(s) can be optionally specified for any of these versions and will not interfere with the conversion process. | | [hidden](./kibana-plugin-core-server.savedobjectstype.hidden.md) | boolean | Is the type hidden by default. If true, repositories will not have access to this type unless explicitly declared as an extraType when creating the repository.See [createInternalRepository](./kibana-plugin-core-server.savedobjectsservicestart.createinternalrepository.md). | | [indexPattern](./kibana-plugin-core-server.savedobjectstype.indexpattern.md) | string | If defined, the type instances will be stored in the given index instead of the default one. | -| [management](./kibana-plugin-core-server.savedobjectstype.management.md) | SavedObjectsTypeManagementDefinition | An optional [saved objects management section](./kibana-plugin-core-server.savedobjectstypemanagementdefinition.md) definition for the type. | +| [management](./kibana-plugin-core-server.savedobjectstype.management.md) | SavedObjectsTypeManagementDefinition<Attributes> | An optional [saved objects management section](./kibana-plugin-core-server.savedobjectstypemanagementdefinition.md) definition for the type. | | [mappings](./kibana-plugin-core-server.savedobjectstype.mappings.md) | SavedObjectsTypeMappingDefinition | The [mapping definition](./kibana-plugin-core-server.savedobjectstypemappingdefinition.md) for the type. | | [migrations](./kibana-plugin-core-server.savedobjectstype.migrations.md) | SavedObjectMigrationMap | (() => SavedObjectMigrationMap) | An optional map of [migrations](./kibana-plugin-core-server.savedobjectmigrationfn.md) or a function returning a map of [migrations](./kibana-plugin-core-server.savedobjectmigrationfn.md) to be used to migrate the type. | | [name](./kibana-plugin-core-server.savedobjectstype.name.md) | string | The name of the type, which is also used as the internal id. | diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectstypemanagementdefinition.getediturl.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectstypemanagementdefinition.getediturl.md index f5488d8f0310d..75f820d7a8e56 100644 --- a/docs/development/core/server/kibana-plugin-core-server.savedobjectstypemanagementdefinition.getediturl.md +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectstypemanagementdefinition.getediturl.md @@ -9,5 +9,5 @@ Function returning the url to use to redirect to the editing page of this object Signature: ```typescript -getEditUrl?: (savedObject: SavedObject) => string; +getEditUrl?: (savedObject: SavedObject) => string; ``` diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectstypemanagementdefinition.getinappurl.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectstypemanagementdefinition.getinappurl.md index 7b31dda402571..d6d50840aaadb 100644 --- a/docs/development/core/server/kibana-plugin-core-server.savedobjectstypemanagementdefinition.getinappurl.md +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectstypemanagementdefinition.getinappurl.md @@ -9,7 +9,7 @@ Function returning the url to use to redirect to this object from the management Signature: ```typescript -getInAppUrl?: (savedObject: SavedObject) => { +getInAppUrl?: (savedObject: SavedObject) => { path: string; uiCapabilitiesPath: string; }; diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectstypemanagementdefinition.gettitle.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectstypemanagementdefinition.gettitle.md index 2f39acc66f451..75784666ef963 100644 --- a/docs/development/core/server/kibana-plugin-core-server.savedobjectstypemanagementdefinition.gettitle.md +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectstypemanagementdefinition.gettitle.md @@ -9,5 +9,5 @@ Function returning the title to display in the management table. If not defined, Signature: ```typescript -getTitle?: (savedObject: SavedObject) => string; +getTitle?: (savedObject: SavedObject) => string; ``` diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectstypemanagementdefinition.isexportable.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectstypemanagementdefinition.isexportable.md new file mode 100644 index 0000000000000..fef178e1d9847 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectstypemanagementdefinition.isexportable.md @@ -0,0 +1,49 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [SavedObjectsTypeManagementDefinition](./kibana-plugin-core-server.savedobjectstypemanagementdefinition.md) > [isExportable](./kibana-plugin-core-server.savedobjectstypemanagementdefinition.isexportable.md) + +## SavedObjectsTypeManagementDefinition.isExportable property + +Optional hook to specify whether an object should be exportable. + +If specified, `isExportable` will be called during export for each of this type's objects in the export, and the ones not matching the predicate will be excluded from the export. + +When implementing both `isExportable` and `onExport`, it is mandatory that `isExportable` returns the same value for an object before and after going though the export transform. E.g `isExportable(objectBeforeTransform) === isExportable(objectAfterTransform)` + +Signature: + +```typescript +isExportable?: SavedObjectsExportablePredicate; +``` + +## Remarks + +`importableAndExportable` must be `true` to specify this property. + +## Example + +Registering a type with a per-object exportability predicate + +```ts +// src/plugins/my_plugin/server/plugin.ts +import { myType } from './saved_objects'; + +export class Plugin() { + setup: (core: CoreSetup) => { + core.savedObjects.registerType({ + ...myType, + management: { + ...myType.management, + isExportable: (object) => { + if (object.attributes.myCustomAttr === 'foo') { + return false; + } + return true; + } + }, + }); + } +} + +``` + diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectstypemanagementdefinition.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectstypemanagementdefinition.md index e9cc2b12108d6..8c42884eb0b31 100644 --- a/docs/development/core/server/kibana-plugin-core-server.savedobjectstypemanagementdefinition.md +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectstypemanagementdefinition.md @@ -9,7 +9,7 @@ Configuration options for the [type](./kibana-plugin-core-server.savedobjectstyp Signature: ```typescript -export interface SavedObjectsTypeManagementDefinition +export interface SavedObjectsTypeManagementDefinition ``` ## Properties @@ -17,11 +17,12 @@ export interface SavedObjectsTypeManagementDefinition | Property | Type | Description | | --- | --- | --- | | [defaultSearchField](./kibana-plugin-core-server.savedobjectstypemanagementdefinition.defaultsearchfield.md) | string | The default search field to use for this type. Defaults to id. | -| [getEditUrl](./kibana-plugin-core-server.savedobjectstypemanagementdefinition.getediturl.md) | (savedObject: SavedObject<any>) => string | Function returning the url to use to redirect to the editing page of this object. If not defined, editing will not be allowed. | -| [getInAppUrl](./kibana-plugin-core-server.savedobjectstypemanagementdefinition.getinappurl.md) | (savedObject: SavedObject<any>) => {
path: string;
uiCapabilitiesPath: string;
} | Function returning the url to use to redirect to this object from the management section. If not defined, redirecting to the object will not be allowed. | -| [getTitle](./kibana-plugin-core-server.savedobjectstypemanagementdefinition.gettitle.md) | (savedObject: SavedObject<any>) => string | Function returning the title to display in the management table. If not defined, will use the object's type and id to generate a label. | +| [getEditUrl](./kibana-plugin-core-server.savedobjectstypemanagementdefinition.getediturl.md) | (savedObject: SavedObject<Attributes>) => string | Function returning the url to use to redirect to the editing page of this object. If not defined, editing will not be allowed. | +| [getInAppUrl](./kibana-plugin-core-server.savedobjectstypemanagementdefinition.getinappurl.md) | (savedObject: SavedObject<Attributes>) => {
path: string;
uiCapabilitiesPath: string;
} | Function returning the url to use to redirect to this object from the management section. If not defined, redirecting to the object will not be allowed. | +| [getTitle](./kibana-plugin-core-server.savedobjectstypemanagementdefinition.gettitle.md) | (savedObject: SavedObject<Attributes>) => string | Function returning the title to display in the management table. If not defined, will use the object's type and id to generate a label. | | [icon](./kibana-plugin-core-server.savedobjectstypemanagementdefinition.icon.md) | string | The eui icon name to display in the management table. If not defined, the default icon will be used. | | [importableAndExportable](./kibana-plugin-core-server.savedobjectstypemanagementdefinition.importableandexportable.md) | boolean | Is the type importable or exportable. Defaults to false. | -| [onExport](./kibana-plugin-core-server.savedobjectstypemanagementdefinition.onexport.md) | SavedObjectsExportTransform | An optional export transform function that can be used transform the objects of the registered type during the export process.It can be used to either mutate the exported objects, or add additional objects (of any type) to the export list.See [the transform type documentation](./kibana-plugin-core-server.savedobjectsexporttransform.md) for more info and examples. | -| [onImport](./kibana-plugin-core-server.savedobjectstypemanagementdefinition.onimport.md) | SavedObjectsImportHook | An optional [import hook](./kibana-plugin-core-server.savedobjectsimporthook.md) to use when importing given type.Import hooks are executed during the savedObjects import process and allow to interact with the imported objects. See the [hook documentation](./kibana-plugin-core-server.savedobjectsimporthook.md) for more info. | +| [isExportable](./kibana-plugin-core-server.savedobjectstypemanagementdefinition.isexportable.md) | SavedObjectsExportablePredicate<Attributes> | Optional hook to specify whether an object should be exportable.If specified, isExportable will be called during export for each of this type's objects in the export, and the ones not matching the predicate will be excluded from the export.When implementing both isExportable and onExport, it is mandatory that isExportable returns the same value for an object before and after going though the export transform. E.g isExportable(objectBeforeTransform) === isExportable(objectAfterTransform) | +| [onExport](./kibana-plugin-core-server.savedobjectstypemanagementdefinition.onexport.md) | SavedObjectsExportTransform<Attributes> | An optional export transform function that can be used transform the objects of the registered type during the export process.It can be used to either mutate the exported objects, or add additional objects (of any type) to the export list.See [the transform type documentation](./kibana-plugin-core-server.savedobjectsexporttransform.md) for more info and examples.When implementing both isExportable and onExport, it is mandatory that isExportable returns the same value for an object before and after going though the export transform. E.g isExportable(objectBeforeTransform) === isExportable(objectAfterTransform) | +| [onImport](./kibana-plugin-core-server.savedobjectstypemanagementdefinition.onimport.md) | SavedObjectsImportHook<Attributes> | An optional [import hook](./kibana-plugin-core-server.savedobjectsimporthook.md) to use when importing given type.Import hooks are executed during the savedObjects import process and allow to interact with the imported objects. See the [hook documentation](./kibana-plugin-core-server.savedobjectsimporthook.md) for more info. | diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectstypemanagementdefinition.onexport.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectstypemanagementdefinition.onexport.md index 6302b36a73c68..a0d41d2d64967 100644 --- a/docs/development/core/server/kibana-plugin-core-server.savedobjectstypemanagementdefinition.onexport.md +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectstypemanagementdefinition.onexport.md @@ -10,10 +10,12 @@ It can be used to either mutate the exported objects, or add additional objects See [the transform type documentation](./kibana-plugin-core-server.savedobjectsexporttransform.md) for more info and examples. +When implementing both `isExportable` and `onExport`, it is mandatory that `isExportable` returns the same value for an object before and after going though the export transform. E.g `isExportable(objectBeforeTransform) === isExportable(objectAfterTransform)` + Signature: ```typescript -onExport?: SavedObjectsExportTransform; +onExport?: SavedObjectsExportTransform; ``` ## Remarks diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectstypemanagementdefinition.onimport.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectstypemanagementdefinition.onimport.md index f6634c01c66ba..332247b8eb8e1 100644 --- a/docs/development/core/server/kibana-plugin-core-server.savedobjectstypemanagementdefinition.onimport.md +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectstypemanagementdefinition.onimport.md @@ -11,7 +11,7 @@ Import hooks are executed during the savedObjects import process and allow to in Signature: ```typescript -onImport?: SavedObjectsImportHook; +onImport?: SavedObjectsImportHook; ``` ## Remarks diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjecttyperegistry.getalltypes.md b/docs/development/core/server/kibana-plugin-core-server.savedobjecttyperegistry.getalltypes.md index c839dd16d9a47..20d631ff74aca 100644 --- a/docs/development/core/server/kibana-plugin-core-server.savedobjecttyperegistry.getalltypes.md +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjecttyperegistry.getalltypes.md @@ -11,9 +11,9 @@ To only get the visible types (which is the most common use case), use `getVisib Signature: ```typescript -getAllTypes(): SavedObjectsType[]; +getAllTypes(): SavedObjectsType[]; ``` Returns: -`SavedObjectsType[]` +`SavedObjectsType[]` diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjecttyperegistry.getimportableandexportabletypes.md b/docs/development/core/server/kibana-plugin-core-server.savedobjecttyperegistry.getimportableandexportabletypes.md index ab8a79c3a8455..1e29e632a6ec3 100644 --- a/docs/development/core/server/kibana-plugin-core-server.savedobjecttyperegistry.getimportableandexportabletypes.md +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjecttyperegistry.getimportableandexportabletypes.md @@ -9,9 +9,9 @@ Return all [types](./kibana-plugin-core-server.savedobjectstype.md) currently re Signature: ```typescript -getImportableAndExportableTypes(): SavedObjectsType[]; +getImportableAndExportableTypes(): SavedObjectsType[]; ``` Returns: -`SavedObjectsType[]` +`SavedObjectsType[]` diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjecttyperegistry.gettype.md b/docs/development/core/server/kibana-plugin-core-server.savedobjecttyperegistry.gettype.md index cfa52882bb89d..160aadb73cced 100644 --- a/docs/development/core/server/kibana-plugin-core-server.savedobjecttyperegistry.gettype.md +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjecttyperegistry.gettype.md @@ -9,7 +9,7 @@ Return the [type](./kibana-plugin-core-server.savedobjectstype.md) definition fo Signature: ```typescript -getType(type: string): SavedObjectsType | undefined; +getType(type: string): SavedObjectsType | undefined; ``` ## Parameters @@ -20,5 +20,5 @@ getType(type: string): SavedObjectsType | undefined; Returns: -`SavedObjectsType | undefined` +`SavedObjectsType | undefined` diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjecttyperegistry.getvisibletypes.md b/docs/development/core/server/kibana-plugin-core-server.savedobjecttyperegistry.getvisibletypes.md index a773c6a0a674f..05f22dcf7010b 100644 --- a/docs/development/core/server/kibana-plugin-core-server.savedobjecttyperegistry.getvisibletypes.md +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjecttyperegistry.getvisibletypes.md @@ -11,9 +11,9 @@ A visible type is a type that doesn't explicitly define `hidden=true` during reg Signature: ```typescript -getVisibleTypes(): SavedObjectsType[]; +getVisibleTypes(): SavedObjectsType[]; ``` Returns: -`SavedObjectsType[]` +`SavedObjectsType[]` diff --git a/docs/management/connectors/action-types/email.asciidoc b/docs/management/connectors/action-types/email.asciidoc index 719d00c16c932..bab04b8052674 100644 --- a/docs/management/connectors/action-types/email.asciidoc +++ b/docs/management/connectors/action-types/email.asciidoc @@ -95,6 +95,7 @@ The email connector can send email using many popular SMTP email services. For more information about configuring the email connector to work with different email systems, refer to: +* <> * <> * <> * <> @@ -102,6 +103,29 @@ For more information about configuring the email connector to work with differen For other email servers, you can check the list of well-known services that Nodemailer supports in the JSON file https://github.com/nodemailer/nodemailer/blob/master/lib/well-known/services.json[well-known/services.json]. The properties of the objects in those files — `host`, `port`, and `secure` — correspond to the same email connector configuration properties. A missing `secure` property in the "well-known/services.json" file is considered `false`. Typically, `port: 465` uses `secure: true`, and `port: 25` and `port: 587` use `secure: false`. +[float] +[[elasticcloud]] +==== Sending email from Elastic Cloud + +IMPORTANT: These instructions require you to link:{cloud}/ec-watcher.html#ec-watcher-whitelist[whitelist] the email addresses that notifications get sent first. + +Use the following connector settings to send email from Elastic Cloud: + +Sender:: +`noreply@watcheralert.found.io` + +Host:: +`dockerhost` + +Port:: +`10025` + +Secure:: +Toggle off + +Authentication:: +Toggle off + [float] [[gmail]] ==== Sending email from Gmail diff --git a/docs/setup/settings.asciidoc b/docs/setup/settings.asciidoc index ddb906f390a2d..c3c29adcea18f 100644 --- a/docs/setup/settings.asciidoc +++ b/docs/setup/settings.asciidoc @@ -325,6 +325,9 @@ The time interval policy will rotate the log file every given interval of time. When `includeElasticMapsService` is turned off, only the vector layers configured by <> and the tile layer configured by <> are available in <>. *Default: `true`* +| `map.emsUrl:` + | Specifies the URL of a self hosted <> + | `map.proxyElasticMapsServiceInMaps:` | Set to `true` to proxy all <> Elastic Maps Service requests through the {kib} server. *Default: `false`* diff --git a/package.json b/package.json index b8a52f14c52ca..310350baf7b2d 100644 --- a/package.json +++ b/package.json @@ -155,7 +155,7 @@ "@kbn/std": "link:bazel-bin/packages/kbn-std", "@kbn/tinymath": "link:bazel-bin/packages/kbn-tinymath", "@kbn/ui-framework": "link:packages/kbn-ui-framework", - "@kbn/ui-shared-deps": "link:packages/kbn-ui-shared-deps", + "@kbn/ui-shared-deps": "link:bazel-bin/packages/kbn-ui-shared-deps", "@kbn/utility-types": "link:bazel-bin/packages/kbn-utility-types", "@kbn/common-utils": "link:bazel-bin/packages/kbn-common-utils", "@kbn/utils": "link:bazel-bin/packages/kbn-utils", @@ -291,6 +291,7 @@ "mapbox-gl-draw-rectangle-mode": "1.0.4", "markdown-it": "^10.0.0", "md5": "^2.1.0", + "mdast-util-to-hast": "10.0.1", "memoize-one": "^5.0.0", "mime": "^2.4.4", "mime-types": "^2.1.27", diff --git a/packages/BUILD.bazel b/packages/BUILD.bazel index 7bce6c256f2f3..6208910729625 100644 --- a/packages/BUILD.bazel +++ b/packages/BUILD.bazel @@ -46,6 +46,7 @@ filegroup( "//packages/kbn-std:build", "//packages/kbn-telemetry-tools:build", "//packages/kbn-tinymath:build", + "//packages/kbn-ui-shared-deps:build", "//packages/kbn-utility-types:build", "//packages/kbn-utils:build", ], diff --git a/packages/kbn-es-archiver/src/actions/empty_kibana_index.ts b/packages/kbn-es-archiver/src/actions/empty_kibana_index.ts index dbc455bbd2f8f..24a1de10b2b1d 100644 --- a/packages/kbn-es-archiver/src/actions/empty_kibana_index.ts +++ b/packages/kbn-es-archiver/src/actions/empty_kibana_index.ts @@ -22,9 +22,8 @@ export async function emptyKibanaIndexAction({ kbnClient: KbnClient; }) { const stats = createStats('emptyKibanaIndex', log); - const kibanaPluginIds = await kbnClient.plugins.getEnabledIds(); - await cleanKibanaIndices({ client, stats, log, kibanaPluginIds }); + await cleanKibanaIndices({ client, stats, log }); await migrateKibanaIndex(kbnClient); stats.createdIndex('.kibana'); return stats.toJSON(); diff --git a/packages/kbn-es-archiver/src/actions/unload.ts b/packages/kbn-es-archiver/src/actions/unload.ts index d8bc013b40991..98bae36095b88 100644 --- a/packages/kbn-es-archiver/src/actions/unload.ts +++ b/packages/kbn-es-archiver/src/actions/unload.ts @@ -37,7 +37,6 @@ export async function unloadAction({ }) { const name = relative(REPO_ROOT, inputDir); const stats = createStats(name, log); - const kibanaPluginIds = await kbnClient.plugins.getEnabledIds(); const files = prioritizeMappings(await readDirectory(inputDir)); for (const filename of files) { @@ -47,7 +46,7 @@ export async function unloadAction({ createReadStream(resolve(inputDir, filename)) as Readable, ...createParseArchiveStreams({ gzip: isGzip(filename) }), createFilterRecordsStream('index'), - createDeleteIndexStream(client, stats, log, kibanaPluginIds), + createDeleteIndexStream(client, stats, log), ] as [Readable, ...Writable[]]); } diff --git a/packages/kbn-es-archiver/src/lib/indices/delete_index_stream.test.ts b/packages/kbn-es-archiver/src/lib/indices/delete_index_stream.test.ts index 3198ba86207f0..241d4a8944546 100644 --- a/packages/kbn-es-archiver/src/lib/indices/delete_index_stream.test.ts +++ b/packages/kbn-es-archiver/src/lib/indices/delete_index_stream.test.ts @@ -28,7 +28,7 @@ describe('esArchiver: createDeleteIndexStream()', () => { await createPromiseFromStreams([ createListStream([createStubIndexRecord('index1')]), - createDeleteIndexStream(client, stats, log, []), + createDeleteIndexStream(client, stats, log), ]); sinon.assert.notCalled(stats.deletedIndex as sinon.SinonSpy); @@ -43,7 +43,7 @@ describe('esArchiver: createDeleteIndexStream()', () => { await createPromiseFromStreams([ createListStream([createStubIndexRecord('index1')]), - createDeleteIndexStream(client, stats, log, []), + createDeleteIndexStream(client, stats, log), ]); sinon.assert.calledOnce(stats.deletedIndex as sinon.SinonSpy); diff --git a/packages/kbn-es-archiver/src/lib/indices/delete_index_stream.ts b/packages/kbn-es-archiver/src/lib/indices/delete_index_stream.ts index e1552b5ed1e3b..7765419bb9d15 100644 --- a/packages/kbn-es-archiver/src/lib/indices/delete_index_stream.ts +++ b/packages/kbn-es-archiver/src/lib/indices/delete_index_stream.ts @@ -14,12 +14,7 @@ import { Stats } from '../stats'; import { deleteIndex } from './delete_index'; import { cleanKibanaIndices } from './kibana_index'; -export function createDeleteIndexStream( - client: KibanaClient, - stats: Stats, - log: ToolingLog, - kibanaPluginIds: string[] -) { +export function createDeleteIndexStream(client: KibanaClient, stats: Stats, log: ToolingLog) { return new Transform({ readableObjectMode: true, writableObjectMode: true, @@ -29,7 +24,7 @@ export function createDeleteIndexStream( const { index } = record.value; if (index.startsWith('.kibana')) { - await cleanKibanaIndices({ client, stats, log, kibanaPluginIds }); + await cleanKibanaIndices({ client, stats, log }); } else { await deleteIndex({ client, stats, log, index }); } diff --git a/packages/kbn-es-archiver/src/lib/indices/kibana_index.ts b/packages/kbn-es-archiver/src/lib/indices/kibana_index.ts index 0712d2789a91a..635e432468846 100644 --- a/packages/kbn-es-archiver/src/lib/indices/kibana_index.ts +++ b/packages/kbn-es-archiver/src/lib/indices/kibana_index.ts @@ -96,21 +96,11 @@ export async function cleanKibanaIndices({ client, stats, log, - kibanaPluginIds, }: { client: KibanaClient; stats: Stats; log: ToolingLog; - kibanaPluginIds: string[]; }) { - if (!kibanaPluginIds.includes('spaces')) { - return await deleteKibanaIndices({ - client, - stats, - log, - }); - } - while (true) { const resp = await client.deleteByQuery( { diff --git a/packages/kbn-monaco/src/esql/index.ts b/packages/kbn-monaco/src/esql/index.ts index a3f9df00118b7..4b50a222ad2d6 100644 --- a/packages/kbn-monaco/src/esql/index.ts +++ b/packages/kbn-monaco/src/esql/index.ts @@ -6,7 +6,8 @@ * Side Public License, v 1. */ +import { LangModule as LangModuleType } from '../types'; import { ID } from './constants'; import { lexerRules } from './lexer_rules'; -export const EsqlLang = { ID, lexerRules }; +export const EsqlLang: LangModuleType = { ID, lexerRules }; diff --git a/packages/kbn-monaco/src/helpers.ts b/packages/kbn-monaco/src/helpers.ts new file mode 100644 index 0000000000000..e525b8c132132 --- /dev/null +++ b/packages/kbn-monaco/src/helpers.ts @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ +import { monaco } from './monaco_imports'; +import { LangModule as LangModuleType } from './types'; + +function registerLanguage(language: LangModuleType) { + const { ID, lexerRules, languageConfiguration } = language; + + monaco.languages.register({ id: ID }); + monaco.languages.setMonarchTokensProvider(ID, lexerRules); + if (languageConfiguration) { + monaco.languages.setLanguageConfiguration(ID, languageConfiguration); + } +} + +export { registerLanguage }; diff --git a/packages/kbn-monaco/src/index.ts b/packages/kbn-monaco/src/index.ts index ce35d7c3572e6..85d3518461a49 100644 --- a/packages/kbn-monaco/src/index.ts +++ b/packages/kbn-monaco/src/index.ts @@ -12,7 +12,13 @@ import './register_globals'; export { monaco } from './monaco_imports'; export { XJsonLang } from './xjson'; export { PainlessLang, PainlessContext, PainlessAutocompleteField } from './painless'; - /* eslint-disable-next-line @kbn/eslint/module_migration */ import * as BarePluginApi from 'monaco-editor/esm/vs/editor/editor.api'; -export { BarePluginApi }; + +import { registerLanguage } from './helpers'; +import { + LangModule as LangModuleType, + CompleteLangModule as CompleteLangModuleType, +} from './types'; + +export { BarePluginApi, registerLanguage, LangModuleType, CompleteLangModuleType }; diff --git a/packages/kbn-monaco/src/painless/index.ts b/packages/kbn-monaco/src/painless/index.ts index 6858209756430..9863204117b12 100644 --- a/packages/kbn-monaco/src/painless/index.ts +++ b/packages/kbn-monaco/src/painless/index.ts @@ -9,8 +9,9 @@ import { ID } from './constants'; import { lexerRules, languageConfiguration } from './lexer_rules'; import { getSuggestionProvider, getSyntaxErrors } from './language'; +import { CompleteLangModule as CompleteLangModuleType } from '../types'; -export const PainlessLang = { +export const PainlessLang: CompleteLangModuleType = { ID, getSuggestionProvider, lexerRules, diff --git a/packages/kbn-monaco/src/register_globals.ts b/packages/kbn-monaco/src/register_globals.ts index 4047ddedeca42..c6eb68b89e718 100644 --- a/packages/kbn-monaco/src/register_globals.ts +++ b/packages/kbn-monaco/src/register_globals.ts @@ -10,6 +10,8 @@ import { XJsonLang } from './xjson'; import { PainlessLang } from './painless'; import { EsqlLang } from './esql'; import { monaco } from './monaco_imports'; +import { registerLanguage } from './helpers'; + // @ts-ignore import xJsonWorkerSrc from '!!raw-loader!../../target_web/xjson.editor.worker.js'; // @ts-ignore @@ -20,14 +22,9 @@ import painlessWorkerSrc from '!!raw-loader!../../target_web/painless.editor.wor /** * Register languages and lexer rules */ -monaco.languages.register({ id: XJsonLang.ID }); -monaco.languages.setMonarchTokensProvider(XJsonLang.ID, XJsonLang.lexerRules); -monaco.languages.setLanguageConfiguration(XJsonLang.ID, XJsonLang.languageConfiguration); -monaco.languages.register({ id: PainlessLang.ID }); -monaco.languages.setMonarchTokensProvider(PainlessLang.ID, PainlessLang.lexerRules); -monaco.languages.setLanguageConfiguration(PainlessLang.ID, PainlessLang.languageConfiguration); -monaco.languages.register({ id: EsqlLang.ID }); -monaco.languages.setMonarchTokensProvider(EsqlLang.ID, EsqlLang.lexerRules); +registerLanguage(XJsonLang); +registerLanguage(PainlessLang); +registerLanguage(EsqlLang); /** * Create web workers by language ID diff --git a/packages/kbn-monaco/src/types.ts b/packages/kbn-monaco/src/types.ts new file mode 100644 index 0000000000000..f977ada5b624b --- /dev/null +++ b/packages/kbn-monaco/src/types.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 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 { monaco } from './monaco_imports'; + +export interface LangModule { + ID: string; + lexerRules: monaco.languages.IMonarchLanguage; + languageConfiguration?: monaco.languages.LanguageConfiguration; + getSuggestionProvider?: Function; + getSyntaxErrors?: Function; +} + +export interface CompleteLangModule extends LangModule { + languageConfiguration: monaco.languages.LanguageConfiguration; + getSuggestionProvider: Function; + getSyntaxErrors: Function; +} diff --git a/packages/kbn-monaco/src/xjson/index.ts b/packages/kbn-monaco/src/xjson/index.ts index 5e278795fef12..e9ece97ac0023 100644 --- a/packages/kbn-monaco/src/xjson/index.ts +++ b/packages/kbn-monaco/src/xjson/index.ts @@ -12,5 +12,6 @@ import './language'; import { ID } from './constants'; import { lexerRules, languageConfiguration } from './lexer_rules'; +import { LangModule as LangModuleType } from '../types'; -export const XJsonLang = { ID, lexerRules, languageConfiguration }; +export const XJsonLang: LangModuleType = { ID, lexerRules, languageConfiguration }; diff --git a/packages/kbn-optimizer/package.json b/packages/kbn-optimizer/package.json index 54fcdc3bb130f..a6c8284ad15f6 100644 --- a/packages/kbn-optimizer/package.json +++ b/packages/kbn-optimizer/package.json @@ -9,8 +9,5 @@ "build": "../../node_modules/.bin/tsc", "kbn:bootstrap": "yarn build", "kbn:watch": "yarn build --watch" - }, - "dependencies": { - "@kbn/ui-shared-deps": "link:../kbn-ui-shared-deps" } } \ No newline at end of file diff --git a/packages/kbn-optimizer/src/integration_tests/basic_optimization.test.ts b/packages/kbn-optimizer/src/integration_tests/basic_optimization.test.ts index 50c9e7e12904f..97a7f33be673d 100644 --- a/packages/kbn-optimizer/src/integration_tests/basic_optimization.test.ts +++ b/packages/kbn-optimizer/src/integration_tests/basic_optimization.test.ts @@ -15,7 +15,7 @@ import cpy from 'cpy'; import del from 'del'; import { tap, filter } from 'rxjs/operators'; import { REPO_ROOT } from '@kbn/utils'; -import { ToolingLog } from '@kbn/dev-utils'; +import { ToolingLog, createReplaceSerializer } from '@kbn/dev-utils'; import { runOptimizer, OptimizerConfig, OptimizerUpdate, logOptimizerState } from '../index'; import { allValuesFrom } from '../common'; @@ -29,6 +29,8 @@ expect.addSnapshotSerializer({ test: (value: any) => typeof value === 'string' && value.includes(REPO_ROOT), }); +expect.addSnapshotSerializer(createReplaceSerializer(/\w+-fastbuild/, '-fastbuild')); + const log = new ToolingLog({ level: 'error', writeTo: { @@ -130,13 +132,13 @@ it('builds expected bundles, saves bundle counts to metadata', async () => { expect(foo.cache.getModuleCount()).toBe(6); expect(foo.cache.getReferencedFiles()).toMatchInlineSnapshot(` Array [ + /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/bazel-out/-fastbuild/bin/packages/kbn-ui-shared-deps/target/public_path_module_creator.js, /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/plugins/foo/kibana.json, /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/plugins/foo/public/async_import.ts, /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/plugins/foo/public/ext.ts, /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/plugins/foo/public/index.ts, /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/plugins/foo/public/lib.ts, /packages/kbn-optimizer/src/worker/entry_point_creator.ts, - /packages/kbn-ui-shared-deps/public_path_module_creator.js, ] `); @@ -153,6 +155,7 @@ it('builds expected bundles, saves bundle counts to metadata', async () => { /node_modules/@kbn/optimizer/postcss.config.js, /node_modules/css-loader/package.json, /node_modules/style-loader/package.json, + /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/bazel-out/-fastbuild/bin/packages/kbn-ui-shared-deps/target/public_path_module_creator.js, /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/plugins/bar/kibana.json, /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/plugins/bar/public/index.scss, /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/plugins/bar/public/index.ts, @@ -162,7 +165,6 @@ it('builds expected bundles, saves bundle counts to metadata', async () => { /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/src/core/public/core_app/styles/_globals_v8dark.scss, /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/src/core/public/core_app/styles/_globals_v8light.scss, /packages/kbn-optimizer/src/worker/entry_point_creator.ts, - /packages/kbn-ui-shared-deps/public_path_module_creator.js, ] `); @@ -173,10 +175,10 @@ it('builds expected bundles, saves bundle counts to metadata', async () => { expect(baz.cache.getReferencedFiles()).toMatchInlineSnapshot(` Array [ + /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/bazel-out/-fastbuild/bin/packages/kbn-ui-shared-deps/target/public_path_module_creator.js, /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/x-pack/baz/kibana.json, /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/x-pack/baz/public/index.ts, /packages/kbn-optimizer/src/worker/entry_point_creator.ts, - /packages/kbn-ui-shared-deps/public_path_module_creator.js, ] `); }); diff --git a/packages/kbn-optimizer/src/worker/populate_bundle_cache_plugin.ts b/packages/kbn-optimizer/src/worker/populate_bundle_cache_plugin.ts index 6d296b9be089c..8d890b31b639d 100644 --- a/packages/kbn-optimizer/src/worker/populate_bundle_cache_plugin.ts +++ b/packages/kbn-optimizer/src/worker/populate_bundle_cache_plugin.ts @@ -54,8 +54,19 @@ export class PopulateBundleCachePlugin { for (const module of compilation.modules) { if (isNormalModule(module)) { moduleCount += 1; - const path = getModulePath(module); - const parsedPath = parseFilePath(path); + let path = getModulePath(module); + let parsedPath = parseFilePath(path); + + if (parsedPath.dirs.includes('bazel-out')) { + const index = parsedPath.dirs.indexOf('bazel-out'); + path = Path.join( + workerConfig.repoRoot, + 'bazel-out', + ...parsedPath.dirs.slice(index + 1), + parsedPath.filename ?? '' + ); + parsedPath = parseFilePath(path); + } if (!parsedPath.dirs.includes('node_modules')) { referencedFiles.add(path); diff --git a/packages/kbn-storybook/package.json b/packages/kbn-storybook/package.json index 0e70f7c340a90..f2e4c9b3418b1 100644 --- a/packages/kbn-storybook/package.json +++ b/packages/kbn-storybook/package.json @@ -11,6 +11,6 @@ "scripts": { "build": "../../node_modules/.bin/tsc", "kbn:bootstrap": "yarn build", - "watch": "yarn build --watch" + "kbn:watch": "yarn build --watch" } } \ No newline at end of file diff --git a/packages/kbn-storybook/webpack.config.ts b/packages/kbn-storybook/webpack.config.ts index b1efa43fa194b..972caf8d481fe 100644 --- a/packages/kbn-storybook/webpack.config.ts +++ b/packages/kbn-storybook/webpack.config.ts @@ -71,11 +71,12 @@ export default function ({ config: storybookConfig }: { config: Configuration }) ], }, resolve: { - // Tell Webpack about the scss extension - extensions: ['.scss'], + extensions: ['.js', '.ts', '.tsx', '.json'], + mainFields: ['browser', 'main'], alias: { core_app_image_assets: resolve(REPO_ROOT, 'src/core/public/core_app/images'), }, + symlinks: false, }, stats, }; diff --git a/packages/kbn-ui-shared-deps/BUILD.bazel b/packages/kbn-ui-shared-deps/BUILD.bazel new file mode 100644 index 0000000000000..c04f88a42cce0 --- /dev/null +++ b/packages/kbn-ui-shared-deps/BUILD.bazel @@ -0,0 +1,145 @@ +load("@npm//@bazel/typescript:index.bzl", "ts_config", "ts_project") +load("@build_bazel_rules_nodejs//:index.bzl", "js_library", "pkg_npm") +load("@npm//webpack-cli:index.bzl", webpack = "webpack_cli") + +PKG_BASE_NAME = "kbn-ui-shared-deps" +PKG_REQUIRE_NAME = "@kbn/ui-shared-deps" + +SOURCE_FILES = glob( + [ + "src/**/*", + ], + exclude = [ + "**/*.md", + ], +) + +SRCS = SOURCE_FILES + +filegroup( + name = "srcs", + srcs = SRCS, +) + +NPM_MODULE_EXTRA_FILES = [ + "flot_charts/package.json", + "theme/package.json", + "package.json", + "README.md" +] + +SRC_DEPS = [ + "//packages/elastic-datemath", + "//packages/elastic-safer-lodash-set", + "//packages/kbn-analytics", + "//packages/kbn-babel-preset", + "//packages/kbn-i18n", + "//packages/kbn-monaco", + "//packages/kbn-std", + "//packages/kbn-utils", + "@npm//@elastic/charts", + "@npm//@elastic/eui", + "@npm//@elastic/numeral", + "@npm//abortcontroller-polyfill", + "@npm//angular", + "@npm//babel-loader", + "@npm//compression-webpack-plugin", + "@npm//core-js", + "@npm//css-minimizer-webpack-plugin", + "@npm//css-loader", + "@npm//fflate", + "@npm//jquery", + "@npm//loader-utils", + # TODO: we can remove this once EUI patches the dependencies + "@npm//mdast-util-to-hast", + "@npm//mini-css-extract-plugin", + "@npm//moment", + "@npm//moment-timezone", + "@npm//raw-loader", + "@npm//react", + "@npm//react-dom", + "@npm//react-intl", + "@npm//react-is", + "@npm//react-router", + "@npm//react-router-dom", + "@npm//regenerator-runtime", + "@npm//resize-observer-polyfill", + "@npm//rison-node", + "@npm//rxjs", + "@npm//styled-components", + "@npm//symbol-observable", + "@npm//terser-webpack-plugin", + "@npm//url-loader", + "@npm//val-loader", + "@npm//whatwg-fetch" +] + +TYPES_DEPS = [ + "@npm//@types/node", +] + +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, + allow_js = True, + declaration = True, + declaration_map = True, + incremental = True, + out_dir = "target", + source_map = True, + root_dir = "src", + tsconfig = ":tsconfig", +) + +webpack( + name = "shared_built_assets", + data = DEPS + [ + "//:package.json", + ":srcs", + ":tsconfig", + ":webpack.config.js", + ], + output_dir = True, + args = [ + "--config", + "$(location webpack.config.js)", + "--output-path", + "$(@D)", + "--display=minimal" + ], +) + +js_library( + name = PKG_BASE_NAME, + srcs = NPM_MODULE_EXTRA_FILES, + deps = DEPS + [":tsc", ":shared_built_assets"], + 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-ui-shared-deps/flot_charts/package.json b/packages/kbn-ui-shared-deps/flot_charts/package.json new file mode 100644 index 0000000000000..03d7ac348fcb9 --- /dev/null +++ b/packages/kbn-ui-shared-deps/flot_charts/package.json @@ -0,0 +1,4 @@ +{ + "main": "../target/flot_charts/index.js", + "types": "../target/flot_charts/index.d.ts" +} \ No newline at end of file diff --git a/packages/kbn-ui-shared-deps/index.d.ts b/packages/kbn-ui-shared-deps/index.d.ts deleted file mode 100644 index 5d2986daeeb3b..0000000000000 --- a/packages/kbn-ui-shared-deps/index.d.ts +++ /dev/null @@ -1,59 +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. - */ - -/** - * Absolute path to the distributable directory - */ -export const distDir: string; - -/** - * Filename of the main bundle file in the distributable directory - */ -export const jsFilename: string; - -/** - * Filename of files that must be loaded before the jsFilename - */ -export const jsDepFilenames: string[]; - -/** - * Filename of the unthemed css file in the distributable directory - */ -export const baseCssDistFilename: string; - -/** - * Filename of the dark-theme css file in the distributable directory - */ -export const darkCssDistFilename: string; - -/** - * Filename of the dark-theme css file in the distributable directory - */ -export const darkV8CssDistFilename: string; - -/** - * Filename of the light-theme css file in the distributable directory - */ -export const lightCssDistFilename: string; - -/** - * Filename of the light-theme css file in the distributable directory - */ -export const lightV8CssDistFilename: string; - -/** - * Externals mapping inteded to be used in a webpack config - */ -export const externals: { - [key: string]: string; -}; - -/** - * Webpack loader for configuring the public path lookup from `window.__kbnPublicPath__`. - */ -export const publicPathLoader: string; diff --git a/packages/kbn-ui-shared-deps/package.json b/packages/kbn-ui-shared-deps/package.json index 162606585c43e..5ec32ca059aa1 100644 --- a/packages/kbn-ui-shared-deps/package.json +++ b/packages/kbn-ui-shared-deps/package.json @@ -3,9 +3,6 @@ "version": "1.0.0", "private": true, "license": "SSPL-1.0 OR Elastic License 2.0", - "scripts": { - "build": "node scripts/build", - "kbn:bootstrap": "node scripts/build --dev", - "kbn:watch": "node scripts/build --dev --watch" - } + "main": "target/index.js", + "types": "target/index.d.ts" } \ No newline at end of file diff --git a/packages/kbn-ui-shared-deps/scripts/build.js b/packages/kbn-ui-shared-deps/scripts/build.js deleted file mode 100644 index 0993f78590246..0000000000000 --- a/packages/kbn-ui-shared-deps/scripts/build.js +++ /dev/null @@ -1,98 +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, createFailError } = require('@kbn/dev-utils'); -const webpack = require('webpack'); -const Stats = require('webpack/lib/Stats'); -const del = require('del'); - -const { getWebpackConfig } = require('../webpack.config'); - -const DIST_DIR = Path.resolve(__dirname, '../target'); - -run( - async ({ log, flags }) => { - log.info('cleaning previous build output'); - await del(DIST_DIR); - - const compiler = webpack( - getWebpackConfig({ - dev: flags.dev, - }) - ); - - /** @param {webpack.Stats} stats */ - const onCompilationComplete = async (stats) => { - const took = Math.round((stats.endTime - stats.startTime) / 1000); - - if (!stats.hasErrors() && !stats.hasWarnings()) { - log.success(`webpack completed in about ${took} seconds`); - return; - } - - throw createFailError( - `webpack failure in about ${took} seconds\n${stats.toString({ - colors: true, - ...Stats.presetToOptions('minimal'), - })}` - ); - }; - - if (flags.watch) { - compiler.hooks.done.tap('report on stats', (stats) => { - onCompilationComplete(stats).catch((error) => { - log.error(error.message); - }); - }); - - compiler.hooks.watchRun.tap('report on start', () => { - if (process.stdout.isTTY) { - process.stdout.cursorTo(0, 0); - process.stdout.clearScreenDown(); - } - - log.info('Running webpack compilation...'); - }); - - compiler.watch({}, (error) => { - if (error) { - log.error('Fatal webpack error'); - log.error(error); - process.exit(1); - } - }); - - return; - } - - log.info('running webpack'); - await onCompilationComplete( - await new Promise((resolve, reject) => { - compiler.run((error, stats) => { - if (error) { - reject(error); - } else { - resolve(stats); - } - }); - }) - ); - }, - { - description: 'build @kbn/ui-shared-deps', - flags: { - boolean: ['watch', 'dev'], - help: ` - --watch Run in watch mode - --dev Build development friendly version - `, - }, - } -); diff --git a/packages/kbn-ui-shared-deps/entry.js b/packages/kbn-ui-shared-deps/src/entry.js similarity index 100% rename from packages/kbn-ui-shared-deps/entry.js rename to packages/kbn-ui-shared-deps/src/entry.js diff --git a/packages/kbn-ui-shared-deps/flot_charts/API.md b/packages/kbn-ui-shared-deps/src/flot_charts/API.md similarity index 100% rename from packages/kbn-ui-shared-deps/flot_charts/API.md rename to packages/kbn-ui-shared-deps/src/flot_charts/API.md diff --git a/packages/kbn-ui-shared-deps/flot_charts/index.js b/packages/kbn-ui-shared-deps/src/flot_charts/index.js similarity index 100% rename from packages/kbn-ui-shared-deps/flot_charts/index.js rename to packages/kbn-ui-shared-deps/src/flot_charts/index.js diff --git a/packages/kbn-ui-shared-deps/flot_charts/jquery_colorhelpers.js b/packages/kbn-ui-shared-deps/src/flot_charts/jquery_colorhelpers.js similarity index 100% rename from packages/kbn-ui-shared-deps/flot_charts/jquery_colorhelpers.js rename to packages/kbn-ui-shared-deps/src/flot_charts/jquery_colorhelpers.js diff --git a/packages/kbn-ui-shared-deps/flot_charts/jquery_flot.js b/packages/kbn-ui-shared-deps/src/flot_charts/jquery_flot.js similarity index 100% rename from packages/kbn-ui-shared-deps/flot_charts/jquery_flot.js rename to packages/kbn-ui-shared-deps/src/flot_charts/jquery_flot.js diff --git a/packages/kbn-ui-shared-deps/flot_charts/jquery_flot_axislabels.js b/packages/kbn-ui-shared-deps/src/flot_charts/jquery_flot_axislabels.js similarity index 100% rename from packages/kbn-ui-shared-deps/flot_charts/jquery_flot_axislabels.js rename to packages/kbn-ui-shared-deps/src/flot_charts/jquery_flot_axislabels.js diff --git a/packages/kbn-ui-shared-deps/flot_charts/jquery_flot_canvas.js b/packages/kbn-ui-shared-deps/src/flot_charts/jquery_flot_canvas.js similarity index 100% rename from packages/kbn-ui-shared-deps/flot_charts/jquery_flot_canvas.js rename to packages/kbn-ui-shared-deps/src/flot_charts/jquery_flot_canvas.js diff --git a/packages/kbn-ui-shared-deps/flot_charts/jquery_flot_categories.js b/packages/kbn-ui-shared-deps/src/flot_charts/jquery_flot_categories.js similarity index 100% rename from packages/kbn-ui-shared-deps/flot_charts/jquery_flot_categories.js rename to packages/kbn-ui-shared-deps/src/flot_charts/jquery_flot_categories.js diff --git a/packages/kbn-ui-shared-deps/flot_charts/jquery_flot_crosshair.js b/packages/kbn-ui-shared-deps/src/flot_charts/jquery_flot_crosshair.js similarity index 100% rename from packages/kbn-ui-shared-deps/flot_charts/jquery_flot_crosshair.js rename to packages/kbn-ui-shared-deps/src/flot_charts/jquery_flot_crosshair.js diff --git a/packages/kbn-ui-shared-deps/flot_charts/jquery_flot_errorbars.js b/packages/kbn-ui-shared-deps/src/flot_charts/jquery_flot_errorbars.js similarity index 100% rename from packages/kbn-ui-shared-deps/flot_charts/jquery_flot_errorbars.js rename to packages/kbn-ui-shared-deps/src/flot_charts/jquery_flot_errorbars.js diff --git a/packages/kbn-ui-shared-deps/flot_charts/jquery_flot_fillbetween.js b/packages/kbn-ui-shared-deps/src/flot_charts/jquery_flot_fillbetween.js similarity index 100% rename from packages/kbn-ui-shared-deps/flot_charts/jquery_flot_fillbetween.js rename to packages/kbn-ui-shared-deps/src/flot_charts/jquery_flot_fillbetween.js diff --git a/packages/kbn-ui-shared-deps/flot_charts/jquery_flot_image.js b/packages/kbn-ui-shared-deps/src/flot_charts/jquery_flot_image.js similarity index 100% rename from packages/kbn-ui-shared-deps/flot_charts/jquery_flot_image.js rename to packages/kbn-ui-shared-deps/src/flot_charts/jquery_flot_image.js diff --git a/packages/kbn-ui-shared-deps/flot_charts/jquery_flot_log.js b/packages/kbn-ui-shared-deps/src/flot_charts/jquery_flot_log.js similarity index 100% rename from packages/kbn-ui-shared-deps/flot_charts/jquery_flot_log.js rename to packages/kbn-ui-shared-deps/src/flot_charts/jquery_flot_log.js diff --git a/packages/kbn-ui-shared-deps/flot_charts/jquery_flot_navigate.js b/packages/kbn-ui-shared-deps/src/flot_charts/jquery_flot_navigate.js similarity index 100% rename from packages/kbn-ui-shared-deps/flot_charts/jquery_flot_navigate.js rename to packages/kbn-ui-shared-deps/src/flot_charts/jquery_flot_navigate.js diff --git a/packages/kbn-ui-shared-deps/flot_charts/jquery_flot_pie.js b/packages/kbn-ui-shared-deps/src/flot_charts/jquery_flot_pie.js similarity index 100% rename from packages/kbn-ui-shared-deps/flot_charts/jquery_flot_pie.js rename to packages/kbn-ui-shared-deps/src/flot_charts/jquery_flot_pie.js diff --git a/packages/kbn-ui-shared-deps/flot_charts/jquery_flot_resize.js b/packages/kbn-ui-shared-deps/src/flot_charts/jquery_flot_resize.js similarity index 100% rename from packages/kbn-ui-shared-deps/flot_charts/jquery_flot_resize.js rename to packages/kbn-ui-shared-deps/src/flot_charts/jquery_flot_resize.js diff --git a/packages/kbn-ui-shared-deps/flot_charts/jquery_flot_selection.js b/packages/kbn-ui-shared-deps/src/flot_charts/jquery_flot_selection.js similarity index 100% rename from packages/kbn-ui-shared-deps/flot_charts/jquery_flot_selection.js rename to packages/kbn-ui-shared-deps/src/flot_charts/jquery_flot_selection.js diff --git a/packages/kbn-ui-shared-deps/flot_charts/jquery_flot_stack.js b/packages/kbn-ui-shared-deps/src/flot_charts/jquery_flot_stack.js similarity index 100% rename from packages/kbn-ui-shared-deps/flot_charts/jquery_flot_stack.js rename to packages/kbn-ui-shared-deps/src/flot_charts/jquery_flot_stack.js diff --git a/packages/kbn-ui-shared-deps/flot_charts/jquery_flot_symbol.js b/packages/kbn-ui-shared-deps/src/flot_charts/jquery_flot_symbol.js similarity index 100% rename from packages/kbn-ui-shared-deps/flot_charts/jquery_flot_symbol.js rename to packages/kbn-ui-shared-deps/src/flot_charts/jquery_flot_symbol.js diff --git a/packages/kbn-ui-shared-deps/flot_charts/jquery_flot_threshold.js b/packages/kbn-ui-shared-deps/src/flot_charts/jquery_flot_threshold.js similarity index 100% rename from packages/kbn-ui-shared-deps/flot_charts/jquery_flot_threshold.js rename to packages/kbn-ui-shared-deps/src/flot_charts/jquery_flot_threshold.js diff --git a/packages/kbn-ui-shared-deps/flot_charts/jquery_flot_time.js b/packages/kbn-ui-shared-deps/src/flot_charts/jquery_flot_time.js similarity index 100% rename from packages/kbn-ui-shared-deps/flot_charts/jquery_flot_time.js rename to packages/kbn-ui-shared-deps/src/flot_charts/jquery_flot_time.js diff --git a/packages/kbn-ui-shared-deps/index.js b/packages/kbn-ui-shared-deps/src/index.js similarity index 78% rename from packages/kbn-ui-shared-deps/index.js rename to packages/kbn-ui-shared-deps/src/index.js index 877bf3df6c039..c5853dc091875 100644 --- a/packages/kbn-ui-shared-deps/index.js +++ b/packages/kbn-ui-shared-deps/src/index.js @@ -8,14 +8,49 @@ const Path = require('path'); -exports.distDir = Path.resolve(__dirname, 'target'); +/** + * Absolute path to the distributable directory + */ +exports.distDir = Path.resolve(__dirname, '..', 'shared_built_assets'); + +/** + * Filename of files that must be loaded before the jsFilename + */ exports.jsDepFilenames = ['kbn-ui-shared-deps.@elastic.js']; + +/** + * Filename of the main bundle file in the distributable directory + */ exports.jsFilename = 'kbn-ui-shared-deps.js'; + +/** + * Filename of the unthemed css file in the distributable directory + */ exports.baseCssDistFilename = 'kbn-ui-shared-deps.css'; + +/** + * Filename of the light-theme css file in the distributable directory + */ exports.lightCssDistFilename = 'kbn-ui-shared-deps.v7.light.css'; + +/** + * Filename of the light-theme css file in the distributable directory + */ exports.lightV8CssDistFilename = 'kbn-ui-shared-deps.v8.light.css'; + +/** + * Filename of the dark-theme css file in the distributable directory + */ exports.darkCssDistFilename = 'kbn-ui-shared-deps.v7.dark.css'; + +/** + * Filename of the dark-theme css file in the distributable directory + */ exports.darkV8CssDistFilename = 'kbn-ui-shared-deps.v8.dark.css'; + +/** + * Externals mapping inteded to be used in a webpack config + */ exports.externals = { // stateful deps angular: '__kbnSharedDeps__.Angular', @@ -63,4 +98,8 @@ exports.externals = { '@elastic/safer-lodash-set': '__kbnSharedDeps__.SaferLodashSet', 'rison-node': '__kbnSharedDeps__.RisonNode', }; + +/** + * Webpack loader for configuring the public path lookup from `window.__kbnPublicPath__`. + */ exports.publicPathLoader = require.resolve('./public_path_loader'); diff --git a/packages/kbn-ui-shared-deps/polyfills.js b/packages/kbn-ui-shared-deps/src/polyfills.js similarity index 100% rename from packages/kbn-ui-shared-deps/polyfills.js rename to packages/kbn-ui-shared-deps/src/polyfills.js diff --git a/packages/kbn-ui-shared-deps/public_path_loader.js b/packages/kbn-ui-shared-deps/src/public_path_loader.js similarity index 100% rename from packages/kbn-ui-shared-deps/public_path_loader.js rename to packages/kbn-ui-shared-deps/src/public_path_loader.js diff --git a/packages/kbn-ui-shared-deps/public_path_module_creator.js b/packages/kbn-ui-shared-deps/src/public_path_module_creator.js similarity index 100% rename from packages/kbn-ui-shared-deps/public_path_module_creator.js rename to packages/kbn-ui-shared-deps/src/public_path_module_creator.js diff --git a/packages/kbn-ui-shared-deps/theme.ts b/packages/kbn-ui-shared-deps/src/theme.ts similarity index 100% rename from packages/kbn-ui-shared-deps/theme.ts rename to packages/kbn-ui-shared-deps/src/theme.ts diff --git a/packages/kbn-ui-shared-deps/theme/package.json b/packages/kbn-ui-shared-deps/theme/package.json new file mode 100644 index 0000000000000..2d41937701a29 --- /dev/null +++ b/packages/kbn-ui-shared-deps/theme/package.json @@ -0,0 +1,4 @@ +{ + "main": "../target/theme.js", + "types": "../target/theme.d.ts" +} \ No newline at end of file diff --git a/packages/kbn-ui-shared-deps/tsconfig.json b/packages/kbn-ui-shared-deps/tsconfig.json index 88699027f85de..0fd49ede21830 100644 --- a/packages/kbn-ui-shared-deps/tsconfig.json +++ b/packages/kbn-ui-shared-deps/tsconfig.json @@ -1,10 +1,20 @@ { "extends": "../../tsconfig.base.json", "compilerOptions": { - "tsBuildInfoFile": "../../build/tsbuildinfo/packages/kbn-ui-shared-deps" + "allowJs": true, + "incremental": true, + "outDir": "./target", + "declaration": true, + "declarationMap": true, + "rootDir": "src", + "sourceMap": true, + "sourceRoot": "../../../../packages/kbn-ui-shared-deps/src", + "types": [ + "node", + "resize-observer-polyfill" + ] }, "include": [ - "index.d.ts", - "theme.ts" + "src/**/*", ] } diff --git a/packages/kbn-ui-shared-deps/webpack.config.js b/packages/kbn-ui-shared-deps/webpack.config.js index 76e6843bea2f8..438b1e0b2e77b 100644 --- a/packages/kbn-ui-shared-deps/webpack.config.js +++ b/packages/kbn-ui-shared-deps/webpack.config.js @@ -7,6 +7,7 @@ */ const Path = require('path'); +const Os = require('os'); const MiniCssExtractPlugin = require('mini-css-extract-plugin'); const CssMinimizerPlugin = require('css-minimizer-webpack-plugin'); @@ -14,24 +15,23 @@ const TerserPlugin = require('terser-webpack-plugin'); const CompressionPlugin = require('compression-webpack-plugin'); const { REPO_ROOT } = require('@kbn/utils'); -const webpack = require('webpack'); const { RawSource } = require('webpack-sources'); -const UiSharedDeps = require('./index'); +const UiSharedDeps = require('./src/index'); const MOMENT_SRC = require.resolve('moment/min/moment-with-locales.js'); -exports.getWebpackConfig = ({ dev = false } = {}) => ({ - mode: dev ? 'development' : 'production', +module.exports = { + mode: 'production', entry: { - 'kbn-ui-shared-deps': './entry.js', + 'kbn-ui-shared-deps': './src/entry.js', 'kbn-ui-shared-deps.v7.dark': ['@elastic/eui/dist/eui_theme_dark.css'], 'kbn-ui-shared-deps.v7.light': ['@elastic/eui/dist/eui_theme_light.css'], 'kbn-ui-shared-deps.v8.dark': ['@elastic/eui/dist/eui_theme_amsterdam_dark.css'], 'kbn-ui-shared-deps.v8.light': ['@elastic/eui/dist/eui_theme_amsterdam_light.css'], }, context: __dirname, - devtool: dev ? '#cheap-source-map' : false, + devtool: 'cheap-source-map', output: { path: UiSharedDeps.distDir, filename: '[name].js', @@ -39,13 +39,14 @@ exports.getWebpackConfig = ({ dev = false } = {}) => ({ devtoolModuleFilenameTemplate: (info) => `kbn-ui-shared-deps/${Path.relative(REPO_ROOT, info.absoluteResourcePath)}`, library: '__kbnSharedDeps__', + futureEmitAssets: true, }, module: { noParse: [MOMENT_SRC], rules: [ { - include: [require.resolve('./entry.js')], + include: [require.resolve('./src/entry.js')], use: [ { loader: UiSharedDeps.publicPathLoader, @@ -60,7 +61,7 @@ exports.getWebpackConfig = ({ dev = false } = {}) => ({ use: [MiniCssExtractPlugin.loader, 'css-loader'], }, { - include: [require.resolve('./theme.ts')], + include: [require.resolve('./src/theme.ts')], use: [ { loader: 'babel-loader', @@ -71,7 +72,7 @@ exports.getWebpackConfig = ({ dev = false } = {}) => ({ ], }, { - test: !dev ? /[\\\/]@elastic[\\\/]eui[\\\/].*\.js$/ : () => false, + test: /[\\\/]@elastic[\\\/]eui[\\\/].*\.js$/, use: [ { loader: 'babel-loader', @@ -110,6 +111,7 @@ exports.getWebpackConfig = ({ dev = false } = {}) => ({ optimization: { minimizer: [ new CssMinimizerPlugin({ + parallel: Math.min(Os.cpus().length, 2), minimizerOptions: { preset: [ 'default', @@ -123,7 +125,7 @@ exports.getWebpackConfig = ({ dev = false } = {}) => ({ cache: false, sourceMap: false, extractComments: false, - parallel: false, + parallel: Math.min(Os.cpus().length, 2), terserOptions: { compress: true, mangle: true, @@ -154,54 +156,44 @@ exports.getWebpackConfig = ({ dev = false } = {}) => ({ new MiniCssExtractPlugin({ filename: '[name].css', }), - new webpack.DefinePlugin({ - 'process.env.NODE_ENV': dev ? '"development"' : '"production"', + new CompressionPlugin({ + algorithm: 'brotliCompress', + filename: '[path].br', + test: /\.(js|css)$/, + cache: false, }), - ...(dev - ? [] - : [ - new CompressionPlugin({ - algorithm: 'brotliCompress', - filename: '[path].br', - test: /\.(js|css)$/, - cache: false, - }), - new CompressionPlugin({ - algorithm: 'gzip', - filename: '[path].gz', - test: /\.(js|css)$/, - cache: false, - }), - new (class MetricsPlugin { - apply(compiler) { - compiler.hooks.emit.tap('MetricsPlugin', (compilation) => { - const metrics = [ - { - group: 'page load bundle size', - id: 'kbnUiSharedDeps-js', - value: compilation.assets['kbn-ui-shared-deps.js'].size(), - }, - { - group: 'page load bundle size', - id: 'kbnUiSharedDeps-css', - value: - compilation.assets['kbn-ui-shared-deps.css'].size() + - compilation.assets['kbn-ui-shared-deps.v7.light.css'].size(), - }, - { - group: 'page load bundle size', - id: 'kbnUiSharedDeps-elastic', - value: compilation.assets['kbn-ui-shared-deps.@elastic.js'].size(), - }, - ]; + new CompressionPlugin({ + algorithm: 'gzip', + filename: '[path].gz', + test: /\.(js|css)$/, + cache: false, + }), + new (class MetricsPlugin { + apply(compiler) { + compiler.hooks.emit.tap('MetricsPlugin', (compilation) => { + const metrics = [ + { + group: 'page load bundle size', + id: 'kbnUiSharedDeps-js', + value: compilation.assets['kbn-ui-shared-deps.js'].size(), + }, + { + group: 'page load bundle size', + id: 'kbnUiSharedDeps-css', + value: + compilation.assets['kbn-ui-shared-deps.css'].size() + + compilation.assets['kbn-ui-shared-deps.v7.light.css'].size(), + }, + { + group: 'page load bundle size', + id: 'kbnUiSharedDeps-elastic', + value: compilation.assets['kbn-ui-shared-deps.@elastic.js'].size(), + }, + ]; - compilation.emitAsset( - 'metrics.json', - new RawSource(JSON.stringify(metrics, null, 2)) - ); - }); - } - })(), - ]), + compilation.emitAsset('metrics.json', new RawSource(JSON.stringify(metrics, null, 2))); + }); + } + })(), ], -}); +}; diff --git a/src/core/public/doc_links/doc_links_service.ts b/src/core/public/doc_links/doc_links_service.ts index 06277d9351922..95091a761639b 100644 --- a/src/core/public/doc_links/doc_links_service.ts +++ b/src/core/public/doc_links/doc_links_service.ts @@ -22,6 +22,7 @@ export class DocLinksService { const ELASTIC_WEBSITE_URL = 'https://www.elastic.co/'; const ELASTICSEARCH_DOCS = `${ELASTIC_WEBSITE_URL}guide/en/elasticsearch/reference/${DOC_LINK_VERSION}/`; const KIBANA_DOCS = `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/`; + const FLEET_DOCS = `${ELASTIC_WEBSITE_URL}guide/en/fleet/${DOC_LINK_VERSION}/`; const PLUGIN_DOCS = `${ELASTIC_WEBSITE_URL}guide/en/elasticsearch/plugins/${DOC_LINK_VERSION}/`; return deepFreeze({ @@ -400,6 +401,19 @@ export class DocLinksService { urlDecode: `${ELASTICSEARCH_DOCS}urldecode-processor.html`, userAgent: `${ELASTICSEARCH_DOCS}user-agent-processor.html`, }, + fleet: { + guide: `${FLEET_DOCS}index.html`, + fleetServer: `${FLEET_DOCS}fleet-server.html`, + fleetServerAddFleetServer: `${FLEET_DOCS}fleet-server.html#add-fleet-server`, + settings: `${FLEET_DOCS}fleet-settings.html#fleet-server-hosts-setting`, + settingsFleetServerHostSettings: `${FLEET_DOCS}fleet-settings.html#fleet-server-hosts-setting`, + troubleshooting: `${FLEET_DOCS}fleet-troubleshooting.html`, + elasticAgent: `${FLEET_DOCS}elastic-agent-installation-configuration.html`, + datastreams: `${FLEET_DOCS}data-streams.html`, + datastreamsNamingScheme: `${FLEET_DOCS}data-streams.html#data-streams-naming-scheme`, + upgradeElasticAgent: `${FLEET_DOCS}upgrade-elastic-agent.html`, + upgradeElasticAgent712lower: `${FLEET_DOCS}upgrade-elastic-agent.html#upgrade-7.12-lower`, + }, }, }); } @@ -587,5 +601,18 @@ export interface DocLinksStart { readonly plugins: Record; readonly snapshotRestore: Record; readonly ingest: Record; + readonly fleet: Readonly<{ + guide: string; + fleetServer: string; + fleetServerAddFleetServer: string; + settings: string; + settingsFleetServerHostSettings: string; + troubleshooting: string; + elasticAgent: string; + datastreams: string; + datastreamsNamingScheme: string; + upgradeElasticAgent: string; + upgradeElasticAgent712lower: string; + }>; }; } diff --git a/src/core/public/public.api.md b/src/core/public/public.api.md index d3426b50f7614..6cc2b3f321fb7 100644 --- a/src/core/public/public.api.md +++ b/src/core/public/public.api.md @@ -664,6 +664,19 @@ export interface DocLinksStart { readonly plugins: Record; readonly snapshotRestore: Record; readonly ingest: Record; + readonly fleet: Readonly<{ + guide: string; + fleetServer: string; + fleetServerAddFleetServer: string; + settings: string; + settingsFleetServerHostSettings: string; + troubleshooting: string; + elasticAgent: string; + datastreams: string; + datastreamsNamingScheme: string; + upgradeElasticAgent: string; + upgradeElasticAgent712lower: string; + }>; }; } diff --git a/src/core/server/core_usage_data/core_usage_data_service.ts b/src/core/server/core_usage_data/core_usage_data_service.ts index dc24f889cd8dd..afe1b45175f86 100644 --- a/src/core/server/core_usage_data/core_usage_data_service.ts +++ b/src/core/server/core_usage_data/core_usage_data_service.ts @@ -126,14 +126,12 @@ export class CoreUsageDataService implements CoreService objs ): jest.MockedFunction => jest.fn(implementation); +const toMap = (record: Record): Map => new Map(Object.entries(record)); + const expectedContext = { request: expect.any(KibanaRequest), }; @@ -49,10 +51,10 @@ describe('applyExportTransforms', () => { await applyExportTransforms({ request, objects: [foo1, bar1, foo2], - transforms: { + transforms: toMap({ foo: fooTransform, bar: barTransform, - }, + }), }); expect(fooTransform).toHaveBeenCalledTimes(1); @@ -71,10 +73,10 @@ describe('applyExportTransforms', () => { await applyExportTransforms({ request, objects: [foo1], - transforms: { + transforms: toMap({ foo: fooTransform, bar: barTransform, - }, + }), }); expect(fooTransform).toHaveBeenCalledTimes(1); @@ -100,10 +102,10 @@ describe('applyExportTransforms', () => { const result = await applyExportTransforms({ request, objects: [foo1, bar1, foo2], - transforms: { + transforms: toMap({ foo: fooTransform, bar: barTransform, - }, + }), }); expect(result).toEqual([foo1, foo2, dolly1, bar1, hello1]); @@ -123,9 +125,9 @@ describe('applyExportTransforms', () => { const result = await applyExportTransforms({ request, objects: [foo1, foo2, bar1, bar2], - transforms: { + transforms: toMap({ foo: fooTransform, - }, + }), }); expect(result).toEqual([foo1, foo2, dolly1, bar1, bar2]); @@ -150,9 +152,9 @@ describe('applyExportTransforms', () => { const result = await applyExportTransforms({ request, objects: [foo1, foo2], - transforms: { + transforms: toMap({ foo: fooTransform, - }, + }), }); expect(result).toEqual([foo1, foo2].map(disableFoo)); @@ -175,10 +177,10 @@ describe('applyExportTransforms', () => { const result = await applyExportTransforms({ request, objects: [foo1, bar1], - transforms: { + transforms: toMap({ foo: fooTransform, bar: barTransform, - }, + }), }); expect(result).toEqual([foo1, dolly1, bar1, hello1]); @@ -201,10 +203,10 @@ describe('applyExportTransforms', () => { const result = await applyExportTransforms({ request, objects: [foo1, bar1], - transforms: { + transforms: toMap({ foo: fooTransform, bar: barTransform, - }, + }), sortFunction: (obj1, obj2) => (obj1.id > obj2.id ? 1 : -1), }); @@ -223,9 +225,9 @@ describe('applyExportTransforms', () => { applyExportTransforms({ request, objects: [foo1, foo2], - transforms: { + transforms: toMap({ foo: fooTransform, - }, + }), }) ).rejects.toThrowErrorMatchingInlineSnapshot( `"Invalid transform performed on objects to export"` @@ -247,9 +249,9 @@ describe('applyExportTransforms', () => { applyExportTransforms({ request, objects: [foo1, foo2], - transforms: { + transforms: toMap({ foo: fooTransform, - }, + }), }) ).rejects.toThrowErrorMatchingInlineSnapshot( `"Invalid transform performed on objects to export"` @@ -271,9 +273,9 @@ describe('applyExportTransforms', () => { applyExportTransforms({ request, objects: [foo1, foo2], - transforms: { + transforms: toMap({ foo: fooTransform, - }, + }), }) ).rejects.toThrowErrorMatchingInlineSnapshot( `"Invalid transform performed on objects to export"` @@ -291,9 +293,9 @@ describe('applyExportTransforms', () => { applyExportTransforms({ request, objects: [foo1], - transforms: { + transforms: toMap({ foo: fooTransform, - }, + }), }) ).rejects.toThrowErrorMatchingInlineSnapshot(`"Error transforming objects to export"`); }); diff --git a/src/core/server/saved_objects/export/apply_export_transforms.ts b/src/core/server/saved_objects/export/apply_export_transforms.ts index 2a788a32b92f6..78e1dd7d6c117 100644 --- a/src/core/server/saved_objects/export/apply_export_transforms.ts +++ b/src/core/server/saved_objects/export/apply_export_transforms.ts @@ -15,7 +15,7 @@ import { getObjKey, SavedObjectComparator } from './utils'; interface ApplyExportTransformsOptions { objects: SavedObject[]; request: KibanaRequest; - transforms: Record; + transforms: Map; sortFunction?: SavedObjectComparator; } @@ -30,7 +30,7 @@ export const applyExportTransforms = async ({ let finalObjects: SavedObject[] = []; for (const [type, typeObjs] of Object.entries(byType)) { - const typeTransformFn = transforms[type]; + const typeTransformFn = transforms.get(type); if (typeTransformFn) { finalObjects = [ ...finalObjects, diff --git a/src/core/server/saved_objects/export/collect_exported_objects.test.ts b/src/core/server/saved_objects/export/collect_exported_objects.test.ts index 0929ff0d40910..aab9f9134ee2c 100644 --- a/src/core/server/saved_objects/export/collect_exported_objects.test.ts +++ b/src/core/server/saved_objects/export/collect_exported_objects.test.ts @@ -9,9 +9,12 @@ import { applyExportTransformsMock } from './collect_exported_objects.test.mocks'; import { savedObjectsClientMock } from '../../mocks'; import { httpServerMock } from '../../http/http_server.mocks'; +import { loggerMock } from '../../logging/logger.mock'; import { SavedObject, SavedObjectError } from '../../../types'; +import { SavedObjectTypeRegistry } from '../saved_objects_type_registry'; import type { SavedObjectsExportTransform } from './types'; -import { collectExportedObjects } from './collect_exported_objects'; +import { collectExportedObjects, ExclusionReason } from './collect_exported_objects'; +import { SavedObjectsExportablePredicate } from '../types'; const createObject = (parts: Partial): SavedObject => ({ id: 'id', @@ -29,14 +32,48 @@ const createError = (parts: Partial = {}): SavedObjectError => }); const toIdTuple = (obj: SavedObject) => ({ type: obj.type, id: obj.id }); +const toExcludedObject = (obj: SavedObject, reason: ExclusionReason = 'excluded') => ({ + type: obj.type, + id: obj.id, + reason, +}); + +const toMap = (record: Record): Map => new Map(Object.entries(record)); describe('collectExportedObjects', () => { let savedObjectsClient: ReturnType; let request: ReturnType; + let logger: ReturnType; + let typeRegistry: SavedObjectTypeRegistry; + + const registerType = ( + name: string, + { + onExport, + isExportable, + }: { + onExport?: SavedObjectsExportTransform; + isExportable?: SavedObjectsExportablePredicate; + } = {} + ) => { + typeRegistry.registerType({ + name, + hidden: false, + namespaceType: 'single', + mappings: { properties: {} }, + management: { + importableAndExportable: true, + onExport, + isExportable, + }, + }); + }; beforeEach(() => { + typeRegistry = new SavedObjectTypeRegistry(); savedObjectsClient = savedObjectsClientMock.create(); request = httpServerMock.createKibanaRequest(); + logger = loggerMock.create(); applyExportTransformsMock.mockImplementation(({ objects }) => objects); savedObjectsClient.bulkGet.mockResolvedValue({ saved_objects: [] }); }); @@ -58,23 +95,62 @@ describe('collectExportedObjects', () => { }); const fooTransform: SavedObjectsExportTransform = jest.fn(); + registerType('foo', { onExport: fooTransform }); await collectExportedObjects({ objects: [obj1, obj2], savedObjectsClient, request, - exportTransforms: { foo: fooTransform }, + typeRegistry, includeReferences: true, + logger, }); expect(applyExportTransformsMock).toHaveBeenCalledTimes(1); expect(applyExportTransformsMock).toHaveBeenCalledWith({ objects: [obj1, obj2], - transforms: { foo: fooTransform }, + transforms: toMap({ foo: fooTransform }), request, }); }); + it('calls `isExportable` with the correct parameters', async () => { + const foo1 = createObject({ + type: 'foo', + id: '1', + }); + const foo2 = createObject({ + type: 'foo', + id: '2', + }); + const bar3 = createObject({ + type: 'bar', + id: '3', + }); + + const fooExportable: SavedObjectsExportablePredicate = jest.fn().mockReturnValue(true); + registerType('foo', { isExportable: fooExportable }); + + const barExportable: SavedObjectsExportablePredicate = jest.fn().mockReturnValue(true); + registerType('bar', { isExportable: barExportable }); + + await collectExportedObjects({ + objects: [foo1, foo2, bar3], + savedObjectsClient, + request, + typeRegistry, + includeReferences: true, + logger, + }); + + expect(fooExportable).toHaveBeenCalledTimes(2); + expect(fooExportable).toHaveBeenCalledWith(foo1); + expect(fooExportable).toHaveBeenCalledWith(foo2); + + expect(barExportable).toHaveBeenCalledTimes(1); + expect(barExportable).toHaveBeenCalledWith(bar3); + }); + it('returns the collected objects', async () => { const foo1 = createObject({ type: 'foo', @@ -96,6 +172,10 @@ describe('collectExportedObjects', () => { id: '3', }); + registerType('foo'); + registerType('bar'); + registerType('dolly'); + applyExportTransformsMock.mockImplementationOnce(({ objects }) => [...objects, dolly3]); savedObjectsClient.bulkGet.mockResolvedValueOnce({ saved_objects: [bar2], @@ -105,14 +185,220 @@ describe('collectExportedObjects', () => { objects: [foo1], savedObjectsClient, request, - exportTransforms: {}, + typeRegistry, includeReferences: true, + logger, }); expect(missingRefs).toHaveLength(0); expect(objects.map(toIdTuple)).toEqual([foo1, dolly3, bar2].map(toIdTuple)); }); + it('excludes objects filtered by the `isExportable` predicate', async () => { + const foo1 = createObject({ + type: 'foo', + id: '1', + }); + const foo2 = createObject({ + type: 'foo', + id: '2', + }); + const bar3 = createObject({ + type: 'bar', + id: '3', + }); + + registerType('foo', { isExportable: (obj) => obj.id !== '2' }); + registerType('bar', { isExportable: () => true }); + + const { objects, excludedObjects } = await collectExportedObjects({ + objects: [foo1, foo2, bar3], + savedObjectsClient, + request, + typeRegistry, + includeReferences: true, + logger, + }); + + expect(objects).toEqual([foo1, bar3]); + expect(excludedObjects).toEqual([foo2].map((obj) => toExcludedObject(obj))); + }); + + it('excludes objects when the predicate throws', async () => { + const foo1 = createObject({ + type: 'foo', + id: '1', + }); + const foo2 = createObject({ + type: 'foo', + id: '2', + }); + const bar3 = createObject({ + type: 'bar', + id: '3', + }); + + registerType('foo', { + isExportable: (obj) => { + if (obj.id === '1') { + throw new Error('reason'); + } + return true; + }, + }); + registerType('bar', { isExportable: () => true }); + + const { objects, excludedObjects } = await collectExportedObjects({ + objects: [foo1, foo2, bar3], + savedObjectsClient, + request, + typeRegistry, + includeReferences: true, + logger, + }); + + expect(objects).toEqual([foo2, bar3]); + expect(excludedObjects).toEqual( + [foo1].map((obj) => toExcludedObject(obj, 'predicate_error')) + ); + }); + + it('logs an error for each predicate error', async () => { + const foo1 = createObject({ + type: 'foo', + id: '1', + }); + const foo2 = createObject({ + type: 'foo', + id: '2', + }); + const foo3 = createObject({ + type: 'foo', + id: '3', + }); + + registerType('foo', { + isExportable: (obj) => { + if (obj.id !== '2') { + throw new Error('reason'); + } + return true; + }, + }); + + const { objects, excludedObjects } = await collectExportedObjects({ + objects: [foo1, foo2, foo3], + savedObjectsClient, + request, + typeRegistry, + includeReferences: true, + logger, + }); + + expect(objects).toEqual([foo2]); + expect(excludedObjects).toEqual( + [foo1, foo3].map((obj) => toExcludedObject(obj, 'predicate_error')) + ); + + expect(logger.error).toHaveBeenCalledTimes(2); + const logMessages = logger.error.mock.calls.map((call) => call[0]); + + expect( + (logMessages[0] as string).startsWith( + `Error invoking "isExportable" for object foo:1. Error was: Error: reason` + ) + ).toBe(true); + expect( + (logMessages[1] as string).startsWith( + `Error invoking "isExportable" for object foo:3. Error was: Error: reason` + ) + ).toBe(true); + }); + + it('excludes references filtered by the `isExportable` predicate', async () => { + const foo1 = createObject({ + type: 'foo', + id: '1', + references: [ + { + type: 'bar', + id: '2', + name: 'bar-2', + }, + { + type: 'excluded', + id: '1', + name: 'excluded-1', + }, + ], + }); + const bar2 = createObject({ + type: 'bar', + id: '2', + }); + const excluded1 = createObject({ + type: 'excluded', + id: '1', + }); + + registerType('foo'); + registerType('bar'); + registerType('excluded', { isExportable: () => false }); + + savedObjectsClient.bulkGet.mockResolvedValueOnce({ + saved_objects: [bar2, excluded1], + }); + + const { objects, excludedObjects } = await collectExportedObjects({ + objects: [foo1], + savedObjectsClient, + request, + typeRegistry, + includeReferences: true, + logger, + }); + + expect(objects).toEqual([foo1, bar2]); + expect(excludedObjects).toEqual([excluded1].map((obj) => toExcludedObject(obj))); + }); + + it('excludes additional objects filtered by the `isExportable` predicate', async () => { + const foo1 = createObject({ + type: 'foo', + id: '1', + }); + const bar2 = createObject({ + type: 'bar', + id: '2', + }); + const excluded1 = createObject({ + type: 'excluded', + id: '1', + }); + + registerType('foo'); + registerType('bar'); + registerType('excluded', { isExportable: () => false }); + + applyExportTransformsMock.mockImplementationOnce(({ objects }) => [ + ...objects, + bar2, + excluded1, + ]); + + const { objects, excludedObjects } = await collectExportedObjects({ + objects: [foo1], + savedObjectsClient, + request, + typeRegistry, + includeReferences: true, + logger, + }); + + expect(objects).toEqual([foo1, bar2]); + expect(excludedObjects).toEqual([excluded1].map((obj) => toExcludedObject(obj))); + }); + it('returns the missing references', async () => { const foo1 = createObject({ type: 'foo', @@ -163,8 +449,9 @@ describe('collectExportedObjects', () => { objects: [foo1], savedObjectsClient, request, - exportTransforms: {}, + typeRegistry, includeReferences: true, + logger, }); expect(missingRefs).toEqual([missing1, missing2].map(toIdTuple)); @@ -185,8 +472,9 @@ describe('collectExportedObjects', () => { objects: [obj1, obj2], savedObjectsClient, request, - exportTransforms: {}, + typeRegistry, includeReferences: true, + logger, }); expect(missingRefs).toHaveLength(0); @@ -228,8 +516,9 @@ describe('collectExportedObjects', () => { objects: [foo1], savedObjectsClient, request, - exportTransforms: {}, + typeRegistry, includeReferences: true, + logger, }); expect(savedObjectsClient.bulkGet).toHaveBeenCalledTimes(1); @@ -241,12 +530,12 @@ describe('collectExportedObjects', () => { expect(applyExportTransformsMock).toHaveBeenCalledTimes(2); expect(applyExportTransformsMock).toHaveBeenCalledWith({ objects: [foo1], - transforms: {}, + transforms: toMap({}), request, }); expect(applyExportTransformsMock).toHaveBeenCalledWith({ objects: [bar2], - transforms: {}, + transforms: toMap({}), request, }); }); @@ -302,8 +591,9 @@ describe('collectExportedObjects', () => { objects: [foo1], savedObjectsClient, request, - exportTransforms: {}, + typeRegistry, includeReferences: true, + logger, }); expect(savedObjectsClient.bulkGet).toHaveBeenCalledTimes(2); @@ -366,8 +656,9 @@ describe('collectExportedObjects', () => { objects: [foo1, bar2], savedObjectsClient, request, - exportTransforms: {}, + typeRegistry, includeReferences: true, + logger, }); expect(savedObjectsClient.bulkGet).toHaveBeenCalledTimes(1); @@ -411,8 +702,9 @@ describe('collectExportedObjects', () => { objects: [foo1], savedObjectsClient, request, - exportTransforms: {}, + typeRegistry, includeReferences: true, + logger, }); expect(savedObjectsClient.bulkGet).toHaveBeenCalledTimes(1); @@ -474,8 +766,9 @@ describe('collectExportedObjects', () => { objects: [foo1], savedObjectsClient, request, - exportTransforms: {}, + typeRegistry, includeReferences: true, + logger, }); expect(savedObjectsClient.bulkGet).toHaveBeenCalledTimes(2); @@ -490,6 +783,67 @@ describe('collectExportedObjects', () => { expect.any(Object) ); }); + + it('excludes references filtered by the `isExportable` predicate for additional objects returned by the export transform', async () => { + const foo1 = createObject({ + type: 'foo', + id: '1', + }); + const bar2 = createObject({ + type: 'bar', + id: '2', + references: [ + { + type: 'dolly', + id: '3', + name: 'dolly-3', + }, + { + type: 'baz', + id: '4', + name: 'baz-4', + }, + ], + }); + const dolly3 = createObject({ + type: 'dolly', + id: '3', + references: [ + { + type: 'baz', + id: '4', + name: 'baz-4', + }, + ], + }); + const baz4 = createObject({ + type: 'baz', + id: '4', + }); + + registerType('foo'); + registerType('bar'); + registerType('dolly'); + registerType('baz', { isExportable: () => false }); + + applyExportTransformsMock.mockImplementationOnce(({ objects }) => [...objects, bar2]); + + savedObjectsClient.bulkGet.mockResolvedValueOnce({ + saved_objects: [dolly3, baz4], + }); + + const { objects, excludedObjects } = await collectExportedObjects({ + objects: [foo1], + savedObjectsClient, + request, + typeRegistry, + includeReferences: true, + logger, + }); + + expect(objects).toEqual([foo1, bar2, dolly3]); + expect(excludedObjects).toEqual([baz4].map((obj) => toExcludedObject(obj))); + }); }); describe('when `includeReferences` is `false`', () => { @@ -510,8 +864,9 @@ describe('collectExportedObjects', () => { objects: [obj1], savedObjectsClient, request, - exportTransforms: {}, + typeRegistry, includeReferences: false, + logger, }); expect(missingRefs).toHaveLength(0); diff --git a/src/core/server/saved_objects/export/collect_exported_objects.ts b/src/core/server/saved_objects/export/collect_exported_objects.ts index d45782a83c284..4789fd3bff67f 100644 --- a/src/core/server/saved_objects/export/collect_exported_objects.ts +++ b/src/core/server/saved_objects/export/collect_exported_objects.ts @@ -8,7 +8,9 @@ import type { SavedObject } from '../../../types'; import type { KibanaRequest } from '../../http'; -import { SavedObjectsClientContract } from '../types'; +import type { Logger } from '../../logging'; +import { SavedObjectsClientContract, SavedObjectsExportablePredicate } from '../types'; +import { ISavedObjectTypeRegistry } from '../saved_objects_type_registry'; import type { SavedObjectsExportTransform } from './types'; import { applyExportTransforms } from './apply_export_transforms'; @@ -22,41 +24,80 @@ interface CollectExportedObjectOptions { /** The http request initiating the export. */ request: KibanaRequest; /** export transform per type */ - exportTransforms: Record; + typeRegistry: ISavedObjectTypeRegistry; + /** logger to use to log potential errors */ + logger: Logger; } interface CollectExportedObjectResult { objects: SavedObject[]; + excludedObjects: ExcludedObject[]; missingRefs: CollectedReference[]; } +interface ExcludedObject { + id: string; + type: string; + reason: ExclusionReason; +} + +export type ExclusionReason = 'predicate_error' | 'excluded'; + export const collectExportedObjects = async ({ objects, includeReferences = true, namespace, request, - exportTransforms, + typeRegistry, savedObjectsClient, + logger, }: CollectExportedObjectOptions): Promise => { + const exportTransforms = buildTransforms(typeRegistry); + const isExportable = buildIsExportable(typeRegistry); + const collectedObjects: SavedObject[] = []; const collectedMissingRefs: CollectedReference[] = []; + const collectedNonExportableObjects: ExcludedObject[] = []; const alreadyProcessed: Set = new Set(); let currentObjects = objects; do { - const transformed = ( + currentObjects = currentObjects.filter((object) => !alreadyProcessed.has(objKey(object))); + + // first, evict current objects that are not exportable + const { + exportable: untransformedExportableInitialObjects, + nonExportable: nonExportableInitialObjects, + } = await splitByExportability(currentObjects, isExportable, logger); + collectedNonExportableObjects.push(...nonExportableInitialObjects); + nonExportableInitialObjects.forEach((obj) => alreadyProcessed.add(objKey(obj))); + + // second, apply export transforms to exportable objects + const transformedObjects = ( await applyExportTransforms({ request, - objects: currentObjects, + objects: untransformedExportableInitialObjects, transforms: exportTransforms, }) ).filter((object) => !alreadyProcessed.has(objKey(object))); + transformedObjects.forEach((obj) => alreadyProcessed.add(objKey(obj))); - transformed.forEach((obj) => alreadyProcessed.add(objKey(obj))); - collectedObjects.push(...transformed); + // last, evict additional objects that are not exportable + const { included: exportableInitialObjects, excluded: additionalObjects } = splitByKeys( + transformedObjects, + untransformedExportableInitialObjects.map((obj) => objKey(obj)) + ); + const { + exportable: exportableAdditionalObjects, + nonExportable: nonExportableAdditionalObjects, + } = await splitByExportability(additionalObjects, isExportable, logger); + const allExportableObjects = [...exportableInitialObjects, ...exportableAdditionalObjects]; + collectedNonExportableObjects.push(...nonExportableAdditionalObjects); + collectedObjects.push(...allExportableObjects); + // if `includeReferences` is true, recurse on exportable objects' references. if (includeReferences) { - const references = collectReferences(transformed, alreadyProcessed); + const references = collectReferences(allExportableObjects, alreadyProcessed); if (references.length) { const { objects: fetchedObjects, missingRefs } = await fetchReferences({ references, @@ -75,6 +116,7 @@ export const collectExportedObjects = async ({ return { objects: collectedObjects, + excludedObjects: collectedNonExportableObjects, missingRefs: collectedMissingRefs, }; }; @@ -126,3 +168,83 @@ const fetchReferences = async ({ .map((obj) => ({ type: obj.type, id: obj.id })), }; }; + +const buildTransforms = (typeRegistry: ISavedObjectTypeRegistry) => + typeRegistry.getAllTypes().reduce((transformMap, type) => { + if (type.management?.onExport) { + transformMap.set(type.name, type.management.onExport); + } + return transformMap; + }, new Map()); + +const buildIsExportable = ( + typeRegistry: ISavedObjectTypeRegistry +): SavedObjectsExportablePredicate => { + const exportablePerType = typeRegistry.getAllTypes().reduce((exportableMap, type) => { + if (type.management?.isExportable) { + exportableMap.set(type.name, type.management.isExportable); + } + return exportableMap; + }, new Map()); + + return (obj: SavedObject) => { + const typePredicate = exportablePerType.get(obj.type); + return typePredicate ? typePredicate(obj) : true; + }; +}; + +const splitByExportability = ( + objects: SavedObject[], + isExportable: SavedObjectsExportablePredicate, + logger: Logger +) => { + const exportableObjects: SavedObject[] = []; + const nonExportableObjects: ExcludedObject[] = []; + + objects.forEach((obj) => { + try { + const exportable = isExportable(obj); + if (exportable) { + exportableObjects.push(obj); + } else { + nonExportableObjects.push({ + id: obj.id, + type: obj.type, + reason: 'excluded', + }); + } + } catch (e) { + logger.error( + `Error invoking "isExportable" for object ${obj.type}:${obj.id}. Error was: ${ + e.stack ?? e.message + }` + ); + nonExportableObjects.push({ + id: obj.id, + type: obj.type, + reason: 'predicate_error', + }); + } + }); + + return { + exportable: exportableObjects, + nonExportable: nonExportableObjects, + }; +}; + +const splitByKeys = (objects: SavedObject[], keys: ObjectKey[]) => { + const included: SavedObject[] = []; + const excluded: SavedObject[] = []; + objects.forEach((obj) => { + if (keys.includes(objKey(obj))) { + included.push(obj); + } else { + excluded.push(obj); + } + }); + return { + included, + excluded, + }; +}; diff --git a/src/core/server/saved_objects/export/index.ts b/src/core/server/saved_objects/export/index.ts index 4af184e54b49c..d9b48ce431117 100644 --- a/src/core/server/saved_objects/export/index.ts +++ b/src/core/server/saved_objects/export/index.ts @@ -13,6 +13,7 @@ export type { SavedObjectsExportResultDetails, SavedObjectsExportTransformContext, SavedObjectsExportTransform, + SavedObjectsExportExcludedObject, } from './types'; export { SavedObjectsExporter } from './saved_objects_exporter'; export type { ISavedObjectsExporter } from './saved_objects_exporter'; diff --git a/src/core/server/saved_objects/export/saved_objects_exporter.test.ts b/src/core/server/saved_objects/export/saved_objects_exporter.test.ts index 6bdb8003de49d..5968c8dabe8a8 100644 --- a/src/core/server/saved_objects/export/saved_objects_exporter.test.ts +++ b/src/core/server/saved_objects/export/saved_objects_exporter.test.ts @@ -77,32 +77,34 @@ describe('getSortedObjectsForExport()', () => { const response = await readStreamToCompletion(exportStream); expect(response).toMatchInlineSnapshot(` - Array [ - Object { - "attributes": Object {}, - "id": "1", - "references": Array [], - "type": "index-pattern", - }, - Object { - "attributes": Object {}, - "id": "2", - "references": Array [ - Object { - "id": "1", - "name": "name", - "type": "index-pattern", - }, - ], - "type": "search", - }, - Object { - "exportedCount": 2, - "missingRefCount": 0, - "missingReferences": Array [], - }, - ] - `); + Array [ + Object { + "attributes": Object {}, + "id": "1", + "references": Array [], + "type": "index-pattern", + }, + Object { + "attributes": Object {}, + "id": "2", + "references": Array [ + Object { + "id": "1", + "name": "name", + "type": "index-pattern", + }, + ], + "type": "search", + }, + Object { + "excludedObjects": Array [], + "excludedObjectsCount": 0, + "exportedCount": 2, + "missingRefCount": 0, + "missingReferences": Array [], + }, + ] + `); expect(savedObjectsClient.find).toMatchInlineSnapshot(` [MockFunction] { "calls": Array [ @@ -185,6 +187,8 @@ describe('getSortedObjectsForExport()', () => { expect(savedObjectsClient.find).toHaveBeenCalledTimes(1); expect(response[response.length - 1]).toMatchInlineSnapshot(` Object { + "excludedObjects": Array [], + "excludedObjectsCount": 0, "exportedCount": 20, "missingRefCount": 0, "missingReferences": Array [], @@ -269,6 +273,8 @@ describe('getSortedObjectsForExport()', () => { expect(savedObjectsClient.find).toHaveBeenCalledTimes(2); expect(response[response.length - 1]).toMatchInlineSnapshot(` Object { + "excludedObjects": Array [], + "excludedObjectsCount": 0, "exportedCount": 1500, "missingRefCount": 0, "missingReferences": Array [], @@ -422,32 +428,34 @@ describe('getSortedObjectsForExport()', () => { const response = await readStreamToCompletion(exportStream); expect(response).toMatchInlineSnapshot(` - Array [ - Object { - "attributes": Object {}, - "id": "1", - "references": Array [], - "type": "index-pattern", - }, - Object { - "attributes": Object {}, - "id": "2", - "references": Array [ - Object { - "id": "1", - "name": "name", - "type": "index-pattern", - }, - ], - "type": "search", - }, - Object { - "exportedCount": 2, - "missingRefCount": 0, - "missingReferences": Array [], - }, - ] - `); + Array [ + Object { + "attributes": Object {}, + "id": "1", + "references": Array [], + "type": "index-pattern", + }, + Object { + "attributes": Object {}, + "id": "2", + "references": Array [ + Object { + "id": "1", + "name": "name", + "type": "index-pattern", + }, + ], + "type": "search", + }, + Object { + "excludedObjects": Array [], + "excludedObjectsCount": 0, + "exportedCount": 2, + "missingRefCount": 0, + "missingReferences": Array [], + }, + ] + `); expect(savedObjectsClient.find).toMatchInlineSnapshot(` [MockFunction] { "calls": Array [ @@ -579,32 +587,34 @@ describe('getSortedObjectsForExport()', () => { const response = await readStreamToCompletion(exportStream); expect(response).toMatchInlineSnapshot(` - Array [ - Object { - "attributes": Object {}, - "id": "1", - "references": Array [], - "type": "index-pattern", - }, - Object { - "attributes": Object {}, - "id": "2", - "references": Array [ - Object { - "id": "1", - "name": "name", - "type": "index-pattern", - }, - ], - "type": "search", - }, - Object { - "exportedCount": 2, - "missingRefCount": 0, - "missingReferences": Array [], - }, - ] - `); + Array [ + Object { + "attributes": Object {}, + "id": "1", + "references": Array [], + "type": "index-pattern", + }, + Object { + "attributes": Object {}, + "id": "2", + "references": Array [ + Object { + "id": "1", + "name": "name", + "type": "index-pattern", + }, + ], + "type": "search", + }, + Object { + "excludedObjects": Array [], + "excludedObjectsCount": 0, + "exportedCount": 2, + "missingRefCount": 0, + "missingReferences": Array [], + }, + ] + `); expect(savedObjectsClient.find).toMatchInlineSnapshot(` [MockFunction] { "calls": Array [ @@ -674,26 +684,28 @@ describe('getSortedObjectsForExport()', () => { const response = await readStreamToCompletion(exportStream); expect(response).toMatchInlineSnapshot(` - Array [ - Object { - "attributes": Object {}, - "id": "2", - "references": Array [ - Object { - "id": "1", - "name": "name", - "type": "index-pattern", - }, - ], - "type": "search", - }, - Object { - "exportedCount": 1, - "missingRefCount": 0, - "missingReferences": Array [], - }, - ] - `); + Array [ + Object { + "attributes": Object {}, + "id": "2", + "references": Array [ + Object { + "id": "1", + "name": "name", + "type": "index-pattern", + }, + ], + "type": "search", + }, + Object { + "excludedObjects": Array [], + "excludedObjectsCount": 0, + "exportedCount": 1, + "missingRefCount": 0, + "missingReferences": Array [], + }, + ] + `); expect(savedObjectsClient.find).toMatchInlineSnapshot(` [MockFunction] { "calls": Array [ @@ -770,32 +782,34 @@ describe('getSortedObjectsForExport()', () => { const response = await readStreamToCompletion(exportStream); expect(response).toMatchInlineSnapshot(` - Array [ - Object { - "attributes": Object {}, - "id": "1", - "references": Array [], - "type": "index-pattern", - }, - Object { - "attributes": Object {}, - "id": "2", - "references": Array [ - Object { - "id": "1", - "name": "name", - "type": "index-pattern", - }, - ], - "type": "search", - }, - Object { - "exportedCount": 2, - "missingRefCount": 0, - "missingReferences": Array [], - }, - ] - `); + Array [ + Object { + "attributes": Object {}, + "id": "1", + "references": Array [], + "type": "index-pattern", + }, + Object { + "attributes": Object {}, + "id": "2", + "references": Array [ + Object { + "id": "1", + "name": "name", + "type": "index-pattern", + }, + ], + "type": "search", + }, + Object { + "excludedObjects": Array [], + "excludedObjectsCount": 0, + "exportedCount": 2, + "missingRefCount": 0, + "missingReferences": Array [], + }, + ] + `); expect(savedObjectsClient.find).toMatchInlineSnapshot(` [MockFunction] { "calls": Array [ @@ -929,38 +943,40 @@ describe('getSortedObjectsForExport()', () => { }); const response = await readStreamToCompletion(exportStream); expect(response).toMatchInlineSnapshot(` - Array [ - Object { - "attributes": Object { - "name": "foo", - }, - "id": "1", - "references": Array [], - "type": "index-pattern", - }, - Object { - "attributes": Object { - "name": "bar", - }, - "id": "2", - "references": Array [], - "type": "index-pattern", - }, - Object { - "attributes": Object { - "name": "baz", - }, - "id": "3", - "references": Array [], - "type": "index-pattern", - }, - Object { - "exportedCount": 3, - "missingRefCount": 0, - "missingReferences": Array [], - }, - ] - `); + Array [ + Object { + "attributes": Object { + "name": "foo", + }, + "id": "1", + "references": Array [], + "type": "index-pattern", + }, + Object { + "attributes": Object { + "name": "bar", + }, + "id": "2", + "references": Array [], + "type": "index-pattern", + }, + Object { + "attributes": Object { + "name": "baz", + }, + "id": "3", + "references": Array [], + "type": "index-pattern", + }, + Object { + "excludedObjects": Array [], + "excludedObjectsCount": 0, + "exportedCount": 3, + "missingRefCount": 0, + "missingReferences": Array [], + }, + ] + `); }); }); @@ -1003,32 +1019,34 @@ describe('getSortedObjectsForExport()', () => { }); const response = await readStreamToCompletion(exportStream); expect(response).toMatchInlineSnapshot(` - Array [ - Object { - "attributes": Object {}, - "id": "1", - "references": Array [], - "type": "index-pattern", - }, - Object { - "attributes": Object {}, - "id": "2", - "references": Array [ - Object { - "id": "1", - "name": "name", - "type": "index-pattern", - }, - ], - "type": "search", - }, - Object { - "exportedCount": 2, - "missingRefCount": 0, - "missingReferences": Array [], - }, - ] - `); + Array [ + Object { + "attributes": Object {}, + "id": "1", + "references": Array [], + "type": "index-pattern", + }, + Object { + "attributes": Object {}, + "id": "2", + "references": Array [ + Object { + "id": "1", + "name": "name", + "type": "index-pattern", + }, + ], + "type": "search", + }, + Object { + "excludedObjects": Array [], + "excludedObjectsCount": 0, + "exportedCount": 2, + "missingRefCount": 0, + "missingReferences": Array [], + }, + ] + `); expect(savedObjectsClient.bulkGet).toMatchInlineSnapshot(` [MockFunction] { "calls": Array [ @@ -1211,32 +1229,34 @@ describe('getSortedObjectsForExport()', () => { }); const response = await readStreamToCompletion(exportStream); expect(response).toMatchInlineSnapshot(` - Array [ - Object { - "attributes": Object {}, - "id": "1", - "references": Array [], - "type": "index-pattern", - }, - Object { - "attributes": Object {}, - "id": "2", - "references": Array [ - Object { - "id": "1", - "name": "name", - "type": "index-pattern", - }, - ], - "type": "search", - }, - Object { - "exportedCount": 2, - "missingRefCount": 0, - "missingReferences": Array [], - }, - ] - `); + Array [ + Object { + "attributes": Object {}, + "id": "1", + "references": Array [], + "type": "index-pattern", + }, + Object { + "attributes": Object {}, + "id": "2", + "references": Array [ + Object { + "id": "1", + "name": "name", + "type": "index-pattern", + }, + ], + "type": "search", + }, + Object { + "excludedObjects": Array [], + "excludedObjectsCount": 0, + "exportedCount": 2, + "missingRefCount": 0, + "missingReferences": Array [], + }, + ] + `); expect(savedObjectsClient.bulkGet).toMatchInlineSnapshot(` [MockFunction] { "calls": Array [ diff --git a/src/core/server/saved_objects/export/saved_objects_exporter.ts b/src/core/server/saved_objects/export/saved_objects_exporter.ts index 9d56bb4872a6d..211dcdc4ee62d 100644 --- a/src/core/server/saved_objects/export/saved_objects_exporter.ts +++ b/src/core/server/saved_objects/export/saved_objects_exporter.ts @@ -18,7 +18,6 @@ import { SavedObjectExportBaseOptions, SavedObjectsExportByObjectOptions, SavedObjectsExportByTypeOptions, - SavedObjectsExportTransform, } from './types'; import { SavedObjectsExportError } from './errors'; import { collectExportedObjects } from './collect_exported_objects'; @@ -34,8 +33,8 @@ export type ISavedObjectsExporter = PublicMethodsOf; */ export class SavedObjectsExporter { readonly #savedObjectsClient: SavedObjectsClientContract; - readonly #exportTransforms: Record; readonly #exportSizeLimit: number; + readonly #typeRegistry: ISavedObjectTypeRegistry; readonly #log: Logger; constructor({ @@ -52,15 +51,7 @@ export class SavedObjectsExporter { this.#log = logger; this.#savedObjectsClient = savedObjectsClient; this.#exportSizeLimit = exportSizeLimit; - this.#exportTransforms = typeRegistry.getAllTypes().reduce((transforms, type) => { - if (type.management?.onExport) { - return { - ...transforms, - [type.name]: type.management.onExport, - }; - } - return transforms; - }, {} as Record); + this.#typeRegistry = typeRegistry; } /** @@ -121,13 +112,15 @@ export class SavedObjectsExporter { const { objects: collectedObjects, missingRefs: missingReferences, + excludedObjects, } = await collectExportedObjects({ objects: savedObjects, includeReferences: includeReferencesDeep, namespace, request, - exportTransforms: this.#exportTransforms, + typeRegistry: this.#typeRegistry, savedObjectsClient: this.#savedObjectsClient, + logger: this.#log, }); // sort with the provided sort function then with the default export sorting @@ -142,6 +135,8 @@ export class SavedObjectsExporter { exportedCount: exportedObjects.length, missingRefCount: missingReferences.length, missingReferences, + excludedObjectsCount: excludedObjects.length, + excludedObjects, }; this.#log.debug(`Exporting [${redactedObjects.length}] saved objects.`); return createListStream([...redactedObjects, ...(excludeExportDetails ? [] : [exportDetails])]); diff --git a/src/core/server/saved_objects/export/types.ts b/src/core/server/saved_objects/export/types.ts index 7891af6df5b1b..a805ec3a06c1b 100644 --- a/src/core/server/saved_objects/export/types.ts +++ b/src/core/server/saved_objects/export/types.ts @@ -72,6 +72,20 @@ export interface SavedObjectsExportResultDetails { /** the missing reference type. */ type: string; }>; + /** number of objects that were excluded from the export */ + excludedObjectsCount: number; + /** excluded objects details */ + excludedObjects: SavedObjectsExportExcludedObject[]; +} + +/** @public */ +export interface SavedObjectsExportExcludedObject { + /** id of the excluded object */ + id: string; + /** type of the excluded object */ + type: string; + /** optional cause of the exclusion */ + reason?: string; } /** @@ -158,7 +172,7 @@ export interface SavedObjectsExportTransformContext { * * @public */ -export type SavedObjectsExportTransform = ( +export type SavedObjectsExportTransform = ( context: SavedObjectsExportTransformContext, objects: Array> ) => SavedObject[] | Promise; diff --git a/src/core/server/saved_objects/index.ts b/src/core/server/saved_objects/index.ts index b1b1584d7de88..5f853d49219dc 100644 --- a/src/core/server/saved_objects/index.ts +++ b/src/core/server/saved_objects/index.ts @@ -41,6 +41,7 @@ export type { SavedObjectsExportError, SavedObjectsExportTransformContext, SavedObjectsExportTransform, + SavedObjectsExportExcludedObject, } from './export'; export { SavedObjectsSerializer } from './serialization'; 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 79f5bd09889db..87b8ee0809064 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 @@ -748,7 +748,8 @@ describe('DocumentMigrator', () => { migrator.migrate(_.cloneDeep(failedDoc)); expect('Did not throw').toEqual('But it should have!'); } catch (error) { - expect(error.message).toBe('Dang diggity!'); + expect(error.message).toEqual('Migration function for version 1.2.3 threw an error'); + expect(error.stack.includes(`Caused by:\nError: Dang diggity!`)).toBe(true); expect(error).toBeInstanceOf(TransformSavedObjectDocumentError); } }); 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 a32cc999c5559..de8adc23996fd 100644 --- a/src/core/server/saved_objects/migrations/core/document_migrator.ts +++ b/src/core/server/saved_objects/migrations/core/document_migrator.ts @@ -260,6 +260,7 @@ function validateMigrationsMapObject( throw new Error(`${prefix} Got ${obj}.`); } } + function assertValidSemver(version: string, type: string) { if (!Semver.valid(version)) { throw new Error( @@ -272,6 +273,7 @@ function validateMigrationsMapObject( ); } } + function assertValidTransform(fn: any, version: string, type: string) { if (typeof fn !== 'function') { throw new Error(`Invalid migration ${type}.${version}: expected a function, but got ${fn}.`); @@ -680,7 +682,7 @@ function wrapWithTry( return { transformedDoc: result, additionalDocs: [] }; } catch (error) { log.error(error); - throw new TransformSavedObjectDocumentError(error); + throw new TransformSavedObjectDocumentError(error, version); } }; } diff --git a/src/core/server/saved_objects/migrations/core/index_migrator.ts b/src/core/server/saved_objects/migrations/core/index_migrator.ts index 14dba1db9b624..0ec6fe89de1f1 100644 --- a/src/core/server/saved_objects/migrations/core/index_migrator.ts +++ b/src/core/server/saved_objects/migrations/core/index_migrator.ts @@ -187,12 +187,8 @@ async function migrateSourceToDest(context: Context) { await Index.write( client, dest.indexName, - await migrateRawDocs( - serializer, - documentMigrator.migrateAndConvert, - // @ts-expect-error @elastic/elasticsearch `Hit._id` may be a string | number in ES, but we always expect strings in the SO index. - docs - ) + // @ts-expect-error @elastic/elasticsearch _source is optional + await migrateRawDocs(serializer, documentMigrator.migrateAndConvert, 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 7a6f72a881cd6..0481e6118acb0 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 @@ -233,7 +233,7 @@ describe('migrateRawDocsSafely', () => { 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(new Error('error during transform')); + throw new TransformSavedObjectDocumentError(new Error('error during transform'), '8.0.0'); }); const task = migrateRawDocsSafely( new SavedObjectsSerializer(new SavedObjectTypeRegistry()), @@ -247,7 +247,7 @@ describe('migrateRawDocsSafely', () => { expect(result.left.transformErrors.length).toEqual(1); expect(result.left.transformErrors[0]).toMatchInlineSnapshot(` Object { - "err": [Error: error during transform], + "err": [Error: Migration function for version 8.0.0 threw an error], "rawId": "a:b", } `); 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 index 1efb1bd726216..66ee385b44f46 100644 --- 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 @@ -10,10 +10,38 @@ import { TransformSavedObjectDocumentError } from './transform_saved_object_docu describe('TransformSavedObjectDocumentError', () => { it('is a special error', () => { const originalError = new Error('Dang diggity!'); - const err = new TransformSavedObjectDocumentError(originalError); + const err = new TransformSavedObjectDocumentError(originalError, '8.0.0'); + expect(err).toBeInstanceOf(TransformSavedObjectDocumentError); expect(err.stack).not.toBeNull(); expect(err.originalError).toBe(originalError); - expect(err.message).toMatchInlineSnapshot(`"Dang diggity!"`); + expect(err.message).toEqual(`Migration function for version 8.0.0 threw an error`); + }); + + it('adds the stack from the original error', () => { + const originalError = new Error('Some went wrong'); + originalError.stack = 'some stack trace'; + + const err = new TransformSavedObjectDocumentError(originalError, '8.0.0'); + const stackLines = err.stack!.split('\n'); + const stackLength = stackLines.length; + + expect(stackLength).toBeGreaterThan(3); + expect(stackLines[0]).toEqual(`Error: Migration function for version 8.0.0 threw an error`); + expect(stackLines[stackLength - 2]).toEqual(`Caused by:`); + expect(stackLines[stackLength - 1]).toEqual(`some stack trace`); + }); + + it('uses the message if the original error does not have a stack', () => { + const originalError = new Error('Some went wrong'); + delete originalError.stack; + + const err = new TransformSavedObjectDocumentError(originalError, '8.0.0'); + const stackLines = err.stack!.split('\n'); + const stackLength = stackLines.length; + + expect(stackLength).toBeGreaterThan(3); + expect(stackLines[stackLength - 2]).toEqual(`Caused by:`); + expect(stackLines[stackLength - 1]).toEqual(`Some went wrong`); }); }); 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 index 2dc553545a08d..11ad643687d85 100644 --- 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 @@ -10,9 +10,13 @@ * 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 */ - export class TransformSavedObjectDocumentError extends Error { - constructor(public readonly originalError: Error) { - super(`${originalError.message}`); + constructor(public readonly originalError: Error, public readonly version: string) { + super(`Migration function for version ${version} threw an error`); + appendCauseStack(this, originalError); } } + +const appendCauseStack = (error: Error, cause: Error) => { + error.stack = (error.stack ?? '') + `\nCaused by:\n${cause.stack ?? cause.message}`; +}; diff --git a/src/core/server/saved_objects/migrationsv2/integration_tests/archives/8.0.0_document_migration_failure.zip b/src/core/server/saved_objects/migrationsv2/integration_tests/archives/8.0.0_document_migration_failure.zip new file mode 100644 index 0000000000000..9dc4de75c5d98 Binary files /dev/null and b/src/core/server/saved_objects/migrationsv2/integration_tests/archives/8.0.0_document_migration_failure.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 83d97555a4798..3bbdc27e1dd2f 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 @@ -17,6 +17,7 @@ const logFilePath = Path.join(__dirname, 'cleanup_test.log'); const asyncUnlink = Util.promisify(Fs.unlink); const asyncReadFile = Util.promisify(Fs.readFile); + async function removeLogFile() { // ignore errors if it doesn't exist await asyncUnlink(logFilePath).catch(() => void 0); @@ -99,9 +100,10 @@ describe('migration v2', () => { esServer = await startES(); await root.setup(); - await expect(root.start()).rejects.toThrow( - '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.' - ); + await expect(root.start()).rejects.toThrowErrorMatchingInlineSnapshot(` + "Unable to complete saved object migrations for the [.kibana] index: Migrations failed. Reason: 1 corrupt saved object documents were found: index-pattern:test_index* + To allow migrations to proceed, please delete or fix these documents." + `); const logFileContent = await asyncReadFile(logFilePath, 'utf-8'); const records = logFileContent 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 index 9a09fb47d0609..7561536b1ed4b 100644 --- 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 @@ -15,6 +15,7 @@ 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); @@ -110,11 +111,13 @@ describe('migration v2 with corrupt saved object documents', () => { const errorMessage = err.message; expect( errorMessage.startsWith( - 'Unable to complete saved object migrations for the [.kibana] index: Migrations failed. Reason: Corrupt saved object documents: ' + 'Unable to complete saved object migrations for the [.kibana] index: Migrations failed. Reason: 19 corrupt saved object documents were found: ' ) ).toBeTruthy(); expect( - errorMessage.endsWith(' To allow migrations to proceed, please delete these documents.') + errorMessage.endsWith( + 'To allow migrations to proceed, please delete or fix these documents.' + ) ).toBeTruthy(); const expectedCorruptDocIds = [ '"foo:my_name"', diff --git a/src/core/server/saved_objects/migrationsv2/integration_tests/migration_7_13_0_transform_failures.test.ts b/src/core/server/saved_objects/migrationsv2/integration_tests/migration_7_13_0_transform_failures.test.ts index c014f7de395e0..73c7016d32c56 100644 --- a/src/core/server/saved_objects/migrationsv2/integration_tests/migration_7_13_0_transform_failures.test.ts +++ b/src/core/server/saved_objects/migrationsv2/integration_tests/migration_7_13_0_transform_failures.test.ts @@ -15,6 +15,7 @@ import { Root } from '../../../root'; const logFilePath = Path.join(__dirname, '7_13_corrupt_transform_failures_test.log'); const asyncUnlink = Util.promisify(Fs.unlink); + async function removeLogFile() { // ignore errors if it doesn't exist await asyncUnlink(logFilePath).catch(() => void 0); @@ -98,11 +99,13 @@ describe('migration v2', () => { const errorMessage = err.message; expect( errorMessage.startsWith( - 'Unable to complete saved object migrations for the [.kibana] index: Migrations failed. Reason: Corrupt saved object documents: ' + 'Unable to complete saved object migrations for the [.kibana] index: Migrations failed. Reason: 7 corrupt saved object documents were found: ' ) ).toBeTruthy(); expect( - errorMessage.endsWith(' To allow migrations to proceed, please delete these documents.') + errorMessage.endsWith( + 'To allow migrations to proceed, please delete or fix these documents.' + ) ).toBeTruthy(); const expectedCorruptDocIds = [ @@ -117,9 +120,13 @@ describe('migration v2', () => { for (const corruptDocId of expectedCorruptDocIds) { expect(errorMessage.includes(corruptDocId)).toBeTruthy(); } - const expectedTransformErrorMessage = - 'Transformation errors: space:default: Document "default" has property "space" which belongs to a more recent version of Kibana [6.6.0]. The last known version is [undefined]'; - expect(errorMessage.includes(expectedTransformErrorMessage)).toBeTruthy(); + + expect(errorMessage.includes('7 transformation errors were encountered:')).toBeTruthy(); + expect( + errorMessage.includes( + 'space:default: Error: Document "default" has property "space" which belongs to a more recent version of Kibana [6.6.0]. The last known version is [undefined]' + ) + ).toBeTruthy(); } }); }); diff --git a/src/core/server/saved_objects/migrationsv2/integration_tests/type_migration_failure.test.ts b/src/core/server/saved_objects/migrationsv2/integration_tests/type_migration_failure.test.ts new file mode 100644 index 0000000000000..ac40933d2a7de --- /dev/null +++ b/src/core/server/saved_objects/migrationsv2/integration_tests/type_migration_failure.test.ts @@ -0,0 +1,201 @@ +/* + * Copyright 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', + // contains 4 `foo` objects, all with a `migrationVersion` of `7.13.0` + // - foo:1 and foo:2 have correct values for their `number` property (13 and 42 respectively) + // - foo:3 and foo:4 don't have the property, and will fail during the `7.14.0` registered migration + // contains migrated index with 8.0 aliases to skip migration, but run outdated doc search + dataArchive: Path.join(__dirname, 'archives', '8.0.0_document_migration_failure.zip'), + }, + }, + }); + + root = createRoot(); + + esServer = await startES(); + const coreSetup = await root.setup(); + + coreSetup.savedObjects.registerType({ + name: 'foo', + hidden: false, + mappings: { + properties: { + number: { type: 'integer' }, + }, + }, + namespaceType: 'agnostic', + migrations: { + '7.14.0': (doc) => { + if (doc.attributes.number === undefined) { + throw new Error('"number" attribute should be present'); + } + doc.attributes = { + ...doc.attributes, + number: doc.attributes.number + 9000, + }; + return doc; + }, + }, + }); + + try { + await root.start(); + expect(true).toEqual(false); + } catch (err) { + const errorMessage = err.message; + const errorLines = errorMessage.split('\n'); + + expect(errorLines[0]).toEqual( + `Unable to complete saved object migrations for the [.kibana] index: Migrations failed. Reason: 2 transformation errors were encountered:` + ); + expect(errorLines[errorLines.length - 1]).toEqual( + `To allow migrations to proceed, please delete or fix these documents.` + ); + + expectMatchOrder(errorLines, [ + { + mode: 'equal', + value: '- foo:3: Error: Migration function for version 7.14.0 threw an error', + }, + { + mode: 'contain', + value: 'at transform', + }, + { + mode: 'equal', + value: 'Caused by:', + }, + { + mode: 'equal', + value: 'Error: "number" attribute should be present', + }, + { + mode: 'contain', + value: 'at migrationFn', + }, + { + mode: 'equal', + value: '- foo:4: Error: Migration function for version 7.14.0 threw an error', + }, + { + mode: 'contain', + value: 'at transform', + }, + { + mode: 'equal', + value: 'Caused by:', + }, + { + mode: 'equal', + value: 'Error: "number" attribute should be present', + }, + { + mode: 'contain', + value: 'at migrationFn', + }, + ]); + } + }); +}); + +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, + } + ); +} + +type FindInOrderPattern = { mode: 'equal'; value: string } | { mode: 'contain'; value: string }; + +const expectMatchOrder = (lines: string[], patterns: FindInOrderPattern[]) => { + let lineIdx = 0; + let patternIdx = 0; + + while (lineIdx < lines.length && patternIdx < patterns.length) { + const line = lines[lineIdx]; + const pattern = patterns[patternIdx]; + if (lineMatch(line, pattern)) { + patternIdx++; + } + lineIdx++; + } + + expect(patternIdx).toEqual(patterns.length); +}; + +const lineMatch = (line: string, pattern: FindInOrderPattern) => { + if (pattern.mode === 'contain') { + return line.trim().includes(pattern.value.trim()); + } + return line.trim() === pattern.value.trim(); +}; diff --git a/src/core/server/saved_objects/migrationsv2/model.test.ts b/src/core/server/saved_objects/migrationsv2/model.test.ts index 86dc590aabdad..ea8bc7f110735 100644 --- a/src/core/server/saved_objects/migrationsv2/model.test.ts +++ b/src/core/server/saved_objects/migrationsv2/model.test.ts @@ -863,9 +863,10 @@ describe('migrations v2 model', () => { }); 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."` - ); + expect(newState.reason).toMatchInlineSnapshot(` + "Migrations failed. Reason: 1 corrupt saved object documents were found: a:b + To allow migrations to proceed, please delete or fix these documents." + `); expect(newState.logs).toStrictEqual([]); // No logs because no hits }); }); @@ -1158,7 +1159,10 @@ describe('migrations v2 model', () => { 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(originalTransformError); + const transFormErr = new TransformSavedObjectDocumentError( + originalTransformError, + '7.11.0' + ); const transformationErrors = [ { rawId: 'bob:tail', err: transFormErr }, ] as TransformErrorObjects[]; @@ -1175,8 +1179,8 @@ describe('migrations v2 model', () => { 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('1 corrupt saved object documents were found')).toBe(true); + expect(newState.reason.includes('1 transformation errors were encountered')).toBe(true); expect(newState.reason.includes('bob:tail')).toBe(true); expect(newState.logs).toStrictEqual([]); // No logs because no hits }); @@ -1222,7 +1226,7 @@ describe('migrations v2 model', () => { const outdatedDocuments = [{ _id: '1', _source: { type: 'vis' } }]; const corruptDocumentIds = ['a:somethingelse']; const originalTransformError = new Error('Dang diggity!'); - const transFormErr = new TransformSavedObjectDocumentError(originalTransformError); + const transFormErr = new TransformSavedObjectDocumentError(originalTransformError, '7.11.0'); const transformationErrors = [ { rawId: 'bob:tail', err: transFormErr }, ] as TransformErrorObjects[]; diff --git a/src/core/server/saved_objects/migrationsv2/model.ts b/src/core/server/saved_objects/migrationsv2/model.ts index 252d7424c339c..6aa119af2f6c8 100644 --- a/src/core/server/saved_objects/migrationsv2/model.ts +++ b/src/core/server/saved_objects/migrationsv2/model.ts @@ -112,17 +112,22 @@ function extractTransformFailuresReason( ): string { const corruptDocumentIdReason = corruptDocumentIds.length > 0 - ? ` Corrupt saved object documents: ${corruptDocumentIds.join(',')}` + ? ` ${ + corruptDocumentIds.length + } corrupt saved object documents were found: ${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.length} transformation errors were encountered:\n ` + transformErrors - .map((errObj) => `${errObj.rawId}: ${errObj.err.message}\n ${errObj.err.stack ?? ''}`) - .join('/n') + .map((errObj) => `- ${errObj.rawId}: ${errObj.err.stack ?? errObj.err.message}\n`) + .join('') : ''; - return `Migrations failed. Reason:${corruptDocumentIdReason}${transformErrorsReason}. To allow migrations to proceed, please delete these documents.`; + return ( + `Migrations failed. Reason:${corruptDocumentIdReason}${transformErrorsReason}\n` + + `To allow migrations to proceed, please delete or fix these documents.` + ); } const delayRetryState = ( diff --git a/src/core/server/saved_objects/saved_objects_service.ts b/src/core/server/saved_objects/saved_objects_service.ts index b95f187cd44ca..e50c1e540bfaf 100644 --- a/src/core/server/saved_objects/saved_objects_service.ts +++ b/src/core/server/saved_objects/saved_objects_service.ts @@ -141,7 +141,7 @@ export interface SavedObjectsServiceSetup { * } * ``` */ - registerType: (type: SavedObjectsType) => void; + registerType: (type: SavedObjectsType) => void; } /** diff --git a/src/core/server/saved_objects/service/lib/repository.ts b/src/core/server/saved_objects/service/lib/repository.ts index 6b51bd57248a1..1577f773434b9 100644 --- a/src/core/server/saved_objects/service/lib/repository.ts +++ b/src/core/server/saved_objects/service/lib/repository.ts @@ -897,10 +897,10 @@ export class SavedObjectsRepository { total: body.hits.total, saved_objects: body.hits.hits.map( (hit: estypes.SearchHit): SavedObjectsFindResult => ({ - // @ts-expect-error @elastic/elasticsearch declared Id as string | number + // @ts-expect-error @elastic/elasticsearch _source is optional ...this._rawToSavedObject(hit), score: hit._score!, - // @ts-expect-error @elastic/elasticsearch declared sort as string | number + // @ts-expect-error @elastic/elasticsearch _source is optional sort: hit.sort, }) ), diff --git a/src/core/server/saved_objects/types.ts b/src/core/server/saved_objects/types.ts index 964ba671b5964..1bb214de701e2 100644 --- a/src/core/server/saved_objects/types.ts +++ b/src/core/server/saved_objects/types.ts @@ -253,7 +253,7 @@ export type SavedObjectsNamespaceType = 'single' | 'multiple' | 'multiple-isolat * * @public */ -export interface SavedObjectsType { +export interface SavedObjectsType { /** * The name of the type, which is also used as the internal id. */ @@ -337,7 +337,7 @@ export interface SavedObjectsType { /** * An optional {@link SavedObjectsTypeManagementDefinition | saved objects management section} definition for the type. */ - management?: SavedObjectsTypeManagementDefinition; + management?: SavedObjectsTypeManagementDefinition; } /** @@ -345,7 +345,7 @@ export interface SavedObjectsType { * * @public */ -export interface SavedObjectsTypeManagementDefinition { +export interface SavedObjectsTypeManagementDefinition { /** * Is the type importable or exportable. Defaults to `false`. */ @@ -363,12 +363,12 @@ export interface SavedObjectsTypeManagementDefinition { * Function returning the title to display in the management table. * If not defined, will use the object's type and id to generate a label. */ - getTitle?: (savedObject: SavedObject) => string; + getTitle?: (savedObject: SavedObject) => string; /** * Function returning the url to use to redirect to the editing page of this object. * If not defined, editing will not be allowed. */ - getEditUrl?: (savedObject: SavedObject) => string; + getEditUrl?: (savedObject: SavedObject) => string; /** * Function returning the url to use to redirect to this object from the management section. * If not defined, redirecting to the object will not be allowed. @@ -377,7 +377,9 @@ export interface SavedObjectsTypeManagementDefinition { * the object page, relative to the base path. `uiCapabilitiesPath` is the path to check in the * {@link Capabilities | uiCapabilities} to check if the user has permission to access the object. */ - getInAppUrl?: (savedObject: SavedObject) => { path: string; uiCapabilitiesPath: string }; + getInAppUrl?: ( + savedObject: SavedObject + ) => { path: string; uiCapabilitiesPath: string }; /** * An optional export transform function that can be used transform the objects of the registered type during * the export process. @@ -386,9 +388,14 @@ export interface SavedObjectsTypeManagementDefinition { * * See {@link SavedObjectsExportTransform | the transform type documentation} for more info and examples. * + * When implementing both `isExportable` and `onExport`, it is mandatory that + * `isExportable` returns the same value for an object before and after going + * though the export transform. + * E.g `isExportable(objectBeforeTransform) === isExportable(objectAfterTransform)` + * * @remarks `importableAndExportable` must be `true` to specify this property. */ - onExport?: SavedObjectsExportTransform; + onExport?: SavedObjectsExportTransform; /** * An optional {@link SavedObjectsImportHook | import hook} to use when importing given type. * @@ -431,5 +438,52 @@ export interface SavedObjectsTypeManagementDefinition { * @remarks messages returned in the warnings are user facing and must be translated. * @remarks `importableAndExportable` must be `true` to specify this property. */ - onImport?: SavedObjectsImportHook; + onImport?: SavedObjectsImportHook; + + /** + * Optional hook to specify whether an object should be exportable. + * + * If specified, `isExportable` will be called during export for each + * of this type's objects in the export, and the ones not matching the + * predicate will be excluded from the export. + * + * When implementing both `isExportable` and `onExport`, it is mandatory that + * `isExportable` returns the same value for an object before and after going + * though the export transform. + * E.g `isExportable(objectBeforeTransform) === isExportable(objectAfterTransform)` + * + * @example + * Registering a type with a per-object exportability predicate + * ```ts + * // src/plugins/my_plugin/server/plugin.ts + * import { myType } from './saved_objects'; + * + * export class Plugin() { + * setup: (core: CoreSetup) => { + * core.savedObjects.registerType({ + * ...myType, + * management: { + * ...myType.management, + * isExportable: (object) => { + * if (object.attributes.myCustomAttr === 'foo') { + * return false; + * } + * return true; + * } + * }, + * }); + * } + * } + * ``` + * + * @remarks `importableAndExportable` must be `true` to specify this property. + */ + isExportable?: SavedObjectsExportablePredicate; } + +/** + * @public + */ +export type SavedObjectsExportablePredicate = ( + obj: SavedObject +) => boolean; diff --git a/src/core/server/server.api.md b/src/core/server/server.api.md index ce13174ee19cc..9e7721fde90e7 100644 --- a/src/core/server/server.api.md +++ b/src/core/server/server.api.md @@ -2508,8 +2508,17 @@ export class SavedObjectsExportError extends Error { readonly type: string; } +// @public (undocumented) +export interface SavedObjectsExportExcludedObject { + id: string; + reason?: string; + type: string; +} + // @public export interface SavedObjectsExportResultDetails { + excludedObjects: SavedObjectsExportExcludedObject[]; + excludedObjectsCount: number; exportedCount: number; missingRefCount: number; missingReferences: Array<{ @@ -2519,7 +2528,7 @@ export interface SavedObjectsExportResultDetails { } // @public -export type SavedObjectsExportTransform = (context: SavedObjectsExportTransformContext, objects: Array>) => SavedObject[] | Promise; +export type SavedObjectsExportTransform = (context: SavedObjectsExportTransformContext, objects: Array>) => SavedObject[] | Promise; // @public export interface SavedObjectsExportTransformContext { @@ -2930,7 +2939,7 @@ export class SavedObjectsSerializer { // @public export interface SavedObjectsServiceSetup { addClientWrapper: (priority: number, id: string, factory: SavedObjectsClientWrapperFactory) => void; - registerType: (type: SavedObjectsType) => void; + registerType: (type: SavedObjectsType) => void; setClientFactoryProvider: (clientFactoryProvider: SavedObjectsClientFactoryProvider) => void; } @@ -2956,12 +2965,12 @@ export interface SavedObjectStatusMeta { } // @public (undocumented) -export interface SavedObjectsType { +export interface SavedObjectsType { convertToAliasScript?: string; convertToMultiNamespaceTypeVersion?: string; hidden: boolean; indexPattern?: string; - management?: SavedObjectsTypeManagementDefinition; + management?: SavedObjectsTypeManagementDefinition; mappings: SavedObjectsTypeMappingDefinition; migrations?: SavedObjectMigrationMap | (() => SavedObjectMigrationMap); name: string; @@ -2969,18 +2978,20 @@ export interface SavedObjectsType { } // @public -export interface SavedObjectsTypeManagementDefinition { +export interface SavedObjectsTypeManagementDefinition { defaultSearchField?: string; - getEditUrl?: (savedObject: SavedObject) => string; - getInAppUrl?: (savedObject: SavedObject) => { + getEditUrl?: (savedObject: SavedObject) => string; + getInAppUrl?: (savedObject: SavedObject) => { path: string; uiCapabilitiesPath: string; }; - getTitle?: (savedObject: SavedObject) => string; + getTitle?: (savedObject: SavedObject) => string; icon?: string; importableAndExportable?: boolean; - onExport?: SavedObjectsExportTransform; - onImport?: SavedObjectsImportHook; + // Warning: (ae-forgotten-export) The symbol "SavedObjectsExportablePredicate" needs to be exported by the entry point index.d.ts + isExportable?: SavedObjectsExportablePredicate; + onExport?: SavedObjectsExportTransform; + onImport?: SavedObjectsImportHook; } // @public @@ -3045,11 +3056,11 @@ export class SavedObjectsUtils { // @public export class SavedObjectTypeRegistry { - getAllTypes(): SavedObjectsType[]; - getImportableAndExportableTypes(): SavedObjectsType[]; + getAllTypes(): SavedObjectsType[]; + getImportableAndExportableTypes(): SavedObjectsType[]; getIndex(type: string): string | undefined; - getType(type: string): SavedObjectsType | undefined; - getVisibleTypes(): SavedObjectsType[]; + getType(type: string): SavedObjectsType | undefined; + getVisibleTypes(): SavedObjectsType[]; isHidden(type: string): boolean; isImportableAndExportable(type: string): boolean; isMultiNamespace(type: string): boolean; diff --git a/typings/elasticsearch/index.d.ts b/src/core/types/elasticsearch/index.ts similarity index 94% rename from typings/elasticsearch/index.d.ts rename to src/core/types/elasticsearch/index.ts index 1951434890c50..bec611778e6f5 100644 --- a/typings/elasticsearch/index.d.ts +++ b/src/core/types/elasticsearch/index.ts @@ -29,4 +29,4 @@ export type ESSearchResponse< TOptions extends { restTotalHitsAsInt: boolean } = { restTotalHitsAsInt: false } > = InferSearchResponseOf; -export { InferSearchResponseOf, AggregationResultOf, SearchHit }; +export type { InferSearchResponseOf, AggregationResultOf, SearchHit }; diff --git a/typings/elasticsearch/search.d.ts b/src/core/types/elasticsearch/search.ts similarity index 99% rename from typings/elasticsearch/search.d.ts rename to src/core/types/elasticsearch/search.ts index 36a684fb097a5..0960fb189a341 100644 --- a/typings/elasticsearch/search.d.ts +++ b/src/core/types/elasticsearch/search.ts @@ -417,7 +417,9 @@ export type AggregateOf< { key: string; from?: number; + from_as_string?: string; to?: number; + to_as_string?: string; doc_count: number; }, TAggregationContainer extends { range: { ranges: Array } } diff --git a/src/plugins/dashboard/public/plugin.tsx b/src/plugins/dashboard/public/plugin.tsx index b73fe5f2ba410..b5d6eda71ca4a 100644 --- a/src/plugins/dashboard/public/plugin.tsx +++ b/src/plugins/dashboard/public/plugin.tsx @@ -205,19 +205,6 @@ export class DashboardPlugin }; }; - if (share) { - this.dashboardUrlGenerator = share.urlGenerators.registerUrlGenerator( - createDashboardUrlGenerator(async () => { - const [coreStart, , selfStart] = await core.getStartServices(); - return { - appBasePath: coreStart.application.getUrlForApp('dashboards'), - useHashedUrl: coreStart.uiSettings.get('state:storeInSessionStorage'), - savedDashboardLoader: selfStart.getSavedDashboardLoader(), - }; - }) - ); - } - const { appMounted, appUnMounted, diff --git a/src/plugins/discover/kibana.json b/src/plugins/discover/kibana.json index 6ea22001f5d80..04469e0ef4276 100644 --- a/src/plugins/discover/kibana.json +++ b/src/plugins/discover/kibana.json @@ -16,5 +16,10 @@ "indexPatternFieldEditor" ], "optionalPlugins": ["home", "share", "usageCollection"], - "requiredBundles": ["kibanaUtils", "home", "kibanaReact"] + "requiredBundles": ["kibanaUtils", "home", "kibanaReact"], + "owner": { + "name": "Kibana App", + "githubTeam": "kibana-app" + }, + "description": "This plugin contains the Discover application and the saved search embeddable." } diff --git a/src/plugins/kibana_react/public/code_editor/index.tsx b/src/plugins/kibana_react/public/code_editor/index.tsx index 2440974c3b1d1..9e3824b784219 100644 --- a/src/plugins/kibana_react/public/code_editor/index.tsx +++ b/src/plugins/kibana_react/public/code_editor/index.tsx @@ -17,6 +17,9 @@ 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 { Props } from './code_editor'; +import './register_languages'; + +export * from './languages'; const LazyBaseEditor = React.lazy(() => import('./code_editor')); diff --git a/src/plugins/kibana_react/public/url_template_editor/constants.ts b/src/plugins/kibana_react/public/code_editor/languages/css/constants.ts similarity index 90% rename from src/plugins/kibana_react/public/url_template_editor/constants.ts rename to src/plugins/kibana_react/public/code_editor/languages/css/constants.ts index 6c1a1dbce5d67..2f465775e2a1b 100644 --- a/src/plugins/kibana_react/public/url_template_editor/constants.ts +++ b/src/plugins/kibana_react/public/code_editor/languages/css/constants.ts @@ -6,4 +6,4 @@ * Side Public License, v 1. */ -export const LANG = 'handlebars_url'; +export const LANG = 'css'; diff --git a/src/plugins/kibana_react/public/code_editor/languages/css/index.ts b/src/plugins/kibana_react/public/code_editor/languages/css/index.ts new file mode 100644 index 0000000000000..fa1cbf4808a4e --- /dev/null +++ b/src/plugins/kibana_react/public/code_editor/languages/css/index.ts @@ -0,0 +1,12 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ +import { LangModuleType } from '@kbn/monaco'; +import { lexerRules, languageConfiguration } from './language'; +import { LANG } from './constants'; + +export const Lang: LangModuleType = { ID: LANG, lexerRules, languageConfiguration }; diff --git a/src/plugins/kibana_react/public/code_editor/languages/css/language.ts b/src/plugins/kibana_react/public/code_editor/languages/css/language.ts new file mode 100644 index 0000000000000..5bdd6c8eb8b1f --- /dev/null +++ b/src/plugins/kibana_react/public/code_editor/languages/css/language.ts @@ -0,0 +1,12 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +/* eslint-disable @kbn/eslint/module_migration */ +import { conf, language } from 'monaco-editor/esm/vs/basic-languages/css/css'; + +export { conf as languageConfiguration, language as lexerRules }; diff --git a/src/plugins/kibana_react/public/code_editor/languages/handlebars/constants.ts b/src/plugins/kibana_react/public/code_editor/languages/handlebars/constants.ts new file mode 100644 index 0000000000000..1634c02429f59 --- /dev/null +++ b/src/plugins/kibana_react/public/code_editor/languages/handlebars/constants.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export const LANG = 'handlebars'; diff --git a/src/plugins/kibana_react/public/code_editor/languages/handlebars/index.ts b/src/plugins/kibana_react/public/code_editor/languages/handlebars/index.ts new file mode 100644 index 0000000000000..ff3c08267da9b --- /dev/null +++ b/src/plugins/kibana_react/public/code_editor/languages/handlebars/index.ts @@ -0,0 +1,13 @@ +/* + * Copyright 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 { LangModuleType } from '@kbn/monaco'; +import { languageConfiguration, lexerRules } from './language'; +import { LANG } from './constants'; + +export const Lang: LangModuleType = { ID: LANG, languageConfiguration, lexerRules }; diff --git a/src/plugins/kibana_react/public/url_template_editor/language.ts b/src/plugins/kibana_react/public/code_editor/languages/handlebars/language.ts similarity index 96% rename from src/plugins/kibana_react/public/url_template_editor/language.ts rename to src/plugins/kibana_react/public/code_editor/languages/handlebars/language.ts index 278a7130ad1fa..7f760836088d6 100644 --- a/src/plugins/kibana_react/public/url_template_editor/language.ts +++ b/src/plugins/kibana_react/public/code_editor/languages/handlebars/language.ts @@ -13,7 +13,7 @@ import { monaco } from '@kbn/monaco'; -export const conf: monaco.languages.LanguageConfiguration = { +export const languageConfiguration: monaco.languages.LanguageConfiguration = { wordPattern: /(-?\d*\.\d\w*)|([^\`\~\!\@\$\^\&\*\(\)\=\+\[\{\]\}\\\|\;\:\'\"\,\.\<\>\/\s]+)/g, comments: { @@ -42,7 +42,7 @@ export const conf: monaco.languages.LanguageConfiguration = { ], }; -export const language: monaco.languages.IMonarchLanguage = { +export const lexerRules: monaco.languages.IMonarchLanguage = { // Set defaultToken to invalid to see what you do not tokenize yet. defaultToken: 'invalid', tokenPostfix: '', diff --git a/src/plugins/kibana_react/public/code_editor/languages/index.ts b/src/plugins/kibana_react/public/code_editor/languages/index.ts new file mode 100644 index 0000000000000..ff7da1725fa7f --- /dev/null +++ b/src/plugins/kibana_react/public/code_editor/languages/index.ts @@ -0,0 +1,13 @@ +/* + * Copyright 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 { Lang as CssLang } from './css'; +import { Lang as HandlebarsLang } from './handlebars'; +import { Lang as MarkdownLang } from './markdown'; + +export { CssLang, HandlebarsLang, MarkdownLang }; diff --git a/src/plugins/kibana_react/public/code_editor/languages/markdown/constants.ts b/src/plugins/kibana_react/public/code_editor/languages/markdown/constants.ts new file mode 100644 index 0000000000000..bd8aa23256637 --- /dev/null +++ b/src/plugins/kibana_react/public/code_editor/languages/markdown/constants.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export const LANG = 'markdown'; diff --git a/src/plugins/kibana_react/public/code_editor/languages/markdown/index.ts b/src/plugins/kibana_react/public/code_editor/languages/markdown/index.ts new file mode 100644 index 0000000000000..f501de74debec --- /dev/null +++ b/src/plugins/kibana_react/public/code_editor/languages/markdown/index.ts @@ -0,0 +1,12 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ +import { LangModuleType } from '@kbn/monaco'; +import { languageConfiguration, lexerRules } from './language'; +import { LANG } from './constants'; + +export const Lang: LangModuleType = { ID: LANG, languageConfiguration, lexerRules }; diff --git a/src/plugins/kibana_react/public/code_editor/languages/markdown/language.ts b/src/plugins/kibana_react/public/code_editor/languages/markdown/language.ts new file mode 100644 index 0000000000000..d8a1234fcf191 --- /dev/null +++ b/src/plugins/kibana_react/public/code_editor/languages/markdown/language.ts @@ -0,0 +1,12 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +/* eslint-disable @kbn/eslint/module_migration */ +import { conf, language } from 'monaco-editor/esm/vs/basic-languages/markdown/markdown'; + +export { conf as languageConfiguration, language as lexerRules }; diff --git a/src/plugins/kibana_react/public/code_editor/register_languages.ts b/src/plugins/kibana_react/public/code_editor/register_languages.ts new file mode 100644 index 0000000000000..b4a0f4d53cdf4 --- /dev/null +++ b/src/plugins/kibana_react/public/code_editor/register_languages.ts @@ -0,0 +1,13 @@ +/* + * Copyright 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 { registerLanguage } from '@kbn/monaco'; +import { CssLang, HandlebarsLang, MarkdownLang } from './languages'; + +registerLanguage(CssLang); +registerLanguage(HandlebarsLang); +registerLanguage(MarkdownLang); diff --git a/src/plugins/kibana_react/public/url_template_editor/url_template_editor.tsx b/src/plugins/kibana_react/public/url_template_editor/url_template_editor.tsx index f830b4012976a..0fed4d37e4f7f 100644 --- a/src/plugins/kibana_react/public/url_template_editor/url_template_editor.tsx +++ b/src/plugins/kibana_react/public/url_template_editor/url_template_editor.tsx @@ -9,18 +9,10 @@ import * as React from 'react'; import { monaco } from '@kbn/monaco'; import { Props as CodeEditorProps } from '../code_editor/code_editor'; -import { CodeEditor } from '../code_editor'; -import { LANG } from './constants'; -import { language, conf } from './language'; +import { CodeEditor, HandlebarsLang } from '../code_editor'; import './styles.scss'; -monaco.languages.register({ - id: LANG, -}); -monaco.languages.setMonarchTokensProvider(LANG, language); -monaco.languages.setLanguageConfiguration(LANG, conf); - export interface UrlTemplateEditorVariable { label: string; title?: string; @@ -74,7 +66,7 @@ export const UrlTemplateEditor: React.FC = ({ return; } - const { dispose } = monaco.languages.registerCompletionItemProvider(LANG, { + const { dispose } = monaco.languages.registerCompletionItemProvider(HandlebarsLang.ID, { triggerCharacters: ['{', '/', '?', '&', '='], provideCompletionItems(model, position, context, token) { const { lineNumber } = position; @@ -132,7 +124,7 @@ export const UrlTemplateEditor: React.FC = ({ return (
{ }; const detailsLine = ( exported: number, - missingRefs: SavedObjectsExportResultDetails['missingReferences'] = [] + { + missingRefs = [], + excludedObjects = [], + }: { + missingRefs?: SavedObjectsExportResultDetails['missingReferences']; + excludedObjects?: SavedObjectsExportResultDetails['excludedObjects']; + } = {} ) => { return ( JSON.stringify({ exportedCount: exported, missingRefCount: missingRefs.length, missingReferences: missingRefs, - }) + '\n' + excludedObjectsCount: excludedObjects.length, + excludedObjects, + } as SavedObjectsExportResultDetails) + '\n' ); }; @@ -43,6 +51,8 @@ describe('extractExportDetails', () => { exportedCount: 3, missingRefCount: 0, missingReferences: [], + excludedObjectsCount: 0, + excludedObjects: [], }); }); @@ -51,10 +61,12 @@ describe('extractExportDetails', () => { [ [ objLine('1', 'index-pattern'), - detailsLine(1, [ - { id: '2', type: 'index-pattern' }, - { id: '3', type: 'index-pattern' }, - ]), + detailsLine(1, { + missingRefs: [ + { id: '2', type: 'index-pattern' }, + { id: '3', type: 'index-pattern' }, + ], + }), ].join(''), ], { @@ -71,6 +83,39 @@ describe('extractExportDetails', () => { { id: '2', type: 'index-pattern' }, { id: '3', type: 'index-pattern' }, ], + excludedObjectsCount: 0, + excludedObjects: [], + }); + }); + + it('should properly extract the excluded objects', async () => { + const exportData = new Blob( + [ + [ + objLine('1', 'index-pattern'), + detailsLine(1, { + excludedObjects: [ + { id: '2', type: 'index-pattern', reason: 'foo' }, + { id: '3', type: 'index-pattern' }, + ], + }), + ].join(''), + ], + { + type: 'application/ndjson', + endings: 'transparent', + } + ); + const result = await extractExportDetails(exportData); + expect(result).toEqual({ + exportedCount: 1, + missingRefCount: 0, + missingReferences: [], + excludedObjectsCount: 2, + excludedObjects: [ + { id: '2', type: 'index-pattern', reason: 'foo' }, + { id: '3', type: 'index-pattern' }, + ], }); }); diff --git a/src/plugins/saved_objects_management/public/lib/extract_export_details.ts b/src/plugins/saved_objects_management/public/lib/extract_export_details.ts index 40f8039a8cdae..4d142330dca88 100644 --- a/src/plugins/saved_objects_management/public/lib/extract_export_details.ts +++ b/src/plugins/saved_objects_management/public/lib/extract_export_details.ts @@ -33,6 +33,12 @@ export interface SavedObjectsExportResultDetails { id: string; type: string; }>; + excludedObjectsCount: number; + excludedObjects: Array<{ + id: string; + type: string; + reason?: string; + }>; } function isExportDetails(object: any): object is SavedObjectsExportResultDetails { diff --git a/src/plugins/saved_objects_management/public/management_section/objects_table/saved_objects_table.test.tsx b/src/plugins/saved_objects_management/public/management_section/objects_table/saved_objects_table.test.tsx index 364b3ab0d9eb6..9b8474fc08bbd 100644 --- a/src/plugins/saved_objects_management/public/management_section/objects_table/saved_objects_table.test.tsx +++ b/src/plugins/saved_objects_management/public/management_section/objects_table/saved_objects_table.test.tsx @@ -258,7 +258,7 @@ describe('SavedObjectsTable', () => { }); }); - it('should display a warning is export contains missing references', async () => { + it('should display a warning if the export contains missing references', async () => { const mockSelectedSavedObjects = [ { id: '1', type: 'index-pattern' }, { id: '3', type: 'dashboard' }, @@ -280,6 +280,8 @@ describe('SavedObjectsTable', () => { exportedCount: 2, missingRefCount: 1, missingReferences: [{ id: '7', type: 'visualisation' }], + excludedObjectsCount: 0, + excludedObjects: [], })); const component = shallowRender({ savedObjectsClient: mockSavedObjectsClient }); @@ -303,6 +305,53 @@ describe('SavedObjectsTable', () => { }); }); + it('should display a specific message if the export contains excluded objects', async () => { + const mockSelectedSavedObjects = [ + { id: '1', type: 'index-pattern' }, + { id: '3', type: 'dashboard' }, + ] as SavedObjectWithMetadata[]; + + const mockSavedObjects = mockSelectedSavedObjects.map((obj) => ({ + _id: obj.id, + _source: {}, + })); + + const mockSavedObjectsClient = { + ...defaultProps.savedObjectsClient, + bulkGet: jest.fn().mockImplementation(() => ({ + savedObjects: mockSavedObjects, + })), + }; + + extractExportDetailsMock.mockImplementation(() => ({ + exportedCount: 2, + missingRefCount: 0, + missingReferences: [], + excludedObjectsCount: 1, + excludedObjects: [{ id: '7', type: 'visualisation' }], + })); + + const component = shallowRender({ savedObjectsClient: mockSavedObjectsClient }); + + // Ensure all promises resolve + await new Promise((resolve) => process.nextTick(resolve)); + // Ensure the state changes are reflected + component.update(); + + // Set some as selected + component.instance().onSelectionChanged(mockSelectedSavedObjects); + + await component.instance().onExport(true); + + expect(fetchExportObjectsMock).toHaveBeenCalledWith(http, mockSelectedSavedObjects, true); + expect(notifications.toasts.addSuccess).toHaveBeenCalledWith({ + title: + 'Your file is downloading in the background. ' + + 'Some objects were excluded from the export. ' + + 'Please see the last line in the exported file for a list of excluded objects.', + }); + }); + it('should allow the user to choose when exporting all', async () => { const component = shallowRender(); diff --git a/src/plugins/saved_objects_management/public/management_section/objects_table/saved_objects_table.tsx b/src/plugins/saved_objects_management/public/management_section/objects_table/saved_objects_table.tsx index e23c74bc1bc19..42c1220ef5540 100644 --- a/src/plugins/saved_objects_management/public/management_section/objects_table/saved_objects_table.tsx +++ b/src/plugins/saved_objects_management/public/management_section/objects_table/saved_objects_table.tsx @@ -358,7 +358,7 @@ export class SavedObjectsTable extends Component { @@ -395,31 +395,45 @@ export class SavedObjectsTable extends Component { + showExportCompleteMessage = (exportDetails: SavedObjectsExportResultDetails | undefined) => { const { notifications } = this.props; - if (exportDetails && exportDetails.missingReferences.length > 0) { - notifications.toasts.addWarning({ - title: i18n.translate( - 'savedObjectsManagement.objectsTable.export.successWithMissingRefsNotification', - { - defaultMessage: - 'Your file is downloading in the background. ' + - 'Some related objects could not be found. ' + - 'Please see the last line in the exported file for a list of missing objects.', - } - ), - }); - } else { - notifications.toasts.addSuccess({ - title: i18n.translate('savedObjectsManagement.objectsTable.export.successNotification', { - defaultMessage: 'Your file is downloading in the background', - }), - }); + if (exportDetails) { + if (exportDetails.missingReferences.length > 0) { + return notifications.toasts.addWarning({ + title: i18n.translate( + 'savedObjectsManagement.objectsTable.export.successWithMissingRefsNotification', + { + defaultMessage: + 'Your file is downloading in the background. ' + + 'Some related objects could not be found. ' + + 'Please see the last line in the exported file for a list of missing objects.', + } + ), + }); + } + if (exportDetails.excludedObjects.length > 0) { + return notifications.toasts.addSuccess({ + title: i18n.translate( + 'savedObjectsManagement.objectsTable.export.successWithExcludedObjectsNotification', + { + defaultMessage: + 'Your file is downloading in the background. ' + + 'Some objects were excluded from the export. ' + + 'Please see the last line in the exported file for a list of excluded objects.', + } + ), + }); + } } + return notifications.toasts.addSuccess({ + title: i18n.translate('savedObjectsManagement.objectsTable.export.successNotification', { + defaultMessage: 'Your file is downloading in the background', + }), + }); }; finishImport = () => { diff --git a/src/plugins/security_oss/server/check_cluster_data.test.ts b/src/plugins/security_oss/server/check_cluster_data.test.ts index 9e9459a68754c..6aa1cc9a28c39 100644 --- a/src/plugins/security_oss/server/check_cluster_data.test.ts +++ b/src/plugins/security_oss/server/check_cluster_data.test.ts @@ -27,20 +27,19 @@ describe('checkClusterForUserData', () => { it('returns false if data only exists in system indices', async () => { const esClient = elasticsearchServiceMock.createElasticsearchClient(); esClient.cat.indices.mockResolvedValue( - // @ts-expect-error @elastic/elasticsearch ES types don't support array response format elasticsearchServiceMock.createApiResponse({ body: [ { index: '.kibana', - 'docs.count': 500, + 'docs.count': '500', }, { index: 'kibana_sample_ecommerce_data', - 'docs.count': 20, + 'docs.count': '20', }, { index: '.somethingElse', - 'docs.count': 20, + 'docs.count': '20', }, ], }) @@ -56,16 +55,15 @@ describe('checkClusterForUserData', () => { it('returns true if data exists in non-system indices', async () => { const esClient = elasticsearchServiceMock.createElasticsearchClient(); esClient.cat.indices.mockResolvedValue( - // @ts-expect-error @elastic/elasticsearch ES types don't support array response format elasticsearchServiceMock.createApiResponse({ body: [ { index: '.kibana', - 'docs.count': 500, + 'docs.count': '500', }, { index: 'some_real_index', - 'docs.count': 20, + 'docs.count': '20', }, ], }) @@ -87,23 +85,21 @@ describe('checkClusterForUserData', () => { ) .mockRejectedValueOnce(new Error('something terrible happened')) .mockResolvedValueOnce( - // @ts-expect-error @elastic/elasticsearch ES types don't support array response format elasticsearchServiceMock.createApiResponse({ body: [ { index: '.kibana', - 'docs.count': 500, + 'docs.count': '500', }, ], }) ) .mockResolvedValueOnce( - // @ts-expect-error @elastic/elasticsearch ES types don't support array response format elasticsearchServiceMock.createApiResponse({ body: [ { index: 'some_real_index', - 'docs.count': 20, + 'docs.count': '20', }, ], }) diff --git a/src/plugins/vis_type_timeseries/public/application/components/annotation_row.tsx b/src/plugins/vis_type_timeseries/public/application/components/annotation_row.tsx new file mode 100644 index 0000000000000..715cf4d6709da --- /dev/null +++ b/src/plugins/vis_type_timeseries/public/application/components/annotation_row.tsx @@ -0,0 +1,280 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React, { useState, useEffect, useCallback, useMemo, ChangeEvent } from 'react'; +import { i18n } from '@kbn/i18n'; +import { + EuiCode, + EuiComboBoxOptionOption, + EuiFieldText, + EuiFlexGroup, + EuiFlexItem, + EuiFormRow, + EuiSpacer, + htmlIdGenerator, +} from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; + +import { getDataStart } from '../../services'; +import { KBN_FIELD_TYPES, Query } from '../../../../../plugins/data/public'; + +import { AddDeleteButtons } from './add_delete_buttons'; +import { ColorPicker } from './color_picker'; +import { FieldSelect } from './aggs/field_select'; +import { IndexPatternSelect } from './lib/index_pattern_select'; +import { QueryBarWrapper } from './query_bar_wrapper'; +import { YesNo } from './yes_no'; +import { fetchIndexPattern } from '../../../common/index_patterns_utils'; +import { getDefaultQueryLanguage } from './lib/get_default_query_language'; + +// @ts-expect-error not typed yet +import { IconSelect } from './icon_select/icon_select'; + +import type { Annotation, FetchedIndexPattern, IndexPatternValue } from '../../../common/types'; +import type { VisFields } from '../lib/fetch_fields'; + +const RESTRICT_FIELDS = [KBN_FIELD_TYPES.DATE]; + +const INDEX_PATTERN_KEY = 'index_pattern'; +const TIME_FIELD_KEY = 'time_field'; + +export interface AnnotationRowProps { + annotation: Annotation; + fields: VisFields; + onChange: (partialModel: Partial) => void; + handleAdd: () => void; + handleDelete: () => void; +} + +const getAnnotationDefaults = () => ({ + fields: '', + template: '', + index_pattern: '', + query_string: { query: '', language: getDefaultQueryLanguage() }, +}); + +export const AnnotationRow = ({ + annotation, + fields, + onChange, + handleAdd, + handleDelete, +}: AnnotationRowProps) => { + const model = useMemo(() => ({ ...getAnnotationDefaults(), ...annotation }), [annotation]); + const htmlId = htmlIdGenerator(model.id); + + const [fetchedIndex, setFetchedIndex] = useState(null); + + useEffect(() => { + const updateFetchedIndex = async (index: IndexPatternValue) => { + const { indexPatterns } = getDataStart(); + + setFetchedIndex( + index + ? await fetchIndexPattern(index, indexPatterns) + : { + indexPattern: undefined, + indexPatternString: undefined, + } + ); + }; + + updateFetchedIndex(model.index_pattern); + }, [model.index_pattern]); + + const togglePanelActivation = useCallback( + () => + onChange({ + hidden: !model.hidden, + }), + [model.hidden, onChange] + ); + + const handleChange = useCallback( + (name: string) => ( + event: Array> | ChangeEvent + ) => + onChange({ + [name]: Array.isArray(event) ? event?.[0]?.value : event.target.value, + }), + [onChange] + ); + + const handleQueryChange = useCallback( + (filter: Query) => + onChange({ + query_string: filter, + }), + [onChange] + ); + + return ( +
+ + + + + + + + + + + + + } + restrict={RESTRICT_FIELDS} + value={model.time_field} + onChange={handleChange(TIME_FIELD_KEY)} + indexPattern={model.index_pattern} + fields={fields} + /> + + + + + + + + + } + fullWidth + > + + + + + + + + + + + + + + + + + + + + + } + > + + + + + + } + fullWidth + > + + + + + + } + helpText={ + + {'{{field}}'} }} + /> + + } + fullWidth + > + + + + + + + + + + +
+ ); +}; diff --git a/src/plugins/vis_type_timeseries/public/application/components/annotations_editor.js b/src/plugins/vis_type_timeseries/public/application/components/annotations_editor.js deleted file mode 100644 index 09ce57639b952..0000000000000 --- a/src/plugins/vis_type_timeseries/public/application/components/annotations_editor.js +++ /dev/null @@ -1,322 +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 { i18n } from '@kbn/i18n'; -import PropTypes from 'prop-types'; -import React, { Component } from 'react'; -import _ from 'lodash'; -import { collectionActions } from './lib/collection_actions'; -import { KBN_FIELD_TYPES } from '../../../../../plugins/data/public'; -import { AddDeleteButtons } from './add_delete_buttons'; -import { ColorPicker } from './color_picker'; -import { FieldSelect } from './aggs/field_select'; -import uuid from 'uuid'; -import { IconSelect } from './icon_select/icon_select'; -import { YesNo } from './yes_no'; -import { QueryBarWrapper } from './query_bar_wrapper'; -import { getDefaultQueryLanguage } from './lib/get_default_query_language'; -import { - htmlIdGenerator, - EuiFlexGroup, - EuiFlexItem, - EuiFormRow, - EuiSpacer, - EuiFieldText, - EuiTitle, - EuiButton, - EuiCode, - EuiText, -} from '@elastic/eui'; -import { FormattedMessage } from '@kbn/i18n/react'; -import { IndexPatternSelect } from './lib/index_pattern_select'; - -function newAnnotation() { - return { - id: uuid.v1(), - color: '#F00', - index_pattern: '', - time_field: '', - icon: 'fa-tag', - ignore_global_filters: 1, - ignore_panel_filters: 1, - }; -} - -const RESTRICT_FIELDS = [KBN_FIELD_TYPES.DATE]; - -export class AnnotationsEditor extends Component { - constructor(props) { - super(props); - this.renderRow = this.renderRow.bind(this); - } - - handleChange(item, name) { - return (e) => { - const handleChange = collectionActions.handleChange.bind(null, this.props); - const part = {}; - part[name] = _.get(e, '[0].value', _.get(e, 'target.value')); - handleChange(_.assign({}, item, part)); - }; - } - - handleQueryChange = (model, filter) => { - const part = { query_string: filter }; - collectionActions.handleChange(this.props, { - ...model, - ...part, - }); - }; - renderRow(row) { - const defaults = { - fields: '', - template: '', - index_pattern: '', - query_string: { query: '', language: getDefaultQueryLanguage() }, - }; - const model = { ...defaults, ...row }; - const handleChange = (part) => { - const fn = collectionActions.handleChange.bind(null, this.props); - fn(_.assign({}, model, part)); - }; - const togglePanelActivation = () => { - handleChange({ - hidden: !model.hidden, - }); - }; - const htmlId = htmlIdGenerator(model.id); - const handleAdd = collectionActions.handleAdd.bind(null, this.props, newAnnotation); - const handleDelete = collectionActions.handleDelete.bind(null, this.props, model); - - return ( -
- - - - - - - - - - - - - } - restrict={RESTRICT_FIELDS} - value={model.time_field} - onChange={this.handleChange(model, 'time_field')} - indexPattern={model.index_pattern} - fields={this.props.fields} - fullWidth - /> - - - - - - - - - } - fullWidth - > - this.handleQueryChange(model, query)} - indexPatterns={[model.index_pattern]} - /> - - - - - - - - - - - - - - - - - - - - } - > - - - - - - } - fullWidth - > - - - - - - } - helpText={ - - {'{{field}}'} }} - /> - - } - fullWidth - > - - - - - - - - - - -
- ); - } - - render() { - const { model } = this.props; - let content; - if (!model.annotations || !model.annotations.length) { - const handleAdd = collectionActions.handleAdd.bind(null, this.props, newAnnotation); - content = ( - -

- -

- - - -
- ); - } else { - const annotations = model.annotations.map(this.renderRow); - content = ( -
- - - - - - - - {annotations} -
- ); - } - return
{content}
; - } -} - -AnnotationsEditor.defaultProps = { - name: 'annotations', -}; - -AnnotationsEditor.propTypes = { - fields: PropTypes.object, - model: PropTypes.object, - name: PropTypes.string, - onChange: PropTypes.func, - uiSettings: PropTypes.object, -}; diff --git a/src/plugins/vis_type_timeseries/public/application/components/annotations_editor.tsx b/src/plugins/vis_type_timeseries/public/application/components/annotations_editor.tsx new file mode 100644 index 0000000000000..b3b4993d2ca06 --- /dev/null +++ b/src/plugins/vis_type_timeseries/public/application/components/annotations_editor.tsx @@ -0,0 +1,113 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React, { useCallback } from 'react'; +import uuid from 'uuid'; +import { EuiSpacer, EuiTitle, EuiButton, EuiText } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; + +import { AnnotationRow } from './annotation_row'; +import { collectionActions, CollectionActionsProps } from './lib/collection_actions'; + +import type { Panel, Annotation } from '../../../common/types'; +import type { VisFields } from '../lib/fetch_fields'; + +interface AnnotationsEditorProps { + fields: VisFields; + model: Panel; + onChange: (partialModel: Partial) => void; +} + +export const newAnnotation = () => ({ + id: uuid.v1(), + color: '#F00', + index_pattern: '', + time_field: '', + icon: 'fa-tag', + ignore_global_filters: 1, + ignore_panel_filters: 1, +}); + +const NoContent = ({ handleAdd }: { handleAdd: () => void }) => ( + +

+ +

+ + + +
+); + +const getCollectionActionsProps = (props: AnnotationsEditorProps) => + ({ + name: 'annotations', + ...props, + } as CollectionActionsProps); + +export const AnnotationsEditor = (props: AnnotationsEditorProps) => { + const { annotations } = props.model; + + const handleAdd = useCallback( + () => collectionActions.handleAdd(getCollectionActionsProps(props), newAnnotation), + [props] + ); + + const handleDelete = useCallback( + (annotation) => () => + collectionActions.handleDelete(getCollectionActionsProps(props), annotation), + [props] + ); + + const onChange = useCallback( + (annotation: Annotation) => { + return (part: Partial) => + collectionActions.handleChange(getCollectionActionsProps(props), { + ...annotation, + ...part, + }); + }, + [props] + ); + + return ( +
+ {annotations?.length ? ( +
+ + + + + + + {annotations.map((annotation) => ( + + ))} +
+ ) : ( + + )} +
+ ); +}; diff --git a/src/plugins/vis_type_timeseries/public/application/components/markdown_editor.js b/src/plugins/vis_type_timeseries/public/application/components/markdown_editor.js index 684225786e1cc..b9aa70f5207af 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/markdown_editor.js +++ b/src/plugins/vis_type_timeseries/public/application/components/markdown_editor.js @@ -15,10 +15,9 @@ import React, { Component } from 'react'; import { createTickFormatter } from './lib/tick_formatter'; import { convertSeriesToVars } from './lib/convert_series_to_vars'; import _ from 'lodash'; -import 'brace/mode/markdown'; -import 'brace/theme/github'; +import { CodeEditor, MarkdownLang } from '../../../../kibana_react/public'; -import { EuiText, EuiCodeBlock, EuiSpacer, EuiTitle, EuiCodeEditor } from '@elastic/eui'; +import { EuiText, EuiCodeBlock, EuiSpacer, EuiTitle } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; @@ -101,14 +100,13 @@ export class MarkdownEditor extends Component { return (
- diff --git a/src/plugins/vis_type_timeseries/public/application/components/panel_config/markdown.tsx b/src/plugins/vis_type_timeseries/public/application/components/panel_config/markdown.tsx index c33b4df914a81..7f82f95d250ea 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/panel_config/markdown.tsx +++ b/src/plugins/vis_type_timeseries/public/application/components/panel_config/markdown.tsx @@ -20,7 +20,6 @@ import { EuiSpacer, EuiTitle, EuiHorizontalRule, - EuiCodeEditor, } from '@elastic/eui'; // @ts-expect-error import less from 'less/lib/less-browser'; @@ -43,6 +42,7 @@ import { getDefaultQueryLanguage } from '../lib/get_default_query_language'; import { VisDataContext } from '../../contexts/vis_data_context'; import { PanelConfigProps, PANEL_CONFIG_TABS } from './types'; import { TimeseriesVisParams } from '../../../types'; +import { CodeEditor, CssLang } from '../../../../../kibana_react/public'; const lessC = less(window, { env: 'production' }); @@ -281,12 +281,10 @@ export class MarkdownPanelConfig extends Component< - diff --git a/src/plugins/vis_type_timeseries/public/application/components/panel_config/timeseries.tsx b/src/plugins/vis_type_timeseries/public/application/components/panel_config/timeseries.tsx index b57d72ea6bb30..e1d94f3bf3ebe 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/panel_config/timeseries.tsx +++ b/src/plugins/vis_type_timeseries/public/application/components/panel_config/timeseries.tsx @@ -28,9 +28,8 @@ import { // @ts-expect-error not typed yet import { SeriesEditor } from '../series_editor'; // @ts-expect-error not typed yet -import { AnnotationsEditor } from '../annotations_editor'; -// @ts-expect-error not typed yet import { IndexPattern } from '../index_pattern'; +import { AnnotationsEditor } from '../annotations_editor'; import { createSelectHandler } from '../lib/create_select_handler'; import { ColorPicker } from '../color_picker'; import { YesNo } from '../yes_no'; @@ -162,7 +161,6 @@ export class TimeseriesPanelConfig extends Component< ); diff --git a/src/plugins/vis_type_timeseries/public/application/components/yes_no.tsx b/src/plugins/vis_type_timeseries/public/application/components/yes_no.tsx index 3c02deb177f9e..81ce4f50b0313 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/yes_no.tsx +++ b/src/plugins/vis_type_timeseries/public/application/components/yes_no.tsx @@ -11,21 +11,21 @@ import { EuiRadio, htmlIdGenerator } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { TimeseriesVisParams } from '../../types'; -interface YesNoProps { - name: ParamName; +interface YesNoProps { + name: string; value: boolean | number | undefined; disabled?: boolean; 'data-test-subj'?: string; onChange: (partialModel: Partial) => void; } -export function YesNo({ +export function YesNo({ name, value, disabled, 'data-test-subj': dataTestSubj, onChange, -}: YesNoProps) { +}: YesNoProps) { const handleChange = useCallback( (val: number) => { return () => onChange({ [name]: val }); diff --git a/src/plugins/vis_type_timeseries/server/lib/get_vis_data.ts b/src/plugins/vis_type_timeseries/server/lib/get_vis_data.ts index 5cdea62af9536..dd45812f4ebfc 100644 --- a/src/plugins/vis_type_timeseries/server/lib/get_vis_data.ts +++ b/src/plugins/vis_type_timeseries/server/lib/get_vis_data.ts @@ -38,10 +38,9 @@ export async function getVisData( indexPatternsService, uiSettings, searchStrategyRegistry: framework.searchStrategyRegistry, - cachedIndexPatternFetcher: getCachedIndexPatternFetcher( - indexPatternsService, - Boolean(panel.use_kibana_indexes) - ), + cachedIndexPatternFetcher: getCachedIndexPatternFetcher(indexPatternsService, { + fetchKibanaIndexForStringIndexes: Boolean(panel.use_kibana_indexes), + }), }; return panel.type === PANEL_TYPES.TABLE diff --git a/src/plugins/vis_type_timeseries/server/lib/search_strategies/lib/cached_index_pattern_fetcher.ts b/src/plugins/vis_type_timeseries/server/lib/search_strategies/lib/cached_index_pattern_fetcher.ts index 26ea191ab9217..5f989a50ca639 100644 --- a/src/plugins/vis_type_timeseries/server/lib/search_strategies/lib/cached_index_pattern_fetcher.ts +++ b/src/plugins/vis_type_timeseries/server/lib/search_strategies/lib/cached_index_pattern_fetcher.ts @@ -13,12 +13,19 @@ import type { IndexPatternValue, FetchedIndexPattern } from '../../../../common/ export const getCachedIndexPatternFetcher = ( indexPatternsService: IndexPatternsService, - fetchKibanaIndexForStringIndexes: boolean = false + globalOptions: { + fetchKibanaIndexForStringIndexes: boolean; + } = { + fetchKibanaIndexForStringIndexes: false, + } ) => { const cache = new Map(); - return async (indexPatternValue: IndexPatternValue): Promise => { - const key = getIndexPatternKey(indexPatternValue); + return async ( + indexPatternValue: IndexPatternValue, + fetchKibanaIndexForStringIndexes: boolean = globalOptions.fetchKibanaIndexForStringIndexes + ): Promise => { + const key = `${getIndexPatternKey(indexPatternValue)}:${fetchKibanaIndexForStringIndexes}`; if (cache.has(key)) { return cache.get(key); diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/get_interval_and_timefield.test.ts b/src/plugins/vis_type_timeseries/server/lib/vis_data/get_interval_and_timefield.test.ts index 9520b876a5810..0d1ca9cba022a 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/get_interval_and_timefield.test.ts +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/get_interval_and_timefield.test.ts @@ -16,7 +16,7 @@ describe('getIntervalAndTimefield(panel, series)', () => { const panel = { time_field: '@timestamp', interval: 'auto' } as Panel; const series = {} as Series; - expect(getIntervalAndTimefield(panel, series, index)).toEqual({ + expect(getIntervalAndTimefield(panel, index, series)).toEqual({ timeField: '@timestamp', interval: 'auto', }); @@ -30,7 +30,7 @@ describe('getIntervalAndTimefield(panel, series)', () => { series_time_field: 'time', } as unknown) as Series; - expect(getIntervalAndTimefield(panel, series, index)).toEqual({ + expect(getIntervalAndTimefield(panel, index, series)).toEqual({ timeField: 'time', interval: '1m', }); diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/get_interval_and_timefield.ts b/src/plugins/vis_type_timeseries/server/lib/vis_data/get_interval_and_timefield.ts index 90fb722c54f5b..0e90dfe77e814 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/get_interval_and_timefield.ts +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/get_interval_and_timefield.ts @@ -7,12 +7,13 @@ */ import { AUTO_INTERVAL } from '../../../common/constants'; -import type { FetchedIndexPattern, Panel, Series } from '../../../common/types'; import { validateField } from '../../../common/fields_utils'; -export function getIntervalAndTimefield(panel: Panel, series: Series, index: FetchedIndexPattern) { +import type { FetchedIndexPattern, Panel, Series } from '../../../common/types'; + +export function getIntervalAndTimefield(panel: Panel, index: FetchedIndexPattern, series?: Series) { const timeField = - (series.override_index_pattern ? series.series_time_field : panel.time_field) || + (series?.override_index_pattern ? series.series_time_field : panel.time_field) || index.indexPattern?.timeFieldName; if (panel.use_kibana_indexes) { @@ -22,7 +23,7 @@ export function getIntervalAndTimefield(panel: Panel, series: Series, index: Fet let interval = panel.interval; let maxBars = panel.max_bars; - if (series.override_index_pattern) { + if (series?.override_index_pattern) { interval = series.series_interval || AUTO_INTERVAL; maxBars = series.series_max_bars; } diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/get_table_data.ts b/src/plugins/vis_type_timeseries/server/lib/vis_data/get_table_data.ts index 82411ed70d8ee..db2e027f7815c 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/get_table_data.ts +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/get_table_data.ts @@ -25,6 +25,7 @@ import type { VisTypeTimeseriesVisDataRequest, } from '../../types'; import type { Panel } from '../../../common/types'; +import { getIntervalAndTimefield } from './get_interval_and_timefield'; export async function getTableData( requestContext: VisTypeTimeseriesRequestHandlerContext, @@ -66,6 +67,18 @@ export async function getTableData( return panel.pivot_id; }; + const buildSeriesMetaParams = async () => { + let index = panelIndex; + + /** This part of code is required to try to get the default timefield for string indices. + * The rest of the functionality available for Kibana indexes should not be active **/ + if (!panel.use_kibana_indexes && index.indexPatternString) { + index = await services.cachedIndexPatternFetcher(index.indexPatternString, true); + } + + return getIntervalAndTimefield(panel, index); + }; + const meta = { type: panel.type, uiRestrictions: capabilities.uiRestrictions, @@ -78,7 +91,8 @@ export async function getTableData( services.esQueryConfig, panelIndex, capabilities, - services.uiSettings + services.uiSettings, + buildSeriesMetaParams ); const [resp] = await searchStrategy.search(requestContext, req, [ 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 253612c0274ad..cec3e82d5e37c 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 @@ -9,7 +9,6 @@ import { overwrite } from '../../helpers'; import { getBucketSize } from '../../helpers/get_bucket_size'; import { offsetTime } from '../../offset_time'; -import { getIntervalAndTimefield } from '../../get_interval_and_timefield'; import { isLastValueTimerangeMode } from '../../helpers/get_timerange_mode'; import { search, UI_SETTINGS } from '../../../../../../../plugins/data/server'; @@ -22,13 +21,14 @@ export function dateHistogram( esQueryConfig, seriesIndex, capabilities, - uiSettings + uiSettings, + buildSeriesMetaParams ) { return (next) => async (doc) => { const maxBarsUiSettings = await uiSettings.get(UI_SETTINGS.HISTOGRAM_MAX_BARS); const barTargetUiSettings = await uiSettings.get(UI_SETTINGS.HISTOGRAM_BAR_TARGET); - const { timeField, interval, maxBars } = getIntervalAndTimefield(panel, series, seriesIndex); + const { timeField, interval, maxBars } = await buildSeriesMetaParams(); const { from, to } = offsetTime(req, series.offset_time); let bucketInterval; 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 2cd7a213b273e..08b9801254c2e 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 @@ -8,6 +8,7 @@ import { DefaultSearchCapabilities } from '../../../search_strategies/capabilities/default_search_capabilities'; import { dateHistogram } from './date_histogram'; +import { getIntervalAndTimefield } from '../../get_interval_and_timefield'; import { UI_SETTINGS } from '../../../../../../data/common'; describe('dateHistogram(req, panel, series)', () => { @@ -18,6 +19,7 @@ describe('dateHistogram(req, panel, series)', () => { let config; let indexPattern; let uiSettings; + let buildSeriesMetaParams; beforeEach(() => { req = { @@ -44,14 +46,24 @@ describe('dateHistogram(req, panel, series)', () => { uiSettings = { get: async (key) => (key === UI_SETTINGS.HISTOGRAM_MAX_BARS ? 100 : 50), }; + buildSeriesMetaParams = jest.fn(async () => { + return getIntervalAndTimefield(panel, indexPattern, series); + }); }); test('calls next when finished', async () => { const next = jest.fn(); - await dateHistogram(req, panel, series, config, indexPattern, capabilities, uiSettings)(next)( - {} - ); + await dateHistogram( + req, + panel, + series, + config, + indexPattern, + capabilities, + uiSettings, + buildSeriesMetaParams + )(next)({}); expect(next.mock.calls.length).toEqual(1); }); @@ -65,7 +77,8 @@ describe('dateHistogram(req, panel, series)', () => { config, indexPattern, capabilities, - uiSettings + uiSettings, + buildSeriesMetaParams )(next)({}); expect(doc).toEqual({ @@ -106,7 +119,8 @@ describe('dateHistogram(req, panel, series)', () => { config, indexPattern, capabilities, - uiSettings + uiSettings, + buildSeriesMetaParams )(next)({}); expect(doc).toEqual({ @@ -150,7 +164,8 @@ describe('dateHistogram(req, panel, series)', () => { config, indexPattern, capabilities, - uiSettings + uiSettings, + buildSeriesMetaParams )(next)({}); expect(doc).toEqual({ @@ -197,7 +212,8 @@ describe('dateHistogram(req, panel, series)', () => { config, indexPattern, capabilities, - uiSettings + uiSettings, + buildSeriesMetaParams )(next)({}); expect(doc.aggs.test.aggs.timeseries.auto_date_histogram).toBeUndefined(); @@ -219,7 +235,8 @@ describe('dateHistogram(req, panel, series)', () => { config, indexPattern, capabilities, - uiSettings + uiSettings, + buildSeriesMetaParams )(next)({}); expect(doc.aggs.test.meta).toMatchInlineSnapshot(` @@ -242,7 +259,8 @@ describe('dateHistogram(req, panel, series)', () => { config, indexPattern, capabilities, - uiSettings + uiSettings, + buildSeriesMetaParams )(next)({}); expect(doc).toEqual({ diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/positive_rate.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/positive_rate.js index 208321a98737e..91016384794c4 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/positive_rate.js +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/positive_rate.js @@ -7,7 +7,6 @@ */ import { getBucketSize } from '../../helpers/get_bucket_size'; -import { getIntervalAndTimefield } from '../../get_interval_and_timefield'; import { bucketTransform } from '../../helpers/bucket_transform'; import { overwrite } from '../../helpers'; import { UI_SETTINGS } from '../../../../../../data/common'; @@ -58,12 +57,13 @@ export function positiveRate( esQueryConfig, seriesIndex, capabilities, - uiSettings + uiSettings, + buildSeriesMetaParams ) { return (next) => async (doc) => { const barTargetUiSettings = await uiSettings.get(UI_SETTINGS.HISTOGRAM_BAR_TARGET); - const { interval } = getIntervalAndTimefield(panel, series, seriesIndex); + const { interval } = await buildSeriesMetaParams(); const { intervalString } = getBucketSize(req, interval, capabilities, barTargetUiSettings); if (series.metrics.some(filter)) { diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/positive_rate.test.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/positive_rate.test.js index b79e8de13062c..aac0063a54bef 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/positive_rate.test.js +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/positive_rate.test.js @@ -12,6 +12,7 @@ describe('positiveRate(req, panel, series)', () => { let series; let req; let uiSettings; + let buildSeriesMetaParams; beforeEach(() => { panel = { @@ -42,6 +43,9 @@ describe('positiveRate(req, panel, series)', () => { uiSettings = { get: async () => 50, }; + buildSeriesMetaParams = jest.fn().mockResolvedValue({ + interval: 'auto', + }); }); test('calls next when finished', async () => { @@ -53,7 +57,8 @@ describe('positiveRate(req, panel, series)', () => { {}, {}, { maxBucketsLimit: 2000, getValidTimeInterval: jest.fn(() => '1d') }, - uiSettings + uiSettings, + buildSeriesMetaParams )(next)({}); expect(next.mock.calls.length).toEqual(1); @@ -68,7 +73,8 @@ describe('positiveRate(req, panel, series)', () => { {}, {}, { maxBucketsLimit: 2000, getValidTimeInterval: jest.fn(() => '1d') }, - uiSettings + uiSettings, + buildSeriesMetaParams )(next)({}); expect(doc).toEqual({ diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/query.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/query.js index a5f4e17289e06..5031a0f2ec185 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/query.js +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/query.js @@ -7,18 +7,28 @@ */ import { offsetTime } from '../../offset_time'; -import { getIntervalAndTimefield } from '../../get_interval_and_timefield'; import { esQuery } from '../../../../../../data/server'; -export function query(req, panel, series, esQueryConfig, seriesIndex) { - return (next) => (doc) => { - const { timeField } = getIntervalAndTimefield(panel, series, seriesIndex); +export function query( + req, + panel, + series, + esQueryConfig, + seriesIndex, + capabilities, + uiSettings, + buildSeriesMetaParams +) { + return (next) => async (doc) => { + const { timeField } = await buildSeriesMetaParams(); const { from, to } = offsetTime(req, series.offset_time); doc.size = 0; + const ignoreGlobalFilter = panel.ignore_global_filter || series.ignore_global_filter; const queries = !ignoreGlobalFilter ? req.body.query : []; const filters = !ignoreGlobalFilter ? req.body.filters : []; + doc.query = esQuery.buildEsQuery(seriesIndex.indexPattern, queries, filters, esQueryConfig); const timerange = { diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/query.test.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/query.test.js index b3e88dbf1c6b9..849ae2e71bffc 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/query.test.js +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/query.test.js @@ -13,6 +13,7 @@ describe('query', () => { let series; let req; let seriesIndex; + let buildSeriesMetaParams; const config = { allowLeadingWildcards: true, @@ -35,17 +36,32 @@ describe('query', () => { }; series = { id: 'test' }; seriesIndex = {}; + buildSeriesMetaParams = jest.fn().mockResolvedValue({ + timeField: panel.time_field, + interval: panel.interval, + }); }); - test('calls next when finished', () => { + test('calls next when finished', async () => { const next = jest.fn(); - query(req, panel, series, config, seriesIndex)(next)({}); + await query(req, panel, series, config, seriesIndex, null, null, buildSeriesMetaParams)(next)( + {} + ); expect(next.mock.calls.length).toEqual(1); }); - test('returns doc with query for timerange', () => { + test('returns doc with query for timerange', async () => { const next = (doc) => doc; - const doc = query(req, panel, series, config, seriesIndex)(next)({}); + const doc = await query( + req, + panel, + series, + config, + seriesIndex, + null, + null, + buildSeriesMetaParams + )(next)({}); expect(doc).toEqual({ size: 0, query: { @@ -69,10 +85,19 @@ describe('query', () => { }); }); - test('returns doc with query for timerange (offset by 1h)', () => { + test('returns doc with query for timerange (offset by 1h)', async () => { series.offset_time = '1h'; const next = (doc) => doc; - const doc = query(req, panel, series, config, seriesIndex)(next)({}); + const doc = await query( + req, + panel, + series, + config, + seriesIndex, + null, + null, + buildSeriesMetaParams + )(next)({}); expect(doc).toEqual({ size: 0, query: { @@ -96,7 +121,7 @@ describe('query', () => { }); }); - test('returns doc with global query', () => { + test('returns doc with global query', async () => { req.body.filters = [ { bool: { @@ -111,7 +136,16 @@ describe('query', () => { }, ]; const next = (doc) => doc; - const doc = query(req, panel, series, config, seriesIndex)(next)({}); + const doc = await query( + req, + panel, + series, + config, + seriesIndex, + null, + null, + buildSeriesMetaParams + )(next)({}); expect(doc).toEqual({ size: 0, query: { @@ -147,10 +181,19 @@ describe('query', () => { }); }); - test('returns doc with series filter', () => { + test('returns doc with series filter', async () => { series.filter = { query: 'host:web-server', language: 'lucene' }; const next = (doc) => doc; - const doc = query(req, panel, series, config, seriesIndex)(next)({}); + const doc = await query( + req, + panel, + series, + config, + seriesIndex, + null, + null, + buildSeriesMetaParams + )(next)({}); expect(doc).toEqual({ size: 0, query: { @@ -188,7 +231,7 @@ describe('query', () => { }, }); }); - test('returns doc with panel filter and global', () => { + test('returns doc with panel filter and global', async () => { req.body.filters = [ { bool: { @@ -204,7 +247,16 @@ describe('query', () => { ]; panel.filter = { query: 'host:web-server', language: 'lucene' }; const next = (doc) => doc; - const doc = query(req, panel, series, config, seriesIndex)(next)({}); + const doc = await query( + req, + panel, + series, + config, + seriesIndex, + null, + null, + buildSeriesMetaParams + )(next)({}); expect(doc).toEqual({ size: 0, query: { @@ -255,7 +307,7 @@ describe('query', () => { }); }); - test('returns doc with panel filter (ignoring globals)', () => { + test('returns doc with panel filter (ignoring globals)', async () => { req.body.filters = [ { bool: { @@ -272,7 +324,16 @@ describe('query', () => { panel.filter = { query: 'host:web-server', language: 'lucene' }; panel.ignore_global_filter = true; const next = (doc) => doc; - const doc = query(req, panel, series, config, seriesIndex)(next)({}); + const doc = await query( + req, + panel, + series, + config, + seriesIndex, + null, + null, + buildSeriesMetaParams + )(next)({}); expect(doc).toEqual({ size: 0, query: { @@ -311,7 +372,7 @@ describe('query', () => { }); }); - test('returns doc with panel filter (ignoring globals from series)', () => { + test('returns doc with panel filter (ignoring globals from series)', async () => { req.body.filters = [ { bool: { @@ -328,7 +389,16 @@ describe('query', () => { panel.filter = { query: 'host:web-server', language: 'lucene' }; series.ignore_global_filter = true; const next = (doc) => doc; - const doc = query(req, panel, series, config, seriesIndex)(next)({}); + const doc = await query( + req, + panel, + series, + config, + seriesIndex, + null, + null, + buildSeriesMetaParams + )(next)({}); expect(doc).toEqual({ size: 0, query: { 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 92ac4078a3835..f37c27f74184f 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 @@ -9,17 +9,24 @@ import { overwrite } from '../../helpers'; import { getBucketSize } from '../../helpers/get_bucket_size'; import { isLastValueTimerangeMode } from '../../helpers/get_timerange_mode'; -import { getIntervalAndTimefield } from '../../get_interval_and_timefield'; import { getTimerange } from '../../helpers/get_timerange'; import { calculateAggRoot } from './calculate_agg_root'; import { search, UI_SETTINGS } from '../../../../../../../plugins/data/server'; const { dateHistogramInterval } = search.aggs; -export function dateHistogram(req, panel, esQueryConfig, seriesIndex, capabilities, uiSettings) { +export function dateHistogram( + req, + panel, + esQueryConfig, + seriesIndex, + capabilities, + uiSettings, + buildSeriesMetaParams +) { return (next) => async (doc) => { const barTargetUiSettings = await uiSettings.get(UI_SETTINGS.HISTOGRAM_BAR_TARGET); - const { timeField, interval } = getIntervalAndTimefield(panel, {}, seriesIndex); + const { timeField, interval } = await buildSeriesMetaParams(); const { from, to } = getTimerange(req); const meta = { diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/positive_rate.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/positive_rate.js index 3390362b56115..dafb2741d6ab4 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/positive_rate.js +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/positive_rate.js @@ -7,15 +7,22 @@ */ import { getBucketSize } from '../../helpers/get_bucket_size'; -import { getIntervalAndTimefield } from '../../get_interval_and_timefield'; import { calculateAggRoot } from './calculate_agg_root'; import { createPositiveRate, filter } from '../series/positive_rate'; import { UI_SETTINGS } from '../../../../../../data/common'; -export function positiveRate(req, panel, esQueryConfig, seriesIndex, capabilities, uiSettings) { +export function positiveRate( + req, + panel, + esQueryConfig, + seriesIndex, + capabilities, + uiSettings, + buildSeriesMetaParams +) { return (next) => async (doc) => { const barTargetUiSettings = await uiSettings.get(UI_SETTINGS.HISTOGRAM_BAR_TARGET); - const { interval } = getIntervalAndTimefield(panel, {}, seriesIndex); + const { interval } = await buildSeriesMetaParams(); const { intervalString } = getBucketSize(req, interval, capabilities, barTargetUiSettings); panel.series.forEach((column) => { diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/query.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/query.js index 66783e0cdfaef..7e555557ee1f7 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/query.js +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/query.js @@ -7,12 +7,19 @@ */ import { getTimerange } from '../../helpers/get_timerange'; -import { getIntervalAndTimefield } from '../../get_interval_and_timefield'; import { esQuery } from '../../../../../../data/server'; -export function query(req, panel, esQueryConfig, seriesIndex) { - return (next) => (doc) => { - const { timeField } = getIntervalAndTimefield(panel, {}, seriesIndex); +export function query( + req, + panel, + esQueryConfig, + seriesIndex, + capabilities, + uiSettings, + buildSeriesMetaParams +) { + return (next) => async (doc) => { + const { timeField } = await buildSeriesMetaParams(); const { from, to } = getTimerange(req); doc.size = 0; 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 46acbb27e15e1..14b4fc89712e2 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 @@ -89,7 +89,10 @@ describe('buildRequestBody(req)', () => { capabilities, { get: async () => 50, - } + }, + jest.fn().mockResolvedValue({ + timeField: '@timestamp', + }) ); expect(doc).toEqual({ diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/series/get_request_params.ts b/src/plugins/vis_type_timeseries/server/lib/vis_data/series/get_request_params.ts index c565e2b86eaa3..a2248308dc571 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/series/get_request_params.ts +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/series/get_request_params.ts @@ -7,6 +7,7 @@ */ import { buildRequestBody } from './build_request_body'; +import { getIntervalAndTimefield } from '../get_interval_and_timefield'; import type { FetchedIndexPattern, Panel, Series } from '../../../../common/types'; import type { @@ -34,6 +35,18 @@ export async function getSeriesRequestParams( seriesIndex = await cachedIndexPatternFetcher(series.series_index_pattern ?? ''); } + const buildSeriesMetaParams = async () => { + let index = seriesIndex; + + /** This part of code is required to try to get the default timefield for string indices. + * The rest of the functionality available for Kibana indexes should not be active **/ + if (!panel.use_kibana_indexes && index.indexPatternString) { + index = await cachedIndexPatternFetcher(index.indexPatternString, true); + } + + return getIntervalAndTimefield(panel, index, series); + }; + const request = await buildRequestBody( req, panel, @@ -41,7 +54,8 @@ export async function getSeriesRequestParams( esQueryConfig, seriesIndex, capabilities, - uiSettings + uiSettings, + buildSeriesMetaParams ); return { diff --git a/test/functional/config.js b/test/functional/config.js index eac21e5a45618..bab1148cf372a 100644 --- a/test/functional/config.js +++ b/test/functional/config.js @@ -42,9 +42,14 @@ export default async function ({ readConfigFile }) { serverArgs: [ ...commonConfig.get('kbnTestServer.serverArgs'), '--telemetry.optIn=false', - '--xpack.security.enabled=false', '--savedObjects.maxImportPayloadBytes=10485760', '--xpack.maps.showMapVisualizationTypes=true', + + // to be re-enabled once kibana/issues/102552 is completed + '--xpack.security.enabled=false', + '--monitoring.enabled=false', + '--xpack.reporting.enabled=false', + '--enterpriseSearch.enabled=false', ], }, diff --git a/test/functional/fixtures/es_archiver/saved_objects_management/export_exclusion/data.json b/test/functional/fixtures/es_archiver/saved_objects_management/export_exclusion/data.json new file mode 100644 index 0000000000000..f7015ee20251d --- /dev/null +++ b/test/functional/fixtures/es_archiver/saved_objects_management/export_exclusion/data.json @@ -0,0 +1,135 @@ +{ + "type": "doc", + "value": { + "index": ".kibana", + "type": "doc", + "id": "test-is-exportable:1", + "source": { + "test-is-exportable": { + "title": "obj 1", + "enabled": true + }, + "type": "test-is-exportable", + "migrationVersion": {}, + "updated_at": "2018-12-21T00:43:07.096Z", + "references": [ + { + "type": "test-is-exportable", + "id": "2", + "name": "ref-1" + }, + { + "type": "test-is-exportable", + "id": "3", + "name": "ref-2" + } + ] + } + } +} + +{ + "type": "doc", + "value": { + "index": ".kibana", + "type": "doc", + "id": "test-is-exportable:2", + "source": { + "test-is-exportable": { + "title": "obj 2", + "enabled": false + }, + "type": "test-is-exportable", + "migrationVersion": {}, + "updated_at": "2018-12-21T00:43:07.096Z", + "references": [] + } + } +} + +{ + "type": "doc", + "value": { + "index": ".kibana", + "type": "doc", + "id": "test-is-exportable:3", + "source": { + "test-is-exportable": { + "title": "obj 3", + "enabled": true + }, + "type": "test-is-exportable", + "migrationVersion": {}, + "updated_at": "2018-12-21T00:43:07.096Z", + "references": [ + { + "type": "test-is-exportable", + "id": "4", + "name": "ref-1" + }, + { + "type": "test-is-exportable", + "id": "5", + "name": "ref-2" + } + ] + } + } +} + +{ + "type": "doc", + "value": { + "index": ".kibana", + "type": "doc", + "id": "test-is-exportable:4", + "source": { + "test-is-exportable": { + "title": "obj 4", + "enabled": false + }, + "type": "test-is-exportable", + "migrationVersion": {}, + "updated_at": "2018-12-21T00:43:07.096Z", + "references": [] + } + } +} + +{ + "type": "doc", + "value": { + "index": ".kibana", + "type": "doc", + "id": "test-is-exportable:5", + "source": { + "test-is-exportable": { + "title": "obj 5", + "enabled": true + }, + "type": "test-is-exportable", + "migrationVersion": {}, + "updated_at": "2018-12-21T00:43:07.096Z", + "references": [] + } + } +} + +{ + "type": "doc", + "value": { + "index": ".kibana", + "type": "doc", + "id": "test-is-exportable:error", + "source": { + "test-is-exportable": { + "title": "obj error", + "enabled": true + }, + "type": "test-is-exportable", + "migrationVersion": {}, + "updated_at": "2018-12-21T00:43:07.096Z", + "references": [] + } + } +} diff --git a/test/functional/fixtures/es_archiver/saved_objects_management/export_exclusion/mappings.json b/test/functional/fixtures/es_archiver/saved_objects_management/export_exclusion/mappings.json new file mode 100644 index 0000000000000..abec2eeb77492 --- /dev/null +++ b/test/functional/fixtures/es_archiver/saved_objects_management/export_exclusion/mappings.json @@ -0,0 +1,505 @@ +{ + "type": "index", + "value": { + "index": ".kibana", + "settings": { + "index": { + "number_of_shards": "1", + "auto_expand_replicas": "0-1", + "number_of_replicas": "0" + } + }, + "mappings": { + "dynamic": "strict", + "properties": { + "test-export-transform": { + "properties": { + "title": { "type": "text" }, + "enabled": { "type": "boolean" } + } + }, + "test-is-exportable": { + "properties": { + "title": { "type": "text" }, + "enabled": { "type": "boolean" } + } + }, + "test-export-add": { + "properties": { + "title": { "type": "text" } + } + }, + "test-export-add-dep": { + "properties": { + "title": { "type": "text" } + } + }, + "test-export-transform-error": { + "properties": { + "title": { "type": "text" } + } + }, + "test-export-invalid-transform": { + "properties": { + "title": { "type": "text" } + } + }, + "apm-telemetry": { + "properties": { + "has_any_services": { + "type": "boolean" + }, + "services_per_agent": { + "properties": { + "go": { + "type": "long", + "null_value": 0 + }, + "java": { + "type": "long", + "null_value": 0 + }, + "js-base": { + "type": "long", + "null_value": 0 + }, + "nodejs": { + "type": "long", + "null_value": 0 + }, + "python": { + "type": "long", + "null_value": 0 + }, + "ruby": { + "type": "long", + "null_value": 0 + } + } + } + } + }, + "canvas-workpad": { + "dynamic": "false", + "properties": { + "@created": { + "type": "date" + }, + "@timestamp": { + "type": "date" + }, + "id": { + "type": "text", + "index": false + }, + "name": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword" + } + } + } + } + }, + "config": { + "dynamic": "true", + "properties": { + "accessibility:disableAnimations": { + "type": "boolean" + }, + "buildNum": { + "type": "keyword" + }, + "dateFormat:tz": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + }, + "defaultIndex": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + }, + "telemetry:optIn": { + "type": "boolean" + } + } + }, + "dashboard": { + "properties": { + "description": { + "type": "text" + }, + "hits": { + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "optionsJSON": { + "type": "text" + }, + "panelsJSON": { + "type": "text" + }, + "refreshInterval": { + "properties": { + "display": { + "type": "keyword" + }, + "pause": { + "type": "boolean" + }, + "section": { + "type": "integer" + }, + "value": { + "type": "integer" + } + } + }, + "timeFrom": { + "type": "keyword" + }, + "timeRestore": { + "type": "boolean" + }, + "timeTo": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "uiStateJSON": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "map": { + "properties": { + "bounds": { + "dynamic": false, + "properties": {} + }, + "description": { + "type": "text" + }, + "layerListJSON": { + "type": "text" + }, + "mapStateJSON": { + "type": "text" + }, + "title": { + "type": "text" + }, + "uiStateJSON": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "graph-workspace": { + "properties": { + "description": { + "type": "text" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "numLinks": { + "type": "integer" + }, + "numVertices": { + "type": "integer" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" + }, + "wsState": { + "type": "text" + } + } + }, + "index-pattern": { + "properties": { + "fieldFormatMap": { + "type": "text" + }, + "fields": { + "type": "text" + }, + "intervalName": { + "type": "keyword" + }, + "notExpandable": { + "type": "boolean" + }, + "sourceFilters": { + "type": "text" + }, + "timeFieldName": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "type": { + "type": "keyword" + }, + "typeMeta": { + "type": "keyword" + } + } + }, + "kql-telemetry": { + "properties": { + "optInCount": { + "type": "long" + }, + "optOutCount": { + "type": "long" + } + } + }, + "migrationVersion": { + "dynamic": "true", + "properties": { + "index-pattern": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + }, + "space": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + } + } + }, + "namespace": { + "type": "keyword" + }, + "search": { + "properties": { + "columns": { + "type": "keyword" + }, + "description": { + "type": "text" + }, + "hits": { + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "sort": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "server": { + "properties": { + "uuid": { + "type": "keyword" + } + } + }, + "space": { + "properties": { + "_reserved": { + "type": "boolean" + }, + "color": { + "type": "keyword" + }, + "description": { + "type": "text" + }, + "disabledFeatures": { + "type": "keyword" + }, + "initials": { + "type": "keyword" + }, + "name": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 2048 + } + } + } + } + }, + "spaceId": { + "type": "keyword" + }, + "telemetry": { + "properties": { + "enabled": { + "type": "boolean" + } + } + }, + "timelion-sheet": { + "properties": { + "description": { + "type": "text" + }, + "hits": { + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "timelion_chart_height": { + "type": "integer" + }, + "timelion_columns": { + "type": "integer" + }, + "timelion_interval": { + "type": "keyword" + }, + "timelion_other_interval": { + "type": "keyword" + }, + "timelion_rows": { + "type": "integer" + }, + "timelion_sheet": { + "type": "text" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "type": { + "type": "keyword" + }, + "updated_at": { + "type": "date" + }, + "url": { + "properties": { + "accessCount": { + "type": "long" + }, + "accessDate": { + "type": "date" + }, + "createDate": { + "type": "date" + }, + "url": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 2048 + } + } + } + } + }, + "visualization": { + "properties": { + "description": { + "type": "text" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "savedSearchId": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "uiStateJSON": { + "type": "text" + }, + "version": { + "type": "integer" + }, + "visState": { + "type": "text" + } + } + }, + "references": { + "properties": { + "id": { + "type": "keyword" + }, + "name": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + }, + "type": "nested" + } + } + } + } +} diff --git a/test/functional/page_objects/visual_builder_page.ts b/test/functional/page_objects/visual_builder_page.ts index d796067372fa8..6e263dd1cdbbf 100644 --- a/test/functional/page_objects/visual_builder_page.ts +++ b/test/functional/page_objects/visual_builder_page.ts @@ -132,19 +132,18 @@ export class VisualBuilderPageObject extends FtrService { } public async clearMarkdown() { - // Since we use ACE editor and that isn't really storing its value inside - // a textarea we must really select all text and remove it, and cannot use - // clearValue(). await this.retry.waitForWithTimeout('text area is cleared', 20000, async () => { - const editor = await this.testSubjects.find('codeEditorContainer'); - const $ = await editor.parseDomContent(); - const value = $('.ace_line').text(); - if (value.length > 0) { - this.log.debug('Clearing text area input'); - this.waitForMarkdownTextAreaCleaned(); - } - - return value.length === 0; + const input = await this.find.byCssSelector('.tvbMarkdownEditor__editor textarea'); + await input.clickMouseButton(); + await input.clearValueWithKeyboard(); + + const linesContainer = await this.find.byCssSelector( + '.tvbMarkdownEditor__editor .view-lines' + ); + // lines of code in monaco-editor + // text is not present in textarea + const lines = await linesContainer.findAllByClassName('mtk1'); + return lines.length === 0; }); } diff --git a/test/plugin_functional/plugins/saved_object_export_transforms/server/plugin.ts b/test/plugin_functional/plugins/saved_object_export_transforms/server/plugin.ts index 408ac03dd946b..15afdb229b1fd 100644 --- a/test/plugin_functional/plugins/saved_object_export_transforms/server/plugin.ts +++ b/test/plugin_functional/plugins/saved_object_export_transforms/server/plugin.ts @@ -152,6 +152,30 @@ export class SavedObjectExportTransformsPlugin implements Plugin { getTitle: (obj) => obj.attributes.title, }, }); + + // example of a SO type implementing the `isExportable` API + savedObjects.registerType<{ enabled: boolean; title: string }>({ + name: 'test-is-exportable', + hidden: false, + namespaceType: 'single', + mappings: { + properties: { + title: { type: 'text' }, + enabled: { type: 'boolean' }, + }, + }, + management: { + defaultSearchField: 'title', + importableAndExportable: true, + getTitle: (obj) => obj.attributes.title, + isExportable: (obj) => { + if (obj.id === 'error') { + throw new Error('something went wrong'); + } + return obj.attributes.enabled === true; + }, + }, + }); } public start() {} diff --git a/test/plugin_functional/test_suites/saved_objects_management/export_transform.ts b/test/plugin_functional/test_suites/saved_objects_management/export_transform.ts index 0351c5abdde46..8437e050091fb 100644 --- a/test/plugin_functional/test_suites/saved_objects_management/export_transform.ts +++ b/test/plugin_functional/test_suites/saved_objects_management/export_transform.ts @@ -8,6 +8,7 @@ import expect from '@kbn/expect'; import type { SavedObject } from '../../../../src/core/types'; +import type { SavedObjectsExportResultDetails } from '../../../../src/core/server'; import { PluginFunctionalProviderContext } from '../../services'; function parseNdJson(input: string): Array> { @@ -139,7 +140,7 @@ export default function ({ getService }: PluginFunctionalProviderContext) { }); }); - describe('FOO nested export transforms', () => { + describe('nested export transforms', () => { before(async () => { await esArchiver.load( 'test/functional/fixtures/es_archiver/saved_objects_management/nested_export_transform' @@ -183,5 +184,121 @@ export default function ({ getService }: PluginFunctionalProviderContext) { }); }); }); + + describe('isExportable API', () => { + before(async () => { + await esArchiver.load( + 'test/functional/fixtures/es_archiver/saved_objects_management/export_exclusion' + ); + }); + + after(async () => { + await esArchiver.unload( + 'test/functional/fixtures/es_archiver/saved_objects_management/export_exclusion' + ); + }); + + it('should only export objects returning `true` for `isExportable`', async () => { + await supertest + .post('/api/saved_objects/_export') + .set('kbn-xsrf', 'true') + .send({ + objects: [ + { + type: 'test-is-exportable', + id: '1', + }, + ], + includeReferencesDeep: true, + excludeExportDetails: true, + }) + .expect(200) + .then((resp) => { + const objects = parseNdJson(resp.text).sort((obj1, obj2) => + obj1.id.localeCompare(obj2.id) + ); + expect(objects.map((obj) => `${obj.type}:${obj.id}`)).to.eql([ + 'test-is-exportable:1', + 'test-is-exportable:3', + 'test-is-exportable:5', + ]); + }); + }); + + it('lists objects that got filtered', async () => { + await supertest + .post('/api/saved_objects/_export') + .set('kbn-xsrf', 'true') + .send({ + objects: [ + { + type: 'test-is-exportable', + id: '1', + }, + ], + includeReferencesDeep: true, + excludeExportDetails: false, + }) + .expect(200) + .then((resp) => { + const objects = parseNdJson(resp.text); + const exportDetails = (objects[ + objects.length - 1 + ] as unknown) as SavedObjectsExportResultDetails; + + expect(exportDetails.excludedObjectsCount).to.eql(2); + expect(exportDetails.excludedObjects).to.eql([ + { + type: 'test-is-exportable', + id: '2', + reason: 'excluded', + }, + { + type: 'test-is-exportable', + id: '4', + reason: 'excluded', + }, + ]); + }); + }); + + it('excludes objects if `isExportable` throws', async () => { + await supertest + .post('/api/saved_objects/_export') + .set('kbn-xsrf', 'true') + .send({ + objects: [ + { + type: 'test-is-exportable', + id: '5', + }, + { + type: 'test-is-exportable', + id: 'error', + }, + ], + includeReferencesDeep: true, + excludeExportDetails: false, + }) + .expect(200) + .then((resp) => { + const objects = parseNdJson(resp.text); + expect(objects.length).to.eql(2); + expect([objects[0]].map((obj) => `${obj.type}:${obj.id}`)).to.eql([ + 'test-is-exportable:5', + ]); + const exportDetails = (objects[ + objects.length - 1 + ] as unknown) as SavedObjectsExportResultDetails; + expect(exportDetails.excludedObjects).to.eql([ + { + type: 'test-is-exportable', + id: 'error', + reason: 'predicate_error', + }, + ]); + }); + }); + }); }); } diff --git a/test/scripts/jenkins_baseline.sh b/test/scripts/jenkins_baseline.sh index 58d86cddf65fa..40bfc6e83ad1b 100755 --- a/test/scripts/jenkins_baseline.sh +++ b/test/scripts/jenkins_baseline.sh @@ -9,7 +9,7 @@ node scripts/build --debug --oss echo " -> shipping metrics from build to ci-stats" node scripts/ship_ci_stats \ --metrics target/optimizer_bundle_metrics.json \ - --metrics packages/kbn-ui-shared-deps/target/metrics.json + --metrics node_modules/@kbn/ui-shared-deps/shared_built_assets/metrics.json linuxBuild="$(find "$KIBANA_DIR/target" -name 'kibana-*-linux-x86_64.tar.gz')" installDir="$PARENT_DIR/install/kibana" diff --git a/test/scripts/jenkins_build_kibana.sh b/test/scripts/jenkins_build_kibana.sh index 32fb98929e21c..198723908cf48 100755 --- a/test/scripts/jenkins_build_kibana.sh +++ b/test/scripts/jenkins_build_kibana.sh @@ -38,7 +38,7 @@ if [[ -z "$CODE_COVERAGE" ]] ; then echo " -> shipping metrics from build to ci-stats" node scripts/ship_ci_stats \ --metrics target/optimizer_bundle_metrics.json \ - --metrics packages/kbn-ui-shared-deps/target/metrics.json + --metrics node_modules/@kbn/ui-shared-deps/shared_built_assets/metrics.json linuxBuild="$(find "$KIBANA_DIR/target" -name 'kibana-*-linux-x86_64.tar.gz')" installDir="$KIBANA_DIR/install/kibana" diff --git a/test/scripts/jenkins_storybook.sh b/test/scripts/jenkins_storybook.sh index 5c99654f16cbe..73ab43fd02eba 100755 --- a/test/scripts/jenkins_storybook.sh +++ b/test/scripts/jenkins_storybook.sh @@ -2,9 +2,6 @@ source src/dev/ci_setup/setup_env.sh -cd "$XPACK_DIR/plugins/canvas" -node scripts/storybook --dll - cd "$KIBANA_DIR" yarn storybook --site apm diff --git a/test/scripts/jenkins_xpack_baseline.sh b/test/scripts/jenkins_xpack_baseline.sh index 93363687b39a9..8d5624949505a 100755 --- a/test/scripts/jenkins_xpack_baseline.sh +++ b/test/scripts/jenkins_xpack_baseline.sh @@ -10,7 +10,7 @@ node scripts/build --debug --no-oss echo " -> shipping metrics from build to ci-stats" node scripts/ship_ci_stats \ --metrics target/optimizer_bundle_metrics.json \ - --metrics packages/kbn-ui-shared-deps/target/metrics.json + --metrics node_modules/@kbn/ui-shared-deps/shared_built_assets/metrics.json linuxBuild="$(find "$KIBANA_DIR/target" -name 'kibana-*-linux-x86_64.tar.gz')" installDir="$KIBANA_DIR/install/kibana" diff --git a/typings/index.d.ts b/typings/index.d.ts index c7186a0e5795b..2a5c5e3fa430f 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -33,3 +33,7 @@ declare module 'axios/lib/adapters/xhr'; // See https://github.com/storybookjs/storybook/issues/11684 declare module 'react-syntax-highlighter/dist/cjs/create-element'; declare module 'react-syntax-highlighter/dist/cjs/prism-light'; + +// Monaco languages support +declare module 'monaco-editor/esm/vs/basic-languages/markdown/markdown'; +declare module 'monaco-editor/esm/vs/basic-languages/css/css'; diff --git a/x-pack/package.json b/x-pack/package.json index 04f808c89764d..84fd5ba081d8f 100644 --- a/x-pack/package.json +++ b/x-pack/package.json @@ -7,7 +7,6 @@ "scripts": { "github-checks-reporter": "../node_modules/.bin/github-checks-reporter", "kbn": "node ../scripts/kbn", - "kbn:bootstrap": "node plugins/canvas/scripts/storybook --clean", "start": "node ../scripts/kibana --dev", "build": "node --preserve-symlinks ../node_modules/.bin/gulp build", "test:jest": "node ../scripts/jest" diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/LocalUIFilters/index.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/LocalUIFilters/index.tsx index e09cc2a81c927..abcacbe89587b 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/LocalUIFilters/index.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/LocalUIFilters/index.tsx @@ -15,7 +15,7 @@ import { EuiFlexItem, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { ESFilter } from 'typings/elasticsearch'; +import { ESFilter } from 'src/core/types/elasticsearch'; import { useLocalUIFilters } from '../hooks/useLocalUIFilters'; import { uxFiltersByName, diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/LocalUIFilters/queries.ts b/x-pack/plugins/apm/public/components/app/RumDashboard/LocalUIFilters/queries.ts index 5fe6bc725ef28..12f1fc0f0faea 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/LocalUIFilters/queries.ts +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/LocalUIFilters/queries.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { ESFilter } from 'typings/elasticsearch'; +import { ESFilter } from 'src/core/types/elasticsearch'; import { SERVICE_ENVIRONMENT } from '../../../../../common/elasticsearch_fieldnames'; import { ENVIRONMENT_ALL, diff --git a/x-pack/plugins/apm/public/components/shared/KueryBar/get_bool_filter.ts b/x-pack/plugins/apm/public/components/shared/KueryBar/get_bool_filter.ts index c86cf769d7529..150a4d9efc2cb 100644 --- a/x-pack/plugins/apm/public/components/shared/KueryBar/get_bool_filter.ts +++ b/x-pack/plugins/apm/public/components/shared/KueryBar/get_bool_filter.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { ESFilter } from '../../../../../../../typings/elasticsearch'; +import { ESFilter } from '../../../../../../../src/core/types/elasticsearch'; import { ERROR_GROUP_ID, PROCESSOR_EVENT, diff --git a/x-pack/plugins/apm/public/services/rest/apm_observability_overview_fetchers.ts b/x-pack/plugins/apm/public/services/rest/apm_observability_overview_fetchers.ts index 3a02efd05e5a5..ef61e25af4fc2 100644 --- a/x-pack/plugins/apm/public/services/rest/apm_observability_overview_fetchers.ts +++ b/x-pack/plugins/apm/public/services/rest/apm_observability_overview_fetchers.ts @@ -53,10 +53,8 @@ export const fetchObservabilityOverviewPageData = async ({ }; export async function getHasData() { - const res = await callApmApi({ + return await callApmApi({ endpoint: 'GET /api/apm/observability_overview/has_data', signal: null, }); - - return res.hasData; } diff --git a/x-pack/plugins/apm/public/utils/testHelpers.tsx b/x-pack/plugins/apm/public/utils/testHelpers.tsx index 877dec14ca1b0..9a1d4da8ece7c 100644 --- a/x-pack/plugins/apm/public/utils/testHelpers.tsx +++ b/x-pack/plugins/apm/public/utils/testHelpers.tsx @@ -19,7 +19,7 @@ import { EuiThemeProvider } from '../../../../../src/plugins/kibana_react/common import { ESSearchRequest, ESSearchResponse, -} from '../../../../../typings/elasticsearch'; +} from '../../../../../src/core/types/elasticsearch'; import { PromiseReturnType } from '../../../observability/typings/common'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { APMConfig } from '../../server'; diff --git a/x-pack/plugins/apm/scripts/shared/get_es_client.ts b/x-pack/plugins/apm/scripts/shared/get_es_client.ts index 7a8e09423ff15..3accb832fb0ed 100644 --- a/x-pack/plugins/apm/scripts/shared/get_es_client.ts +++ b/x-pack/plugins/apm/scripts/shared/get_es_client.ts @@ -10,7 +10,7 @@ import { ApiKeyAuth, BasicAuth } from '@elastic/elasticsearch/lib/pool'; import { ESSearchResponse, ESSearchRequest, -} from '../../../../../typings/elasticsearch'; +} from '../../../../../src/core/types/elasticsearch'; export type ESClient = ReturnType; diff --git a/x-pack/plugins/apm/server/lib/alerts/alerting_es_client.ts b/x-pack/plugins/apm/server/lib/alerts/alerting_es_client.ts index 7d5b7d594bdf9..8b4d3e2186c84 100644 --- a/x-pack/plugins/apm/server/lib/alerts/alerting_es_client.ts +++ b/x-pack/plugins/apm/server/lib/alerts/alerting_es_client.ts @@ -8,7 +8,7 @@ import { ESSearchRequest, ESSearchResponse, -} from '../../../../../../typings/elasticsearch'; +} from '../../../../../../src/core/types/elasticsearch'; import { AlertServices } from '../../../../alerting/server'; export async function alertingEsClient({ diff --git a/x-pack/plugins/apm/server/lib/alerts/register_transaction_duration_anomaly_alert_type.ts b/x-pack/plugins/apm/server/lib/alerts/register_transaction_duration_anomaly_alert_type.ts index 4ced6e6abb251..f640925b0a0fa 100644 --- a/x-pack/plugins/apm/server/lib/alerts/register_transaction_duration_anomaly_alert_type.ts +++ b/x-pack/plugins/apm/server/lib/alerts/register_transaction_duration_anomaly_alert_type.ts @@ -7,7 +7,7 @@ import { schema } from '@kbn/config-schema'; import { compact } from 'lodash'; -import { ESSearchResponse } from 'typings/elasticsearch'; +import { ESSearchResponse } from 'src/core/types/elasticsearch'; import { QueryDslQueryContainer } from '@elastic/elasticsearch/api/types'; import { ALERT_EVALUATION_THRESHOLD, diff --git a/x-pack/plugins/apm/server/lib/apm_telemetry/collect_data_telemetry/index.ts b/x-pack/plugins/apm/server/lib/apm_telemetry/collect_data_telemetry/index.ts index 87686d2c30cae..22a2090dbb6cd 100644 --- a/x-pack/plugins/apm/server/lib/apm_telemetry/collect_data_telemetry/index.ts +++ b/x-pack/plugins/apm/server/lib/apm_telemetry/collect_data_telemetry/index.ts @@ -11,7 +11,7 @@ import { IndicesStats } from '@elastic/elasticsearch/api/requestParams'; import { ESSearchRequest, ESSearchResponse, -} from '../../../../../../../typings/elasticsearch'; +} from '../../../../../../../src/core/types/elasticsearch'; import { ApmIndicesConfig } from '../../settings/apm_indices/get_apm_indices'; import { tasks } from './tasks'; import { APMDataTelemetry } from '../types'; diff --git a/x-pack/plugins/apm/server/lib/correlations/errors/get_correlations_for_failed_transactions.ts b/x-pack/plugins/apm/server/lib/correlations/errors/get_correlations_for_failed_transactions.ts index 11e9f99ddb356..081c66dc2c471 100644 --- a/x-pack/plugins/apm/server/lib/correlations/errors/get_correlations_for_failed_transactions.ts +++ b/x-pack/plugins/apm/server/lib/correlations/errors/get_correlations_for_failed_transactions.ts @@ -11,8 +11,8 @@ import { processSignificantTermAggs, TopSigTerm, } from '../process_significant_term_aggs'; -import { AggregationOptionsByType } from '../../../../../../../typings/elasticsearch'; -import { ESFilter } from '../../../../../../../typings/elasticsearch'; +import { AggregationOptionsByType } from '../../../../../../../src/core/types/elasticsearch'; +import { ESFilter } from '../../../../../../../src/core/types/elasticsearch'; import { EVENT_OUTCOME } from '../../../../common/elasticsearch_fieldnames'; import { ProcessorEvent } from '../../../../common/processor_event'; import { Setup, SetupTimeRange } from '../../helpers/setup_request'; diff --git a/x-pack/plugins/apm/server/lib/correlations/get_filters.ts b/x-pack/plugins/apm/server/lib/correlations/get_filters.ts index 92fc9c5d9622b..61fec492ad38e 100644 --- a/x-pack/plugins/apm/server/lib/correlations/get_filters.ts +++ b/x-pack/plugins/apm/server/lib/correlations/get_filters.ts @@ -6,7 +6,7 @@ */ import { Setup, SetupTimeRange } from '../helpers/setup_request'; -import { ESFilter } from '../../../../../../typings/elasticsearch'; +import { ESFilter } from '../../../../../../src/core/types/elasticsearch'; import { environmentQuery, rangeQuery, kqlQuery } from '../../utils/queries'; import { SERVICE_NAME, diff --git a/x-pack/plugins/apm/server/lib/correlations/latency/get_correlations_for_slow_transactions.ts b/x-pack/plugins/apm/server/lib/correlations/latency/get_correlations_for_slow_transactions.ts index c37b3e3ab8242..868a36958395b 100644 --- a/x-pack/plugins/apm/server/lib/correlations/latency/get_correlations_for_slow_transactions.ts +++ b/x-pack/plugins/apm/server/lib/correlations/latency/get_correlations_for_slow_transactions.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { AggregationOptionsByType } from '../../../../../../../typings/elasticsearch'; +import { AggregationOptionsByType } from '../../../../../../../src/core/types/elasticsearch'; import { TRANSACTION_DURATION } from '../../../../common/elasticsearch_fieldnames'; import { ProcessorEvent } from '../../../../common/processor_event'; import { getDurationForPercentile } from './get_duration_for_percentile'; diff --git a/x-pack/plugins/apm/server/lib/correlations/latency/get_duration_for_percentile.ts b/x-pack/plugins/apm/server/lib/correlations/latency/get_duration_for_percentile.ts index a686980700d83..902bdb8c7b511 100644 --- a/x-pack/plugins/apm/server/lib/correlations/latency/get_duration_for_percentile.ts +++ b/x-pack/plugins/apm/server/lib/correlations/latency/get_duration_for_percentile.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { ESFilter } from '../../../../../../../typings/elasticsearch'; +import { ESFilter } from '../../../../../../../src/core/types/elasticsearch'; import { TRANSACTION_DURATION } from '../../../../common/elasticsearch_fieldnames'; import { ProcessorEvent } from '../../../../common/processor_event'; import { Setup, SetupTimeRange } from '../../helpers/setup_request'; diff --git a/x-pack/plugins/apm/server/lib/correlations/latency/get_latency_distribution.ts b/x-pack/plugins/apm/server/lib/correlations/latency/get_latency_distribution.ts index be1bb631378cf..ad11d21a710d0 100644 --- a/x-pack/plugins/apm/server/lib/correlations/latency/get_latency_distribution.ts +++ b/x-pack/plugins/apm/server/lib/correlations/latency/get_latency_distribution.ts @@ -5,8 +5,8 @@ * 2.0. */ -import { AggregationOptionsByType } from '../../../../../../../typings/elasticsearch'; -import { ESFilter } from '../../../../../../../typings/elasticsearch'; +import { AggregationOptionsByType } from '../../../../../../../src/core/types/elasticsearch'; +import { ESFilter } from '../../../../../../../src/core/types/elasticsearch'; import { ProcessorEvent } from '../../../../common/processor_event'; import { Setup, SetupTimeRange } from '../../helpers/setup_request'; import { TopSigTerm } from '../process_significant_term_aggs'; diff --git a/x-pack/plugins/apm/server/lib/correlations/latency/get_max_latency.ts b/x-pack/plugins/apm/server/lib/correlations/latency/get_max_latency.ts index f2762086614b4..8b9a6c064b4a0 100644 --- a/x-pack/plugins/apm/server/lib/correlations/latency/get_max_latency.ts +++ b/x-pack/plugins/apm/server/lib/correlations/latency/get_max_latency.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { ESFilter } from '../../../../../../../typings/elasticsearch'; +import { ESFilter } from '../../../../../../../src/core/types/elasticsearch'; import { TRANSACTION_DURATION } from '../../../../common/elasticsearch_fieldnames'; import { ProcessorEvent } from '../../../../common/processor_event'; import { Setup, SetupTimeRange } from '../../helpers/setup_request'; diff --git a/x-pack/plugins/apm/server/lib/correlations/process_significant_term_aggs.ts b/x-pack/plugins/apm/server/lib/correlations/process_significant_term_aggs.ts index cc1e32e47973d..ecb751cad5a3f 100644 --- a/x-pack/plugins/apm/server/lib/correlations/process_significant_term_aggs.ts +++ b/x-pack/plugins/apm/server/lib/correlations/process_significant_term_aggs.ts @@ -9,7 +9,7 @@ import { orderBy } from 'lodash'; import { AggregationOptionsByType, AggregationResultOf, -} from '../../../../../../typings/elasticsearch'; +} from '../../../../../../src/core/types/elasticsearch'; export interface TopSigTerm { fieldName: string; diff --git a/x-pack/plugins/apm/server/lib/errors/distribution/get_buckets.ts b/x-pack/plugins/apm/server/lib/errors/distribution/get_buckets.ts index a51464764f2b4..fa73ce8f2bc85 100644 --- a/x-pack/plugins/apm/server/lib/errors/distribution/get_buckets.ts +++ b/x-pack/plugins/apm/server/lib/errors/distribution/get_buckets.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { ESFilter } from '../../../../../../../typings/elasticsearch'; +import { ESFilter } from '../../../../../../../src/core/types/elasticsearch'; import { ERROR_GROUP_ID, SERVICE_NAME, diff --git a/x-pack/plugins/apm/server/lib/helpers/create_es_client/create_apm_event_client/add_filter_to_exclude_legacy_data.ts b/x-pack/plugins/apm/server/lib/helpers/create_es_client/create_apm_event_client/add_filter_to_exclude_legacy_data.ts index 96bc8897e62fd..60984d65f4499 100644 --- a/x-pack/plugins/apm/server/lib/helpers/create_es_client/create_apm_event_client/add_filter_to_exclude_legacy_data.ts +++ b/x-pack/plugins/apm/server/lib/helpers/create_es_client/create_apm_event_client/add_filter_to_exclude_legacy_data.ts @@ -10,7 +10,7 @@ import { OBSERVER_VERSION_MAJOR } from '../../../../../common/elasticsearch_fiel import { ESSearchRequest, ESFilter, -} from '../../../../../../../../typings/elasticsearch'; +} from '../../../../../../../../src/core/types/elasticsearch'; /* Adds a range query to the ES request to exclude legacy data diff --git a/x-pack/plugins/apm/server/lib/helpers/create_es_client/create_apm_event_client/index.ts b/x-pack/plugins/apm/server/lib/helpers/create_es_client/create_apm_event_client/index.ts index 916a6981f286a..0a464982b6e81 100644 --- a/x-pack/plugins/apm/server/lib/helpers/create_es_client/create_apm_event_client/index.ts +++ b/x-pack/plugins/apm/server/lib/helpers/create_es_client/create_apm_event_client/index.ts @@ -15,7 +15,7 @@ import { import { ESSearchRequest, InferSearchResponseOf, -} from '../../../../../../../../typings/elasticsearch'; +} from '../../../../../../../../src/core/types/elasticsearch'; import { unwrapEsResponse } from '../../../../../../observability/server'; import { ProcessorEvent } from '../../../../../common/processor_event'; import { APMError } from '../../../../../typings/es_schemas/ui/apm_error'; diff --git a/x-pack/plugins/apm/server/lib/helpers/create_es_client/create_apm_event_client/unpack_processor_events.ts b/x-pack/plugins/apm/server/lib/helpers/create_es_client/create_apm_event_client/unpack_processor_events.ts index 76e615f42bb64..8732ba81f9ae6 100644 --- a/x-pack/plugins/apm/server/lib/helpers/create_es_client/create_apm_event_client/unpack_processor_events.ts +++ b/x-pack/plugins/apm/server/lib/helpers/create_es_client/create_apm_event_client/unpack_processor_events.ts @@ -11,7 +11,7 @@ import { ProcessorEvent } from '../../../../../common/processor_event'; import { ESSearchRequest, ESFilter, -} from '../../../../../../../../typings/elasticsearch'; +} from '../../../../../../../../src/core/types/elasticsearch'; import { APMEventESSearchRequest } from '.'; import { ApmIndicesConfig, diff --git a/x-pack/plugins/apm/server/lib/helpers/create_es_client/create_internal_es_client/index.ts b/x-pack/plugins/apm/server/lib/helpers/create_es_client/create_internal_es_client/index.ts index e6b61a709ae35..eb3deb2889360 100644 --- a/x-pack/plugins/apm/server/lib/helpers/create_es_client/create_internal_es_client/index.ts +++ b/x-pack/plugins/apm/server/lib/helpers/create_es_client/create_internal_es_client/index.ts @@ -12,7 +12,7 @@ import { APMRouteHandlerResources } from '../../../../routes/typings'; import { ESSearchResponse, ESSearchRequest, -} from '../../../../../../../../typings/elasticsearch'; +} from '../../../../../../../../src/core/types/elasticsearch'; import { callAsyncWithDebug, getDebugBody, diff --git a/x-pack/plugins/apm/server/lib/helpers/transaction_error_rate.ts b/x-pack/plugins/apm/server/lib/helpers/transaction_error_rate.ts index b60a2a071e6dc..41d9c373710c1 100644 --- a/x-pack/plugins/apm/server/lib/helpers/transaction_error_rate.ts +++ b/x-pack/plugins/apm/server/lib/helpers/transaction_error_rate.ts @@ -10,7 +10,7 @@ import { EventOutcome } from '../../../common/event_outcome'; import { AggregationOptionsByType, AggregationResultOf, -} from '../../../../../../typings/elasticsearch'; +} from '../../../../../../src/core/types/elasticsearch'; export const getOutcomeAggregation = () => ({ terms: { diff --git a/x-pack/plugins/apm/server/lib/metrics/fetch_and_transform_metrics.ts b/x-pack/plugins/apm/server/lib/metrics/fetch_and_transform_metrics.ts index a91571bbc406d..cd94eb8511282 100644 --- a/x-pack/plugins/apm/server/lib/metrics/fetch_and_transform_metrics.ts +++ b/x-pack/plugins/apm/server/lib/metrics/fetch_and_transform_metrics.ts @@ -6,7 +6,7 @@ */ import { Overwrite, Unionize } from 'utility-types'; -import { AggregationOptionsByType } from '../../../../../../typings/elasticsearch'; +import { AggregationOptionsByType } from '../../../../../../src/core/types/elasticsearch'; import { getMetricsProjection } from '../../projections/metrics'; import { mergeProjection } from '../../projections/util/merge_projection'; import { APMEventESSearchRequest } from '../helpers/create_es_client/create_apm_event_client'; diff --git a/x-pack/plugins/apm/server/lib/metrics/transform_metrics_chart.ts b/x-pack/plugins/apm/server/lib/metrics/transform_metrics_chart.ts index 17759f9094a87..999830dabefc4 100644 --- a/x-pack/plugins/apm/server/lib/metrics/transform_metrics_chart.ts +++ b/x-pack/plugins/apm/server/lib/metrics/transform_metrics_chart.ts @@ -6,7 +6,7 @@ */ import theme from '@elastic/eui/dist/eui_theme_light.json'; -import { ESSearchResponse } from '../../../../../../typings/elasticsearch'; +import { ESSearchResponse } from '../../../../../../src/core/types/elasticsearch'; import { getVizColorForIndex } from '../../../common/viz_colors'; import { GenericMetricsRequest } from './fetch_and_transform_metrics'; import { ChartBase } from './types'; diff --git a/x-pack/plugins/apm/server/lib/observability_overview/has_data.ts b/x-pack/plugins/apm/server/lib/observability_overview/has_data.ts index 5c1a33e750e12..3b6993695f3de 100644 --- a/x-pack/plugins/apm/server/lib/observability_overview/has_data.ts +++ b/x-pack/plugins/apm/server/lib/observability_overview/has_data.ts @@ -29,8 +29,14 @@ export async function getHasData({ setup }: { setup: Setup }) { 'observability_overview_has_apm_data', params ); - return response.hits.total.value > 0; + return { + hasData: response.hits.total.value > 0, + indices: setup.indices, + }; } catch (e) { - return false; + return { + hasData: false, + indices: setup.indices, + }; } } diff --git a/x-pack/plugins/apm/server/lib/rum_client/ui_filters/get_es_filter.ts b/x-pack/plugins/apm/server/lib/rum_client/ui_filters/get_es_filter.ts index ce6de1e007625..48beb9bca5241 100644 --- a/x-pack/plugins/apm/server/lib/rum_client/ui_filters/get_es_filter.ts +++ b/x-pack/plugins/apm/server/lib/rum_client/ui_filters/get_es_filter.ts @@ -9,7 +9,7 @@ import { uxLocalUIFilterNames, uxLocalUIFilters, } from '../../../../common/ux_ui_filter'; -import { ESFilter } from '../../../../../../../typings/elasticsearch'; +import { ESFilter } from '../../../../../../../src/core/types/elasticsearch'; import { UxUIFilters } from '../../../../typings/ui_filters'; import { environmentQuery } from '../../../utils/queries'; diff --git a/x-pack/plugins/apm/server/lib/service_map/get_service_anomalies.ts b/x-pack/plugins/apm/server/lib/service_map/get_service_anomalies.ts index 129a0ee73b8cb..7ac56bcd9192d 100644 --- a/x-pack/plugins/apm/server/lib/service_map/get_service_anomalies.ts +++ b/x-pack/plugins/apm/server/lib/service_map/get_service_anomalies.ts @@ -8,7 +8,7 @@ import Boom from '@hapi/boom'; import { sortBy, uniqBy } from 'lodash'; import { estypes } from '@elastic/elasticsearch'; -import { ESSearchResponse } from '../../../../../../typings/elasticsearch'; +import { ESSearchResponse } from '../../../../../../src/core/types/elasticsearch'; import { MlPluginSetup } from '../../../../ml/server'; import { PromiseReturnType } from '../../../../observability/typings/common'; import { getSeverity, ML_ERRORS } from '../../../common/anomaly_detection'; diff --git a/x-pack/plugins/apm/server/lib/service_map/get_service_map_service_node_info.ts b/x-pack/plugins/apm/server/lib/service_map/get_service_map_service_node_info.ts index 2709fb640d8ce..2e0ac303e5157 100644 --- a/x-pack/plugins/apm/server/lib/service_map/get_service_map_service_node_info.ts +++ b/x-pack/plugins/apm/server/lib/service_map/get_service_map_service_node_info.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { ESFilter } from '../../../../../../typings/elasticsearch'; +import { ESFilter } from '../../../../../../src/core/types/elasticsearch'; import { METRIC_CGROUP_MEMORY_USAGE_BYTES, METRIC_SYSTEM_CPU_PERCENT, diff --git a/x-pack/plugins/apm/server/lib/service_map/get_trace_sample_ids.ts b/x-pack/plugins/apm/server/lib/service_map/get_trace_sample_ids.ts index 7894a95cf4d7e..26d7d2d1ee316 100644 --- a/x-pack/plugins/apm/server/lib/service_map/get_trace_sample_ids.ts +++ b/x-pack/plugins/apm/server/lib/service_map/get_trace_sample_ids.ts @@ -8,7 +8,7 @@ import Boom from '@hapi/boom'; import { sortBy, take, uniq } from 'lodash'; import { asMutableArray } from '../../../common/utils/as_mutable_array'; -import { ESFilter } from '../../../../../../typings/elasticsearch'; +import { ESFilter } from '../../../../../../src/core/types/elasticsearch'; import { SERVICE_ENVIRONMENT, SERVICE_NAME, diff --git a/x-pack/plugins/apm/server/lib/services/annotations/get_derived_service_annotations.ts b/x-pack/plugins/apm/server/lib/services/annotations/get_derived_service_annotations.ts index 202b5075d2ea7..08587217980fb 100644 --- a/x-pack/plugins/apm/server/lib/services/annotations/get_derived_service_annotations.ts +++ b/x-pack/plugins/apm/server/lib/services/annotations/get_derived_service_annotations.ts @@ -6,7 +6,7 @@ */ import { isFiniteNumber } from '../../../../common/utils/is_finite_number'; -import { ESFilter } from '../../../../../../../typings/elasticsearch'; +import { ESFilter } from '../../../../../../../src/core/types/elasticsearch'; import { Annotation, AnnotationType } from '../../../../common/annotations'; import { SERVICE_NAME, diff --git a/x-pack/plugins/apm/server/lib/services/annotations/get_stored_annotations.ts b/x-pack/plugins/apm/server/lib/services/annotations/get_stored_annotations.ts index 3e1a8f26de6b4..56b7aa1f465b0 100644 --- a/x-pack/plugins/apm/server/lib/services/annotations/get_stored_annotations.ts +++ b/x-pack/plugins/apm/server/lib/services/annotations/get_stored_annotations.ts @@ -12,7 +12,7 @@ import { unwrapEsResponse, WrappedElasticsearchClientError, } from '../../../../../observability/server'; -import { ESSearchResponse } from '../../../../../../../typings/elasticsearch'; +import { ESSearchResponse } from '../../../../../../../src/core/types/elasticsearch'; import { Annotation as ESAnnotation } from '../../../../../observability/common/annotations'; import { ScopedAnnotationsClient } from '../../../../../observability/server'; import { Annotation, AnnotationType } from '../../../../common/annotations'; diff --git a/x-pack/plugins/apm/server/lib/services/annotations/index.test.ts b/x-pack/plugins/apm/server/lib/services/annotations/index.test.ts index e2597a4a79cba..6d65c971baa33 100644 --- a/x-pack/plugins/apm/server/lib/services/annotations/index.test.ts +++ b/x-pack/plugins/apm/server/lib/services/annotations/index.test.ts @@ -8,7 +8,7 @@ import { ESSearchRequest, ESSearchResponse, -} from '../../../../../../../typings/elasticsearch'; +} from '../../../../../../../src/core/types/elasticsearch'; import { inspectSearchParams, SearchParamsMock, diff --git a/x-pack/plugins/apm/server/lib/services/get_service_instances/get_service_instances_system_metric_statistics.ts b/x-pack/plugins/apm/server/lib/services/get_service_instances/get_service_instances_system_metric_statistics.ts index 526ae19143f13..b817d4fb654ce 100644 --- a/x-pack/plugins/apm/server/lib/services/get_service_instances/get_service_instances_system_metric_statistics.ts +++ b/x-pack/plugins/apm/server/lib/services/get_service_instances/get_service_instances_system_metric_statistics.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { AggregationOptionsByType } from 'typings/elasticsearch'; +import { AggregationOptionsByType } from 'src/core/types/elasticsearch'; import { METRIC_CGROUP_MEMORY_USAGE_BYTES, METRIC_PROCESS_CPU_PERCENT, diff --git a/x-pack/plugins/apm/server/lib/services/get_throughput.ts b/x-pack/plugins/apm/server/lib/services/get_throughput.ts index b0cb917d302fc..0490c31e7c63d 100644 --- a/x-pack/plugins/apm/server/lib/services/get_throughput.ts +++ b/x-pack/plugins/apm/server/lib/services/get_throughput.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { ESFilter } from '../../../../../../typings/elasticsearch'; +import { ESFilter } from '../../../../../../src/core/types/elasticsearch'; import { SERVICE_NAME, TRANSACTION_TYPE, diff --git a/x-pack/plugins/apm/server/lib/services/profiling/get_service_profiling_statistics.ts b/x-pack/plugins/apm/server/lib/services/profiling/get_service_profiling_statistics.ts index bb98abf724db4..4e88c752aa50b 100644 --- a/x-pack/plugins/apm/server/lib/services/profiling/get_service_profiling_statistics.ts +++ b/x-pack/plugins/apm/server/lib/services/profiling/get_service_profiling_statistics.ts @@ -15,7 +15,7 @@ import { getValueTypeConfig, } from '../../../../common/profiling'; import { ProcessorEvent } from '../../../../common/processor_event'; -import { ESFilter } from '../../../../../../../typings/elasticsearch'; +import { ESFilter } from '../../../../../../../src/core/types/elasticsearch'; import { PROFILE_STACK, PROFILE_TOP_ID, diff --git a/x-pack/plugins/apm/server/lib/settings/agent_configuration/convert_settings_to_string.ts b/x-pack/plugins/apm/server/lib/settings/agent_configuration/convert_settings_to_string.ts index 0b826ea10b6c4..3ec10f6bf0c63 100644 --- a/x-pack/plugins/apm/server/lib/settings/agent_configuration/convert_settings_to_string.ts +++ b/x-pack/plugins/apm/server/lib/settings/agent_configuration/convert_settings_to_string.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { SearchHit } from '../../../../../../../typings/elasticsearch'; +import { SearchHit } from '../../../../../../../src/core/types/elasticsearch'; import { AgentConfiguration } from '../../../../common/agent_configuration/configuration_types'; // needed for backwards compatability diff --git a/x-pack/plugins/apm/server/lib/settings/agent_configuration/find_exact_configuration.ts b/x-pack/plugins/apm/server/lib/settings/agent_configuration/find_exact_configuration.ts index 3543d38f7b5d1..90f82442f9bfa 100644 --- a/x-pack/plugins/apm/server/lib/settings/agent_configuration/find_exact_configuration.ts +++ b/x-pack/plugins/apm/server/lib/settings/agent_configuration/find_exact_configuration.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { SearchHit } from '../../../../../../../typings/elasticsearch'; +import { SearchHit } from '../../../../../../../src/core/types/elasticsearch'; import { AgentConfiguration } from '../../../../common/agent_configuration/configuration_types'; import { SERVICE_ENVIRONMENT, diff --git a/x-pack/plugins/apm/server/lib/settings/agent_configuration/search_configurations.ts b/x-pack/plugins/apm/server/lib/settings/agent_configuration/search_configurations.ts index 4e27953b3a315..1e37ae9108573 100644 --- a/x-pack/plugins/apm/server/lib/settings/agent_configuration/search_configurations.ts +++ b/x-pack/plugins/apm/server/lib/settings/agent_configuration/search_configurations.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { SearchHit } from '../../../../../../../typings/elasticsearch'; +import { SearchHit } from '../../../../../../../src/core/types/elasticsearch'; import { SERVICE_NAME, SERVICE_ENVIRONMENT, diff --git a/x-pack/plugins/apm/server/lib/settings/apm_indices/get_apm_indices.ts b/x-pack/plugins/apm/server/lib/settings/apm_indices/get_apm_indices.ts index d8dbc242986a6..0ade96682b362 100644 --- a/x-pack/plugins/apm/server/lib/settings/apm_indices/get_apm_indices.ts +++ b/x-pack/plugins/apm/server/lib/settings/apm_indices/get_apm_indices.ts @@ -16,21 +16,11 @@ import { import { APMConfig } from '../../..'; import { APMRouteHandlerResources } from '../../../routes/typings'; import { withApmSpan } from '../../../utils/with_apm_span'; +import { ApmIndicesConfig } from '../../../../../observability/common/typings'; -type ISavedObjectsClient = Pick; +export { ApmIndicesConfig }; -export interface ApmIndicesConfig { - /* eslint-disable @typescript-eslint/naming-convention */ - 'apm_oss.sourcemapIndices': string; - 'apm_oss.errorIndices': string; - 'apm_oss.onboardingIndices': string; - 'apm_oss.spanIndices': string; - 'apm_oss.transactionIndices': string; - 'apm_oss.metricsIndices': string; - /* eslint-enable @typescript-eslint/naming-convention */ - apmAgentConfigurationIndex: string; - apmCustomLinkIndex: string; -} +type ISavedObjectsClient = Pick; export type ApmIndicesName = keyof ApmIndicesConfig; diff --git a/x-pack/plugins/apm/server/lib/transaction_groups/fetcher.ts b/x-pack/plugins/apm/server/lib/transaction_groups/fetcher.ts index c1bf363b49d1c..85f36b3999060 100644 --- a/x-pack/plugins/apm/server/lib/transaction_groups/fetcher.ts +++ b/x-pack/plugins/apm/server/lib/transaction_groups/fetcher.ts @@ -9,7 +9,7 @@ import { sortBy, take } from 'lodash'; import moment from 'moment'; import { Unionize } from 'utility-types'; import { asMutableArray } from '../../../common/utils/as_mutable_array'; -import { AggregationOptionsByType } from '../../../../../../typings/elasticsearch'; +import { AggregationOptionsByType } from '../../../../../../src/core/types/elasticsearch'; import { PromiseReturnType } from '../../../../observability/typings/common'; import { SERVICE_NAME, diff --git a/x-pack/plugins/apm/server/lib/transactions/get_anomaly_data/fetcher.ts b/x-pack/plugins/apm/server/lib/transactions/get_anomaly_data/fetcher.ts index b105f3b5c0a30..558db17939354 100644 --- a/x-pack/plugins/apm/server/lib/transactions/get_anomaly_data/fetcher.ts +++ b/x-pack/plugins/apm/server/lib/transactions/get_anomaly_data/fetcher.ts @@ -6,7 +6,7 @@ */ import { QueryDslQueryContainer } from '@elastic/elasticsearch/api/types'; -import { ESSearchResponse } from '../../../../../../../typings/elasticsearch'; +import { ESSearchResponse } from '../../../../../../../src/core/types/elasticsearch'; import { PromiseReturnType } from '../../../../../observability/typings/common'; import { rangeQuery } from '../../../../server/utils/queries'; import { asMutableArray } from '../../../../common/utils/as_mutable_array'; diff --git a/x-pack/plugins/apm/server/lib/transactions/get_latency_charts/index.ts b/x-pack/plugins/apm/server/lib/transactions/get_latency_charts/index.ts index 2d350090fa28b..1a183e15fee2b 100644 --- a/x-pack/plugins/apm/server/lib/transactions/get_latency_charts/index.ts +++ b/x-pack/plugins/apm/server/lib/transactions/get_latency_charts/index.ts @@ -6,7 +6,7 @@ */ import { offsetPreviousPeriodCoordinates } from '../../../../common/utils/offset_previous_period_coordinate'; -import { ESFilter } from '../../../../../../../typings/elasticsearch'; +import { ESFilter } from '../../../../../../../src/core/types/elasticsearch'; import { PromiseReturnType } from '../../../../../observability/typings/common'; import { SERVICE_NAME, diff --git a/x-pack/plugins/apm/server/lib/transactions/get_throughput_charts/index.ts b/x-pack/plugins/apm/server/lib/transactions/get_throughput_charts/index.ts index f4d9236395252..ed85e700c3473 100644 --- a/x-pack/plugins/apm/server/lib/transactions/get_throughput_charts/index.ts +++ b/x-pack/plugins/apm/server/lib/transactions/get_throughput_charts/index.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { ESFilter } from '../../../../../../../typings/elasticsearch'; +import { ESFilter } from '../../../../../../../src/core/types/elasticsearch'; import { PromiseReturnType } from '../../../../../observability/typings/common'; import { SERVICE_NAME, diff --git a/x-pack/plugins/apm/server/projections/typings.ts b/x-pack/plugins/apm/server/projections/typings.ts index 60a3317af1864..0843fa4c9dd64 100644 --- a/x-pack/plugins/apm/server/projections/typings.ts +++ b/x-pack/plugins/apm/server/projections/typings.ts @@ -5,7 +5,7 @@ * 2.0. */ import { estypes } from '@elastic/elasticsearch'; -import { AggregationOptionsByType } from '../../../../../typings/elasticsearch'; +import { AggregationOptionsByType } from '../../../../../src/core/types/elasticsearch'; import { APMEventESSearchRequest } from '../lib/helpers/create_es_client/create_apm_event_client'; export type Projection = Omit & { diff --git a/x-pack/plugins/apm/server/routes/observability_overview.ts b/x-pack/plugins/apm/server/routes/observability_overview.ts index d459570cf7337..c2e3d0e81ce0a 100644 --- a/x-pack/plugins/apm/server/routes/observability_overview.ts +++ b/x-pack/plugins/apm/server/routes/observability_overview.ts @@ -21,8 +21,7 @@ const observabilityOverviewHasDataRoute = createApmServerRoute({ options: { tags: ['access:apm'] }, handler: async (resources) => { const setup = await setupRequest(resources); - const res = await getHasData({ setup }); - return { hasData: res }; + return await getHasData({ setup }); }, }); diff --git a/x-pack/plugins/apm/server/utils/queries.ts b/x-pack/plugins/apm/server/utils/queries.ts index f21ef9de0283f..a82b49a84dc6e 100644 --- a/x-pack/plugins/apm/server/utils/queries.ts +++ b/x-pack/plugins/apm/server/utils/queries.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { ESFilter } from '../../../../../typings/elasticsearch'; +import { ESFilter } from '../../../../../src/core/types/elasticsearch'; import { SERVICE_ENVIRONMENT } from '../../common/elasticsearch_fieldnames'; import { ENVIRONMENT_ALL, diff --git a/x-pack/plugins/apm/server/utils/test_helpers.tsx b/x-pack/plugins/apm/server/utils/test_helpers.tsx index 9f271c566e2fa..1d0a47ece9a60 100644 --- a/x-pack/plugins/apm/server/utils/test_helpers.tsx +++ b/x-pack/plugins/apm/server/utils/test_helpers.tsx @@ -10,7 +10,7 @@ import { PromiseReturnType } from '../../../observability/typings/common'; import { ESSearchRequest, ESSearchResponse, -} from '../../../../../typings/elasticsearch'; +} from '../../../../../src/core/types/elasticsearch'; import { UxUIFilters } from '../../typings/ui_filters'; interface Options { diff --git a/x-pack/plugins/canvas/CONTRIBUTING.md b/x-pack/plugins/canvas/CONTRIBUTING.md index 538cc1592c3bc..d3bff67771244 100644 --- a/x-pack/plugins/canvas/CONTRIBUTING.md +++ b/x-pack/plugins/canvas/CONTRIBUTING.md @@ -113,27 +113,4 @@ Canvas uses [Storybook](https://storybook.js.org) to test and develop components ### Using Storybook -The Canvas Storybook instance can be started by running `node scripts/storybook` from the Canvas root directory. It has a number of options: - -``` -node scripts/storybook - - Storybook runner for Canvas. - - Options: - --clean Forces a clean of the Storybook DLL and exits. - --dll Cleans and builds the Storybook dependency DLL and exits. - --stats Produces a Webpack stats file. - --site Produces a site deployment of this Storybook. - --verbose, -v Log verbosely - --debug Log debug messages (less than verbose) - --quiet Only log errors - --silent Don't log anything - --help Show this message -``` - -### What about `kbn-storybook`? - -Canvas wants to move to the Kibana Storybook instance as soon as feasible. There are few tweaks Canvas makes to Storybook, so we're actively working with the maintainers to make that migration successful. - -In the meantime, people can test our progress by running `node scripts/storybook_new` from the Canvas root. +The Canvas Storybook instance can be started by running `yarn storybook canvas` from the Kibana root directory. diff --git a/x-pack/plugins/canvas/scripts/storybook.js b/x-pack/plugins/canvas/scripts/storybook.js deleted file mode 100644 index e6b8a66b9026f..0000000000000 --- a/x-pack/plugins/canvas/scripts/storybook.js +++ /dev/null @@ -1,124 +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. - */ - -const path = require('path'); -const fs = require('fs'); -const del = require('del'); -const { run } = require('@kbn/dev-utils'); -// This is included in the main Kibana package.json -// eslint-disable-next-line import/no-extraneous-dependencies -const storybook = require('@storybook/react/standalone'); -const execa = require('execa'); -const { DLL_OUTPUT } = require('./../storybook/constants'); - -const storybookOptions = { - configDir: path.resolve(__dirname, './../storybook'), - mode: 'dev', -}; - -run( - ({ log, flags }) => { - const { addon, dll, clean, stats, site } = flags; - - // Delete the existing DLL if we're cleaning or building. - if (clean || dll) { - del.sync([DLL_OUTPUT], { force: true }); - - if (clean) { - return; - } - } - - // Build the DLL if necessary. - if (fs.existsSync(DLL_OUTPUT)) { - log.info('storybook: DLL exists from previous build; skipping'); - } else { - log.info('storybook: Building DLL'); - execa.sync( - 'yarn', - [ - 'webpack', - '--config', - 'x-pack/plugins/canvas/storybook/webpack.dll.config.js', - ...(process.stdout.isTTY && !process.env.CI ? ['--progress'] : []), - '--hide-modules', - '--display-entrypoints', - 'false', - ], - { - cwd: path.resolve(__dirname, '../../../..'), - stdio: ['ignore', 'inherit', 'inherit'], - buffer: false, - } - ); - log.success('storybook: DLL built'); - } - - // If we're only building the DLL, we're done. - if (dll) { - return; - } - - // Build statistics and exit - if (stats) { - log.success('storybook: Generating Storybook statistics'); - storybook({ - ...storybookOptions, - smokeTest: true, - }); - return; - } - - // Build the addon - execa.sync('node', ['scripts/build'], { - cwd: path.resolve(__dirname, '../storybook/addon'), - stdio: ['ignore', 'inherit', 'inherit'], - buffer: false, - }); - - // Build site and exit - if (site) { - log.success('storybook: Generating Storybook site'); - storybook({ - ...storybookOptions, - mode: 'static', - outputDir: path.resolve(__dirname, './../storybook/build'), - }); - return; - } - - log.info('storybook: Starting Storybook'); - - if (addon) { - execa('node', ['scripts/build', '--watch'], { - cwd: path.resolve(__dirname, '../storybook/addon'), - stdio: ['ignore', 'inherit', 'inherit'], - buffer: false, - }); - } - - storybook({ - ...storybookOptions, - port: 9001, - }); - }, - { - description: ` - Storybook runner for Canvas. - `, - flags: { - boolean: ['addon', 'dll', 'clean', 'stats', 'site'], - help: ` - --addon Watch the addon source code for changes. - --clean Forces a clean of the Storybook DLL and exits. - --dll Cleans and builds the Storybook dependency DLL and exits. - --stats Produces a Webpack stats file. - --site Produces a site deployment of this Storybook. - `, - }, - } -); diff --git a/x-pack/plugins/canvas/shareable_runtime/README.mdx b/x-pack/plugins/canvas/shareable_runtime/README.mdx index 8f1c9e06ca788..a9d58bb3833d9 100644 --- a/x-pack/plugins/canvas/shareable_runtime/README.mdx +++ b/x-pack/plugins/canvas/shareable_runtime/README.mdx @@ -153,7 +153,7 @@ You can test this functionality in a number of ways. The easiest would be: ### Run the Canvas Storybook -From `/canvas`: `node scripts/storybook` +From `/kibana`: `yarn storybook canvas` ### Run the Jest Tests diff --git a/x-pack/plugins/canvas/storybook/.babelrc b/x-pack/plugins/canvas/storybook/.babelrc deleted file mode 100644 index c8f2d480f3a09..0000000000000 --- a/x-pack/plugins/canvas/storybook/.babelrc +++ /dev/null @@ -1,9 +0,0 @@ -{ - "env": { - "test": { - "plugins": [ - "require-context-hook" - ] - } - } -} diff --git a/x-pack/plugins/canvas/storybook/addon/src/register.tsx b/x-pack/plugins/canvas/storybook/addon/src/register.tsx index 2a0df578ff22c..1e99d6bdb74eb 100644 --- a/x-pack/plugins/canvas/storybook/addon/src/register.tsx +++ b/x-pack/plugins/canvas/storybook/addon/src/register.tsx @@ -9,6 +9,8 @@ import React from 'react'; import { addons, types } from '@storybook/addons'; import { AddonPanel } from '@storybook/components'; import { STORY_CHANGED } from '@storybook/core-events'; +import { create } from '@storybook/theming'; +import { PANEL_ID } from '@storybook/addon-actions'; import { ADDON_ID, EVENTS, ACTIONS_PANEL_ID } from './constants'; import { Panel } from './panel'; @@ -32,3 +34,16 @@ addons.register(ADDON_ID, (api) => { }, }); }); + +addons.setConfig({ + theme: create({ + base: 'light', + brandTitle: 'Canvas Storybook', + brandUrl: 'https://github.com/elastic/kibana/tree/master/x-pack/plugins/canvas', + }), + showPanel: true, + isFullscreen: false, + panelPosition: 'bottom', + isToolshown: true, + selectedPanel: PANEL_ID, +}); diff --git a/x-pack/plugins/canvas/storybook/constants.js b/x-pack/plugins/canvas/storybook/constants.js deleted file mode 100644 index c2a4f414588d4..0000000000000 --- a/x-pack/plugins/canvas/storybook/constants.js +++ /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. - */ - -const path = require('path'); - -const DLL_NAME = 'canvas_storybook_dll'; -const KIBANA_ROOT = path.resolve(__dirname, '../../../..'); -const DLL_OUTPUT = path.resolve(KIBANA_ROOT, 'built_assets', DLL_NAME); - -module.exports = { - DLL_NAME, - KIBANA_ROOT, - DLL_OUTPUT, -}; diff --git a/x-pack/plugins/canvas/storybook/constants.ts b/x-pack/plugins/canvas/storybook/constants.ts new file mode 100644 index 0000000000000..711b939179452 --- /dev/null +++ b/x-pack/plugins/canvas/storybook/constants.ts @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import path from 'path'; + +export const KIBANA_ROOT = path.resolve(__dirname, '../../../..'); diff --git a/x-pack/plugins/canvas/storybook/dll_contexts.js b/x-pack/plugins/canvas/storybook/dll_contexts.js deleted file mode 100644 index 12f0723ae8b77..0000000000000 --- a/x-pack/plugins/canvas/storybook/dll_contexts.js +++ /dev/null @@ -1,13 +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. - */ - -// This file defines CSS and Legacy style contexts for use in the DLL. This file -// is also require'd in the Storybook config so that the Storybook Webpack instance -// is aware of them, and can load them from the DLL. - -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -require('../../../../src/core/server/core_app/assets/legacy_light_theme.css'); diff --git a/x-pack/plugins/canvas/storybook/main.ts b/x-pack/plugins/canvas/storybook/main.ts index 79e64caffd3dc..80a8aeb14a804 100644 --- a/x-pack/plugins/canvas/storybook/main.ts +++ b/x-pack/plugins/canvas/storybook/main.ts @@ -5,23 +5,58 @@ * 2.0. */ -/* eslint-disable @typescript-eslint/no-var-requires */ -const { existsSync } = require('fs'); -const { join } = require('path'); +import { resolve } from 'path'; +import webpackMerge from 'webpack-merge'; +import { defaultConfig } from '@kbn/storybook'; -// Check for DLL if we're not running in Jest -if ( - !existsSync(join(__dirname, '../../../../built_assets/canvas_storybook_dll/manifest.json')) && - !process.env.JEST_WORKER_ID -) { - // eslint-disable-next-line no-console - console.error( - 'No DLL found. Run `node scripts/storybook --dll` from the Canvas plugin directory.' - ); - process.exit(1); -} +import type { Configuration } from 'webpack'; + +import { KIBANA_ROOT } from './constants'; + +const canvasWebpack = { + module: { + rules: [ + // Enable CSS Modules in Storybook (Shareable Runtime) + { + test: /\.module\.s(a|c)ss$/, + loader: [ + 'style-loader', + { + loader: 'css-loader', + options: { + importLoaders: 2, + modules: { + localIdentName: '[name]__[local]___[hash:base64:5]', + }, + }, + }, + { + loader: 'postcss-loader', + options: { + path: resolve(KIBANA_ROOT, 'src/optimize/postcss.config.js'), + }, + }, + { + loader: 'sass-loader', + }, + ], + }, + // Exclude large-dependency, troublesome or irrelevant modules. + { + test: [ + resolve(KIBANA_ROOT, 'x-pack/plugins/canvas/public/components/embeddable_flyout'), + resolve(KIBANA_ROOT, 'x-pack/plugins/reporting/public'), + resolve(KIBANA_ROOT, 'src/plugins/kibana_legacy/public/angular'), + resolve(KIBANA_ROOT, 'src/plugins/kibana_legacy/public/paginate'), + ], + use: 'null-loader', + }, + ], + }, +}; module.exports = { - stories: ['../**/*.stories.tsx'], - addons: ['@storybook/addon-actions', '@storybook/addon-knobs', './addon/target/register'], + ...defaultConfig, + addons: [...(defaultConfig.addons || []), './addon/target/register'], + webpackFinal: (config: Configuration) => webpackMerge(config, canvasWebpack), }; diff --git a/x-pack/plugins/canvas/storybook/manager.ts b/x-pack/plugins/canvas/storybook/manager.ts deleted file mode 100644 index 27abc9eba3854..0000000000000 --- a/x-pack/plugins/canvas/storybook/manager.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 { addons } from '@storybook/addons'; -import { create } from '@storybook/theming'; -import { PANEL_ID } from '@storybook/addon-actions'; - -addons.setConfig({ - theme: create({ - base: 'light', - brandTitle: 'Canvas Storybook', - brandUrl: 'https://github.com/elastic/kibana/tree/master/x-pack/plugins/canvas', - }), - showPanel: true, - isFullscreen: false, - panelPosition: 'bottom', - isToolshown: true, - selectedPanel: PANEL_ID, -}); diff --git a/x-pack/plugins/canvas/storybook/middleware.ts b/x-pack/plugins/canvas/storybook/middleware.ts deleted file mode 100644 index 1662c28b238d7..0000000000000 --- a/x-pack/plugins/canvas/storybook/middleware.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 path from 'path'; -// @ts-expect-error -import serve from 'serve-static'; - -// Extend the Storybook Middleware to include a route to access Legacy UI assets -module.exports = function (router: { get: (...args: any[]) => void }) { - router.get( - '/ui', - serve(path.resolve(__dirname, '../../../../../src/core/server/core_app/assets')) - ); -}; diff --git a/x-pack/plugins/canvas/storybook/preview-head.html b/x-pack/plugins/canvas/storybook/preview-head.html deleted file mode 100644 index f8a7de6ddbaf1..0000000000000 --- a/x-pack/plugins/canvas/storybook/preview-head.html +++ /dev/null @@ -1,6 +0,0 @@ - - - diff --git a/x-pack/plugins/canvas/storybook/preview.ts b/x-pack/plugins/canvas/storybook/preview.ts index 3c38f4ed5c682..f885a654cdab8 100644 --- a/x-pack/plugins/canvas/storybook/preview.ts +++ b/x-pack/plugins/canvas/storybook/preview.ts @@ -10,9 +10,6 @@ import { action } from '@storybook/addon-actions'; import { startServices } from '../public/services/stubs'; import { addDecorators } from './decorators'; -// Import the modules from the DLL. -import './dll_contexts'; - // Import Canvas CSS import '../public/style/index.scss'; diff --git a/x-pack/plugins/canvas/storybook/webpack.config.js b/x-pack/plugins/canvas/storybook/webpack.config.js deleted file mode 100644 index 77b8d343a2bea..0000000000000 --- a/x-pack/plugins/canvas/storybook/webpack.config.js +++ /dev/null @@ -1,210 +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. - */ - -const path = require('path'); -const webpack = require('webpack'); -const webpackMerge = require('webpack-merge'); -const { stringifyRequest } = require('loader-utils'); -const CopyWebpackPlugin = require('copy-webpack-plugin'); -const { DLL_OUTPUT, KIBANA_ROOT } = require('./constants'); - -// Extend the Storybook Webpack config with some customizations -module.exports = async ({ config: storybookConfig }) => { - const config = { - module: { - rules: [ - // Include the React preset from Kibana for JS(X) and TS(X) - { - test: /\.(j|t)sx?$/, - exclude: /node_modules/, - loaders: 'babel-loader', - options: { - presets: [require.resolve('@kbn/babel-preset/webpack_preset')], - }, - }, - // Parse props data for .tsx files - // This is notoriously slow, and is making Storybook unusable. Disabling for now. - // See: https://github.com/storybookjs/storybook/issues/7998 - // - // { - // test: /\.tsx$/, - // // Exclude example files, as we don't display props info for them - // exclude: /\.examples.tsx$/, - // use: [ - // // Parse TS comments to create Props tables in the UI - // require.resolve('react-docgen-typescript-loader'), - // ], - // }, - // Enable SASS, but exclude CSS Modules in Storybook - { - test: /\.scss$/, - exclude: /\.module.(s(a|c)ss)$/, - use: [ - { loader: 'style-loader' }, - { loader: 'css-loader', options: { importLoaders: 2 } }, - { - loader: 'postcss-loader', - options: { - path: path.resolve(KIBANA_ROOT, 'src/optimize/postcss.config.js'), - }, - }, - { - loader: 'sass-loader', - options: { - prependData(loaderContext) { - return `@import ${stringifyRequest( - loaderContext, - path.resolve( - KIBANA_ROOT, - 'src/core/public/core_app/styles/_globals_v7light.scss' - ) - )};\n`; - }, - sassOptions: { - includePaths: [path.resolve(KIBANA_ROOT, 'node_modules')], - }, - }, - }, - ], - }, - // Enable CSS Modules in Storybook (Shareable Runtime) - { - test: /\.module\.s(a|c)ss$/, - loader: [ - 'style-loader', - { - loader: 'css-loader', - options: { - importLoaders: 2, - modules: { - localIdentName: '[name]__[local]___[hash:base64:5]', - }, - }, - }, - { - loader: 'postcss-loader', - options: { - path: path.resolve(KIBANA_ROOT, 'src/optimize/postcss.config.js'), - }, - }, - { - loader: 'sass-loader', - }, - ], - }, - { - test: /\.mjs$/, - include: /node_modules/, - type: 'javascript/auto', - }, - // Exclude large-dependency, troublesome or irrelevant modules. - { - test: [ - path.resolve(__dirname, '../public/components/embeddable_flyout'), - path.resolve(__dirname, '../../reporting/public'), - path.resolve(__dirname, '../../../../src/plugins/kibana_legacy/public/angular'), - path.resolve(__dirname, '../../../../src/plugins/kibana_legacy/public/paginate'), - ], - use: 'null-loader', - }, - ], - }, - plugins: [ - // Reference the built DLL file of static(ish) dependencies, which are removed - // during kbn:bootstrap and rebuilt if missing. - new webpack.DllReferencePlugin({ - manifest: path.resolve(DLL_OUTPUT, 'manifest.json'), - context: KIBANA_ROOT, - }), - // Ensure jQuery is global for Storybook, specifically for the runtime. - new webpack.ProvidePlugin({ - $: 'jquery', - jQuery: 'jquery', - }), - // Copy the DLL files to the Webpack build for use in the Storybook UI - new CopyWebpackPlugin({ - patterns: [ - { - from: path.resolve(DLL_OUTPUT, 'dll.js'), - to: 'dll.js', - }, - { - from: path.resolve(DLL_OUTPUT, 'dll.css'), - to: 'dll.css', - }, - ], - }), - // replace imports for `uiExports/*` modules with a synthetic module - // created by create_ui_exports_module.js - new webpack.NormalModuleReplacementPlugin(/^uiExports\//, (resource) => { - // uiExports used by Canvas - const extensions = { - hacks: [], - chromeNavControls: [], - }; - - // everything following the first / in the request is - // treated as a type of appExtension - const type = resource.request.slice(resource.request.indexOf('/') + 1); - - resource.request = [ - // the "val-loader" is used to execute create_ui_exports_module - // and use its return value as the source for the module in the - // bundle. This allows us to bypass writing to the file system - require.resolve('val-loader'), - '!', - require.resolve(KIBANA_ROOT + '/src/optimize/create_ui_exports_module'), - '?', - // this JSON is parsed by create_ui_exports_module and determines - // what require() calls it will execute within the bundle - JSON.stringify({ type, modules: extensions[type] || [] }), - ].join(''); - }), - - new webpack.NormalModuleReplacementPlugin( - /lib\/download_workpad/, - path.resolve(__dirname, '../tasks/mocks/downloadWorkpad') - ), - new webpack.NormalModuleReplacementPlugin( - /(lib)?\/custom_element_service/, - path.resolve(__dirname, '../tasks/mocks/customElementService') - ), - new webpack.NormalModuleReplacementPlugin( - /(lib)?\/ui_metric/, - path.resolve(__dirname, '../tasks/mocks/uiMetric') - ), - new webpack.NormalModuleReplacementPlugin( - /lib\/es_service/, - path.resolve(__dirname, '../tasks/mocks/esService') - ), - ], - resolve: { - extensions: ['.ts', '.tsx', '.scss', '.mjs', '.html'], - alias: { - 'ui/url/absolute_to_parsed_url': path.resolve( - __dirname, - '../tasks/mocks/uiAbsoluteToParsedUrl' - ), - }, - symlinks: false, - }, - }; - - // Find and alter the CSS rule to replace the Kibana public path string with a path - // to the route we've added in middleware.js - const cssRule = storybookConfig.module.rules.find((rule) => rule.test.source.includes('.css$')); - cssRule.use.push({ - loader: 'string-replace-loader', - options: { - search: '__REPLACE_WITH_PUBLIC_PATH__', - replace: '/', - flags: 'g', - }, - }); - - return webpackMerge(storybookConfig, config); -}; diff --git a/x-pack/plugins/canvas/storybook/webpack.dll.config.js b/x-pack/plugins/canvas/storybook/webpack.dll.config.js deleted file mode 100644 index c13fabe998921..0000000000000 --- a/x-pack/plugins/canvas/storybook/webpack.dll.config.js +++ /dev/null @@ -1,110 +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. - */ - -const path = require('path'); -const webpack = require('webpack'); -const MiniCssExtractPlugin = require('mini-css-extract-plugin'); - -const { DLL_NAME, DLL_OUTPUT, KIBANA_ROOT } = require('./constants'); - -// This is the Webpack config for the DLL of CSS and JS assets that are -// not expected to change during development. This saves compile and run -// times considerably. -module.exports = { - context: KIBANA_ROOT, - mode: 'development', - - // This is a (potentially growing) list of modules that can be safely - // included in the DLL. Only add to this list modules or other code - // which Storybook stories and their components would require, but don't - // change during development. - entry: [ - '@elastic/eui/dist/eui_theme_light.css', - '@kbn/ui-framework/dist/kui_light.css', - '@storybook/addon-actions/register', - '@storybook/core', - '@storybook/core/dist/server/common/polyfills.js', - '@storybook/react', - '@storybook/theming', - 'angular-mocks', - 'angular', - 'brace', - 'chroma-js', - 'highlight.js', - 'html-entities', - 'jsondiffpatch', - 'jquery', - 'lodash', - 'markdown-it', - 'monaco-editor', - 'prop-types', - 'react-ace', - 'react-beautiful-dnd', - 'react-dom', - 'react-focus-lock', - 'react-markdown', - 'react-monaco-editor', - 'react-resize-detector', - 'react-virtualized', - 'react', - 'recompose', - 'redux-actions', - 'remark-parse', - 'rxjs', - 'sinon', - 'tinycolor2', - // Include the DLL UI contexts from Kibana - require.resolve('./dll_contexts'), - ], - plugins: [ - // Produce the DLL and its manifest - new webpack.DllPlugin({ - name: DLL_NAME, - path: path.resolve(DLL_OUTPUT, 'manifest.json'), - }), - // Produce the DLL CSS file - new MiniCssExtractPlugin({ - filename: 'dll.css', - }), - ], - // Output the DLL JS file - output: { - path: DLL_OUTPUT, - filename: 'dll.js', - library: DLL_NAME, - }, - module: { - rules: [ - { - test: /\.css$/, - use: [ - { - loader: MiniCssExtractPlugin.loader, - options: {}, - }, - { loader: 'css-loader' }, - { - loader: 'string-replace-loader', - options: { - search: '__REPLACE_WITH_PUBLIC_PATH__', - replace: '/', - flags: 'g', - }, - }, - ], - }, - { - test: /\.(woff|woff2|ttf|eot|svg|ico)(\?|$)/, - loader: 'file-loader', - }, - ], - }, - node: { - fs: 'empty', - child_process: 'empty', - }, -}; diff --git a/x-pack/plugins/cases/common/api/cases/alerts.ts b/x-pack/plugins/cases/common/api/cases/alerts.ts new file mode 100644 index 0000000000000..1a1abb4cbb66a --- /dev/null +++ b/x-pack/plugins/cases/common/api/cases/alerts.ts @@ -0,0 +1,18 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import * as rt from 'io-ts'; + +const AlertRt = rt.type({ + id: rt.string, + index: rt.string, + attached_at: rt.string, +}); + +export const AlertResponseRt = rt.array(AlertRt); + +export type AlertResponse = rt.TypeOf; diff --git a/x-pack/plugins/cases/common/api/cases/index.ts b/x-pack/plugins/cases/common/api/cases/index.ts index 0f78ca9b35377..ba8ad15d02e27 100644 --- a/x-pack/plugins/cases/common/api/cases/index.ts +++ b/x-pack/plugins/cases/common/api/cases/index.ts @@ -12,3 +12,4 @@ export * from './status'; export * from './user_actions'; export * from './sub_case'; export * from './constants'; +export * from './alerts'; diff --git a/x-pack/plugins/cases/common/constants.ts b/x-pack/plugins/cases/common/constants.ts index f0d3e8ccbcdea..317fe1d8ed144 100644 --- a/x-pack/plugins/cases/common/constants.ts +++ b/x-pack/plugins/cases/common/constants.ts @@ -51,6 +51,7 @@ export const CASE_TAGS_URL = `${CASES_URL}/tags`; export const CASE_USER_ACTIONS_URL = `${CASE_DETAILS_URL}/user_actions`; export const CASE_ALERTS_URL = `${CASES_URL}/alerts/{alert_id}`; +export const CASE_DETAILS_ALERTS_URL = `${CASE_DETAILS_URL}/alerts`; /** * Action routes diff --git a/x-pack/plugins/cases/docs/cases_client/classes/client.casesclient.md b/x-pack/plugins/cases/docs/cases_client/classes/client.casesclient.md index 98e2f284da4a6..a20f018cffeb8 100644 --- a/x-pack/plugins/cases/docs/cases_client/classes/client.casesclient.md +++ b/x-pack/plugins/cases/docs/cases_client/classes/client.casesclient.md @@ -45,7 +45,7 @@ Client wrapper that contains accessor methods for individual entities within the **Returns:** [*CasesClient*](client.casesclient.md) -Defined in: [client.ts:28](https://github.com/jonathan-buttner/kibana/blob/2085a3b4480/x-pack/plugins/cases/server/client/client.ts#L28) +Defined in: [client.ts:28](https://github.com/jonathan-buttner/kibana/blob/0e98e105663/x-pack/plugins/cases/server/client/client.ts#L28) ## Properties @@ -53,7 +53,7 @@ Defined in: [client.ts:28](https://github.com/jonathan-buttner/kibana/blob/2085a • `Private` `Readonly` **\_attachments**: [*AttachmentsSubClient*](../interfaces/attachments_client.attachmentssubclient.md) -Defined in: [client.ts:24](https://github.com/jonathan-buttner/kibana/blob/2085a3b4480/x-pack/plugins/cases/server/client/client.ts#L24) +Defined in: [client.ts:24](https://github.com/jonathan-buttner/kibana/blob/0e98e105663/x-pack/plugins/cases/server/client/client.ts#L24) ___ @@ -61,7 +61,7 @@ ___ • `Private` `Readonly` **\_cases**: [*CasesSubClient*](../interfaces/cases_client.casessubclient.md) -Defined in: [client.ts:23](https://github.com/jonathan-buttner/kibana/blob/2085a3b4480/x-pack/plugins/cases/server/client/client.ts#L23) +Defined in: [client.ts:23](https://github.com/jonathan-buttner/kibana/blob/0e98e105663/x-pack/plugins/cases/server/client/client.ts#L23) ___ @@ -69,7 +69,7 @@ ___ • `Private` `Readonly` **\_casesClientInternal**: *CasesClientInternal* -Defined in: [client.ts:22](https://github.com/jonathan-buttner/kibana/blob/2085a3b4480/x-pack/plugins/cases/server/client/client.ts#L22) +Defined in: [client.ts:22](https://github.com/jonathan-buttner/kibana/blob/0e98e105663/x-pack/plugins/cases/server/client/client.ts#L22) ___ @@ -77,7 +77,7 @@ ___ • `Private` `Readonly` **\_configure**: [*ConfigureSubClient*](../interfaces/configure_client.configuresubclient.md) -Defined in: [client.ts:27](https://github.com/jonathan-buttner/kibana/blob/2085a3b4480/x-pack/plugins/cases/server/client/client.ts#L27) +Defined in: [client.ts:27](https://github.com/jonathan-buttner/kibana/blob/0e98e105663/x-pack/plugins/cases/server/client/client.ts#L27) ___ @@ -85,7 +85,7 @@ ___ • `Private` `Readonly` **\_stats**: [*StatsSubClient*](../interfaces/stats_client.statssubclient.md) -Defined in: [client.ts:28](https://github.com/jonathan-buttner/kibana/blob/2085a3b4480/x-pack/plugins/cases/server/client/client.ts#L28) +Defined in: [client.ts:28](https://github.com/jonathan-buttner/kibana/blob/0e98e105663/x-pack/plugins/cases/server/client/client.ts#L28) ___ @@ -93,7 +93,7 @@ ___ • `Private` `Readonly` **\_subCases**: [*SubCasesClient*](../interfaces/sub_cases_client.subcasesclient.md) -Defined in: [client.ts:26](https://github.com/jonathan-buttner/kibana/blob/2085a3b4480/x-pack/plugins/cases/server/client/client.ts#L26) +Defined in: [client.ts:26](https://github.com/jonathan-buttner/kibana/blob/0e98e105663/x-pack/plugins/cases/server/client/client.ts#L26) ___ @@ -101,7 +101,7 @@ ___ • `Private` `Readonly` **\_userActions**: [*UserActionsSubClient*](../interfaces/user_actions_client.useractionssubclient.md) -Defined in: [client.ts:25](https://github.com/jonathan-buttner/kibana/blob/2085a3b4480/x-pack/plugins/cases/server/client/client.ts#L25) +Defined in: [client.ts:25](https://github.com/jonathan-buttner/kibana/blob/0e98e105663/x-pack/plugins/cases/server/client/client.ts#L25) ## Accessors @@ -113,7 +113,7 @@ Retrieves an interface for interacting with attachments (comments) entities. **Returns:** [*AttachmentsSubClient*](../interfaces/attachments_client.attachmentssubclient.md) -Defined in: [client.ts:50](https://github.com/jonathan-buttner/kibana/blob/2085a3b4480/x-pack/plugins/cases/server/client/client.ts#L50) +Defined in: [client.ts:50](https://github.com/jonathan-buttner/kibana/blob/0e98e105663/x-pack/plugins/cases/server/client/client.ts#L50) ___ @@ -125,7 +125,7 @@ Retrieves an interface for interacting with cases entities. **Returns:** [*CasesSubClient*](../interfaces/cases_client.casessubclient.md) -Defined in: [client.ts:43](https://github.com/jonathan-buttner/kibana/blob/2085a3b4480/x-pack/plugins/cases/server/client/client.ts#L43) +Defined in: [client.ts:43](https://github.com/jonathan-buttner/kibana/blob/0e98e105663/x-pack/plugins/cases/server/client/client.ts#L43) ___ @@ -137,7 +137,7 @@ Retrieves an interface for interacting with the configuration of external connec **Returns:** [*ConfigureSubClient*](../interfaces/configure_client.configuresubclient.md) -Defined in: [client.ts:76](https://github.com/jonathan-buttner/kibana/blob/2085a3b4480/x-pack/plugins/cases/server/client/client.ts#L76) +Defined in: [client.ts:76](https://github.com/jonathan-buttner/kibana/blob/0e98e105663/x-pack/plugins/cases/server/client/client.ts#L76) ___ @@ -149,7 +149,7 @@ Retrieves an interface for retrieving statistics related to the cases entities. **Returns:** [*StatsSubClient*](../interfaces/stats_client.statssubclient.md) -Defined in: [client.ts:83](https://github.com/jonathan-buttner/kibana/blob/2085a3b4480/x-pack/plugins/cases/server/client/client.ts#L83) +Defined in: [client.ts:83](https://github.com/jonathan-buttner/kibana/blob/0e98e105663/x-pack/plugins/cases/server/client/client.ts#L83) ___ @@ -163,7 +163,7 @@ Currently this functionality is disabled and will throw an error if this functio **Returns:** [*SubCasesClient*](../interfaces/sub_cases_client.subcasesclient.md) -Defined in: [client.ts:66](https://github.com/jonathan-buttner/kibana/blob/2085a3b4480/x-pack/plugins/cases/server/client/client.ts#L66) +Defined in: [client.ts:66](https://github.com/jonathan-buttner/kibana/blob/0e98e105663/x-pack/plugins/cases/server/client/client.ts#L66) ___ @@ -175,4 +175,4 @@ Retrieves an interface for interacting with the user actions associated with the **Returns:** [*UserActionsSubClient*](../interfaces/user_actions_client.useractionssubclient.md) -Defined in: [client.ts:57](https://github.com/jonathan-buttner/kibana/blob/2085a3b4480/x-pack/plugins/cases/server/client/client.ts#L57) +Defined in: [client.ts:57](https://github.com/jonathan-buttner/kibana/blob/0e98e105663/x-pack/plugins/cases/server/client/client.ts#L57) diff --git a/x-pack/plugins/cases/docs/cases_client/interfaces/attachments_add.addargs.md b/x-pack/plugins/cases/docs/cases_client/interfaces/attachments_add.addargs.md index 1bbca9167a5c2..d5233ab6d8cb4 100644 --- a/x-pack/plugins/cases/docs/cases_client/interfaces/attachments_add.addargs.md +++ b/x-pack/plugins/cases/docs/cases_client/interfaces/attachments_add.addargs.md @@ -21,7 +21,7 @@ The arguments needed for creating a new attachment to a case. The case ID that this attachment will be associated with -Defined in: [attachments/add.ts:308](https://github.com/jonathan-buttner/kibana/blob/2085a3b4480/x-pack/plugins/cases/server/client/attachments/add.ts#L308) +Defined in: [attachments/add.ts:305](https://github.com/jonathan-buttner/kibana/blob/0e98e105663/x-pack/plugins/cases/server/client/attachments/add.ts#L305) ___ @@ -31,4 +31,4 @@ ___ The attachment values. -Defined in: [attachments/add.ts:312](https://github.com/jonathan-buttner/kibana/blob/2085a3b4480/x-pack/plugins/cases/server/client/attachments/add.ts#L312) +Defined in: [attachments/add.ts:309](https://github.com/jonathan-buttner/kibana/blob/0e98e105663/x-pack/plugins/cases/server/client/attachments/add.ts#L309) diff --git a/x-pack/plugins/cases/docs/cases_client/interfaces/attachments_client.attachmentssubclient.md b/x-pack/plugins/cases/docs/cases_client/interfaces/attachments_client.attachmentssubclient.md index e9f65bcf9915a..1a9a687aa812b 100644 --- a/x-pack/plugins/cases/docs/cases_client/interfaces/attachments_client.attachmentssubclient.md +++ b/x-pack/plugins/cases/docs/cases_client/interfaces/attachments_client.attachmentssubclient.md @@ -16,6 +16,7 @@ API for interacting with the attachments to a case. - [find](attachments_client.attachmentssubclient.md#find) - [get](attachments_client.attachmentssubclient.md#get) - [getAll](attachments_client.attachmentssubclient.md#getall) +- [getAllAlertsAttachToCase](attachments_client.attachmentssubclient.md#getallalertsattachtocase) - [update](attachments_client.attachmentssubclient.md#update) ## Methods @@ -34,7 +35,7 @@ Adds an attachment to a case. **Returns:** *Promise*<[*ICaseResponse*](typedoc_interfaces.icaseresponse.md)\> -Defined in: [attachments/client.ts:25](https://github.com/jonathan-buttner/kibana/blob/2085a3b4480/x-pack/plugins/cases/server/client/attachments/client.ts#L25) +Defined in: [attachments/client.ts:35](https://github.com/jonathan-buttner/kibana/blob/0e98e105663/x-pack/plugins/cases/server/client/attachments/client.ts#L35) ___ @@ -52,7 +53,7 @@ Deletes a single attachment for a specific case. **Returns:** *Promise* -Defined in: [attachments/client.ts:33](https://github.com/jonathan-buttner/kibana/blob/2085a3b4480/x-pack/plugins/cases/server/client/attachments/client.ts#L33) +Defined in: [attachments/client.ts:43](https://github.com/jonathan-buttner/kibana/blob/0e98e105663/x-pack/plugins/cases/server/client/attachments/client.ts#L43) ___ @@ -70,7 +71,7 @@ Deletes all attachments associated with a single case. **Returns:** *Promise* -Defined in: [attachments/client.ts:29](https://github.com/jonathan-buttner/kibana/blob/2085a3b4480/x-pack/plugins/cases/server/client/attachments/client.ts#L29) +Defined in: [attachments/client.ts:39](https://github.com/jonathan-buttner/kibana/blob/0e98e105663/x-pack/plugins/cases/server/client/attachments/client.ts#L39) ___ @@ -88,7 +89,7 @@ Retrieves all comments matching the search criteria. **Returns:** *Promise*<[*ICommentsResponse*](typedoc_interfaces.icommentsresponse.md)\> -Defined in: [attachments/client.ts:37](https://github.com/jonathan-buttner/kibana/blob/2085a3b4480/x-pack/plugins/cases/server/client/attachments/client.ts#L37) +Defined in: [attachments/client.ts:47](https://github.com/jonathan-buttner/kibana/blob/0e98e105663/x-pack/plugins/cases/server/client/attachments/client.ts#L47) ___ @@ -106,7 +107,7 @@ Retrieves a single attachment for a case. **Returns:** *Promise*<{ `comment`: *string* ; `owner`: *string* ; `type`: user } & { `associationType`: AssociationType ; `created_at`: *string* ; `created_by`: { email: string \| null \| undefined; full\_name: string \| null \| undefined; username: string \| null \| undefined; } ; `owner`: *string* ; `pushed_at`: ``null`` \| *string* ; `pushed_by`: ``null`` \| { email: string \| null \| undefined; full\_name: string \| null \| undefined; username: string \| null \| undefined; } ; `updated_at`: ``null`` \| *string* ; `updated_by`: ``null`` \| { email: string \| null \| undefined; full\_name: string \| null \| undefined; username: string \| null \| undefined; } } & { `id`: *string* ; `version`: *string* } & { `alertId`: *string* \| *string*[] ; `index`: *string* \| *string*[] ; `owner`: *string* ; `rule`: { id: string \| null; name: string \| null; } ; `type`: alert \| generatedAlert } & { `associationType`: AssociationType ; `created_at`: *string* ; `created_by`: { email: string \| null \| undefined; full\_name: string \| null \| undefined; username: string \| null \| undefined; } ; `owner`: *string* ; `pushed_at`: ``null`` \| *string* ; `pushed_by`: ``null`` \| { email: string \| null \| undefined; full\_name: string \| null \| undefined; username: string \| null \| undefined; } ; `updated_at`: ``null`` \| *string* ; `updated_by`: ``null`` \| { email: string \| null \| undefined; full\_name: string \| null \| undefined; username: string \| null \| undefined; } } & { `id`: *string* ; `version`: *string* }\> -Defined in: [attachments/client.ts:45](https://github.com/jonathan-buttner/kibana/blob/2085a3b4480/x-pack/plugins/cases/server/client/attachments/client.ts#L45) +Defined in: [attachments/client.ts:59](https://github.com/jonathan-buttner/kibana/blob/0e98e105663/x-pack/plugins/cases/server/client/attachments/client.ts#L59) ___ @@ -124,7 +125,25 @@ Gets all attachments for a single case. **Returns:** *Promise*<[*IAllCommentsResponse*](typedoc_interfaces.iallcommentsresponse.md)\> -Defined in: [attachments/client.ts:41](https://github.com/jonathan-buttner/kibana/blob/2085a3b4480/x-pack/plugins/cases/server/client/attachments/client.ts#L41) +Defined in: [attachments/client.ts:55](https://github.com/jonathan-buttner/kibana/blob/0e98e105663/x-pack/plugins/cases/server/client/attachments/client.ts#L55) + +___ + +### getAllAlertsAttachToCase + +▸ **getAllAlertsAttachToCase**(`params`: [*GetAllAlertsAttachToCase*](attachments_get.getallalertsattachtocase.md)): *Promise*<{ `attached_at`: *string* ; `id`: *string* ; `index`: *string* }[]\> + +Retrieves all alerts attach to a case given a single case ID + +#### Parameters + +| Name | Type | +| :------ | :------ | +| `params` | [*GetAllAlertsAttachToCase*](attachments_get.getallalertsattachtocase.md) | + +**Returns:** *Promise*<{ `attached_at`: *string* ; `id`: *string* ; `index`: *string* }[]\> + +Defined in: [attachments/client.ts:51](https://github.com/jonathan-buttner/kibana/blob/0e98e105663/x-pack/plugins/cases/server/client/attachments/client.ts#L51) ___ @@ -144,4 +163,4 @@ The request must include all fields for the attachment. Even the fields that are **Returns:** *Promise*<[*ICaseResponse*](typedoc_interfaces.icaseresponse.md)\> -Defined in: [attachments/client.ts:51](https://github.com/jonathan-buttner/kibana/blob/2085a3b4480/x-pack/plugins/cases/server/client/attachments/client.ts#L51) +Defined in: [attachments/client.ts:65](https://github.com/jonathan-buttner/kibana/blob/0e98e105663/x-pack/plugins/cases/server/client/attachments/client.ts#L65) diff --git a/x-pack/plugins/cases/docs/cases_client/interfaces/attachments_delete.deleteallargs.md b/x-pack/plugins/cases/docs/cases_client/interfaces/attachments_delete.deleteallargs.md index 26b00ac6e037e..437758a0147f2 100644 --- a/x-pack/plugins/cases/docs/cases_client/interfaces/attachments_delete.deleteallargs.md +++ b/x-pack/plugins/cases/docs/cases_client/interfaces/attachments_delete.deleteallargs.md @@ -21,7 +21,7 @@ Parameters for deleting all comments of a case or sub case. The case ID to delete all attachments for -Defined in: [attachments/delete.ts:26](https://github.com/jonathan-buttner/kibana/blob/2085a3b4480/x-pack/plugins/cases/server/client/attachments/delete.ts#L26) +Defined in: [attachments/delete.ts:31](https://github.com/jonathan-buttner/kibana/blob/0e98e105663/x-pack/plugins/cases/server/client/attachments/delete.ts#L31) ___ @@ -31,4 +31,4 @@ ___ If specified the caseID will be ignored and this value will be used to find a sub case for deleting all the attachments -Defined in: [attachments/delete.ts:30](https://github.com/jonathan-buttner/kibana/blob/2085a3b4480/x-pack/plugins/cases/server/client/attachments/delete.ts#L30) +Defined in: [attachments/delete.ts:35](https://github.com/jonathan-buttner/kibana/blob/0e98e105663/x-pack/plugins/cases/server/client/attachments/delete.ts#L35) diff --git a/x-pack/plugins/cases/docs/cases_client/interfaces/attachments_delete.deleteargs.md b/x-pack/plugins/cases/docs/cases_client/interfaces/attachments_delete.deleteargs.md index f9d4038eb417a..1afa5679161d9 100644 --- a/x-pack/plugins/cases/docs/cases_client/interfaces/attachments_delete.deleteargs.md +++ b/x-pack/plugins/cases/docs/cases_client/interfaces/attachments_delete.deleteargs.md @@ -22,7 +22,7 @@ Parameters for deleting a single attachment of a case or sub case. The attachment ID to delete -Defined in: [attachments/delete.ts:44](https://github.com/jonathan-buttner/kibana/blob/2085a3b4480/x-pack/plugins/cases/server/client/attachments/delete.ts#L44) +Defined in: [attachments/delete.ts:49](https://github.com/jonathan-buttner/kibana/blob/0e98e105663/x-pack/plugins/cases/server/client/attachments/delete.ts#L49) ___ @@ -32,7 +32,7 @@ ___ The case ID to delete an attachment from -Defined in: [attachments/delete.ts:40](https://github.com/jonathan-buttner/kibana/blob/2085a3b4480/x-pack/plugins/cases/server/client/attachments/delete.ts#L40) +Defined in: [attachments/delete.ts:45](https://github.com/jonathan-buttner/kibana/blob/0e98e105663/x-pack/plugins/cases/server/client/attachments/delete.ts#L45) ___ @@ -42,4 +42,4 @@ ___ If specified the caseID will be ignored and this value will be used to find a sub case for deleting the attachment -Defined in: [attachments/delete.ts:48](https://github.com/jonathan-buttner/kibana/blob/2085a3b4480/x-pack/plugins/cases/server/client/attachments/delete.ts#L48) +Defined in: [attachments/delete.ts:53](https://github.com/jonathan-buttner/kibana/blob/0e98e105663/x-pack/plugins/cases/server/client/attachments/delete.ts#L53) diff --git a/x-pack/plugins/cases/docs/cases_client/interfaces/attachments_get.findargs.md b/x-pack/plugins/cases/docs/cases_client/interfaces/attachments_get.findargs.md index dbbac0065be85..dc0da295b26d2 100644 --- a/x-pack/plugins/cases/docs/cases_client/interfaces/attachments_get.findargs.md +++ b/x-pack/plugins/cases/docs/cases_client/interfaces/attachments_get.findargs.md @@ -21,7 +21,7 @@ Parameters for finding attachments of a case The case ID for finding associated attachments -Defined in: [attachments/get.ts:48](https://github.com/jonathan-buttner/kibana/blob/2085a3b4480/x-pack/plugins/cases/server/client/attachments/get.ts#L48) +Defined in: [attachments/get.ts:47](https://github.com/jonathan-buttner/kibana/blob/0e98e105663/x-pack/plugins/cases/server/client/attachments/get.ts#L47) ___ @@ -48,4 +48,4 @@ Optional parameters for filtering the returned attachments | `sortOrder` | *undefined* \| ``"desc"`` \| ``"asc"`` | | `subCaseId` | *undefined* \| *string* | -Defined in: [attachments/get.ts:52](https://github.com/jonathan-buttner/kibana/blob/2085a3b4480/x-pack/plugins/cases/server/client/attachments/get.ts#L52) +Defined in: [attachments/get.ts:51](https://github.com/jonathan-buttner/kibana/blob/0e98e105663/x-pack/plugins/cases/server/client/attachments/get.ts#L51) diff --git a/x-pack/plugins/cases/docs/cases_client/interfaces/attachments_get.getallalertsattachtocase.md b/x-pack/plugins/cases/docs/cases_client/interfaces/attachments_get.getallalertsattachtocase.md new file mode 100644 index 0000000000000..541d1cf8f1d80 --- /dev/null +++ b/x-pack/plugins/cases/docs/cases_client/interfaces/attachments_get.getallalertsattachtocase.md @@ -0,0 +1,21 @@ +[Cases Client API Interface](../cases_client_api.md) / [attachments/get](../modules/attachments_get.md) / GetAllAlertsAttachToCase + +# Interface: GetAllAlertsAttachToCase + +[attachments/get](../modules/attachments_get.md).GetAllAlertsAttachToCase + +## Table of contents + +### Properties + +- [caseId](attachments_get.getallalertsattachtocase.md#caseid) + +## Properties + +### caseId + +• **caseId**: *string* + +The ID of the case to retrieve the alerts from + +Defined in: [attachments/get.ts:87](https://github.com/jonathan-buttner/kibana/blob/0e98e105663/x-pack/plugins/cases/server/client/attachments/get.ts#L87) diff --git a/x-pack/plugins/cases/docs/cases_client/interfaces/attachments_get.getallargs.md b/x-pack/plugins/cases/docs/cases_client/interfaces/attachments_get.getallargs.md index dbd66291e22de..ae67f85e96fc0 100644 --- a/x-pack/plugins/cases/docs/cases_client/interfaces/attachments_get.getallargs.md +++ b/x-pack/plugins/cases/docs/cases_client/interfaces/attachments_get.getallargs.md @@ -22,7 +22,7 @@ Parameters for retrieving all attachments of a case The case ID to retrieve all attachments for -Defined in: [attachments/get.ts:62](https://github.com/jonathan-buttner/kibana/blob/2085a3b4480/x-pack/plugins/cases/server/client/attachments/get.ts#L62) +Defined in: [attachments/get.ts:61](https://github.com/jonathan-buttner/kibana/blob/0e98e105663/x-pack/plugins/cases/server/client/attachments/get.ts#L61) ___ @@ -32,7 +32,7 @@ ___ Optionally include the attachments associated with a sub case -Defined in: [attachments/get.ts:66](https://github.com/jonathan-buttner/kibana/blob/2085a3b4480/x-pack/plugins/cases/server/client/attachments/get.ts#L66) +Defined in: [attachments/get.ts:65](https://github.com/jonathan-buttner/kibana/blob/0e98e105663/x-pack/plugins/cases/server/client/attachments/get.ts#L65) ___ @@ -42,4 +42,4 @@ ___ If included the case ID will be ignored and the attachments will be retrieved from the specified ID of the sub case -Defined in: [attachments/get.ts:70](https://github.com/jonathan-buttner/kibana/blob/2085a3b4480/x-pack/plugins/cases/server/client/attachments/get.ts#L70) +Defined in: [attachments/get.ts:69](https://github.com/jonathan-buttner/kibana/blob/0e98e105663/x-pack/plugins/cases/server/client/attachments/get.ts#L69) diff --git a/x-pack/plugins/cases/docs/cases_client/interfaces/attachments_get.getargs.md b/x-pack/plugins/cases/docs/cases_client/interfaces/attachments_get.getargs.md index abfd4bb5958d3..2fc569985f980 100644 --- a/x-pack/plugins/cases/docs/cases_client/interfaces/attachments_get.getargs.md +++ b/x-pack/plugins/cases/docs/cases_client/interfaces/attachments_get.getargs.md @@ -19,7 +19,7 @@ The ID of the attachment to retrieve -Defined in: [attachments/get.ts:81](https://github.com/jonathan-buttner/kibana/blob/2085a3b4480/x-pack/plugins/cases/server/client/attachments/get.ts#L81) +Defined in: [attachments/get.ts:80](https://github.com/jonathan-buttner/kibana/blob/0e98e105663/x-pack/plugins/cases/server/client/attachments/get.ts#L80) ___ @@ -29,4 +29,4 @@ ___ The ID of the case to retrieve an attachment from -Defined in: [attachments/get.ts:77](https://github.com/jonathan-buttner/kibana/blob/2085a3b4480/x-pack/plugins/cases/server/client/attachments/get.ts#L77) +Defined in: [attachments/get.ts:76](https://github.com/jonathan-buttner/kibana/blob/0e98e105663/x-pack/plugins/cases/server/client/attachments/get.ts#L76) diff --git a/x-pack/plugins/cases/docs/cases_client/interfaces/attachments_update.updateargs.md b/x-pack/plugins/cases/docs/cases_client/interfaces/attachments_update.updateargs.md index b571067175f62..4b2dd7b404e7a 100644 --- a/x-pack/plugins/cases/docs/cases_client/interfaces/attachments_update.updateargs.md +++ b/x-pack/plugins/cases/docs/cases_client/interfaces/attachments_update.updateargs.md @@ -22,7 +22,7 @@ Parameters for updating a single attachment The ID of the case that is associated with this attachment -Defined in: [attachments/update.ts:29](https://github.com/jonathan-buttner/kibana/blob/2085a3b4480/x-pack/plugins/cases/server/client/attachments/update.ts#L29) +Defined in: [attachments/update.ts:32](https://github.com/jonathan-buttner/kibana/blob/0e98e105663/x-pack/plugins/cases/server/client/attachments/update.ts#L32) ___ @@ -32,7 +32,7 @@ ___ The ID of a sub case, if specified a sub case will be searched for to perform the attachment update instead of on a case -Defined in: [attachments/update.ts:37](https://github.com/jonathan-buttner/kibana/blob/2085a3b4480/x-pack/plugins/cases/server/client/attachments/update.ts#L37) +Defined in: [attachments/update.ts:40](https://github.com/jonathan-buttner/kibana/blob/0e98e105663/x-pack/plugins/cases/server/client/attachments/update.ts#L40) ___ @@ -42,4 +42,4 @@ ___ The full attachment request with the fields updated with appropriate values -Defined in: [attachments/update.ts:33](https://github.com/jonathan-buttner/kibana/blob/2085a3b4480/x-pack/plugins/cases/server/client/attachments/update.ts#L33) +Defined in: [attachments/update.ts:36](https://github.com/jonathan-buttner/kibana/blob/0e98e105663/x-pack/plugins/cases/server/client/attachments/update.ts#L36) diff --git a/x-pack/plugins/cases/docs/cases_client/interfaces/cases_client.casessubclient.md b/x-pack/plugins/cases/docs/cases_client/interfaces/cases_client.casessubclient.md index e7d7dea34d0ad..d86308720cb95 100644 --- a/x-pack/plugins/cases/docs/cases_client/interfaces/cases_client.casessubclient.md +++ b/x-pack/plugins/cases/docs/cases_client/interfaces/cases_client.casessubclient.md @@ -36,7 +36,7 @@ Creates a case. **Returns:** *Promise*<[*ICaseResponse*](typedoc_interfaces.icaseresponse.md)\> -Defined in: [cases/client.ts:48](https://github.com/jonathan-buttner/kibana/blob/2085a3b4480/x-pack/plugins/cases/server/client/cases/client.ts#L48) +Defined in: [cases/client.ts:48](https://github.com/jonathan-buttner/kibana/blob/0e98e105663/x-pack/plugins/cases/server/client/cases/client.ts#L48) ___ @@ -56,7 +56,7 @@ Delete a case and all its comments. **Returns:** *Promise* -Defined in: [cases/client.ts:72](https://github.com/jonathan-buttner/kibana/blob/2085a3b4480/x-pack/plugins/cases/server/client/cases/client.ts#L72) +Defined in: [cases/client.ts:72](https://github.com/jonathan-buttner/kibana/blob/0e98e105663/x-pack/plugins/cases/server/client/cases/client.ts#L72) ___ @@ -76,7 +76,7 @@ If the `owner` field is left empty then all the cases that the user has access t **Returns:** *Promise*<[*ICasesFindResponse*](typedoc_interfaces.icasesfindresponse.md)\> -Defined in: [cases/client.ts:54](https://github.com/jonathan-buttner/kibana/blob/2085a3b4480/x-pack/plugins/cases/server/client/cases/client.ts#L54) +Defined in: [cases/client.ts:54](https://github.com/jonathan-buttner/kibana/blob/0e98e105663/x-pack/plugins/cases/server/client/cases/client.ts#L54) ___ @@ -94,7 +94,7 @@ Retrieves a single case with the specified ID. **Returns:** *Promise*<[*ICaseResponse*](typedoc_interfaces.icaseresponse.md)\> -Defined in: [cases/client.ts:58](https://github.com/jonathan-buttner/kibana/blob/2085a3b4480/x-pack/plugins/cases/server/client/cases/client.ts#L58) +Defined in: [cases/client.ts:58](https://github.com/jonathan-buttner/kibana/blob/0e98e105663/x-pack/plugins/cases/server/client/cases/client.ts#L58) ___ @@ -112,7 +112,7 @@ Retrieves the case IDs given a single alert ID **Returns:** *Promise* -Defined in: [cases/client.ts:84](https://github.com/jonathan-buttner/kibana/blob/2085a3b4480/x-pack/plugins/cases/server/client/cases/client.ts#L84) +Defined in: [cases/client.ts:84](https://github.com/jonathan-buttner/kibana/blob/0e98e105663/x-pack/plugins/cases/server/client/cases/client.ts#L84) ___ @@ -131,7 +131,7 @@ Retrieves all the reporters across all accessible cases. **Returns:** *Promise*<{ `email`: *undefined* \| ``null`` \| *string* ; `full_name`: *undefined* \| ``null`` \| *string* ; `username`: *undefined* \| ``null`` \| *string* }[]\> -Defined in: [cases/client.ts:80](https://github.com/jonathan-buttner/kibana/blob/2085a3b4480/x-pack/plugins/cases/server/client/cases/client.ts#L80) +Defined in: [cases/client.ts:80](https://github.com/jonathan-buttner/kibana/blob/0e98e105663/x-pack/plugins/cases/server/client/cases/client.ts#L80) ___ @@ -150,7 +150,7 @@ Retrieves all the tags across all cases the user making the request has access t **Returns:** *Promise* -Defined in: [cases/client.ts:76](https://github.com/jonathan-buttner/kibana/blob/2085a3b4480/x-pack/plugins/cases/server/client/cases/client.ts#L76) +Defined in: [cases/client.ts:76](https://github.com/jonathan-buttner/kibana/blob/0e98e105663/x-pack/plugins/cases/server/client/cases/client.ts#L76) ___ @@ -168,7 +168,7 @@ Pushes a specific case to an external system. **Returns:** *Promise*<[*ICaseResponse*](typedoc_interfaces.icaseresponse.md)\> -Defined in: [cases/client.ts:62](https://github.com/jonathan-buttner/kibana/blob/2085a3b4480/x-pack/plugins/cases/server/client/cases/client.ts#L62) +Defined in: [cases/client.ts:62](https://github.com/jonathan-buttner/kibana/blob/0e98e105663/x-pack/plugins/cases/server/client/cases/client.ts#L62) ___ @@ -186,4 +186,4 @@ Update the specified cases with the passed in values. **Returns:** *Promise*<[*ICasesResponse*](typedoc_interfaces.icasesresponse.md)\> -Defined in: [cases/client.ts:66](https://github.com/jonathan-buttner/kibana/blob/2085a3b4480/x-pack/plugins/cases/server/client/cases/client.ts#L66) +Defined in: [cases/client.ts:66](https://github.com/jonathan-buttner/kibana/blob/0e98e105663/x-pack/plugins/cases/server/client/cases/client.ts#L66) diff --git a/x-pack/plugins/cases/docs/cases_client/interfaces/cases_get.caseidsbyalertidparams.md b/x-pack/plugins/cases/docs/cases_client/interfaces/cases_get.caseidsbyalertidparams.md index 1b8abba1a4071..274b7a8f2d431 100644 --- a/x-pack/plugins/cases/docs/cases_client/interfaces/cases_get.caseidsbyalertidparams.md +++ b/x-pack/plugins/cases/docs/cases_client/interfaces/cases_get.caseidsbyalertidparams.md @@ -21,7 +21,7 @@ Parameters for finding cases IDs using an alert ID The alert ID to search for -Defined in: [cases/get.ts:47](https://github.com/jonathan-buttner/kibana/blob/2085a3b4480/x-pack/plugins/cases/server/client/cases/get.ts#L47) +Defined in: [cases/get.ts:42](https://github.com/jonathan-buttner/kibana/blob/0e98e105663/x-pack/plugins/cases/server/client/cases/get.ts#L42) ___ @@ -37,4 +37,4 @@ The filtering options when searching for associated cases. | :------ | :------ | | `owner` | *undefined* \| *string* \| *string*[] | -Defined in: [cases/get.ts:51](https://github.com/jonathan-buttner/kibana/blob/2085a3b4480/x-pack/plugins/cases/server/client/cases/get.ts#L51) +Defined in: [cases/get.ts:46](https://github.com/jonathan-buttner/kibana/blob/0e98e105663/x-pack/plugins/cases/server/client/cases/get.ts#L46) diff --git a/x-pack/plugins/cases/docs/cases_client/interfaces/cases_get.getparams.md b/x-pack/plugins/cases/docs/cases_client/interfaces/cases_get.getparams.md index 8c12b5533ac18..a528b7ce6256d 100644 --- a/x-pack/plugins/cases/docs/cases_client/interfaces/cases_get.getparams.md +++ b/x-pack/plugins/cases/docs/cases_client/interfaces/cases_get.getparams.md @@ -22,7 +22,7 @@ The parameters for retrieving a case Case ID -Defined in: [cases/get.ts:122](https://github.com/jonathan-buttner/kibana/blob/2085a3b4480/x-pack/plugins/cases/server/client/cases/get.ts#L122) +Defined in: [cases/get.ts:110](https://github.com/jonathan-buttner/kibana/blob/0e98e105663/x-pack/plugins/cases/server/client/cases/get.ts#L110) ___ @@ -32,7 +32,7 @@ ___ Whether to include the attachments for a case in the response -Defined in: [cases/get.ts:126](https://github.com/jonathan-buttner/kibana/blob/2085a3b4480/x-pack/plugins/cases/server/client/cases/get.ts#L126) +Defined in: [cases/get.ts:114](https://github.com/jonathan-buttner/kibana/blob/0e98e105663/x-pack/plugins/cases/server/client/cases/get.ts#L114) ___ @@ -42,4 +42,4 @@ ___ Whether to include the attachments for all children of a case in the response -Defined in: [cases/get.ts:130](https://github.com/jonathan-buttner/kibana/blob/2085a3b4480/x-pack/plugins/cases/server/client/cases/get.ts#L130) +Defined in: [cases/get.ts:118](https://github.com/jonathan-buttner/kibana/blob/0e98e105663/x-pack/plugins/cases/server/client/cases/get.ts#L118) diff --git a/x-pack/plugins/cases/docs/cases_client/interfaces/cases_push.pushparams.md b/x-pack/plugins/cases/docs/cases_client/interfaces/cases_push.pushparams.md index 9f1810e4f0cc2..979e30cb31d3f 100644 --- a/x-pack/plugins/cases/docs/cases_client/interfaces/cases_push.pushparams.md +++ b/x-pack/plugins/cases/docs/cases_client/interfaces/cases_push.pushparams.md @@ -21,7 +21,7 @@ Parameters for pushing a case to an external system The ID of a case -Defined in: [cases/push.ts:53](https://github.com/jonathan-buttner/kibana/blob/2085a3b4480/x-pack/plugins/cases/server/client/cases/push.ts#L53) +Defined in: [cases/push.ts:53](https://github.com/jonathan-buttner/kibana/blob/0e98e105663/x-pack/plugins/cases/server/client/cases/push.ts#L53) ___ @@ -31,4 +31,4 @@ ___ The ID of an external system to push to -Defined in: [cases/push.ts:57](https://github.com/jonathan-buttner/kibana/blob/2085a3b4480/x-pack/plugins/cases/server/client/cases/push.ts#L57) +Defined in: [cases/push.ts:57](https://github.com/jonathan-buttner/kibana/blob/0e98e105663/x-pack/plugins/cases/server/client/cases/push.ts#L57) diff --git a/x-pack/plugins/cases/docs/cases_client/interfaces/configure_client.configuresubclient.md b/x-pack/plugins/cases/docs/cases_client/interfaces/configure_client.configuresubclient.md index 9b3827a57a9d3..cf69b101ce2bc 100644 --- a/x-pack/plugins/cases/docs/cases_client/interfaces/configure_client.configuresubclient.md +++ b/x-pack/plugins/cases/docs/cases_client/interfaces/configure_client.configuresubclient.md @@ -31,7 +31,7 @@ Creates a configuration if one does not already exist. If one exists it is delet **Returns:** *Promise*<[*ICasesConfigureResponse*](typedoc_interfaces.icasesconfigureresponse.md)\> -Defined in: [configure/client.ts:102](https://github.com/jonathan-buttner/kibana/blob/2085a3b4480/x-pack/plugins/cases/server/client/configure/client.ts#L102) +Defined in: [configure/client.ts:98](https://github.com/jonathan-buttner/kibana/blob/0e98e105663/x-pack/plugins/cases/server/client/configure/client.ts#L98) ___ @@ -50,7 +50,7 @@ Retrieves the external connector configuration for a particular case owner. **Returns:** *Promise*<{} \| [*ICasesConfigureResponse*](typedoc_interfaces.icasesconfigureresponse.md)\> -Defined in: [configure/client.ts:84](https://github.com/jonathan-buttner/kibana/blob/2085a3b4480/x-pack/plugins/cases/server/client/configure/client.ts#L84) +Defined in: [configure/client.ts:80](https://github.com/jonathan-buttner/kibana/blob/0e98e105663/x-pack/plugins/cases/server/client/configure/client.ts#L80) ___ @@ -62,7 +62,7 @@ Retrieves the valid external connectors supported by the cases plugin. **Returns:** *Promise* -Defined in: [configure/client.ts:88](https://github.com/jonathan-buttner/kibana/blob/2085a3b4480/x-pack/plugins/cases/server/client/configure/client.ts#L88) +Defined in: [configure/client.ts:84](https://github.com/jonathan-buttner/kibana/blob/0e98e105663/x-pack/plugins/cases/server/client/configure/client.ts#L84) ___ @@ -81,4 +81,4 @@ Updates a particular configuration with new values. **Returns:** *Promise*<[*ICasesConfigureResponse*](typedoc_interfaces.icasesconfigureresponse.md)\> -Defined in: [configure/client.ts:95](https://github.com/jonathan-buttner/kibana/blob/2085a3b4480/x-pack/plugins/cases/server/client/configure/client.ts#L95) +Defined in: [configure/client.ts:91](https://github.com/jonathan-buttner/kibana/blob/0e98e105663/x-pack/plugins/cases/server/client/configure/client.ts#L91) diff --git a/x-pack/plugins/cases/docs/cases_client/interfaces/stats_client.statssubclient.md b/x-pack/plugins/cases/docs/cases_client/interfaces/stats_client.statssubclient.md index 7e01205395277..761b34b5205ec 100644 --- a/x-pack/plugins/cases/docs/cases_client/interfaces/stats_client.statssubclient.md +++ b/x-pack/plugins/cases/docs/cases_client/interfaces/stats_client.statssubclient.md @@ -29,4 +29,4 @@ Retrieves the total number of open, closed, and in-progress cases. **Returns:** *Promise*<{ `count_closed_cases`: *number* ; `count_in_progress_cases`: *number* ; `count_open_cases`: *number* }\> -Defined in: [stats/client.ts:34](https://github.com/jonathan-buttner/kibana/blob/2085a3b4480/x-pack/plugins/cases/server/client/stats/client.ts#L34) +Defined in: [stats/client.ts:34](https://github.com/jonathan-buttner/kibana/blob/0e98e105663/x-pack/plugins/cases/server/client/stats/client.ts#L34) diff --git a/x-pack/plugins/cases/docs/cases_client/interfaces/sub_cases_client.subcasesclient.md b/x-pack/plugins/cases/docs/cases_client/interfaces/sub_cases_client.subcasesclient.md index 76df26524b7b0..c83c68620e8ac 100644 --- a/x-pack/plugins/cases/docs/cases_client/interfaces/sub_cases_client.subcasesclient.md +++ b/x-pack/plugins/cases/docs/cases_client/interfaces/sub_cases_client.subcasesclient.md @@ -31,7 +31,7 @@ Deletes the specified entities and their attachments. **Returns:** *Promise* -Defined in: [sub_cases/client.ts:60](https://github.com/jonathan-buttner/kibana/blob/2085a3b4480/x-pack/plugins/cases/server/client/sub_cases/client.ts#L60) +Defined in: [sub_cases/client.ts:68](https://github.com/jonathan-buttner/kibana/blob/0e98e105663/x-pack/plugins/cases/server/client/sub_cases/client.ts#L68) ___ @@ -49,7 +49,7 @@ Retrieves the sub cases matching the search criteria. **Returns:** *Promise*<[*ISubCasesFindResponse*](typedoc_interfaces.isubcasesfindresponse.md)\> -Defined in: [sub_cases/client.ts:64](https://github.com/jonathan-buttner/kibana/blob/2085a3b4480/x-pack/plugins/cases/server/client/sub_cases/client.ts#L64) +Defined in: [sub_cases/client.ts:72](https://github.com/jonathan-buttner/kibana/blob/0e98e105663/x-pack/plugins/cases/server/client/sub_cases/client.ts#L72) ___ @@ -67,7 +67,7 @@ Retrieves a single sub case. **Returns:** *Promise*<[*ISubCaseResponse*](typedoc_interfaces.isubcaseresponse.md)\> -Defined in: [sub_cases/client.ts:68](https://github.com/jonathan-buttner/kibana/blob/2085a3b4480/x-pack/plugins/cases/server/client/sub_cases/client.ts#L68) +Defined in: [sub_cases/client.ts:76](https://github.com/jonathan-buttner/kibana/blob/0e98e105663/x-pack/plugins/cases/server/client/sub_cases/client.ts#L76) ___ @@ -86,4 +86,4 @@ Updates the specified sub cases to the new values included in the request. **Returns:** *Promise*<[*ISubCasesResponse*](typedoc_interfaces.isubcasesresponse.md)\> -Defined in: [sub_cases/client.ts:72](https://github.com/jonathan-buttner/kibana/blob/2085a3b4480/x-pack/plugins/cases/server/client/sub_cases/client.ts#L72) +Defined in: [sub_cases/client.ts:80](https://github.com/jonathan-buttner/kibana/blob/0e98e105663/x-pack/plugins/cases/server/client/sub_cases/client.ts#L80) diff --git a/x-pack/plugins/cases/docs/cases_client/interfaces/user_actions_client.useractionget.md b/x-pack/plugins/cases/docs/cases_client/interfaces/user_actions_client.useractionget.md index 2c0c084ab9b30..f992a4116c800 100644 --- a/x-pack/plugins/cases/docs/cases_client/interfaces/user_actions_client.useractionget.md +++ b/x-pack/plugins/cases/docs/cases_client/interfaces/user_actions_client.useractionget.md @@ -21,7 +21,7 @@ Parameters for retrieving user actions for a particular case The ID of the case -Defined in: [user_actions/client.ts:19](https://github.com/jonathan-buttner/kibana/blob/2085a3b4480/x-pack/plugins/cases/server/client/user_actions/client.ts#L19) +Defined in: [user_actions/client.ts:19](https://github.com/jonathan-buttner/kibana/blob/0e98e105663/x-pack/plugins/cases/server/client/user_actions/client.ts#L19) ___ @@ -31,4 +31,4 @@ ___ If specified then a sub case will be used for finding all the user actions -Defined in: [user_actions/client.ts:23](https://github.com/jonathan-buttner/kibana/blob/2085a3b4480/x-pack/plugins/cases/server/client/user_actions/client.ts#L23) +Defined in: [user_actions/client.ts:23](https://github.com/jonathan-buttner/kibana/blob/0e98e105663/x-pack/plugins/cases/server/client/user_actions/client.ts#L23) diff --git a/x-pack/plugins/cases/docs/cases_client/interfaces/user_actions_client.useractionssubclient.md b/x-pack/plugins/cases/docs/cases_client/interfaces/user_actions_client.useractionssubclient.md index f03667eccb858..e838a72159bef 100644 --- a/x-pack/plugins/cases/docs/cases_client/interfaces/user_actions_client.useractionssubclient.md +++ b/x-pack/plugins/cases/docs/cases_client/interfaces/user_actions_client.useractionssubclient.md @@ -28,4 +28,4 @@ Retrieves all user actions for a particular case. **Returns:** *Promise*<[*ICaseUserActionsResponse*](typedoc_interfaces.icaseuseractionsresponse.md)\> -Defined in: [user_actions/client.ts:33](https://github.com/jonathan-buttner/kibana/blob/2085a3b4480/x-pack/plugins/cases/server/client/user_actions/client.ts#L33) +Defined in: [user_actions/client.ts:33](https://github.com/jonathan-buttner/kibana/blob/0e98e105663/x-pack/plugins/cases/server/client/user_actions/client.ts#L33) diff --git a/x-pack/plugins/cases/docs/cases_client/modules/attachments_get.md b/x-pack/plugins/cases/docs/cases_client/modules/attachments_get.md index 99358d6683256..6460511da79a1 100644 --- a/x-pack/plugins/cases/docs/cases_client/modules/attachments_get.md +++ b/x-pack/plugins/cases/docs/cases_client/modules/attachments_get.md @@ -7,5 +7,6 @@ ### Interfaces - [FindArgs](../interfaces/attachments_get.findargs.md) +- [GetAllAlertsAttachToCase](../interfaces/attachments_get.getallalertsattachtocase.md) - [GetAllArgs](../interfaces/attachments_get.getallargs.md) - [GetArgs](../interfaces/attachments_get.getargs.md) diff --git a/x-pack/plugins/cases/docs/cases_client/modules/cases_get.md b/x-pack/plugins/cases/docs/cases_client/modules/cases_get.md index 9e896881df17b..acfa0b918aa9a 100644 --- a/x-pack/plugins/cases/docs/cases_client/modules/cases_get.md +++ b/x-pack/plugins/cases/docs/cases_client/modules/cases_get.md @@ -31,7 +31,7 @@ Retrieves the reporters from all the cases. **Returns:** *Promise* -Defined in: [cases/get.ts:279](https://github.com/jonathan-buttner/kibana/blob/2085a3b4480/x-pack/plugins/cases/server/client/cases/get.ts#L279) +Defined in: [cases/get.ts:255](https://github.com/jonathan-buttner/kibana/blob/0e98e105663/x-pack/plugins/cases/server/client/cases/get.ts#L255) ___ @@ -50,4 +50,4 @@ Retrieves the tags from all the cases. **Returns:** *Promise* -Defined in: [cases/get.ts:217](https://github.com/jonathan-buttner/kibana/blob/2085a3b4480/x-pack/plugins/cases/server/client/cases/get.ts#L217) +Defined in: [cases/get.ts:205](https://github.com/jonathan-buttner/kibana/blob/0e98e105663/x-pack/plugins/cases/server/client/cases/get.ts#L205) diff --git a/x-pack/plugins/cases/public/components/markdown_editor/types.ts b/x-pack/plugins/cases/public/components/markdown_editor/types.ts index bb932f2fcfe22..ccc3c59c8977e 100644 --- a/x-pack/plugins/cases/public/components/markdown_editor/types.ts +++ b/x-pack/plugins/cases/public/components/markdown_editor/types.ts @@ -8,7 +8,6 @@ import { FunctionComponent } from 'react'; import { Plugin, PluggableList } from 'unified'; // Remove after this issue is resolved: https://github.com/elastic/eui/issues/4688 -// eslint-disable-next-line import/no-extraneous-dependencies import { Options as Remark2RehypeOptions } from 'mdast-util-to-hast'; // eslint-disable-next-line import/no-extraneous-dependencies import rehype2react from 'rehype-react'; diff --git a/x-pack/plugins/cases/server/authorization/__snapshots__/audit_logger.test.ts.snap b/x-pack/plugins/cases/server/authorization/__snapshots__/audit_logger.test.ts.snap index 7f5b8406b89f3..3ca77944776b3 100644 --- a/x-pack/plugins/cases/server/authorization/__snapshots__/audit_logger.test.ts.snap +++ b/x-pack/plugins/cases/server/authorization/__snapshots__/audit_logger.test.ts.snap @@ -756,6 +756,90 @@ Object { } `; +exports[`audit_logger log function event structure creates the correct audit event for operation: "getAlertsAttachedToCase" with an error and entity 1`] = ` +Object { + "error": Object { + "code": "Error", + "message": "an error", + }, + "event": Object { + "action": "case_comment_alerts_attach_to_case", + "category": Array [ + "database", + ], + "outcome": "failure", + "type": Array [ + "access", + ], + }, + "kibana": Object { + "saved_object": Object { + "id": "1", + "type": "cases-comments", + }, + }, + "message": "Failed attempt to access cases-comments [id=1] as owner \\"awesome\\"", +} +`; + +exports[`audit_logger log function event structure creates the correct audit event for operation: "getAlertsAttachedToCase" with an error but no entity 1`] = ` +Object { + "error": Object { + "code": "Error", + "message": "an error", + }, + "event": Object { + "action": "case_comment_alerts_attach_to_case", + "category": Array [ + "database", + ], + "outcome": "failure", + "type": Array [ + "access", + ], + }, + "message": "Failed attempt to access a comments as any owners", +} +`; + +exports[`audit_logger log function event structure creates the correct audit event for operation: "getAlertsAttachedToCase" without an error but with an entity 1`] = ` +Object { + "event": Object { + "action": "case_comment_alerts_attach_to_case", + "category": Array [ + "database", + ], + "outcome": "success", + "type": Array [ + "access", + ], + }, + "kibana": Object { + "saved_object": Object { + "id": "5", + "type": "cases-comments", + }, + }, + "message": "User has accessed cases-comments [id=5] as owner \\"super\\"", +} +`; + +exports[`audit_logger log function event structure creates the correct audit event for operation: "getAlertsAttachedToCase" without an error or entity 1`] = ` +Object { + "event": Object { + "action": "case_comment_alerts_attach_to_case", + "category": Array [ + "database", + ], + "outcome": "success", + "type": Array [ + "access", + ], + }, + "message": "User has accessed a comments as any owners", +} +`; + exports[`audit_logger log function event structure creates the correct audit event for operation: "getAllComments" with an error and entity 1`] = ` Object { "error": Object { diff --git a/x-pack/plugins/cases/server/authorization/index.ts b/x-pack/plugins/cases/server/authorization/index.ts index 87683c55f5da0..90b89c7f75766 100644 --- a/x-pack/plugins/cases/server/authorization/index.ts +++ b/x-pack/plugins/cases/server/authorization/index.ts @@ -184,6 +184,14 @@ export const Operations: Record; updateStatus(args: AlertUpdateStatus): Promise; diff --git a/x-pack/plugins/cases/server/client/alerts/get.ts b/x-pack/plugins/cases/server/client/alerts/get.ts index 186f914aa2cd7..2048ccae4fa60 100644 --- a/x-pack/plugins/cases/server/client/alerts/get.ts +++ b/x-pack/plugins/cases/server/client/alerts/get.ts @@ -5,16 +5,11 @@ * 2.0. */ -import { AlertInfo } from '../../common'; -import { CasesClientGetAlertsResponse } from './types'; +import { CasesClientGetAlertsResponse, AlertGet } from './types'; import { CasesClientArgs } from '..'; -interface GetParams { - alertsInfo: AlertInfo[]; -} - export const get = async ( - { alertsInfo }: GetParams, + { alertsInfo }: AlertGet, clientArgs: CasesClientArgs ): Promise => { const { alertsService, scopedClusterClient, logger } = clientArgs; diff --git a/x-pack/plugins/cases/server/client/alerts/types.ts b/x-pack/plugins/cases/server/client/alerts/types.ts index 26a582d92e54b..95cd9ae33bff9 100644 --- a/x-pack/plugins/cases/server/client/alerts/types.ts +++ b/x-pack/plugins/cases/server/client/alerts/types.ts @@ -5,6 +5,9 @@ * 2.0. */ +import { CaseStatuses } from '../../../common/api'; +import { AlertInfo } from '../../common'; + interface Alert { id: string; index: string; @@ -17,3 +20,20 @@ interface Alert { } export type CasesClientGetAlertsResponse = Alert[]; + +/** + * Defines the fields necessary to update an alert's status. + */ +export interface UpdateAlertRequest { + id: string; + index: string; + status: CaseStatuses; +} + +export interface AlertUpdateStatus { + alerts: UpdateAlertRequest[]; +} + +export interface AlertGet { + alertsInfo: AlertInfo[]; +} diff --git a/x-pack/plugins/cases/server/client/alerts/update_status.ts b/x-pack/plugins/cases/server/client/alerts/update_status.ts index 3c7f60ecae15d..a0684b59241b0 100644 --- a/x-pack/plugins/cases/server/client/alerts/update_status.ts +++ b/x-pack/plugins/cases/server/client/alerts/update_status.ts @@ -5,8 +5,8 @@ * 2.0. */ -import { UpdateAlertRequest } from './client'; import { CasesClientArgs } from '..'; +import { UpdateAlertRequest } from './types'; interface UpdateAlertsStatusArgs { alerts: UpdateAlertRequest[]; diff --git a/x-pack/plugins/cases/server/client/attachments/client.ts b/x-pack/plugins/cases/server/client/attachments/client.ts index 6a0f2c71ddd91..a07633c0dd38c 100644 --- a/x-pack/plugins/cases/server/client/attachments/client.ts +++ b/x-pack/plugins/cases/server/client/attachments/client.ts @@ -5,14 +5,24 @@ * 2.0. */ -import { CommentResponse } from '../../../common'; +import { AlertResponse, CommentResponse } from '../../../common'; +import { CasesClient } from '../client'; import { CasesClientInternal } from '../client_internal'; import { IAllCommentsResponse, ICaseResponse, ICommentsResponse } from '../typedoc_interfaces'; import { CasesClientArgs } from '../types'; import { AddArgs, addComment } from './add'; import { DeleteAllArgs, deleteAll, DeleteArgs, deleteComment } from './delete'; -import { find, FindArgs, get, getAll, GetAllArgs, GetArgs } from './get'; +import { + find, + FindArgs, + get, + getAll, + getAllAlertsAttachToCase, + GetAllAlertsAttachToCase, + GetAllArgs, + GetArgs, +} from './get'; import { update, UpdateArgs } from './update'; /** @@ -35,6 +45,10 @@ export interface AttachmentsSubClient { * Retrieves all comments matching the search criteria. */ find(findArgs: FindArgs): Promise; + /** + * Retrieves all alerts attach to a case given a single case ID + */ + getAllAlertsAttachToCase(params: GetAllAlertsAttachToCase): Promise; /** * Gets all attachments for a single case. */ @@ -58,6 +72,7 @@ export interface AttachmentsSubClient { */ export const createAttachmentsSubClient = ( clientArgs: CasesClientArgs, + casesClient: CasesClient, casesClientInternal: CasesClientInternal ): AttachmentsSubClient => { const attachmentSubClient: AttachmentsSubClient = { @@ -65,6 +80,8 @@ export const createAttachmentsSubClient = ( deleteAll: (deleteAllArgs: DeleteAllArgs) => deleteAll(deleteAllArgs, clientArgs), delete: (deleteArgs: DeleteArgs) => deleteComment(deleteArgs, clientArgs), find: (findArgs: FindArgs) => find(findArgs, clientArgs), + getAllAlertsAttachToCase: (params: GetAllAlertsAttachToCase) => + getAllAlertsAttachToCase(params, clientArgs, casesClient), getAll: (getAllArgs: GetAllArgs) => getAll(getAllArgs, clientArgs), get: (getArgs: GetArgs) => get(getArgs, clientArgs), update: (updateArgs: UpdateArgs) => update(updateArgs, clientArgs), diff --git a/x-pack/plugins/cases/server/client/attachments/get.ts b/x-pack/plugins/cases/server/client/attachments/get.ts index 590038a200e48..4f4ade51f9a59 100644 --- a/x-pack/plugins/cases/server/client/attachments/get.ts +++ b/x-pack/plugins/cases/server/client/attachments/get.ts @@ -5,12 +5,14 @@ * 2.0. */ import Boom from '@hapi/boom'; -import { SavedObjectsFindResponse } from 'kibana/server'; +import { SavedObject, SavedObjectsFindResponse } from 'kibana/server'; import { + AlertResponse, AllCommentsResponse, AllCommentsResponseRt, AssociationType, + AttributesTypeAlerts, CommentAttributes, CommentResponse, CommentResponseRt, @@ -26,12 +28,14 @@ import { transformComments, flattenCommentSavedObject, flattenCommentSavedObjects, + getIDsAndIndicesAsArrays, } from '../../common'; import { defaultPage, defaultPerPage } from '../../routes/api'; import { CasesClientArgs } from '../types'; import { combineFilters, stringToKueryNode } from '../utils'; import { Operations } from '../../authorization'; import { includeFieldsRequiredForAuthentication } from '../../authorization/utils'; +import { CasesClient } from '../client'; /** * Parameters for finding attachments of a case @@ -76,6 +80,71 @@ export interface GetArgs { attachmentID: string; } +export interface GetAllAlertsAttachToCase { + /** + * The ID of the case to retrieve the alerts from + */ + caseId: string; +} + +const normalizeAlertResponse = (alerts: Array>): AlertResponse => + alerts.reduce((acc: AlertResponse, alert) => { + const { ids, indices } = getIDsAndIndicesAsArrays(alert.attributes); + + if (ids.length !== indices.length) { + return acc; + } + + return [ + ...acc, + ...ids.map((id, index) => ({ + id, + index: indices[index], + attached_at: alert.attributes.created_at, + })), + ]; + }, []); + +/** + * Retrieves all alerts attached to a specific case. + * + * @ignore + */ +export const getAllAlertsAttachToCase = async ( + { caseId }: GetAllAlertsAttachToCase, + clientArgs: CasesClientArgs, + casesClient: CasesClient +): Promise => { + const { unsecuredSavedObjectsClient, authorization, attachmentService } = clientArgs; + + // This will perform an authorization check to ensure the user has access to the parent case + const theCase = await casesClient.cases.get({ + id: caseId, + includeComments: false, + includeSubCaseComments: false, + }); + + const { + filter: authorizationFilter, + ensureSavedObjectsAreAuthorized, + } = await authorization.getAuthorizationFilter(Operations.getAlertsAttachedToCase); + + const alerts = await attachmentService.getAllAlertsAttachToCase({ + unsecuredSavedObjectsClient, + caseId: theCase.id, + filter: authorizationFilter, + }); + + ensureSavedObjectsAreAuthorized( + alerts.map((alert) => ({ + owner: alert.attributes.owner, + id: alert.id, + })) + ); + + return normalizeAlertResponse(alerts); +}; + /** * Retrieves the attachments for a case entity. This support pagination. * diff --git a/x-pack/plugins/cases/server/client/cases/update.ts b/x-pack/plugins/cases/server/client/cases/update.ts index 5726cfe44f697..e5d9e1cddeee6 100644 --- a/x-pack/plugins/cases/server/client/cases/update.ts +++ b/x-pack/plugins/cases/server/client/cases/update.ts @@ -52,7 +52,7 @@ import { isCommentRequestTypeAlertOrGenAlert, transformCaseConnectorToEsConnector, } from '../../common'; -import { UpdateAlertRequest } from '../alerts/client'; +import { UpdateAlertRequest } from '../alerts/types'; import { CasesClientInternal } from '../client_internal'; import { CasesClientArgs } from '..'; import { Operations, OwnerEntity } from '../../authorization'; diff --git a/x-pack/plugins/cases/server/client/client.ts b/x-pack/plugins/cases/server/client/client.ts index b7b1dd46d003d..37e4cd52681e5 100644 --- a/x-pack/plugins/cases/server/client/client.ts +++ b/x-pack/plugins/cases/server/client/client.ts @@ -30,7 +30,7 @@ export class CasesClient { constructor(args: CasesClientArgs) { this._casesClientInternal = createCasesClientInternal(args); this._cases = createCasesSubClient(args, this, this._casesClientInternal); - this._attachments = createAttachmentsSubClient(args, this._casesClientInternal); + this._attachments = createAttachmentsSubClient(args, this, this._casesClientInternal); this._userActions = createUserActionsSubClient(args); this._subCases = createSubCasesClient(args, this._casesClientInternal); this._configure = createConfigurationSubClient(args, this._casesClientInternal); diff --git a/x-pack/plugins/cases/server/client/mocks.ts b/x-pack/plugins/cases/server/client/mocks.ts index 10b298995f87a..f6a36369c0b03 100644 --- a/x-pack/plugins/cases/server/client/mocks.ts +++ b/x-pack/plugins/cases/server/client/mocks.ts @@ -43,6 +43,7 @@ const createAttachmentsSubClientMock = (): AttachmentsSubClientMock => { getAll: jest.fn(), get: jest.fn(), update: jest.fn(), + getAllAlertsAttachToCase: jest.fn(), }; }; diff --git a/x-pack/plugins/cases/server/client/sub_cases/update.ts b/x-pack/plugins/cases/server/client/sub_cases/update.ts index c2c5f8719e64a..be671a8087f8e 100644 --- a/x-pack/plugins/cases/server/client/sub_cases/update.ts +++ b/x-pack/plugins/cases/server/client/sub_cases/update.ts @@ -44,7 +44,7 @@ import { isCommentRequestTypeAlertOrGenAlert, flattenSubCaseSavedObject, } from '../../common'; -import { UpdateAlertRequest } from '../../client/alerts/client'; +import { UpdateAlertRequest } from '../../client/alerts/types'; import { CasesClientArgs } from '../types'; import { CasesClientInternal } from '../client_internal'; diff --git a/x-pack/plugins/cases/server/common/utils.ts b/x-pack/plugins/cases/server/common/utils.ts index 54124260ae5d2..70ecc50df0f48 100644 --- a/x-pack/plugins/cases/server/common/utils.ts +++ b/x-pack/plugins/cases/server/common/utils.ts @@ -34,7 +34,7 @@ import { SubCasesFindResponse, User, } from '../../common'; -import { UpdateAlertRequest } from '../client/alerts/client'; +import { UpdateAlertRequest } from '../client/alerts/types'; /** * Default sort field for querying saved objects. @@ -271,17 +271,12 @@ const getAndValidateAlertInfoFromComment = (comment: CommentRequest): AlertInfo[ /** * Builds an AlertInfo object accumulating the alert IDs and indices for the passed in alerts. */ -export const getAlertInfoFromComments = (comments: CommentRequest[] | undefined): AlertInfo[] => { - if (comments === undefined) { - return []; - } - - return comments.reduce((acc: AlertInfo[], comment) => { +export const getAlertInfoFromComments = (comments: CommentRequest[] = []): AlertInfo[] => + comments.reduce((acc: AlertInfo[], comment) => { const alertInfo = getAndValidateAlertInfoFromComment(comment); acc.push(...alertInfo); return acc; }, []); -}; type NewCommentArgs = CommentRequest & { associationType: AssociationType; diff --git a/x-pack/plugins/cases/server/routes/api/comments/get_alerts.ts b/x-pack/plugins/cases/server/routes/api/comments/get_alerts.ts new file mode 100644 index 0000000000000..9c0bfac4d9c6e --- /dev/null +++ b/x-pack/plugins/cases/server/routes/api/comments/get_alerts.ts @@ -0,0 +1,41 @@ +/* + * Copyright 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 { RouteDeps } from '../types'; +import { wrapError } from '../utils'; +import { CASE_DETAILS_ALERTS_URL } from '../../../../common/constants'; + +export function initGetAllAlertsAttachToCaseApi({ router, logger }: RouteDeps) { + router.get( + { + path: CASE_DETAILS_ALERTS_URL, + validate: { + params: schema.object({ + case_id: schema.string({ minLength: 1 }), + }), + }, + }, + async (context, request, response) => { + try { + const caseId = request.params.case_id; + + const casesClient = await context.cases.getCasesClient(); + + return response.ok({ + body: await casesClient.attachments.getAllAlertsAttachToCase({ caseId }), + }); + } catch (error) { + logger.error( + `Failed to retrieve alert ids for this case id: ${request.params.case_id}: ${error}` + ); + return response.customError(wrapError(error)); + } + } + ); +} diff --git a/x-pack/plugins/cases/server/routes/api/index.ts b/x-pack/plugins/cases/server/routes/api/index.ts index 4f6db0c1081b1..011464a73396f 100644 --- a/x-pack/plugins/cases/server/routes/api/index.ts +++ b/x-pack/plugins/cases/server/routes/api/index.ts @@ -39,6 +39,7 @@ import { initFindSubCasesApi } from './sub_case/find_sub_cases'; import { initDeleteSubCasesApi } from './sub_case/delete_sub_cases'; import { ENABLE_CASE_CONNECTOR } from '../../../common'; import { initGetCaseIdsByAlertIdApi } from './cases/alerts/get_cases'; +import { initGetAllAlertsAttachToCaseApi } from './comments/get_alerts'; /** * Default page number when interacting with the saved objects API. @@ -89,4 +90,5 @@ export function initCaseApi(deps: RouteDeps) { initGetTagsApi(deps); // Alerts initGetCaseIdsByAlertIdApi(deps); + initGetAllAlertsAttachToCaseApi(deps); } diff --git a/x-pack/plugins/cases/server/services/alerts/index.ts b/x-pack/plugins/cases/server/services/alerts/index.ts index 4b2460d4b68cd..e33b0385bc123 100644 --- a/x-pack/plugins/cases/server/services/alerts/index.ts +++ b/x-pack/plugins/cases/server/services/alerts/index.ts @@ -12,7 +12,7 @@ import type { PublicMethodsOf } from '@kbn/utility-types'; import { ElasticsearchClient, Logger } from 'kibana/server'; import { MAX_ALERTS_PER_SUB_CASE } from '../../../common'; import { AlertInfo, createCaseError } from '../../common'; -import { UpdateAlertRequest } from '../../client/alerts/client'; +import { UpdateAlertRequest } from '../../client/alerts/types'; export type AlertServiceContract = PublicMethodsOf; diff --git a/x-pack/plugins/cases/server/services/attachments/index.ts b/x-pack/plugins/cases/server/services/attachments/index.ts index 685b088701f68..c2d9b4826fc14 100644 --- a/x-pack/plugins/cases/server/services/attachments/index.ts +++ b/x-pack/plugins/cases/server/services/attachments/index.ts @@ -7,12 +7,23 @@ import { Logger, SavedObject, SavedObjectReference } from 'kibana/server'; +import { KueryNode } from '../../../../../../src/plugins/data/common'; import { + AttributesTypeAlerts, CASE_COMMENT_SAVED_OBJECT, CommentAttributes as AttachmentAttributes, CommentPatchAttributes as AttachmentPatchAttributes, + CASE_SAVED_OBJECT, + MAX_DOCS_PER_PAGE, + CommentType, } from '../../../common'; import { ClientArgs } from '..'; +import { buildFilter, combineFilters } from '../../client/utils'; + +interface GetAllAlertsAttachToCaseArgs extends ClientArgs { + caseId: string; + filter?: KueryNode; +} interface GetAttachmentArgs extends ClientArgs { attachmentId: string; @@ -39,6 +50,46 @@ interface BulkUpdateAttachmentArgs extends ClientArgs { export class AttachmentService { constructor(private readonly log: Logger) {} + /** + * Retrieves all the alerts attached to a case. + */ + public async getAllAlertsAttachToCase({ + unsecuredSavedObjectsClient, + caseId, + filter, + }: GetAllAlertsAttachToCaseArgs): Promise>> { + try { + this.log.debug(`Attempting to GET all alerts for case id ${caseId}`); + const alertsFilter = buildFilter({ + filters: [CommentType.alert, CommentType.generatedAlert], + field: 'type', + operator: 'or', + type: CASE_COMMENT_SAVED_OBJECT, + }); + + const combinedFilter = combineFilters([alertsFilter, filter]); + + const finder = unsecuredSavedObjectsClient.createPointInTimeFinder({ + type: CASE_COMMENT_SAVED_OBJECT, + hasReference: { type: CASE_SAVED_OBJECT, id: caseId }, + sortField: 'created_at', + sortOrder: 'asc', + filter: combinedFilter, + perPage: MAX_DOCS_PER_PAGE, + }); + + let result: Array> = []; + for await (const userActionSavedObject of finder.find()) { + result = result.concat(userActionSavedObject.saved_objects); + } + + return result; + } catch (error) { + this.log.error(`Error on GET all alerts for case id ${caseId}: ${error}`); + throw error; + } + } + public async get({ unsecuredSavedObjectsClient, attachmentId, diff --git a/x-pack/plugins/cases/server/services/mocks.ts b/x-pack/plugins/cases/server/services/mocks.ts index ce9aec942220a..c82624cd50ab6 100644 --- a/x-pack/plugins/cases/server/services/mocks.ts +++ b/x-pack/plugins/cases/server/services/mocks.ts @@ -104,6 +104,7 @@ export const createAttachmentServiceMock = (): AttachmentServiceMock => { create: jest.fn(), update: jest.fn(), bulkUpdate: jest.fn(), + getAllAlertsAttachToCase: jest.fn(), }; // the cast here is required because jest.Mocked tries to include private members and would throw an error diff --git a/x-pack/plugins/data_enhanced/server/collectors/fetch.ts b/x-pack/plugins/data_enhanced/server/collectors/fetch.ts index 6feb13432d07b..bfd5ee745cd75 100644 --- a/x-pack/plugins/data_enhanced/server/collectors/fetch.ts +++ b/x-pack/plugins/data_enhanced/server/collectors/fetch.ts @@ -4,7 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ - +import type { estypes } from '@elastic/elasticsearch'; import { Observable } from 'rxjs'; import { first } from 'rxjs/operators'; import { SearchResponse } from 'elasticsearch'; @@ -36,8 +36,12 @@ export function fetchProvider(config$: Observable, logger: L }, }); - // @ts-expect-error @elastic/elasticsearch no way to declare a type for aggregations - const buckets: SessionPersistedTermsBucket[] = esResponse.aggregations!.persisted.buckets; + const aggs = esResponse.aggregations as Record< + string, + estypes.AggregationsMultiBucketAggregate + >; + + const buckets = aggs.persisted.buckets; if (!buckets.length) { return { transientCount: 0, persistedCount: 0, totalCount: 0 }; } diff --git a/x-pack/plugins/data_enhanced/server/search/session/get_search_status.ts b/x-pack/plugins/data_enhanced/server/search/session/get_search_status.ts index e228ba725489c..461c41b46491c 100644 --- a/x-pack/plugins/data_enhanced/server/search/session/get_search_status.ts +++ b/x-pack/plugins/data_enhanced/server/search/session/get_search_status.ts @@ -18,7 +18,7 @@ export async function getSearchStatus( ): Promise> { // TODO: Handle strategies other than the default one try { - // @ts-expect-error @elastic/elasticsearch status method is not defined + // @ts-expect-error start_time_in_millis: EpochMillis is string | number const apiResponse: ApiResponse = await client.asyncSearch.status({ id: asyncId, }); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/connect_instance.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/connect_instance.tsx index fd45d779e6f2a..e3b34050593fa 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/connect_instance.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/connect_instance.tsx @@ -161,13 +161,13 @@ export const ConnectInstance: React.FC = ({ const permissionField = ( <> - +

{CONNECT_DOC_PERMISSIONS_TITLE}

- + {!needsPermissions && ( = ({ )} - {!indexPermissionsValue && ( <> - - +

{CONNECT_NOT_SYNCED_TEXT} {needsPermissions && whichDocsLink} diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/source_features.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/source_features.tsx index 7a66efe4ba5f4..0f170be8ba076 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/source_features.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/source_features.tsx @@ -63,20 +63,20 @@ export const SourceFeatures: React.FC = ({ features, objTy )} - + {title} - {children} + {children} ); }; const SyncFrequencyFeature = ( - +

= ({ features, objTy const SyncedItemsFeature = ( <> - +

{SOURCE_FEATURES_SEARCHABLE}

- +
    {objTypes!.map((objType, i) => (
  • {objType}
  • @@ -178,7 +178,7 @@ export const SourceFeatures: React.FC = ({ features, objTy return ( <> - +

    Included features

    diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_form.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_form.tsx index a44edb5ce9a42..25a0993242822 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_form.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_form.tsx @@ -29,6 +29,7 @@ import styled from 'styled-components'; import { dataTypes } from '../../../../../../common'; import type { NewAgentPolicy, AgentPolicy } from '../../../types'; import { isValidNamespace } from '../../../services'; +import { useStartServices } from '../../../hooks'; import { AgentPolicyDeleteProvider } from './agent_policy_delete_provider'; @@ -83,6 +84,7 @@ export const AgentPolicyForm: React.FunctionComponent = ({ isEditing = false, onDelete = () => {}, }) => { + const { docLinks } = useStartServices(); const [touchedFields, setTouchedFields] = useState<{ [key: string]: boolean }>({}); const fields: Array<{ name: 'name' | 'description' | 'namespace'; @@ -174,10 +176,7 @@ export const AgentPolicyForm: React.FunctionComponent = ({ defaultMessage="Namespaces are a user-configurable arbitrary grouping that makes it easier to search for data and manage user permissions. A policy namespace is used to name its integration's data streams. {fleetUserGuide}." values={{ fleetUserGuide: ( - + {i18n.translate( 'xpack.fleet.agentPolicyForm.nameSpaceFieldDescription.fleetUserGuideLabel', { defaultMessage: 'Learn more' } diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/step_define_package_policy.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/step_define_package_policy.tsx index 7444bed6ed3fd..c276e67cabbff 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/step_define_package_policy.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/step_define_package_policy.tsx @@ -29,6 +29,7 @@ import type { } from '../../../types'; import { packageToPackagePolicy, pkgKeyFromPackageInfo } from '../../../services'; import { Loading } from '../../../components'; +import { useStartServices } from '../../../hooks'; import { isAdvancedVar } from './services'; import type { PackagePolicyValidationResults } from './services'; @@ -52,6 +53,7 @@ export const StepDefinePackagePolicy: React.FunctionComponent<{ validationResults, submitAttempted, }) => { + const { docLinks } = useStartServices(); // Form show/hide states const [isShowingAdvanced, setIsShowingAdvanced] = useState(false); @@ -167,10 +169,7 @@ export const StepDefinePackagePolicy: React.FunctionComponent<{ defaultMessage="Change the default namespace inherited from the selected Agent policy. This setting changes the name of the integration's data stream. {learnMore}." values={{ learnMore: ( - + {i18n.translate( 'xpack.fleet.createPackagePolicy.stepConfigure.packagePolicyNamespaceHelpLearnMoreLabel', { defaultMessage: 'Learn more' } diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_requirements_page/es_requirements_page.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_requirements_page/es_requirements_page.tsx index 5335432a13613..b4e6f1007536f 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_requirements_page/es_requirements_page.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_requirements_page/es_requirements_page.tsx @@ -24,6 +24,7 @@ import { import { WithoutHeaderLayout } from '../../../layouts'; import type { GetFleetStatusResponse } from '../../../types'; +import { useStartServices } from '../../../hooks'; export const RequirementItem: React.FunctionComponent<{ isMissing: boolean }> = ({ isMissing, @@ -50,6 +51,8 @@ export const RequirementItem: React.FunctionComponent<{ isMissing: boolean }> = export const MissingESRequirementsPage: React.FunctionComponent<{ missingRequirements: GetFleetStatusResponse['missing_requirements']; }> = ({ missingRequirements }) => { + const { docLinks } = useStartServices(); + return ( @@ -79,7 +82,7 @@ export const MissingESRequirementsPage: React.FunctionComponent<{ values={{ esSecurityLink: ( @@ -104,7 +107,7 @@ export const MissingESRequirementsPage: React.FunctionComponent<{ true: true, apiKeyLink: ( @@ -128,11 +131,7 @@ xpack.security.authc.api_key.enabled: true`} defaultMessage="For more information, read our {link} guide." values={{ link: ( - + void; }): EuiStepProps => { + const { docLinks } = useStartServices(); + return { title: i18n.translate('xpack.fleet.fleetServerSetup.stepInstallAgentTitle', { defaultMessage: 'Start Fleet Server', @@ -147,7 +149,11 @@ export const FleetServerCommandStep = ({ defaultMessage="From the agent directory, copy and run the appropriate quick start command to start an Elastic Agent as a Fleet Server using the generated token and a self-signed certificate. See the {userGuideLink} for instructions on using your own certificates for production deployment. All commands require administrator privileges." values={{ userGuideLink: ( - + + { platform, setPlatform, } = useFleetServerInstructions(); + const { docLinks } = useStartServices(); return ( @@ -304,7 +307,11 @@ const OnPremInstructions: React.FC = () => { defaultMessage="A Fleet Server is required before you can enroll agents with Fleet. See the {userGuideLink} for more information." values={{ userGuideLink: ( - + { }; const CloudInstructions: React.FC<{ deploymentUrl: string }> = ({ deploymentUrl }) => { + const { docLinks } = useStartServices(); + return ( = ({ deploymentUrl defaultMessage="A Fleet Server is required before you can enroll agents with Fleet. You can add one to your deployment by enabling APM & Fleet. For more information see the {link}" values={{ link: ( - + = ({ onClose }) => { const { getAssetsPath } = useLink(); - const { notifications, cloud } = useStartServices(); + const { notifications, cloud, docLinks } = useStartServices(); const isCloud = !!cloud?.cloudId; @@ -163,7 +163,11 @@ export const FleetServerUpgradeModal: React.FunctionComponent = ({ onClos ), link: ( - + { const { setModal } = useUrlModal(); + const { docLinks } = useStartServices(); + return ( { defaultMessage="A URL for your Fleet Server host is required to enroll agents with Fleet. You can add this information in Fleet Settings. For more information, see the {link}." values={{ link: ( - + void; } export const AlphaFlyout: React.FunctionComponent = ({ onClose }) => { + const { docLinks } = useStartServices(); + return ( @@ -49,11 +53,7 @@ export const AlphaFlyout: React.FunctionComponent = ({ onClose }) => { defaultMessage="Read our {docsLink} or go to our {forumLink} for questions or feedback." values={{ docsLink: ( - + = ({ fleetServerHosts, }) => { const { platform, setPlatform } = usePlatform(); + const { docLinks } = useStartServices(); const enrollArgs = getfleetServerHostsEnrollArgs(apiKey, fleetServerHosts); @@ -85,11 +86,7 @@ export const ManualInstructions: React.FunctionComponent = ({ defaultMessage="See the {link} for RPM / DEB deploy instructions." values={{ link: ( - + = ({ defaultMessage="If you are having trouble connecting, see our {link}." values={{ link: ( - + void) { } export const SettingFlyout: React.FunctionComponent = ({ onClose }) => { + const { docLinks } = useStartServices(); + const settingsRequest = useGetSettings(); const settings = settingsRequest?.data?.item; const outputsRequest = useGetOutputs(); @@ -302,7 +304,7 @@ export const SettingFlyout: React.FunctionComponent = ({ onClose }) => { values={{ link: ( diff --git a/x-pack/plugins/fleet/public/hooks/use_core.ts b/x-pack/plugins/fleet/public/hooks/use_core.ts index be4a21a094bd4..2c817bfc938f8 100644 --- a/x-pack/plugins/fleet/public/hooks/use_core.ts +++ b/x-pack/plugins/fleet/public/hooks/use_core.ts @@ -13,5 +13,6 @@ export function useStartServices(): FleetStartServices { if (services === null) { throw new Error('KibanaContextProvider not initialized'); } + return services; } diff --git a/x-pack/plugins/fleet/server/routes/data_streams/handlers.ts b/x-pack/plugins/fleet/server/routes/data_streams/handlers.ts index b80ddfe8e7c9b..073ff7806d9fe 100644 --- a/x-pack/plugins/fleet/server/routes/data_streams/handlers.ts +++ b/x-pack/plugins/fleet/server/routes/data_streams/handlers.ts @@ -4,7 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ - +import type { estypes } from '@elastic/elasticsearch'; import { keyBy, keys, merge } from 'lodash'; import type { RequestHandler, SavedObjectsBulkGetObject } from 'src/core/server'; @@ -140,10 +140,7 @@ export const getListHandler: RequestHandler = async (context, request, response) // Query backing indices to extract data stream dataset, namespace, and type values const { - body: { - // @ts-expect-error @elastic/elasticsearch aggregations are not typed - aggregations: { dataset, namespace, type }, - }, + body: { aggregations: dataStreamAggs }, } = await esClient.search({ index: dataStream.indices.map((index) => index.index_name), body: { @@ -187,6 +184,11 @@ export const getListHandler: RequestHandler = async (context, request, response) }, }); + const { dataset, namespace, type } = dataStreamAggs as Record< + string, + estypes.AggregationsMultiBucketAggregate<{ key?: string }> + >; + // Set values from backing indices query dataStreamResponse.dataset = dataset.buckets[0]?.key || ''; dataStreamResponse.namespace = namespace.buckets[0]?.key || ''; diff --git a/x-pack/plugins/fleet/server/services/agents/helpers.ts b/x-pack/plugins/fleet/server/services/agents/helpers.ts index 822a2a9df98d5..2618aad38bfbf 100644 --- a/x-pack/plugins/fleet/server/services/agents/helpers.ts +++ b/x-pack/plugins/fleet/server/services/agents/helpers.ts @@ -7,7 +7,7 @@ import type { estypes } from '@elastic/elasticsearch'; -import type { SearchHit } from '../../../../../../typings/elasticsearch'; +import type { SearchHit } from '../../../../../../src/core/types/elasticsearch'; import type { Agent, AgentSOAttributes, FleetServerAgent } from '../../types'; type FleetServerAgentESResponse = diff --git a/x-pack/plugins/fleet/server/services/api_keys/enrollment_api_key.ts b/x-pack/plugins/fleet/server/services/api_keys/enrollment_api_key.ts index f9aab997f063c..14d43e6e219db 100644 --- a/x-pack/plugins/fleet/server/services/api_keys/enrollment_api_key.ts +++ b/x-pack/plugins/fleet/server/services/api_keys/enrollment_api_key.ts @@ -12,7 +12,7 @@ import { ResponseError } from '@elastic/elasticsearch/lib/errors'; import type { SavedObjectsClientContract, ElasticsearchClient } from 'src/core/server'; import { esKuery } from '../../../../../../src/plugins/data/server'; -import type { ESSearchResponse as SearchResponse } from '../../../../../../typings/elasticsearch'; +import type { ESSearchResponse as SearchResponse } from '../../../../../../src/core/types/elasticsearch'; import type { EnrollmentAPIKey, FleetServerEnrollmentAPIKey } from '../../types'; import { IngestManagerError } from '../../errors'; import { ENROLLMENT_API_KEYS_INDEX } from '../../constants'; @@ -47,7 +47,7 @@ export async function listEnrollmentApiKeys( body: query ? { query } : undefined, }); - // @ts-expect-error @elastic/elasticsearch + // @ts-expect-error @elastic/elasticsearch _source is optional const items = res.body.hits.hits.map(esDocToEnrollmentApiKey); return { diff --git a/x-pack/plugins/fleet/server/services/artifacts/mappings.ts b/x-pack/plugins/fleet/server/services/artifacts/mappings.ts index 79d14a27fa827..10dab0400d5aa 100644 --- a/x-pack/plugins/fleet/server/services/artifacts/mappings.ts +++ b/x-pack/plugins/fleet/server/services/artifacts/mappings.ts @@ -5,7 +5,7 @@ * 2.0. */ -import type { SearchHit } from '../../../../../../typings/elasticsearch'; +import type { SearchHit } from '../../../../../../src/core/types/elasticsearch'; import type { Artifact, ArtifactElasticsearchProperties, NewArtifact } from './types'; import { ARTIFACT_DOWNLOAD_RELATIVE_PATH } from './constants'; diff --git a/x-pack/plugins/fleet/server/services/artifacts/mocks.ts b/x-pack/plugins/fleet/server/services/artifacts/mocks.ts index 1a10f93f678b3..8bc1768da23a2 100644 --- a/x-pack/plugins/fleet/server/services/artifacts/mocks.ts +++ b/x-pack/plugins/fleet/server/services/artifacts/mocks.ts @@ -10,7 +10,7 @@ import type { ApiResponse } from '@elastic/elasticsearch'; import { ResponseError } from '@elastic/elasticsearch/lib/errors'; import { elasticsearchServiceMock } from '../../../../../../src/core/server/mocks'; -import type { SearchHit, ESSearchResponse } from '../../../../../../typings/elasticsearch'; +import type { SearchHit, ESSearchResponse } from '../../../../../../src/core/types/elasticsearch'; import type { Artifact, ArtifactElasticsearchProperties, ArtifactsClientInterface } from './types'; import { newArtifactToElasticsearchProperties } from './mappings'; diff --git a/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/evaluate_condition.ts b/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/evaluate_condition.ts index cf3d8a15b7b65..922b10e8bd2b0 100644 --- a/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/evaluate_condition.ts +++ b/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/evaluate_condition.ts @@ -123,7 +123,7 @@ const getData = async ( const client = async ( options: CallWithRequestParams ): Promise> => - // @ts-expect-error @elastic/elasticsearch SearchResponse.body.timeout is not required + // @ts-expect-error SearchResponse.body.timeout is optional (await esClient.search(options)).body as InfraDatabaseSearchResponse; const metrics = [ diff --git a/x-pack/plugins/infra/server/services/log_entries/log_entry_search_strategy.ts b/x-pack/plugins/infra/server/services/log_entries/log_entry_search_strategy.ts index aa34204b9fb44..1f0f13eeb6ca9 100644 --- a/x-pack/plugins/infra/server/services/log_entries/log_entry_search_strategy.ts +++ b/x-pack/plugins/infra/server/services/log_entries/log_entry_search_strategy.ts @@ -84,7 +84,7 @@ export const logEntrySearchStrategyProvider = ({ tiebreakerField, runtimeMappings, }): IEsSearchRequest => ({ - // @ts-expect-error @elastic/elasticsearch declares indices_boost as Record + // @ts-expect-error `Field` is not assignable to `SearchRequest.docvalue_fields` params: createGetLogEntryQuery( indices, params.logEntryId, diff --git a/x-pack/plugins/infra/server/services/log_entries/message/builtin_rules/generic.test.ts b/x-pack/plugins/infra/server/services/log_entries/message/builtin_rules/generic.test.ts index ae5a45c61d3b5..ba8eab91e3456 100644 --- a/x-pack/plugins/infra/server/services/log_entries/message/builtin_rules/generic.test.ts +++ b/x-pack/plugins/infra/server/services/log_entries/message/builtin_rules/generic.test.ts @@ -186,4 +186,58 @@ describe('Generic Rules', () => { `); }); }); + + describe('event.original fallback', () => { + test('includes the event.dataset if present', () => { + const flattenedDocument = { + '@timestamp': ['2016-12-26T16:22:13.000Z'], + 'event.dataset': ['generic.test'], + 'event.original': ['TEST_MESSAGE'], + }; + + expect(format(flattenedDocument, {})).toMatchInlineSnapshot(` + Array [ + Object { + "constant": "[", + }, + Object { + "field": "event.dataset", + "highlights": Array [], + "value": Array [ + "generic.test", + ], + }, + Object { + "constant": "] ", + }, + Object { + "field": "event.original", + "highlights": Array [], + "value": Array [ + "TEST_MESSAGE", + ], + }, + ] + `); + }); + + test('includes the original message', () => { + const flattenedDocument = { + '@timestamp': ['2016-12-26T16:22:13.000Z'], + 'event.original': ['TEST_MESSAGE'], + }; + + expect(format(flattenedDocument, {})).toMatchInlineSnapshot(` + Array [ + Object { + "field": "event.original", + "highlights": Array [], + "value": Array [ + "TEST_MESSAGE", + ], + }, + ] + `); + }); + }); }); diff --git a/x-pack/plugins/infra/server/services/log_entries/message/builtin_rules/generic.ts b/x-pack/plugins/infra/server/services/log_entries/message/builtin_rules/generic.ts index c16d65a75b3e0..07b6cf03e2c5d 100644 --- a/x-pack/plugins/infra/server/services/log_entries/message/builtin_rules/generic.ts +++ b/x-pack/plugins/infra/server/services/log_entries/message/builtin_rules/generic.ts @@ -8,40 +8,15 @@ import { LogMessageFormattingRule } from '../rule_types'; const BUILTIN_GENERIC_MESSAGE_FIELDS = ['message', '@message']; +const BUILTIN_FALLBACK_MESSAGE_FIELDS = ['log.original', 'event.original']; -export const getGenericRules = (genericMessageFields: string[]) => [ - ...Array.from(new Set([...genericMessageFields, ...BUILTIN_GENERIC_MESSAGE_FIELDS])).reduce< - LogMessageFormattingRule[] - >((genericRules, fieldName) => [...genericRules, ...createGenericRulesForField(fieldName)], []), - { - when: { - exists: ['event.dataset', 'log.original'], - }, - format: [ - { - constant: '[', - }, - { - field: 'event.dataset', - }, - { - constant: '] ', - }, - { - field: 'log.original', - }, - ], - }, - { - when: { - exists: ['log.original'], - }, - format: [ - { - field: 'log.original', - }, - ], - }, +export const getGenericRules = (genericMessageFields: string[]): LogMessageFormattingRule[] => [ + ...Array.from(new Set([...genericMessageFields, ...BUILTIN_GENERIC_MESSAGE_FIELDS])).flatMap( + createGenericRulesForField + ), + ...BUILTIN_FALLBACK_MESSAGE_FIELDS.filter( + (fieldName) => !genericMessageFields.includes(fieldName) + ).flatMap(createFallbackRulesForField), ]; const createGenericRulesForField = (fieldName: string) => [ @@ -172,3 +147,35 @@ const createGenericRulesForField = (fieldName: string) => [ ], }, ]; + +const createFallbackRulesForField = (fieldName: string) => [ + { + when: { + exists: ['event.dataset', fieldName], + }, + format: [ + { + constant: '[', + }, + { + field: 'event.dataset', + }, + { + constant: '] ', + }, + { + field: fieldName, + }, + ], + }, + { + when: { + exists: [fieldName], + }, + format: [ + { + field: fieldName, + }, + ], + }, +]; diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/index.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/index.tsx index 8248976ab8452..7551b88039182 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/index.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/index.tsx @@ -194,14 +194,16 @@ export const termsOperation: OperationDefinition - [ + getErrorMessage: (layer, columnId, indexPattern) => { + const messages = [ ...(getInvalidFieldMessage( layer.columns[columnId] as FieldBasedIndexPatternColumn, indexPattern ) || []), getDisallowedTermsMessage(layer, columnId, indexPattern) || '', - ].filter(Boolean), + ].filter(Boolean); + return messages.length ? messages : undefined; + }, isTransferable: (column, newIndexPattern) => { const newField = newIndexPattern.getFieldByName(column.sourceField); diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/terms.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/terms.test.tsx index f5540732953ac..3b557461546ca 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/terms.test.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/terms.test.tsx @@ -996,8 +996,8 @@ describe('terms', () => { indexPatternId: '', }; }); - it('returns empty array', () => { - expect(termsOperation.getErrorMessage!(layer, 'col1', indexPattern)).toEqual([]); + it('returns undefined for no errors found', () => { + expect(termsOperation.getErrorMessage!(layer, 'col1', indexPattern)).toEqual(undefined); }); it('returns error message if the sourceField does not exist in index pattern', () => { layer = { diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/layer_helpers.ts b/x-pack/plugins/lens/public/indexpattern_datasource/operations/layer_helpers.ts index 4e3bcec4b6ca2..f0095b66e2ba6 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/layer_helpers.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/layer_helpers.ts @@ -627,7 +627,7 @@ export function canTransition({ Boolean(newColumn) && !newLayer.incompleteColumns?.[columnId] && filterOperations(newColumn) && - !newDefinition.getErrorMessage?.(newLayer, columnId, indexPattern) + !newDefinition.getErrorMessage?.(newLayer, columnId, indexPattern)?.length ); } catch (e) { return false; diff --git a/x-pack/plugins/lens/server/routes/field_stats.ts b/x-pack/plugins/lens/server/routes/field_stats.ts index ef1043ddd3583..12d3ef3f4a95e 100644 --- a/x-pack/plugins/lens/server/routes/field_stats.ts +++ b/x-pack/plugins/lens/server/routes/field_stats.ts @@ -10,7 +10,7 @@ import { schema } from '@kbn/config-schema'; import { CoreSetup } from 'src/core/server'; import { IFieldType } from 'src/plugins/data/common'; import { SavedObjectNotFound } from '../../../../../src/plugins/kibana_utils/common'; -import { ESSearchResponse } from '../../../../../typings/elasticsearch'; +import { ESSearchResponse } from '../../../../../src/core/types/elasticsearch'; import { FieldStatsResponse, BASE_API_URL } from '../../common'; import { PluginStartContract } from '../plugin'; diff --git a/x-pack/plugins/lens/server/usage/task.ts b/x-pack/plugins/lens/server/usage/task.ts index 9c9ab7fd0b350..9227ca885359b 100644 --- a/x-pack/plugins/lens/server/usage/task.ts +++ b/x-pack/plugins/lens/server/usage/task.ts @@ -16,7 +16,7 @@ import { } from '../../../task_manager/server'; import { getVisualizationCounts } from './visualization_counts'; -import { ESSearchResponse } from '../../../../../typings/elasticsearch'; +import { ESSearchResponse } from '../../../../../src/core/types/elasticsearch'; // This task is responsible for running daily and aggregating all the Lens click event objects // into daily rolled-up documents, which will be used in reporting click stats diff --git a/x-pack/plugins/lens/server/usage/visualization_counts.ts b/x-pack/plugins/lens/server/usage/visualization_counts.ts index f0c48fb1152e8..6e79d5f342377 100644 --- a/x-pack/plugins/lens/server/usage/visualization_counts.ts +++ b/x-pack/plugins/lens/server/usage/visualization_counts.ts @@ -75,7 +75,7 @@ export async function getVisualizationCounts( }, }); - // @ts-expect-error @elastic/elasticsearch no way to declare aggregations for search response + // @ts-expect-error specify aggregations type explicitly const buckets = results.aggregations.groups.buckets; // eslint-disable-next-line @typescript-eslint/no-explicit-any 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 fda8de5da8aae..94a049d10cc45 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 @@ -61,7 +61,7 @@ export const createEndpointEventFiltersList = async ({ os_types: [], tags: [], tie_breaker_id: tieBreaker ?? uuid.v4(), - type: 'endpoint', + type: 'endpoint_events', updated_by: user, version, }, diff --git a/x-pack/plugins/maps/common/constants.ts b/x-pack/plugins/maps/common/constants.ts index c49dd4fb619b5..37a8e8063c4ed 100644 --- a/x-pack/plugins/maps/common/constants.ts +++ b/x-pack/plugins/maps/common/constants.ts @@ -107,7 +107,7 @@ export const SOURCE_DATA_REQUEST_ID = 'source'; export const SOURCE_META_DATA_REQUEST_ID = `${SOURCE_DATA_REQUEST_ID}_${META_DATA_REQUEST_ID_SUFFIX}`; export const SOURCE_FORMATTERS_DATA_REQUEST_ID = `${SOURCE_DATA_REQUEST_ID}_${FORMATTERS_DATA_REQUEST_ID_SUFFIX}`; export const SOURCE_BOUNDS_DATA_REQUEST_ID = `${SOURCE_DATA_REQUEST_ID}_bounds`; -export const IS_EDITABLE_REQUEST_ID = 'isEditable'; +export const SUPPORTS_FEATURE_EDITING_REQUEST_ID = 'SUPPORTS_FEATURE_EDITING_REQUEST_ID'; export const MIN_ZOOM = 0; export const MAX_ZOOM = 24; 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 bcd46568ef58e..8b4d25f4612cc 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 @@ -28,7 +28,7 @@ import { FIELD_ORIGIN, KBN_TOO_MANY_FEATURES_IMAGE_ID, FieldFormatter, - IS_EDITABLE_REQUEST_ID, + SUPPORTS_FEATURE_EDITING_REQUEST_ID, } from '../../../../common/constants'; import { JoinTooltipProperty } from '../../tooltips/join_tooltip_property'; import { DataRequestAbortError } from '../../util/data_request'; @@ -177,10 +177,10 @@ export class VectorLayer extends AbstractLayer implements IVectorLayer { } supportsFeatureEditing(): boolean { - const dataRequest = this.getDataRequest(IS_EDITABLE_REQUEST_ID); - const data = dataRequest?.getData() as { isEditable: boolean } | undefined; + const dataRequest = this.getDataRequest(SUPPORTS_FEATURE_EDITING_REQUEST_ID); + const data = dataRequest?.getData() as { supportsFeatureEditing: boolean } | undefined; - return data ? data.isEditable : false; + return data ? data.supportsFeatureEditing : false; } hasJoins() { @@ -678,7 +678,7 @@ export class VectorLayer extends AbstractLayer implements IVectorLayer { syncContext, source, }); - await this._syncIsEditable({ syncContext }); + await this._syncSupportsFeatureEditing({ syncContext, source }); if ( !sourceResult.featureCollection || !sourceResult.featureCollection.features.length || @@ -696,12 +696,18 @@ export class VectorLayer extends AbstractLayer implements IVectorLayer { } } - async _syncIsEditable({ syncContext }: { syncContext: DataRequestContext }) { + async _syncSupportsFeatureEditing({ + syncContext, + source, + }: { + syncContext: DataRequestContext; + source: IVectorSource; + }) { if (syncContext.dataFilters.isReadOnly) { return; } const { startLoading, stopLoading, onLoadError } = syncContext; - const dataRequestId = IS_EDITABLE_REQUEST_ID; + const dataRequestId = SUPPORTS_FEATURE_EDITING_REQUEST_ID; const requestToken = Symbol(`layer-${this.getId()}-${dataRequestId}`); const prevDataRequest = this.getDataRequest(dataRequestId); if (prevDataRequest) { @@ -709,8 +715,8 @@ export class VectorLayer extends AbstractLayer implements IVectorLayer { } try { startLoading(dataRequestId, requestToken); - const isEditable = await this.getSource().loadIsEditable(); - stopLoading(dataRequestId, requestToken, { isEditable }); + const supportsFeatureEditing = await source.supportsFeatureEditing(); + stopLoading(dataRequestId, requestToken, { supportsFeatureEditing }); } catch (error) { onLoadError(dataRequestId, requestToken, error.message); throw error; 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 a3f8ce8e51ede..a51e291574b70 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 @@ -389,7 +389,7 @@ export class ESSearchSource extends AbstractESSource implements ITiledSingleLaye return !!(scalingType === SCALING_TYPES.TOP_HITS && topHitsSplitField); } - async loadIsEditable(): Promise { + async supportsFeatureEditing(): Promise { if (!getMapAppConfig().enableDrawingFeature) { return false; } 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 2311774691acb..d58e71db2a9ab 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 @@ -228,7 +228,7 @@ export class MVTSingleLayerVectorSource return tooltips; } - async loadIsEditable(): Promise { + async supportsFeatureEditing(): Promise { 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 84aad44c4fdbb..1194d571e344b 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 @@ -66,7 +66,7 @@ export interface IVectorSource extends ISource { getSupportedShapeTypes(): Promise; isBoundsAware(): boolean; getSourceTooltipContent(sourceDataRequest?: DataRequest): SourceTooltipConfig; - loadIsEditable(): Promise; + supportsFeatureEditing(): Promise; addFeature(geometry: Geometry | Position[]): Promise; } @@ -160,7 +160,7 @@ export class AbstractVectorSource extends AbstractSource implements IVectorSourc throw new Error('Should implement VectorSource#addFeature'); } - async loadIsEditable(): Promise { + async supportsFeatureEditing(): Promise { return false; } } diff --git a/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_toc/toc_entry/toc_entry_actions_popover/toc_entry_actions_popover.tsx b/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_toc/toc_entry/toc_entry_actions_popover/toc_entry_actions_popover.tsx index 526c157ceaabc..ab7a54be37404 100644 --- a/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_toc/toc_entry/toc_entry_actions_popover/toc_entry_actions_popover.tsx +++ b/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_toc/toc_entry/toc_entry_actions_popover/toc_entry_actions_popover.tsx @@ -41,11 +41,15 @@ export interface Props { interface State { isPopoverOpen: boolean; supportsFeatureEditing: boolean; - canEditFeatures: boolean; + isFeatureEditingEnabled: boolean; } export class TOCEntryActionsPopover extends Component { - state: State = { isPopoverOpen: false, supportsFeatureEditing: false, canEditFeatures: false }; + state: State = { + isPopoverOpen: false, + supportsFeatureEditing: false, + isFeatureEditingEnabled: false, + }; private _isMounted = false; componentDidMount() { @@ -57,26 +61,26 @@ export class TOCEntryActionsPopover extends Component { } componentDidUpdate() { - this._checkLayerEditable(); + this._loadFeatureEditing(); } - async _checkLayerEditable() { + async _loadFeatureEditing() { if (!(this.props.layer instanceof VectorLayer)) { return; } const supportsFeatureEditing = this.props.layer.supportsFeatureEditing(); - const canEditFeatures = await this._getCanEditFeatures(); + const isFeatureEditingEnabled = await this._getIsFeatureEditingEnabled(); if ( !this._isMounted || (supportsFeatureEditing === this.state.supportsFeatureEditing && - canEditFeatures === this.state.canEditFeatures) + isFeatureEditingEnabled === this.state.isFeatureEditingEnabled) ) { return; } - this.setState({ supportsFeatureEditing, canEditFeatures }); + this.setState({ supportsFeatureEditing, isFeatureEditingEnabled }); } - async _getCanEditFeatures(): Promise { + async _getIsFeatureEditingEnabled(): Promise { const vectorLayer = this.props.layer as VectorLayer; const layerSource = await this.props.layer.getSource(); if (!(layerSource instanceof ESSearchSource)) { @@ -160,13 +164,13 @@ export class TOCEntryActionsPopover extends Component { name: EDIT_FEATURES_LABEL, icon: , 'data-test-subj': 'editLayerButton', - toolTipContent: this.state.canEditFeatures + toolTipContent: this.state.isFeatureEditingEnabled ? null : i18n.translate('xpack.maps.layerTocActions.editLayerTooltip', { defaultMessage: 'Edit features only supported for document layers without clustering, joins, or time filtering', }), - disabled: !this.state.canEditFeatures, + disabled: !this.state.isFeatureEditingEnabled, onClick: async () => { this._closePopover(); const supportedShapeTypes = await (this.props.layer.getSource() as ESSearchSource).getSupportedShapeTypes(); diff --git a/x-pack/plugins/ml/common/types/anomaly_detection_jobs/summary_job.ts b/x-pack/plugins/ml/common/types/anomaly_detection_jobs/summary_job.ts index 624056fdf3b82..e9e89a3c99771 100644 --- a/x-pack/plugins/ml/common/types/anomaly_detection_jobs/summary_job.ts +++ b/x-pack/plugins/ml/common/types/anomaly_detection_jobs/summary_job.ts @@ -36,6 +36,7 @@ export interface MlSummaryJob { earliestStartTimestampMs?: number; awaitingNodeAssignment: boolean; alertingRules?: MlAnomalyDetectionAlertRule[]; + jobTags: Record; } export interface AuditMessage { diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/extract_job_details.js b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/extract_job_details.js index 673484f08e196..dea8fdd30e372 100644 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/extract_job_details.js +++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/extract_job_details.js @@ -26,19 +26,33 @@ export function extractJobDetails(job, basePath, refreshJobList) { items: filterObjects(job, true).map(formatValues), }; + const { job_tags: tags, custom_urls: urls, ...settings } = job.custom_settings ?? {}; const customUrl = { id: 'customUrl', title: i18n.translate('xpack.ml.jobsList.jobDetails.customUrlsTitle', { defaultMessage: 'Custom URLs', }), position: 'right', - items: [], + items: urls ? urls.map((cu) => [cu.url_name, cu.url_value, cu.time_range]) : [], + }; + + const customSettings = { + id: 'analysisConfig', + title: i18n.translate('xpack.ml.jobsList.jobDetails.customSettingsTitle', { + defaultMessage: 'Custom settings', + }), + position: 'right', + items: settings ? filterObjects(settings, true, true) : [], + }; + + const jobTags = { + id: 'analysisConfig', + title: i18n.translate('xpack.ml.jobsList.jobDetails.jobTagsTitle', { + defaultMessage: 'Job tags', + }), + position: 'right', + items: tags ? filterObjects(tags) : [], }; - if (job.custom_settings && job.custom_settings.custom_urls) { - customUrl.items.push( - ...job.custom_settings.custom_urls.map((cu) => [cu.url_name, cu.url_value, cu.time_range]) - ); - } const node = { id: 'node', @@ -213,6 +227,8 @@ export function extractJobDetails(job, basePath, refreshJobList) { analysisConfig, analysisLimits, dataDescription, + customSettings, + jobTags, datafeed, counts, modelSizeStats, diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/job_details.js b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/job_details.js index 812d156421c16..b514c8433daf4 100644 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/job_details.js +++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/job_details.js @@ -41,7 +41,7 @@ export class JobDetailsUI extends Component { } render() { - const { job } = this.state; + const job = this.state.job ?? this.props.job; const { services: { http: { basePath }, @@ -67,6 +67,8 @@ export class JobDetailsUI extends Component { analysisConfig, analysisLimits, dataDescription, + customSettings, + jobTags, datafeed, counts, modelSizeStats, @@ -85,7 +87,7 @@ export class JobDetailsUI extends Component { content: ( ), time: job.open_time, diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/jobs_list_view/jobs_list_view.js b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/jobs_list_view/jobs_list_view.js index 214b7616cf927..bf8db538bc8ae 100644 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/jobs_list_view/jobs_list_view.js +++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/jobs_list_view/jobs_list_view.js @@ -18,7 +18,7 @@ import { EuiSpacer, EuiTitle, } from '@elastic/eui'; -import { isEqual } from 'lodash'; +import { isEqual, debounce } from 'lodash'; import { ml } from '../../../../services/ml_api_service'; import { checkForAutoStartDatafeed, filterJobs, loadFullJob } from '../utils'; @@ -43,6 +43,11 @@ import { JobListMlAnomalyAlertFlyout } from '../../../../../alerting/ml_alerting let deletingJobsRefreshTimeout = null; +const filterJobsDebounce = debounce((jobsSummaryList, filterClauses, callback) => { + const ss = filterJobs(jobsSummaryList, filterClauses); + callback(ss); +}, 500); + // 'isManagementTable' bool prop to determine when to configure table for use in Kibana management page export class JobsListView extends Component { constructor(props) { @@ -221,7 +226,7 @@ export class JobsListView extends Component { refreshSelectedJobs() { const selectedJobsIds = this.state.selectedJobs.map((j) => j.id); - const filteredJobIds = this.state.filteredJobsSummaryList.map((j) => j.id); + const filteredJobIds = (this.state.filteredJobsSummaryList ?? []).map((j) => j.id); // refresh the jobs stored as selected // only select those which are also in the filtered list @@ -232,9 +237,17 @@ export class JobsListView extends Component { this.setState({ selectedJobs }); } - setFilters = (query) => { - const filterClauses = (query && query.ast && query.ast.clauses) || []; - const filteredJobsSummaryList = filterJobs(this.state.jobsSummaryList, filterClauses); + setFilters = async (query) => { + if (query === null) { + this.setState( + { filteredJobsSummaryList: this.state.jobsSummaryList, filterClauses: [] }, + () => { + this.refreshSelectedJobs(); + } + ); + + return; + } this.props.onJobsViewStateUpdate( { @@ -244,11 +257,30 @@ export class JobsListView extends Component { this._isFiltersSet === false ); - this._isFiltersSet = true; + const filterClauses = (query && query.ast && query.ast.clauses) || []; - this.setState({ filteredJobsSummaryList, filterClauses }, () => { - this.refreshSelectedJobs(); - }); + if (filterClauses.length === 0) { + this.setState({ filteredJobsSummaryList: this.state.jobsSummaryList, filterClauses }, () => { + this.refreshSelectedJobs(); + }); + return; + } + + if (this._isFiltersSet === true) { + filterJobsDebounce(this.state.jobsSummaryList, filterClauses, (jobsSummaryList) => { + this.setState({ filteredJobsSummaryList: jobsSummaryList, filterClauses }, () => { + this.refreshSelectedJobs(); + }); + }); + } else { + // first use after page load, do not debounce. + const filteredJobsSummaryList = filterJobs(this.state.jobsSummaryList, filterClauses); + this.setState({ filteredJobsSummaryList, filterClauses }, () => { + this.refreshSelectedJobs(); + }); + } + + this._isFiltersSet = true; }; onRefreshClick = () => { diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/utils.js b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/utils.js index 5b8fa5c672c6e..f004fb6bad49d 100644 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/utils.js +++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/utils.js @@ -347,12 +347,18 @@ export function filterJobs(jobs, clauses) { // if it's an array of job ids if (c.field === 'id') { js = jobs.filter((job) => c.value.indexOf(jobProperty(job, c.field)) >= 0); - } else { + } else if (c.field === 'groups') { // the groups value is an array of group ids js = jobs.filter((job) => jobProperty(job, c.field).some((g) => c.value.indexOf(g) >= 0)); + } else if (c.field === 'job_tags') { + js = jobTagFilter(jobs, c.value); } } else { - js = jobs.filter((job) => jobProperty(job, c.field) === c.value); + if (c.field === 'job_tags') { + js = js = jobTagFilter(jobs, [c.value]); + } else { + js = jobs.filter((job) => jobProperty(job, c.field) === c.value); + } } } @@ -369,6 +375,25 @@ export function filterJobs(jobs, clauses) { return filteredJobs; } +function jobProperty(job, prop) { + const propMap = { + job_state: 'jobState', + datafeed_state: 'datafeedState', + groups: 'groups', + id: 'id', + job_tags: 'jobTags', + }; + return job[propMap[prop]]; +} + +function jobTagFilter(jobs, value) { + return jobs.filter((job) => { + const tags = jobProperty(job, 'job_tags'); + return Object.entries(tags) + .map((t) => t.join(':')) + .find((t) => value.some((t1) => t1 === t)); + }); +} // check to see if a job has been stored in mlJobService.tempJobCloningObjects // if it has, return an object with the minimum properties needed for the // start datafeed modal. @@ -390,13 +415,3 @@ export function checkForAutoStartDatafeed() { }; } } - -function jobProperty(job, prop) { - const propMap = { - job_state: 'jobState', - datafeed_state: 'datafeedState', - groups: 'groups', - id: 'id', - }; - return job[propMap[prop]]; -} diff --git a/x-pack/plugins/ml/public/application/services/job_service.js b/x-pack/plugins/ml/public/application/services/job_service.js index 3c93c8a1ae85a..8560cdd73153b 100644 --- a/x-pack/plugins/ml/public/application/services/job_service.js +++ b/x-pack/plugins/ml/public/application/services/job_service.js @@ -91,26 +91,6 @@ class JobService { }; } - getBlankJob() { - return { - job_id: '', - description: '', - groups: [], - analysis_config: { - bucket_span: '15m', - influencers: [], - detectors: [], - }, - data_description: { - time_field: '', - time_format: '', // 'epoch', - field_delimiter: '', - quote_character: '"', - format: 'delimited', - }, - }; - } - loadJobs() { return new Promise((resolve, reject) => { jobs = []; diff --git a/x-pack/plugins/ml/server/models/job_service/jobs.ts b/x-pack/plugins/ml/server/models/job_service/jobs.ts index b7f8ce569641e..22bac1cb08e19 100644 --- a/x-pack/plugins/ml/server/models/job_service/jobs.ts +++ b/x-pack/plugins/ml/server/models/job_service/jobs.ts @@ -218,6 +218,7 @@ export function jobsProvider( deleting: job.deleting || undefined, awaitingNodeAssignment: isJobAwaitingNodeAssignment(job), alertingRules: job.alerting_rules, + jobTags: job.custom_settings?.job_tags ?? {}, }; if (jobIds.find((j) => j === tempJob.id)) { tempJob.fullJob = job; diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_ccr_read_exceptions.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_ccr_read_exceptions.ts index 213e73a4b9534..a96a7454ea744 100644 --- a/x-pack/plugins/monitoring/server/lib/alerts/fetch_ccr_read_exceptions.ts +++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_ccr_read_exceptions.ts @@ -95,7 +95,7 @@ export async function fetchCCRReadExceptions( const { body: response } = await esClient.search(params); const stats: CCRReadExceptionsStats[] = []; - // @ts-expect-error @elastic/elasticsearch Aggregate does not specify buckets + // @ts-expect-error declare aggegations type explicitly const { buckets: remoteClusterBuckets = [] } = response.aggregations?.remote_clusters; if (!remoteClusterBuckets?.length) { diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_cpu_usage_node_stats.test.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_cpu_usage_node_stats.test.ts index 0fb9dd5298e9e..9cb773c81923b 100644 --- a/x-pack/plugins/monitoring/server/lib/alerts/fetch_cpu_usage_node_stats.test.ts +++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_cpu_usage_node_stats.test.ts @@ -25,7 +25,7 @@ describe('fetchCpuUsageNodeStats', () => { it('fetch normal stats', async () => { esClient.search.mockReturnValue( - // @ts-expect-error @elastic/elasticsearch Aggregate only allows unknown values + // @ts-expect-error not full response interface elasticsearchClientMock.createSuccessTransportRequestPromise({ aggregations: { clusters: { @@ -79,7 +79,7 @@ describe('fetchCpuUsageNodeStats', () => { it('fetch container stats', async () => { esClient.search.mockReturnValue( - // @ts-expect-error @elastic/elasticsearch Aggregate only allows unknown values + // @ts-expect-error not full response interface elasticsearchClientMock.createSuccessTransportRequestPromise({ aggregations: { clusters: { @@ -146,7 +146,7 @@ describe('fetchCpuUsageNodeStats', () => { it('fetch properly return ccs', async () => { esClient.search.mockReturnValue( - // @ts-expect-error @elastic/elasticsearch Aggregate only allows unknown values + // @ts-expect-error not full response interface elasticsearchClientMock.createSuccessTransportRequestPromise({ aggregations: { clusters: { diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_disk_usage_node_stats.test.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_disk_usage_node_stats.test.ts index 8faf79fc4b59c..4766400891af5 100644 --- a/x-pack/plugins/monitoring/server/lib/alerts/fetch_disk_usage_node_stats.test.ts +++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_disk_usage_node_stats.test.ts @@ -25,7 +25,7 @@ describe('fetchDiskUsageNodeStats', () => { it('fetch normal stats', async () => { esClient.search.mockReturnValue( - // @ts-expect-error @elastic/elasticsearch Aggregate only allows unknown values + // @ts-expect-error not full response interface elasticsearchClientMock.createSuccessTransportRequestPromise({ aggregations: { clusters: { diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_disk_usage_node_stats.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_disk_usage_node_stats.ts index a51dccd727966..2e8b5c7478e15 100644 --- a/x-pack/plugins/monitoring/server/lib/alerts/fetch_disk_usage_node_stats.ts +++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_disk_usage_node_stats.ts @@ -101,7 +101,7 @@ export async function fetchDiskUsageNodeStats( const { body: response } = await esClient.search(params); const stats: AlertDiskUsageNodeStats[] = []; - // @ts-expect-error @elastic/elasticsearch Aggregate does not define buckets + // @ts-expect-error declare type for aggregations explicitly const { buckets: clusterBuckets } = response.aggregations?.clusters; if (!clusterBuckets?.length) { diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_index_shard_size.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_index_shard_size.ts index aab3f0101ef83..117894c0d823b 100644 --- a/x-pack/plugins/monitoring/server/lib/alerts/fetch_index_shard_size.ts +++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_index_shard_size.ts @@ -105,7 +105,7 @@ export async function fetchIndexShardSize( }; const { body: response } = await esClient.search(params); - // @ts-expect-error @elastic/elasticsearch Aggregate does not specify buckets + // @ts-expect-error declare aggegations type explicitly const { buckets: clusterBuckets } = response.aggregations?.clusters; const stats: IndexShardSizeStats[] = []; if (!clusterBuckets?.length) { diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_kibana_versions.test.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_kibana_versions.test.ts index 2b966b16f2f5c..f9a03bb73d5fc 100644 --- a/x-pack/plugins/monitoring/server/lib/alerts/fetch_kibana_versions.test.ts +++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_kibana_versions.test.ts @@ -23,7 +23,7 @@ describe('fetchKibanaVersions', () => { it('fetch as expected', async () => { esClient.search.mockReturnValue( - // @ts-expect-error @elastic/elasticsearch Aggregate only allows unknown values + // @ts-expect-error not full response interface elasticsearchClientMock.createSuccessTransportRequestPromise({ aggregations: { index: { diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_logstash_versions.test.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_logstash_versions.test.ts index d7d4e6531f58e..5732fc00f009b 100644 --- a/x-pack/plugins/monitoring/server/lib/alerts/fetch_logstash_versions.test.ts +++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_logstash_versions.test.ts @@ -23,7 +23,7 @@ describe('fetchLogstashVersions', () => { it('fetch as expected', async () => { esClient.search.mockReturnValue( - // @ts-expect-error @elastic/elasticsearch Aggregate only allows unknown values + // @ts-expect-error not full response interface elasticsearchClientMock.createSuccessTransportRequestPromise({ aggregations: { index: { diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_memory_usage_node_stats.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_memory_usage_node_stats.ts index 245838541d435..46bb9c794a6a6 100644 --- a/x-pack/plugins/monitoring/server/lib/alerts/fetch_memory_usage_node_stats.ts +++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_memory_usage_node_stats.ts @@ -94,7 +94,7 @@ export async function fetchMemoryUsageNodeStats( const { body: response } = await esClient.search(params); const stats: AlertMemoryUsageNodeStats[] = []; - // @ts-expect-error @elastic/elasticsearch Aggregate does not define buckets + // @ts-expect-error declare type for aggregations explicitly const { buckets: clusterBuckets } = response.aggregations?.clusters; if (!clusterBuckets?.length) { diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_missing_monitoring_data.test.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_missing_monitoring_data.test.ts index c8d15acf8ff73..980adb009ff8f 100644 --- a/x-pack/plugins/monitoring/server/lib/alerts/fetch_missing_monitoring_data.test.ts +++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_missing_monitoring_data.test.ts @@ -56,7 +56,7 @@ describe('fetchMissingMonitoringData', () => { ]; esClient.search.mockReturnValue( - // @ts-expect-error @elastic/elasticsearch Aggregate only allows unknown values + // @ts-expect-error not full response interface elasticsearchClientMock.createSuccessTransportRequestPromise({ aggregations: { clusters: { @@ -115,7 +115,7 @@ describe('fetchMissingMonitoringData', () => { }, ]; esClient.search.mockReturnValue( - // @ts-expect-error @elastic/elasticsearch Aggregate only allows unknown values + // @ts-expect-error not full response interface elasticsearchClientMock.createSuccessTransportRequestPromise({ aggregations: { clusters: { diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_nodes_from_cluster_stats.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_nodes_from_cluster_stats.ts index d1a343b9b3eef..5f867ca5b6edf 100644 --- a/x-pack/plugins/monitoring/server/lib/alerts/fetch_nodes_from_cluster_stats.ts +++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_nodes_from_cluster_stats.ts @@ -90,7 +90,7 @@ export async function fetchNodesFromClusterStats( const { body: response } = await esClient.search(params); const nodes: AlertClusterStatsNodes[] = []; - // @ts-expect-error @elastic/elasticsearch Aggregate does not define buckets + // @ts-expect-error declare type for aggregations explicitly const clusterBuckets = response.aggregations?.clusters?.buckets; if (!clusterBuckets?.length) { return nodes; diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_thread_pool_rejections_stats.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_thread_pool_rejections_stats.ts index db5943ca67031..954ec3877144f 100644 --- a/x-pack/plugins/monitoring/server/lib/alerts/fetch_thread_pool_rejections_stats.ts +++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_thread_pool_rejections_stats.ts @@ -96,7 +96,7 @@ export async function fetchThreadPoolRejectionStats( const { body: response } = await esClient.search(params); const stats: AlertThreadPoolRejectionsStats[] = []; - // @ts-expect-error @elastic/elasticsearch Aggregate does not specify buckets + // @ts-expect-error declare type for aggregations explicitly const { buckets: clusterBuckets } = response.aggregations?.clusters; if (!clusterBuckets?.length) { diff --git a/x-pack/plugins/observability/common/typings.ts b/x-pack/plugins/observability/common/typings.ts index bd10543ef389b..305a18903fe7e 100644 --- a/x-pack/plugins/observability/common/typings.ts +++ b/x-pack/plugins/observability/common/typings.ts @@ -10,3 +10,16 @@ export type Maybe = T | null | undefined; export const alertStatusRt = t.union([t.literal('all'), t.literal('open'), t.literal('closed')]); export type AlertStatus = t.TypeOf; + +export interface ApmIndicesConfig { + /* eslint-disable @typescript-eslint/naming-convention */ + 'apm_oss.sourcemapIndices': string; + 'apm_oss.errorIndices': string; + 'apm_oss.onboardingIndices': string; + 'apm_oss.spanIndices': string; + 'apm_oss.transactionIndices': string; + 'apm_oss.metricsIndices': string; + /* eslint-enable @typescript-eslint/naming-convention */ + apmAgentConfigurationIndex: string; + apmCustomLinkIndex: string; +} diff --git a/x-pack/plugins/observability/public/components/app/empty_sections/index.tsx b/x-pack/plugins/observability/public/components/app/empty_sections/index.tsx index f7ce8675d8a45..47417a2bbb545 100644 --- a/x-pack/plugins/observability/public/components/app/empty_sections/index.tsx +++ b/x-pack/plugins/observability/public/components/app/empty_sections/index.tsx @@ -13,26 +13,24 @@ import { FETCH_STATUS } from '../../../hooks/use_fetcher'; import { useHasData } from '../../../hooks/use_has_data'; import { usePluginContext } from '../../../hooks/use_plugin_context'; import { getEmptySections } from '../../../pages/overview/empty_section'; -import { UXHasDataResponse } from '../../../typings'; import { EmptySection } from './empty_section'; export function EmptySections() { const { core } = usePluginContext(); const theme = useContext(ThemeContext); - const { hasData } = useHasData(); + const { hasDataMap } = useHasData(); const appEmptySections = getEmptySections({ core }).filter(({ id }) => { if (id === 'alert') { - const { status, hasData: alerts } = hasData.alert || {}; + const { status, hasData: alerts } = hasDataMap.alert || {}; return ( status === FETCH_STATUS.FAILURE || (status === FETCH_STATUS.SUCCESS && (alerts as Alert[]).length === 0) ); } else { - const app = hasData[id]; + const app = hasDataMap[id]; if (app) { - const _hasData = id === 'ux' ? (app.hasData as UXHasDataResponse)?.hasData : app.hasData; - return app.status === FETCH_STATUS.FAILURE || !_hasData; + return app.status === FETCH_STATUS.FAILURE || !app.hasData; } } return false; diff --git a/x-pack/plugins/observability/public/components/app/section/apm/index.test.tsx b/x-pack/plugins/observability/public/components/app/section/apm/index.test.tsx index ad3ecd2740802..16eb8dd24d3c2 100644 --- a/x-pack/plugins/observability/public/components/app/section/apm/index.test.tsx +++ b/x-pack/plugins/observability/public/components/app/section/apm/index.test.tsx @@ -29,7 +29,7 @@ jest.mock('react-router-dom', () => ({ describe('APMSection', () => { beforeAll(() => { jest.spyOn(hasDataHook, 'useHasData').mockReturnValue({ - hasData: { + hasDataMap: { apm: { status: fetcherHook.FETCH_STATUS.SUCCESS, hasData: true, diff --git a/x-pack/plugins/observability/public/components/app/section/apm/index.tsx b/x-pack/plugins/observability/public/components/app/section/apm/index.tsx index e71468d3b028c..7a42e96c3823d 100644 --- a/x-pack/plugins/observability/public/components/app/section/apm/index.tsx +++ b/x-pack/plugins/observability/public/components/app/section/apm/index.tsx @@ -48,7 +48,7 @@ export function APMSection({ bucketSize }: Props) { const theme = useContext(ThemeContext); const chartTheme = useChartTheme(); const history = useHistory(); - const { forceUpdate, hasData } = useHasData(); + const { forceUpdate, hasDataMap } = useHasData(); const { relativeStart, relativeEnd, absoluteStart, absoluteEnd } = useTimeRange(); const { data, status } = useFetcher( @@ -66,7 +66,7 @@ export function APMSection({ bucketSize }: Props) { [bucketSize, relativeStart, relativeEnd, forceUpdate] ); - if (!hasData.apm?.hasData) { + if (!hasDataMap.apm?.hasData) { return null; } diff --git a/x-pack/plugins/observability/public/components/app/section/logs/index.tsx b/x-pack/plugins/observability/public/components/app/section/logs/index.tsx index cb4c831d25022..da5a8f25045a5 100644 --- a/x-pack/plugins/observability/public/components/app/section/logs/index.tsx +++ b/x-pack/plugins/observability/public/components/app/section/logs/index.tsx @@ -47,7 +47,7 @@ function getColorPerItem(series?: LogsFetchDataResponse['series']) { export function LogsSection({ bucketSize }: Props) { const history = useHistory(); const chartTheme = useChartTheme(); - const { forceUpdate, hasData } = useHasData(); + const { forceUpdate, hasDataMap } = useHasData(); const { relativeStart, relativeEnd, absoluteStart, absoluteEnd } = useTimeRange(); const { data, status } = useFetcher( @@ -65,7 +65,7 @@ export function LogsSection({ bucketSize }: Props) { [bucketSize, relativeStart, relativeEnd, forceUpdate] ); - if (!hasData.infra_logs?.hasData) { + if (!hasDataMap.infra_logs?.hasData) { return null; } diff --git a/x-pack/plugins/observability/public/components/app/section/metrics/index.tsx b/x-pack/plugins/observability/public/components/app/section/metrics/index.tsx index 5a642084733c7..2f5bb9bac9348 100644 --- a/x-pack/plugins/observability/public/components/app/section/metrics/index.tsx +++ b/x-pack/plugins/observability/public/components/app/section/metrics/index.tsx @@ -50,7 +50,7 @@ const bytesPerSecondFormatter = (value: NumberOrNull) => value === null ? '' : numeral(value).format('0b') + '/s'; export function MetricsSection({ bucketSize }: Props) { - const { forceUpdate, hasData } = useHasData(); + const { forceUpdate, hasDataMap } = useHasData(); const { relativeStart, relativeEnd, absoluteStart, absoluteEnd } = useTimeRange(); const [sortDirection, setSortDirection] = useState('asc'); const [sortField, setSortField] = useState('uptime'); @@ -88,7 +88,7 @@ export function MetricsSection({ bucketSize }: Props) { [data, setSortField, setSortDirection] ); - if (!hasData.infra_metrics?.hasData) { + if (!hasDataMap.infra_metrics?.hasData) { return null; } diff --git a/x-pack/plugins/observability/public/components/app/section/uptime/index.tsx b/x-pack/plugins/observability/public/components/app/section/uptime/index.tsx index 1dbcdeaee800a..28cbd12663c1b 100644 --- a/x-pack/plugins/observability/public/components/app/section/uptime/index.tsx +++ b/x-pack/plugins/observability/public/components/app/section/uptime/index.tsx @@ -40,7 +40,7 @@ export function UptimeSection({ bucketSize }: Props) { const theme = useContext(ThemeContext); const chartTheme = useChartTheme(); const history = useHistory(); - const { forceUpdate, hasData } = useHasData(); + const { forceUpdate, hasDataMap } = useHasData(); const { relativeStart, relativeEnd, absoluteStart, absoluteEnd } = useTimeRange(); const { data, status } = useFetcher( @@ -58,7 +58,7 @@ export function UptimeSection({ bucketSize }: Props) { [bucketSize, relativeStart, relativeEnd, forceUpdate] ); - if (!hasData.synthetics?.hasData) { + if (!hasDataMap.synthetics?.hasData) { return null; } diff --git a/x-pack/plugins/observability/public/components/app/section/ux/index.test.tsx b/x-pack/plugins/observability/public/components/app/section/ux/index.test.tsx index fab461476e713..61bce8aaf845d 100644 --- a/x-pack/plugins/observability/public/components/app/section/ux/index.test.tsx +++ b/x-pack/plugins/observability/public/components/app/section/ux/index.test.tsx @@ -28,10 +28,11 @@ jest.mock('react-router-dom', () => ({ describe('UXSection', () => { beforeAll(() => { jest.spyOn(hasDataHook, 'useHasData').mockReturnValue({ - hasData: { + hasDataMap: { ux: { status: fetcherHook.FETCH_STATUS.SUCCESS, - hasData: { hasData: true, serviceName: 'elastic-co-frontend' }, + hasData: true, + serviceName: 'elastic-co-frontend', }, }, } as HasDataContextValue); diff --git a/x-pack/plugins/observability/public/components/app/section/ux/index.tsx b/x-pack/plugins/observability/public/components/app/section/ux/index.tsx index 0ac337e5ba0b1..5aa89eb2d3074 100644 --- a/x-pack/plugins/observability/public/components/app/section/ux/index.tsx +++ b/x-pack/plugins/observability/public/components/app/section/ux/index.tsx @@ -12,7 +12,6 @@ import { getDataHandler } from '../../../../data_handler'; import { FETCH_STATUS, useFetcher } from '../../../../hooks/use_fetcher'; import { useHasData } from '../../../../hooks/use_has_data'; import { useTimeRange } from '../../../../hooks/use_time_range'; -import { UXHasDataResponse } from '../../../../typings'; import CoreVitals from '../../../shared/core_web_vitals'; interface Props { @@ -20,10 +19,10 @@ interface Props { } export function UXSection({ bucketSize }: Props) { - const { forceUpdate, hasData } = useHasData(); + const { forceUpdate, hasDataMap } = useHasData(); const { relativeStart, relativeEnd, absoluteStart, absoluteEnd } = useTimeRange(); - const uxHasDataResponse = (hasData.ux?.hasData as UXHasDataResponse) || {}; - const serviceName = uxHasDataResponse.serviceName as string; + const uxHasDataResponse = hasDataMap.ux; + const serviceName = uxHasDataResponse?.serviceName as string; const { data, status } = useFetcher( () => { diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/apm/field_formats.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/apm/field_formats.ts index 8d33dfbab2c62..5c1afbca2a776 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/apm/field_formats.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/apm/field_formats.ts @@ -6,7 +6,11 @@ */ import { FieldFormat } from '../../types'; -import { TRANSACTION_DURATION } from '../constants/elasticsearch_fieldnames'; +import { + METRIC_SYSTEM_CPU_USAGE, + METRIC_SYSTEM_MEMORY_USAGE, + TRANSACTION_DURATION, +} from '../constants/elasticsearch_fieldnames'; export const apmFieldFormats: FieldFormat[] = [ { @@ -18,7 +22,16 @@ export const apmFieldFormats: FieldFormat[] = [ outputFormat: 'asMilliseconds', outputPrecision: 0, showSuffix: true, + useShortSuffix: true, }, }, }, + { + field: METRIC_SYSTEM_MEMORY_USAGE, + format: { id: 'bytes', params: {} }, + }, + { + field: METRIC_SYSTEM_CPU_USAGE, + format: { id: 'percent', params: {} }, + }, ]; diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/constants/constants.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/constants/constants.ts index 26459e676de08..e119507860c5c 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/constants/constants.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/constants/constants.ts @@ -13,12 +13,13 @@ import { BROWSER_VERSION_LABEL, CLS_LABEL, CORE_WEB_VITALS_LABEL, + DEVICE_DISTRIBUTION_LABEL, DEVICE_LABEL, ENVIRONMENT_LABEL, FCP_LABEL, FID_LABEL, HOST_NAME_LABEL, - KIP_OVER_TIME_LABEL, + KPI_OVER_TIME_LABEL, KPI_LABEL, LCP_LABEL, LOCATION_LABEL, @@ -31,6 +32,7 @@ import { OS_LABEL, PERF_DIST_LABEL, PORT_LABEL, + REQUEST_METHOD, SERVICE_NAME_LABEL, TAGS_LABEL, TBT_LABEL, @@ -72,14 +74,17 @@ export const FieldLabels: Record = { 'performance.metric': METRIC_LABEL, 'Business.KPI': KPI_LABEL, + 'http.request.method': REQUEST_METHOD, }; export const DataViewLabels: Record = { dist: PERF_DIST_LABEL, - kpi: KIP_OVER_TIME_LABEL, + kpi: KPI_OVER_TIME_LABEL, cwv: CORE_WEB_VITALS_LABEL, + mdd: DEVICE_DISTRIBUTION_LABEL, }; export const USE_BREAK_DOWN_COLUMN = 'USE_BREAK_DOWN_COLUMN'; export const FILTER_RECORDS = 'FILTER_RECORDS'; +export const TERMS_COLUMN = 'TERMS_COLUMN'; export const OPERATION_COLUMN = 'operation'; diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/constants/elasticsearch_fieldnames.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/constants/elasticsearch_fieldnames.ts index 5ecc5b758de84..01dd2a49b9be0 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/constants/elasticsearch_fieldnames.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/constants/elasticsearch_fieldnames.ts @@ -86,6 +86,8 @@ export const ERROR_PAGE_URL = 'error.page.url'; // METRICS export const METRIC_SYSTEM_FREE_MEMORY = 'system.memory.actual.free'; +export const METRIC_SYSTEM_MEMORY_USAGE = 'system.memory.usage'; +export const METRIC_SYSTEM_CPU_USAGE = 'system.cpu.usage'; export const METRIC_SYSTEM_TOTAL_MEMORY = 'system.memory.total'; export const METRIC_SYSTEM_CPU_PERCENT = 'system.cpu.total.norm.pct'; export const METRIC_PROCESS_CPU_PERCENT = 'system.process.cpu.total.norm.pct'; diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/constants/labels.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/constants/labels.ts index b5816daa419df..73739b7db12ef 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/constants/labels.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/constants/labels.ts @@ -165,7 +165,7 @@ export const KPI_LABEL = i18n.translate('xpack.observability.expView.fieldLabels export const PERF_DIST_LABEL = i18n.translate( 'xpack.observability.expView.fieldLabels.performanceDistribution', { - defaultMessage: 'Performance Distribution', + defaultMessage: 'Performance distribution', } ); @@ -176,6 +176,20 @@ export const CORE_WEB_VITALS_LABEL = i18n.translate( } ); +export const DEVICE_DISTRIBUTION_LABEL = i18n.translate( + 'xpack.observability.expView.fieldLabels.deviceDistribution', + { + defaultMessage: 'Device distribution', + } +); + +export const MOBILE_RESPONSE_LABEL = i18n.translate( + 'xpack.observability.expView.fieldLabels.mobileResponse', + { + defaultMessage: 'Mobile response', + } +); + export const MEMORY_USAGE_LABEL = i18n.translate( 'xpack.observability.expView.fieldLabels.memoryUsage', { @@ -183,7 +197,7 @@ export const MEMORY_USAGE_LABEL = i18n.translate( } ); -export const KIP_OVER_TIME_LABEL = i18n.translate( +export const KPI_OVER_TIME_LABEL = i18n.translate( 'xpack.observability.expView.fieldLabels.kpiOverTime', { defaultMessage: 'KPI over time', @@ -211,3 +225,82 @@ export const UP_LABEL = i18n.translate('xpack.observability.expView.fieldLabels. export const DOWN_LABEL = i18n.translate('xpack.observability.expView.fieldLabels.downPings', { defaultMessage: 'Down Pings', }); + +export const CARRIER_NAME = i18n.translate('xpack.observability.expView.fieldLabels.carrierName', { + defaultMessage: 'Carrier Name', +}); + +export const REQUEST_METHOD = i18n.translate( + 'xpack.observability.expView.fieldLabels.requestMethod', + { + defaultMessage: 'Request Method', + } +); + +export const CONNECTION_TYPE = i18n.translate( + 'xpack.observability.expView.fieldLabels.connectionType', + { + defaultMessage: 'Connection Type', + } +); +export const HOST_OS = i18n.translate('xpack.observability.expView.fieldLabels.hostOS', { + defaultMessage: 'Host OS', +}); + +export const SERVICE_VERSION = i18n.translate( + 'xpack.observability.expView.fieldLabels.serviceVersion', + { + defaultMessage: 'Service Version', + } +); + +export const OS_PLATFORM = i18n.translate('xpack.observability.expView.fieldLabels.osPlatform', { + defaultMessage: 'OS Platform', +}); + +export const DEVICE_MODEL = i18n.translate('xpack.observability.expView.fieldLabels.deviceModel', { + defaultMessage: 'Device Model', +}); + +export const CARRIER_LOCATION = i18n.translate( + 'xpack.observability.expView.fieldLabels.carrierLocation', + { + defaultMessage: 'Carrier Location', + } +); + +export const RESPONSE_LATENCY = i18n.translate( + 'xpack.observability.expView.fieldLabels.responseLatency', + { + defaultMessage: 'Response latency', + } +); + +export const MOBILE_APP = i18n.translate('xpack.observability.expView.fieldLabels.mobileApp', { + defaultMessage: 'Mobile App', +}); + +export const MEMORY_USAGE = i18n.translate( + 'xpack.observability.expView.fieldLabels.mobile.memoryUsage', + { + defaultMessage: 'Memory Usage', + } +); + +export const CPU_USAGE = i18n.translate('xpack.observability.expView.fieldLabels.cpuUsage', { + defaultMessage: 'CPU Usage', +}); + +export const TRANSACTIONS_PER_MINUTE = i18n.translate( + 'xpack.observability.expView.fieldLabels.transactionPerMinute', + { + defaultMessage: 'Transactions per minute', + } +); + +export const NUMBER_OF_DEVICES = i18n.translate( + 'xpack.observability.expView.fieldLabels.numberOfDevices', + { + defaultMessage: 'Number of Devices', + } +); diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/default_configs.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/default_configs.ts index 13a7900ef5764..07342d976cbea 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/default_configs.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/default_configs.ts @@ -12,6 +12,9 @@ import { getSyntheticsKPIConfig } from './synthetics/kpi_over_time_config'; import { getKPITrendsLensConfig } from './rum/kpi_over_time_config'; import { IndexPattern } from '../../../../../../../../src/plugins/data/common'; import { getCoreWebVitalsConfig } from './rum/core_web_vitals_config'; +import { getMobileKPIConfig } from './mobile/kpi_over_time_config'; +import { getMobileKPIDistributionConfig } from './mobile/distribution_config'; +import { getMobileDeviceDistributionConfig } from './mobile/device_distribution_config'; interface Props { reportType: keyof typeof ReportViewTypes; @@ -34,7 +37,14 @@ export const getDefaultConfigs = ({ reportType, dataType, indexPattern }: Props) return getSyntheticsDistributionConfig({ indexPattern }); } return getSyntheticsKPIConfig({ indexPattern }); - + case 'mobile': + if (reportType === 'dist') { + return getMobileKPIDistributionConfig({ indexPattern }); + } + if (reportType === 'mdd') { + return getMobileDeviceDistributionConfig({ indexPattern }); + } + return getMobileKPIConfig({ indexPattern }); default: return getKPITrendsLensConfig({ indexPattern }); } diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes.ts index bc535e29ab435..22ad18c663b32 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes.ts @@ -25,13 +25,14 @@ import { FieldBasedIndexPatternColumn, SumIndexPatternColumn, TermsIndexPatternColumn, + CardinalityIndexPatternColumn, } from '../../../../../../lens/public'; import { buildPhraseFilter, buildPhrasesFilter, IndexPattern, } from '../../../../../../../../src/plugins/data/common'; -import { FieldLabels, FILTER_RECORDS, USE_BREAK_DOWN_COLUMN } from './constants'; +import { FieldLabels, FILTER_RECORDS, USE_BREAK_DOWN_COLUMN, TERMS_COLUMN } from './constants'; import { ColumnFilter, DataSeries, UrlFilter, URLReportDefinition } from '../types'; function getLayerReferenceName(layerId: string) { @@ -55,6 +56,7 @@ export const parseCustomFieldName = ( let fieldName = sourceField; let columnType; let columnFilters; + let timeScale; let columnLabel; const rdf = reportViewConfig.reportDefinitions ?? []; @@ -70,17 +72,19 @@ export const parseCustomFieldName = ( ); columnType = currField?.columnType; columnFilters = currField?.columnFilters; + timeScale = currField?.timeScale; columnLabel = currField?.label; } } else if (customField.options?.[0].field || customField.options?.[0].id) { fieldName = customField.options?.[0].field || customField.options?.[0].id; columnType = customField.options?.[0].columnType; columnFilters = customField.options?.[0].columnFilters; + timeScale = customField.options?.[0].timeScale; columnLabel = customField.options?.[0].label; } } - return { fieldName, columnType, columnFilters, columnLabel }; + return { fieldName, columnType, columnFilters, timeScale, columnLabel }; }; export class LensAttributes { @@ -167,10 +171,10 @@ export class LensAttributes { this.visualization.layers[0].splitAccessor = undefined; } - getNumberRangeColumn(sourceField: string): RangeIndexPatternColumn { + getNumberRangeColumn(sourceField: string, label?: string): RangeIndexPatternColumn { return { sourceField, - label: this.reportViewConfig.labels[sourceField], + label: this.reportViewConfig.labels[sourceField] ?? label, dataType: 'number', operationType: 'range', isBucketed: true, @@ -183,6 +187,10 @@ export class LensAttributes { }; } + getCardinalityColumn(sourceField: string, label?: string) { + return this.getNumberOperationColumn(sourceField, 'unique_count', label); + } + getNumberColumn( sourceField: string, columnType?: string, @@ -190,21 +198,30 @@ export class LensAttributes { label?: string ) { if (columnType === 'operation' || operationType) { - if (operationType === 'median' || operationType === 'average' || operationType === 'sum') { + if ( + operationType === 'median' || + operationType === 'average' || + operationType === 'sum' || + operationType === 'unique_count' + ) { return this.getNumberOperationColumn(sourceField, operationType, label); } if (operationType?.includes('th')) { return this.getPercentileNumberColumn(sourceField, operationType); } } - return this.getNumberRangeColumn(sourceField); + return this.getNumberRangeColumn(sourceField, label); } getNumberOperationColumn( sourceField: string, - operationType: 'average' | 'median' | 'sum', + operationType: 'average' | 'median' | 'sum' | 'unique_count', label?: string - ): AvgIndexPatternColumn | MedianIndexPatternColumn | SumIndexPatternColumn { + ): + | AvgIndexPatternColumn + | MedianIndexPatternColumn + | SumIndexPatternColumn + | CardinalityIndexPatternColumn { return { ...buildNumberColumn(sourceField), label: @@ -247,6 +264,25 @@ export class LensAttributes { }; } + getTermsColumn(sourceField: string, label?: string): TermsIndexPatternColumn { + return { + operationType: 'terms', + sourceField, + label: label || 'Top values of ' + sourceField, + dataType: 'string', + isBucketed: true, + scale: 'ordinal', + params: { + size: 10, + orderBy: { + type: 'alphabetical', + fallback: false, + }, + orderDirection: 'desc', + }, + }; + } + getXAxis() { const { xAxisColumn } = this.reportViewConfig; @@ -263,15 +299,25 @@ export class LensAttributes { label?: string, colIndex?: number ) { - const { fieldMeta, columnType, fieldName, columnFilters, columnLabel } = this.getFieldMeta( - sourceField - ); + const { + fieldMeta, + columnType, + fieldName, + columnFilters, + timeScale, + columnLabel, + } = this.getFieldMeta(sourceField); const { type: fieldType } = fieldMeta ?? {}; + if (columnType === TERMS_COLUMN) { + return this.getTermsColumn(fieldName, columnLabel || label); + } + if (fieldName === 'Records' || columnType === FILTER_RECORDS) { return this.getRecordsColumn( columnLabel || label, - colIndex !== undefined ? columnFilters?.[colIndex] : undefined + colIndex !== undefined ? columnFilters?.[colIndex] : undefined, + timeScale ); } @@ -281,6 +327,9 @@ export class LensAttributes { if (fieldType === 'number') { return this.getNumberColumn(fieldName, columnType, operationType, columnLabel || label); } + if (operationType === 'unique_count') { + return this.getCardinalityColumn(fieldName, columnLabel || label); + } // FIXME review my approach again return this.getDateHistogramColumn(fieldName); @@ -291,13 +340,17 @@ export class LensAttributes { } getFieldMeta(sourceField: string) { - const { fieldName, columnType, columnFilters, columnLabel } = this.getCustomFieldName( - sourceField - ); + const { + fieldName, + columnType, + columnFilters, + timeScale, + columnLabel, + } = this.getCustomFieldName(sourceField); const fieldMeta = this.indexPattern.getFieldByName(fieldName); - return { fieldMeta, fieldName, columnType, columnFilters, columnLabel }; + return { fieldMeta, fieldName, columnType, columnFilters, timeScale, columnLabel }; } getMainYAxis() { @@ -330,7 +383,11 @@ export class LensAttributes { return lensColumns; } - getRecordsColumn(label?: string, columnFilter?: ColumnFilter): CountIndexPatternColumn { + getRecordsColumn( + label?: string, + columnFilter?: ColumnFilter, + timeScale?: string + ): CountIndexPatternColumn { return { dataType: 'number', isBucketed: false, @@ -339,6 +396,7 @@ export class LensAttributes { scale: 'ratio', sourceField: 'Records', filter: columnFilter, + timeScale, } as CountIndexPatternColumn; } diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/mobile/device_distribution_config.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/mobile/device_distribution_config.ts new file mode 100644 index 0000000000000..6f9806660e489 --- /dev/null +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/mobile/device_distribution_config.ts @@ -0,0 +1,49 @@ +/* + * Copyright 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 { ConfigProps, DataSeries } from '../../types'; +import { FieldLabels, USE_BREAK_DOWN_COLUMN } from '../constants'; +import { buildPhraseFilter } from '../utils'; +import { SERVICE_NAME } from '../constants/elasticsearch_fieldnames'; +import { MOBILE_APP, NUMBER_OF_DEVICES } from '../constants/labels'; +import { MobileFields } from './mobile_fields'; + +export function getMobileDeviceDistributionConfig({ indexPattern }: ConfigProps): DataSeries { + return { + reportType: 'mobile-device-distribution', + defaultSeriesType: 'bar', + seriesTypes: ['bar', 'bar_horizontal'], + xAxisColumn: { + sourceField: USE_BREAK_DOWN_COLUMN, + }, + yAxisColumns: [ + { + sourceField: 'labels.device_id', + operationType: 'unique_count', + label: NUMBER_OF_DEVICES, + }, + ], + hasOperationType: false, + defaultFilters: Object.keys(MobileFields), + breakdowns: Object.keys(MobileFields), + filters: [ + ...buildPhraseFilter('agent.name', 'iOS/swift', indexPattern), + ...buildPhraseFilter('processor.event', 'transaction', indexPattern), + ], + labels: { + ...FieldLabels, + ...MobileFields, + [SERVICE_NAME]: MOBILE_APP, + }, + reportDefinitions: [ + { + field: SERVICE_NAME, + required: true, + }, + ], + }; +} diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/mobile/distribution_config.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/mobile/distribution_config.ts new file mode 100644 index 0000000000000..62dd38e55a32a --- /dev/null +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/mobile/distribution_config.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 { ConfigProps, DataSeries } from '../../types'; +import { FieldLabels, OPERATION_COLUMN, RECORDS_FIELD } from '../constants'; +import { buildPhrasesFilter } from '../utils'; +import { + METRIC_SYSTEM_CPU_USAGE, + METRIC_SYSTEM_MEMORY_USAGE, + SERVICE_ENVIRONMENT, + SERVICE_NAME, + TRANSACTION_DURATION, +} from '../constants/elasticsearch_fieldnames'; + +import { CPU_USAGE, MEMORY_USAGE, MOBILE_APP, RESPONSE_LATENCY } from '../constants/labels'; +import { MobileFields } from './mobile_fields'; + +export function getMobileKPIDistributionConfig({ indexPattern }: ConfigProps): DataSeries { + return { + reportType: 'data-distribution', + defaultSeriesType: 'bar', + seriesTypes: ['line', 'bar'], + xAxisColumn: { + sourceField: 'performance.metric', + }, + yAxisColumns: [ + { + sourceField: RECORDS_FIELD, + }, + ], + hasOperationType: false, + defaultFilters: Object.keys(MobileFields), + breakdowns: Object.keys(MobileFields), + filters: [ + ...buildPhrasesFilter('agent.name', ['iOS/swift', 'open-telemetry/swift'], indexPattern), + ], + labels: { + ...FieldLabels, + ...MobileFields, + [SERVICE_NAME]: MOBILE_APP, + }, + reportDefinitions: [ + { + field: SERVICE_NAME, + required: true, + }, + { + field: SERVICE_ENVIRONMENT, + required: true, + }, + { + field: 'performance.metric', + custom: true, + options: [ + { + label: RESPONSE_LATENCY, + field: TRANSACTION_DURATION, + id: TRANSACTION_DURATION, + columnType: OPERATION_COLUMN, + }, + { + label: MEMORY_USAGE, + field: METRIC_SYSTEM_MEMORY_USAGE, + id: METRIC_SYSTEM_MEMORY_USAGE, + columnType: OPERATION_COLUMN, + }, + { + label: CPU_USAGE, + field: METRIC_SYSTEM_CPU_USAGE, + id: METRIC_SYSTEM_CPU_USAGE, + columnType: OPERATION_COLUMN, + }, + ], + }, + ], + }; +} diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/mobile/kpi_over_time_config.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/mobile/kpi_over_time_config.ts new file mode 100644 index 0000000000000..2ed4d95760db7 --- /dev/null +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/mobile/kpi_over_time_config.ts @@ -0,0 +1,102 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { ConfigProps, DataSeries } from '../../types'; +import { FieldLabels, OPERATION_COLUMN, RECORDS_FIELD } from '../constants'; +import { buildPhrasesFilter } from '../utils'; +import { + METRIC_SYSTEM_CPU_USAGE, + METRIC_SYSTEM_MEMORY_USAGE, + SERVICE_ENVIRONMENT, + SERVICE_NAME, + TRANSACTION_DURATION, +} from '../constants/elasticsearch_fieldnames'; +import { + CPU_USAGE, + MEMORY_USAGE, + MOBILE_APP, + RESPONSE_LATENCY, + TRANSACTIONS_PER_MINUTE, +} from '../constants/labels'; +import { MobileFields } from './mobile_fields'; + +export function getMobileKPIConfig({ indexPattern }: ConfigProps): DataSeries { + return { + reportType: 'kpi-over-time', + defaultSeriesType: 'line', + seriesTypes: ['line', 'bar', 'area'], + xAxisColumn: { + sourceField: '@timestamp', + }, + yAxisColumns: [ + { + sourceField: 'business.kpi', + operationType: 'median', + }, + ], + hasOperationType: true, + defaultFilters: Object.keys(MobileFields), + breakdowns: Object.keys(MobileFields), + filters: [ + ...buildPhrasesFilter('agent.name', ['iOS/swift', 'open-telemetry/swift'], indexPattern), + ], + labels: { + ...FieldLabels, + ...MobileFields, + [TRANSACTION_DURATION]: RESPONSE_LATENCY, + [SERVICE_NAME]: MOBILE_APP, + [METRIC_SYSTEM_MEMORY_USAGE]: MEMORY_USAGE, + [METRIC_SYSTEM_CPU_USAGE]: CPU_USAGE, + }, + reportDefinitions: [ + { + field: SERVICE_NAME, + required: true, + }, + { + field: SERVICE_ENVIRONMENT, + required: true, + }, + { + field: 'business.kpi', + custom: true, + options: [ + { + label: RESPONSE_LATENCY, + field: TRANSACTION_DURATION, + id: TRANSACTION_DURATION, + columnType: OPERATION_COLUMN, + }, + { + label: MEMORY_USAGE, + field: METRIC_SYSTEM_MEMORY_USAGE, + id: METRIC_SYSTEM_MEMORY_USAGE, + columnType: OPERATION_COLUMN, + }, + { + label: CPU_USAGE, + field: METRIC_SYSTEM_CPU_USAGE, + id: METRIC_SYSTEM_CPU_USAGE, + columnType: OPERATION_COLUMN, + }, + { + field: RECORDS_FIELD, + id: RECORDS_FIELD, + label: TRANSACTIONS_PER_MINUTE, + columnFilters: [ + { + language: 'kuery', + query: `processor.event: transaction`, + }, + ], + timeScale: 'm', + }, + ], + }, + ], + }; +} diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/mobile/mobile_fields.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/mobile/mobile_fields.ts new file mode 100644 index 0000000000000..4ece4ff056a59 --- /dev/null +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/mobile/mobile_fields.ts @@ -0,0 +1,26 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { + CARRIER_LOCATION, + CARRIER_NAME, + CONNECTION_TYPE, + DEVICE_MODEL, + HOST_OS, + OS_PLATFORM, + SERVICE_VERSION, +} from '../constants/labels'; + +export const MobileFields: Record = { + 'host.os.platform': OS_PLATFORM, + 'host.os.full': HOST_OS, + 'service.version': SERVICE_VERSION, + 'network.carrier.icc': CARRIER_LOCATION, + 'network.carrier.name': CARRIER_NAME, + 'network.connection_type': CONNECTION_TYPE, + 'labels.device_model': DEVICE_MODEL, +}; diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/exploratory_view.test.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/exploratory_view.test.tsx index 487ecdb2bafcc..779049601bd6d 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/exploratory_view.test.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/exploratory_view.test.tsx @@ -63,7 +63,7 @@ describe('ExploratoryView', () => { render(, { initSeries }); expect(await screen.findByText(/open in lens/i)).toBeInTheDocument(); - expect(await screen.findByText('Performance Distribution')).toBeInTheDocument(); + expect((await screen.findAllByText('Performance distribution'))[0]).toBeInTheDocument(); expect(await screen.findByText(/Lens Embeddable Component/i)).toBeInTheDocument(); await waitFor(() => { diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_app_index_pattern.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_app_index_pattern.tsx index 4f13cf6a1f9ca..4259bb778e511 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_app_index_pattern.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_app_index_pattern.tsx @@ -12,7 +12,6 @@ import { useKibana } from '../../../../../../../../src/plugins/kibana_react/publ import { ObservabilityPublicPluginsStart } from '../../../../plugin'; import { ObservabilityIndexPatterns } from '../utils/observability_index_patterns'; import { getDataHandler } from '../../../../data_handler'; -import { HasDataResponse } from '../../../../typings/fetch_overview_data'; export interface IIndexPatternContext { loading: boolean; @@ -41,17 +40,13 @@ export function IndexPatternContextProvider({ children }: ProviderProps) { synthetics: null, ux: null, apm: null, + mobile: null, } as HasAppDataState); const { services: { data }, } = useKibana(); - const checkIfAppHasData = async (dataType: AppDataType) => { - const handler = getDataHandler(dataType); - return handler?.hasData(); - }; - const loadIndexPattern: IIndexPatternContext['loadIndexPattern'] = useCallback( async ({ dataType }) => { setSelectedApp(dataType); @@ -59,15 +54,27 @@ export function IndexPatternContextProvider({ children }: ProviderProps) { if (hasAppData[dataType] === null) { setLoading(true); try { - const hasDataResponse = (await checkIfAppHasData(dataType)) as HasDataResponse; - - const hasDataT = hasDataResponse.hasData; - + let hasDataT = false; + let indices: string | undefined = ''; + switch (dataType) { + case 'ux': + case 'synthetics': + const resultUx = await getDataHandler(dataType)?.hasData(); + hasDataT = Boolean(resultUx?.hasData); + indices = resultUx?.indices; + break; + case 'apm': + case 'mobile': + const resultApm = await getDataHandler('apm')?.hasData(); + hasDataT = Boolean(resultApm?.hasData); + indices = resultApm?.indices['apm_oss.transactionIndices']; + break; + } setHasAppData((prevState) => ({ ...prevState, [dataType]: hasDataT })); - if (hasDataT || hasAppData?.[dataType]) { + if (hasDataT && indices) { const obsvIndexP = new ObservabilityIndexPatterns(data); - const indPattern = await obsvIndexP.getIndexPattern(dataType, hasDataResponse.indices); + const indPattern = await obsvIndexP.getIndexPattern(dataType, indices); setIndexPatterns((prevState) => ({ ...prevState, [dataType]: indPattern })); } diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/data_types_col.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/data_types_col.tsx index 3fe88de518f75..985afdf888868 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/data_types_col.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/data_types_col.tsx @@ -15,6 +15,7 @@ import { useSeriesStorage } from '../../hooks/use_series_storage'; export const dataTypes: Array<{ id: AppDataType; label: string }> = [ { id: 'synthetics', label: 'Synthetic Monitoring' }, { id: 'ux', label: 'User Experience (RUM)' }, + { id: 'mobile', label: 'Mobile Experience' }, // { id: 'infra_logs', label: 'Logs' }, // { id: 'infra_metrics', label: 'Metrics' }, // { id: 'apm', label: 'APM' }, diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_definition_field.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_definition_field.tsx index a4c4b4d1c78c0..d36e33f16424c 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_definition_field.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_definition_field.tsx @@ -11,7 +11,7 @@ import { isEmpty } from 'lodash'; import FieldValueSuggestions from '../../../field_value_suggestions'; import { useSeriesStorage } from '../../hooks/use_series_storage'; import { useAppIndexPatternContext } from '../../hooks/use_app_index_pattern'; -import { ESFilter } from '../../../../../../../../../typings/elasticsearch'; +import { ESFilter } from '../../../../../../../../../src/core/types/elasticsearch'; import { PersistableFilter } from '../../../../../../../lens/common'; import { ExistsFilter } from '../../../../../../../../../src/plugins/data/common/es_query/filters'; import { buildPhrasesFilter } from '../../configurations/utils'; diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_filters.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_filters.tsx index 9687f1bea4ec9..4571ecfe252e9 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_filters.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_filters.tsx @@ -20,8 +20,10 @@ export function ReportFilters({ ); } diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/series_builder.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/series_builder.tsx index e24d246d60e58..9aef16931d7ec 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/series_builder.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/series_builder.tsx @@ -18,16 +18,27 @@ import { ReportBreakdowns } from './columns/report_breakdowns'; import { NEW_SERIES_KEY, useSeriesStorage } from '../hooks/use_series_storage'; import { useAppIndexPatternContext } from '../hooks/use_app_index_pattern'; import { getDefaultConfigs } from '../configurations/default_configs'; +import { + CORE_WEB_VITALS_LABEL, + DEVICE_DISTRIBUTION_LABEL, + KPI_OVER_TIME_LABEL, + PERF_DIST_LABEL, +} from '../configurations/constants/labels'; export const ReportTypes: Record> = { synthetics: [ - { id: 'kpi', label: 'KPI over time' }, - { id: 'dist', label: 'Performance distribution' }, + { id: 'kpi', label: KPI_OVER_TIME_LABEL }, + { id: 'dist', label: PERF_DIST_LABEL }, ], ux: [ - { id: 'kpi', label: 'KPI over time' }, - { id: 'dist', label: 'Performance distribution' }, - { id: 'cwv', label: 'Core Web Vitals' }, + { id: 'kpi', label: KPI_OVER_TIME_LABEL }, + { id: 'dist', label: PERF_DIST_LABEL }, + { id: 'cwv', label: CORE_WEB_VITALS_LABEL }, + ], + mobile: [ + { id: 'kpi', label: KPI_OVER_TIME_LABEL }, + { id: 'dist', label: PERF_DIST_LABEL }, + { id: 'mdd', label: DEVICE_DISTRIBUTION_LABEL }, ], apm: [], infra_logs: [], diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/filter_expanded.test.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/filter_expanded.test.tsx index cfac838ba5aeb..2fadb0e56433e 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/filter_expanded.test.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/filter_expanded.test.tsx @@ -22,6 +22,7 @@ describe('FilterExpanded', function () { label={'Browser Family'} field={USER_AGENT_NAME} goBack={jest.fn()} + filters={[]} />, { initSeries } ); @@ -38,6 +39,7 @@ describe('FilterExpanded', function () { label={'Browser Family'} field={USER_AGENT_NAME} goBack={goBack} + filters={[]} />, { initSeries } ); @@ -64,6 +66,7 @@ describe('FilterExpanded', function () { label={'Browser Family'} field={USER_AGENT_NAME} goBack={goBack} + filters={[]} />, { initSeries } ); @@ -90,6 +93,7 @@ describe('FilterExpanded', function () { label={'Browser Family'} field={USER_AGENT_NAME} goBack={jest.fn()} + filters={[]} />, { initSeries } ); diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/filter_expanded.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/filter_expanded.tsx index 0d5b73f14671d..a78f6adeca39f 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/filter_expanded.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/filter_expanded.tsx @@ -6,17 +6,21 @@ */ import React, { useState, Fragment } from 'react'; -import { EuiFieldSearch, EuiSpacer, EuiButtonEmpty, EuiFilterGroup } from '@elastic/eui'; +import { EuiFieldSearch, EuiSpacer, EuiButtonEmpty, EuiFilterGroup, EuiText } from '@elastic/eui'; import styled from 'styled-components'; import { rgba } from 'polished'; import { i18n } from '@kbn/i18n'; +import { QueryDslQueryContainer } from '@elastic/elasticsearch/api/types'; import { map } from 'lodash'; import { useAppIndexPatternContext } from '../../hooks/use_app_index_pattern'; import { useSeriesStorage } from '../../hooks/use_series_storage'; -import { UrlFilter } from '../../types'; +import { DataSeries, UrlFilter } from '../../types'; import { FilterValueButton } from './filter_value_btn'; import { useValuesList } from '../../../../../hooks/use_values_list'; import { euiStyled } from '../../../../../../../../../src/plugins/kibana_react/common'; +import { ESFilter } from '../../../../../../../../../src/core/types/elasticsearch'; +import { PersistableFilter } from '../../../../../../../lens/common'; +import { ExistsFilter } from '../../../../../../../../../src/plugins/data/common/es_query/filters'; interface Props { seriesId: string; @@ -25,9 +29,18 @@ interface Props { isNegated?: boolean; goBack: () => void; nestedField?: string; + filters: DataSeries['filters']; } -export function FilterExpanded({ seriesId, field, label, goBack, nestedField, isNegated }: Props) { +export function FilterExpanded({ + seriesId, + field, + label, + goBack, + nestedField, + isNegated, + filters: defaultFilters, +}: Props) { const { indexPattern } = useAppIndexPatternContext(); const [value, setValue] = useState(''); @@ -38,12 +51,25 @@ export function FilterExpanded({ seriesId, field, label, goBack, nestedField, is const series = getSeries(seriesId); + const queryFilters: ESFilter[] = []; + + defaultFilters?.forEach((qFilter: PersistableFilter | ExistsFilter) => { + if (qFilter.query) { + queryFilters.push(qFilter.query); + } + const asExistFilter = qFilter as ExistsFilter; + if (asExistFilter?.exists) { + queryFilters.push(asExistFilter.exists as QueryDslQueryContainer); + } + }); + const { values, loading } = useValuesList({ query: value, indexPatternTitle: indexPattern?.title, sourceField: field, time: series.time, keepHistory: true, + filters: queryFilters, }); const filters = series?.filters ?? []; @@ -73,6 +99,13 @@ export function FilterExpanded({ seriesId, field, label, goBack, nestedField, is /> + {displayValues.length === 0 && !loading && ( + + {i18n.translate('xpack.observability.filters.expanded.noFilter', { + defaultMessage: 'No filters found.', + })} + + )} {displayValues.map((opt) => ( diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/series_filter.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/series_filter.tsx index 9e5770c2de8f9..b7e20b341b572 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/series_filter.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/series_filter.tsx @@ -24,8 +24,10 @@ import { useSeriesStorage } from '../../hooks/use_series_storage'; interface Props { seriesId: string; defaultFilters: DataSeries['defaultFilters']; + filters: DataSeries['filters']; series: DataSeries; isNew?: boolean; + labels?: Record; } export interface Field { @@ -35,21 +37,28 @@ export interface Field { isNegated?: boolean; } -export function SeriesFilter({ series, isNew, seriesId, defaultFilters = [] }: Props) { +export function SeriesFilter({ + series, + isNew, + seriesId, + defaultFilters = [], + filters, + labels, +}: Props) { const [isPopoverVisible, setIsPopoverVisible] = useState(false); const [selectedField, setSelectedField] = useState(); const options: Field[] = defaultFilters.map((field) => { if (typeof field === 'string') { - return { label: FieldLabels[field], field }; + return { label: labels?.[field] ?? FieldLabels[field], field }; } return { field: field.field, nested: field.nested, isNegated: field.isNegated, - label: FieldLabels[field.field], + label: labels?.[field.field] ?? FieldLabels[field.field], }; }); @@ -102,6 +111,7 @@ export function SeriesFilter({ series, isNew, seriesId, defaultFilters = [] }: P goBack={() => { setSelectedField(undefined); }} + filters={filters} /> ) : null; diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/series_editor.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/series_editor.tsx index 79218aa111f16..17d4356dcf65b 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/series_editor.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/series_editor.tsx @@ -49,7 +49,12 @@ export function SeriesEditor() { field: 'defaultFilters', width: '15%', render: (defaultFilters: string[], { id, seriesConfig }: EditItem) => ( - + ), }, { diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/types.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/types.ts index 98605dfdb4ca3..73b4d7794dd51 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/types.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/types.ts @@ -23,6 +23,7 @@ export const ReportViewTypes = { dist: 'data-distribution', kpi: 'kpi-over-time', cwv: 'core-web-vitals', + mdd: 'mobile-device-distribution', } as const; type ValueOf = T[keyof T]; @@ -45,8 +46,9 @@ export interface ReportDefinition { field?: string; label: string; description?: string; - columnType?: 'range' | 'operation' | 'FILTER_RECORDS'; + columnType?: 'range' | 'operation' | 'FILTER_RECORDS' | 'TERMS_COLUMN'; columnFilters?: ColumnFilter[]; + timeScale?: string; }>; } @@ -94,15 +96,15 @@ export interface ConfigProps { indexPattern: IIndexPattern; } -export type AppDataType = 'synthetics' | 'ux' | 'infra_logs' | 'infra_metrics' | 'apm'; +export type AppDataType = 'synthetics' | 'ux' | 'infra_logs' | 'infra_metrics' | 'apm' | 'mobile'; -type FormatType = 'duration' | 'number'; +type FormatType = 'duration' | 'number' | 'bytes' | 'percent'; type InputFormat = 'microseconds' | 'milliseconds' | 'seconds'; type OutputFormat = 'asSeconds' | 'asMilliseconds' | 'humanize' | 'humanizePrecise'; export interface FieldFormatParams { - inputFormat: InputFormat; - outputFormat: OutputFormat; + inputFormat?: InputFormat; + outputFormat?: OutputFormat; outputPrecision?: number; showSuffix?: boolean; useShortSuffix?: boolean; diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/utils/observability_index_patterns.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/utils/observability_index_patterns.ts index 858eb52555da6..634408dd614da 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/utils/observability_index_patterns.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/utils/observability_index_patterns.ts @@ -23,6 +23,7 @@ const appFieldFormats: Record = { ux: rumFieldFormats, apm: apmFieldFormats, synthetics: syntheticsFieldFormats, + mobile: apmFieldFormats, }; function getFieldFormatsForApp(app: AppDataType) { @@ -35,6 +36,7 @@ export const indexPatternList: Record = { ux: 'rum_static_index_pattern_id', infra_logs: 'infra_logs_static_index_pattern_id', infra_metrics: 'infra_metrics_static_index_pattern_id', + mobile: 'mobile_static_index_pattern_id', }; const appToPatternMap: Record = { @@ -43,6 +45,7 @@ const appToPatternMap: Record = { ux: '(rum-data-view)*', infra_logs: '', infra_metrics: '', + mobile: '(mobile-data-view)*', }; const getAppIndicesWithPattern = (app: AppDataType, indices: string) => { @@ -124,6 +127,7 @@ export class ObservabilityIndexPatterns { if (!this.data) { throw new Error('data is not defined'); } + try { const indexPatternId = getAppIndexPatternId(app, indices); const indexPatternTitle = getAppIndicesWithPattern(app, indices); diff --git a/x-pack/plugins/observability/public/components/shared/field_value_suggestions/types.ts b/x-pack/plugins/observability/public/components/shared/field_value_suggestions/types.ts index 48745c5a8f8a6..ab24f4064c02e 100644 --- a/x-pack/plugins/observability/public/components/shared/field_value_suggestions/types.ts +++ b/x-pack/plugins/observability/public/components/shared/field_value_suggestions/types.ts @@ -7,7 +7,7 @@ import { PopoverAnchorPosition } from '@elastic/eui'; import { Dispatch, SetStateAction } from 'react'; -import { ESFilter } from 'typings/elasticsearch'; +import { ESFilter } from 'src/core/types/elasticsearch'; interface CommonProps { selectedValue?: string[]; diff --git a/x-pack/plugins/observability/public/context/has_data_context.test.tsx b/x-pack/plugins/observability/public/context/has_data_context.test.tsx index b5a0806306461..f2f550e35ac6b 100644 --- a/x-pack/plugins/observability/public/context/has_data_context.test.tsx +++ b/x-pack/plugins/observability/public/context/has_data_context.test.tsx @@ -5,7 +5,6 @@ * 2.0. */ -// import { act, getByText } from '@testing-library/react'; import { renderHook } from '@testing-library/react-hooks'; import { CoreStart } from 'kibana/public'; import React from 'react'; @@ -19,10 +18,17 @@ import * as pluginContext from '../hooks/use_plugin_context'; import { PluginContextValue } from './plugin_context'; import { Router } from 'react-router-dom'; import { createMemoryHistory } from 'history'; +import { ApmIndicesConfig } from '../../common/typings'; +import { act } from '@testing-library/react'; const relativeStart = '2020-10-08T06:00:00.000Z'; const relativeEnd = '2020-10-08T07:00:00.000Z'; +const sampleAPMIndices = { + // eslint-disable-next-line @typescript-eslint/naming-convention + 'apm_oss.transactionIndices': 'apm-*', +} as ApmIndicesConfig; + function wrapper({ children }: { children: React.ReactElement }) { const history = createMemoryHistory(); return ( @@ -76,17 +82,18 @@ describe('HasDataContextProvider', () => { it('hasAnyData returns false and all apps return undefined', async () => { const { result, waitForNextUpdate } = renderHook(() => useHasData(), { wrapper }); expect(result.current).toMatchObject({ - hasData: {}, + hasDataMap: {}, hasAnyData: false, isAllRequestsComplete: false, forceUpdate: expect.any(String), onRefreshTimeRange: expect.any(Function), }); - - await waitForNextUpdate(); + await act(async () => { + await waitForNextUpdate(); + }); expect(result.current).toEqual({ - hasData: { + hasDataMap: { apm: { hasData: undefined, status: 'success' }, synthetics: { hasData: undefined, status: 'success' }, infra_logs: { hasData: undefined, status: 'success' }, @@ -105,16 +112,16 @@ describe('HasDataContextProvider', () => { describe('all apps return false', () => { beforeAll(() => { registerApps([ - { appName: 'apm', hasData: async () => false }, + { appName: 'apm', hasData: async () => ({ hasData: false }) }, { appName: 'infra_logs', hasData: async () => false }, { appName: 'infra_metrics', hasData: async () => false }, { appName: 'synthetics', - hasData: async () => ({ hasData: false, indices: 'heartbeat-*, synthetics-*' }), + hasData: async () => ({ hasData: false }), }, { appName: 'ux', - hasData: async () => ({ hasData: false, serviceName: undefined, indices: 'apm-*' }), + hasData: async () => ({ hasData: false }), }, ]); }); @@ -124,29 +131,28 @@ describe('HasDataContextProvider', () => { it('hasAnyData returns false and all apps return false', async () => { const { result, waitForNextUpdate } = renderHook(() => useHasData(), { wrapper }); expect(result.current).toEqual({ - hasData: {}, + hasDataMap: {}, hasAnyData: false, isAllRequestsComplete: false, forceUpdate: expect.any(String), onRefreshTimeRange: expect.any(Function), }); - await waitForNextUpdate(); + await act(async () => { + await waitForNextUpdate(); + }); expect(result.current).toEqual({ - hasData: { + hasDataMap: { apm: { hasData: false, status: 'success' }, synthetics: { - hasData: { - hasData: false, - indices: 'heartbeat-*, synthetics-*', - }, + hasData: false, status: 'success', }, infra_logs: { hasData: false, status: 'success' }, infra_metrics: { hasData: false, status: 'success' }, ux: { - hasData: { hasData: false, serviceName: undefined, indices: 'apm-*' }, + hasData: false, status: 'success', }, alert: { hasData: [], status: 'success' }, @@ -162,7 +168,7 @@ describe('HasDataContextProvider', () => { describe('at least one app returns true', () => { beforeAll(() => { registerApps([ - { appName: 'apm', hasData: async () => true }, + { appName: 'apm', hasData: async () => ({ hasData: true }) }, { appName: 'infra_logs', hasData: async () => false }, { appName: 'infra_metrics', hasData: async () => false }, { @@ -181,29 +187,30 @@ describe('HasDataContextProvider', () => { it('hasAnyData returns true apm returns true and all other apps return false', async () => { const { result, waitForNextUpdate } = renderHook(() => useHasData(), { wrapper }); expect(result.current).toEqual({ - hasData: {}, + hasDataMap: {}, hasAnyData: false, isAllRequestsComplete: false, forceUpdate: expect.any(String), onRefreshTimeRange: expect.any(Function), }); - await waitForNextUpdate(); + await act(async () => { + await waitForNextUpdate(); + }); expect(result.current).toEqual({ - hasData: { + hasDataMap: { apm: { hasData: true, status: 'success' }, synthetics: { - hasData: { - hasData: false, - indices: 'heartbeat-*, synthetics-*', - }, + hasData: false, + indices: 'heartbeat-*, synthetics-*', status: 'success', }, infra_logs: { hasData: false, status: 'success' }, infra_metrics: { hasData: false, status: 'success' }, ux: { - hasData: { hasData: false, serviceName: undefined, indices: 'apm-*' }, + hasData: false, + indices: 'apm-*', status: 'success', }, alert: { hasData: [], status: 'success' }, @@ -219,7 +226,7 @@ describe('HasDataContextProvider', () => { describe('all apps return true', () => { beforeAll(() => { registerApps([ - { appName: 'apm', hasData: async () => true }, + { appName: 'apm', hasData: async () => ({ hasData: true }) }, { appName: 'infra_logs', hasData: async () => true }, { appName: 'infra_metrics', hasData: async () => true }, { @@ -238,32 +245,34 @@ describe('HasDataContextProvider', () => { it('hasAnyData returns true and all apps return true', async () => { const { result, waitForNextUpdate } = renderHook(() => useHasData(), { wrapper }); expect(result.current).toEqual({ - hasData: {}, + hasDataMap: {}, hasAnyData: false, isAllRequestsComplete: false, forceUpdate: expect.any(String), onRefreshTimeRange: expect.any(Function), }); - await waitForNextUpdate(); + await act(async () => { + await waitForNextUpdate(); + }); expect(result.current).toEqual({ - hasData: { + hasDataMap: { apm: { hasData: true, status: 'success', }, synthetics: { - hasData: { - hasData: true, - indices: 'heartbeat-*, synthetics-*', - }, + hasData: true, + indices: 'heartbeat-*, synthetics-*', status: 'success', }, infra_logs: { hasData: true, status: 'success' }, infra_metrics: { hasData: true, status: 'success' }, ux: { - hasData: { hasData: true, serviceName: 'ux', indices: 'apm-*' }, + hasData: true, + serviceName: 'ux', + indices: 'apm-*', status: 'success', }, alert: { hasData: [], status: 'success' }, @@ -279,7 +288,9 @@ describe('HasDataContextProvider', () => { describe('only apm is registered', () => { describe('when apm returns true', () => { beforeAll(() => { - registerApps([{ appName: 'apm', hasData: async () => true }]); + registerApps([ + { appName: 'apm', hasData: async () => ({ hasData: true, indices: sampleAPMIndices }) }, + ]); }); afterAll(unregisterAll); @@ -289,18 +300,20 @@ describe('HasDataContextProvider', () => { wrapper, }); expect(result.current).toEqual({ - hasData: {}, + hasDataMap: {}, hasAnyData: false, isAllRequestsComplete: false, forceUpdate: expect.any(String), onRefreshTimeRange: expect.any(Function), }); - await waitForNextUpdate(); + await act(async () => { + await waitForNextUpdate(); + }); expect(result.current).toEqual({ - hasData: { - apm: { hasData: true, status: 'success' }, + hasDataMap: { + apm: { hasData: true, indices: sampleAPMIndices, status: 'success' }, synthetics: { hasData: undefined, status: 'success' }, infra_logs: { hasData: undefined, status: 'success' }, infra_metrics: { hasData: undefined, status: 'success' }, @@ -317,7 +330,12 @@ describe('HasDataContextProvider', () => { describe('when apm returns false', () => { beforeAll(() => { - registerApps([{ appName: 'apm', hasData: async () => false }]); + registerApps([ + { + appName: 'apm', + hasData: async () => ({ indices: sampleAPMIndices, hasData: false }), + }, + ]); }); afterAll(unregisterAll); @@ -327,18 +345,24 @@ describe('HasDataContextProvider', () => { wrapper, }); expect(result.current).toEqual({ - hasData: {}, + hasDataMap: {}, hasAnyData: false, isAllRequestsComplete: false, forceUpdate: expect.any(String), onRefreshTimeRange: expect.any(Function), }); - await waitForNextUpdate(); + await act(async () => { + await waitForNextUpdate(); + }); expect(result.current).toEqual({ - hasData: { - apm: { hasData: false, status: 'success' }, + hasDataMap: { + apm: { + hasData: false, + indices: sampleAPMIndices, + status: 'success', + }, synthetics: { hasData: undefined, status: 'success' }, infra_logs: { hasData: undefined, status: 'success' }, infra_metrics: { hasData: undefined, status: 'success' }, @@ -381,29 +405,31 @@ describe('HasDataContextProvider', () => { it('hasAnyData returns true, apm is undefined and all other apps return true', async () => { const { result, waitForNextUpdate } = renderHook(() => useHasData(), { wrapper }); expect(result.current).toEqual({ - hasData: {}, + hasDataMap: {}, hasAnyData: false, isAllRequestsComplete: false, forceUpdate: expect.any(String), onRefreshTimeRange: expect.any(Function), }); - await waitForNextUpdate(); + await act(async () => { + await waitForNextUpdate(); + }); expect(result.current).toEqual({ - hasData: { + hasDataMap: { apm: { hasData: undefined, status: 'failure' }, synthetics: { - hasData: { - hasData: true, - indices: 'heartbeat-*, synthetics-*', - }, + hasData: true, + indices: 'heartbeat-*, synthetics-*', status: 'success', }, infra_logs: { hasData: true, status: 'success' }, infra_metrics: { hasData: true, status: 'success' }, ux: { - hasData: { hasData: true, serviceName: 'ux', indices: 'apm-*' }, + hasData: true, + serviceName: 'ux', + indices: 'apm-*', status: 'success', }, alert: { hasData: [], status: 'success' }, @@ -457,17 +483,19 @@ describe('HasDataContextProvider', () => { it('hasAnyData returns false and all apps return undefined', async () => { const { result, waitForNextUpdate } = renderHook(() => useHasData(), { wrapper }); expect(result.current).toEqual({ - hasData: {}, + hasDataMap: {}, hasAnyData: false, isAllRequestsComplete: false, forceUpdate: expect.any(String), onRefreshTimeRange: expect.any(Function), }); - await waitForNextUpdate(); + await act(async () => { + await waitForNextUpdate(); + }); expect(result.current).toEqual({ - hasData: { + hasDataMap: { apm: { hasData: undefined, status: 'failure' }, synthetics: { hasData: undefined, status: 'failure' }, infra_logs: { hasData: undefined, status: 'failure' }, @@ -505,17 +533,19 @@ describe('HasDataContextProvider', () => { it('returns all alerts available', async () => { const { result, waitForNextUpdate } = renderHook(() => useHasData(), { wrapper }); expect(result.current).toEqual({ - hasData: {}, + hasDataMap: {}, hasAnyData: false, isAllRequestsComplete: false, forceUpdate: expect.any(String), onRefreshTimeRange: expect.any(Function), }); - await waitForNextUpdate(); + await act(async () => { + await waitForNextUpdate(); + }); expect(result.current).toEqual({ - hasData: { + hasDataMap: { apm: { hasData: undefined, status: 'success' }, synthetics: { hasData: undefined, status: 'success' }, infra_logs: { hasData: undefined, status: 'success' }, diff --git a/x-pack/plugins/observability/public/context/has_data_context.tsx b/x-pack/plugins/observability/public/context/has_data_context.tsx index 97aa72f07b09c..047a596ea349e 100644 --- a/x-pack/plugins/observability/public/context/has_data_context.tsx +++ b/x-pack/plugins/observability/public/context/has_data_context.tsx @@ -14,17 +14,23 @@ import { FETCH_STATUS } from '../hooks/use_fetcher'; import { usePluginContext } from '../hooks/use_plugin_context'; import { useTimeRange } from '../hooks/use_time_range'; import { getObservabilityAlerts } from '../services/get_observability_alerts'; -import { ObservabilityFetchDataPlugins, UXHasDataResponse } from '../typings/fetch_overview_data'; +import { ObservabilityFetchDataPlugins } from '../typings/fetch_overview_data'; +import { ApmIndicesConfig } from '../../common/typings'; type DataContextApps = ObservabilityFetchDataPlugins | 'alert'; export type HasDataMap = Record< DataContextApps, - { status: FETCH_STATUS; hasData?: boolean | UXHasDataResponse | Alert[] } + { + status: FETCH_STATUS; + hasData?: boolean | Alert[]; + indices?: string | ApmIndicesConfig; + serviceName?: string; + } >; export interface HasDataContextValue { - hasData: Partial; + hasDataMap: Partial; hasAnyData: boolean; isAllRequestsComplete: boolean; onRefreshTimeRange: () => void; @@ -40,7 +46,7 @@ export function HasDataContextProvider({ children }: { children: React.ReactNode const [forceUpdate, setForceUpdate] = useState(''); const { absoluteStart, absoluteEnd } = useTimeRange(); - const [hasData, setHasData] = useState({}); + const [hasDataMap, setHasDataMap] = useState({}); const isExploratoryView = useRouteMatch('/exploratory-view'); @@ -49,23 +55,53 @@ export function HasDataContextProvider({ children }: { children: React.ReactNode if (!isExploratoryView) apps.forEach(async (app) => { try { - if (app !== 'alert') { - const params = - app === 'ux' - ? { absoluteTime: { start: absoluteStart, end: absoluteEnd } } - : undefined; - - const result = await getDataHandler(app)?.hasData(params); - setHasData((prevState) => ({ + const updateState = ({ + hasData, + indices, + serviceName, + }: { + hasData?: boolean; + serviceName?: string; + indices?: string | ApmIndicesConfig; + }) => { + setHasDataMap((prevState) => ({ ...prevState, [app]: { - hasData: result, + hasData, + ...(serviceName ? { serviceName } : {}), + ...(indices ? { indices } : {}), status: FETCH_STATUS.SUCCESS, }, })); + }; + switch (app) { + case 'ux': + const params = { absoluteTime: { start: absoluteStart, end: absoluteEnd } }; + const resultUx = await getDataHandler(app)?.hasData(params); + updateState({ + hasData: resultUx?.hasData, + indices: resultUx?.indices, + serviceName: resultUx?.serviceName as string, + }); + break; + case 'synthetics': + const resultSy = await getDataHandler(app)?.hasData(); + updateState({ hasData: resultSy?.hasData, indices: resultSy?.indices }); + + break; + case 'apm': + const resultApm = await getDataHandler(app)?.hasData(); + updateState({ hasData: resultApm?.hasData, indices: resultApm?.indices }); + + break; + case 'infra_logs': + case 'infra_metrics': + const resultInfra = await getDataHandler(app)?.hasData(); + updateState({ hasData: resultInfra }); + break; } } catch (e) { - setHasData((prevState) => ({ + setHasDataMap((prevState) => ({ ...prevState, [app]: { hasData: undefined, @@ -83,7 +119,7 @@ export function HasDataContextProvider({ children }: { children: React.ReactNode async function fetchAlerts() { try { const alerts = await getObservabilityAlerts({ core }); - setHasData((prevState) => ({ + setHasDataMap((prevState) => ({ ...prevState, alert: { hasData: alerts, @@ -91,7 +127,7 @@ export function HasDataContextProvider({ children }: { children: React.ReactNode }, })); } catch (e) { - setHasData((prevState) => ({ + setHasDataMap((prevState) => ({ ...prevState, alert: { hasData: undefined, @@ -105,18 +141,18 @@ export function HasDataContextProvider({ children }: { children: React.ReactNode }, [forceUpdate, core]); const isAllRequestsComplete = apps.every((app) => { - const appStatus = hasData[app]?.status; + const appStatus = hasDataMap[app]?.status; return appStatus !== undefined && appStatus !== FETCH_STATUS.LOADING; }); - const hasAnyData = (Object.keys(hasData) as ObservabilityFetchDataPlugins[]).some( - (app) => hasData[app]?.hasData === true + const hasAnyData = (Object.keys(hasDataMap) as ObservabilityFetchDataPlugins[]).some( + (app) => hasDataMap[app]?.hasData === true ); return ( { const originalConsole = global.console; beforeAll(() => { - // mocks console to avoid poluting the test output + // mocks console to avoid polluting the test output global.console = ({ error: jest.fn() } as unknown) as typeof console; }); @@ -58,7 +64,7 @@ describe('registerDataHandler', () => { }, }; }, - hasData: async () => true, + hasData: async () => ({ hasData: true, indices: sampleAPMIndices }), }); it('registered data handler', () => { diff --git a/x-pack/plugins/observability/public/hooks/use_es_search.ts b/x-pack/plugins/observability/public/hooks/use_es_search.ts index 18a846a6f85d2..27c4081a99775 100644 --- a/x-pack/plugins/observability/public/hooks/use_es_search.ts +++ b/x-pack/plugins/observability/public/hooks/use_es_search.ts @@ -7,7 +7,7 @@ import { estypes } from '@elastic/elasticsearch'; import { DataPublicPluginStart } from '../../../../../src/plugins/data/public'; -import { ESSearchResponse } from '../../../../../typings/elasticsearch'; +import { ESSearchResponse } from '../../../../../src/core/types/elasticsearch'; import { useKibana } from '../../../../../src/plugins/kibana_react/public'; import { isCompleteResponse } from '../../../../../src/plugins/data/common'; import { useFetcher } from './use_fetcher'; diff --git a/x-pack/plugins/observability/public/hooks/use_values_list.ts b/x-pack/plugins/observability/public/hooks/use_values_list.ts index e563293d26d6f..094b7a0f36921 100644 --- a/x-pack/plugins/observability/public/hooks/use_values_list.ts +++ b/x-pack/plugins/observability/public/hooks/use_values_list.ts @@ -8,7 +8,7 @@ import { capitalize, union } from 'lodash'; import { useEffect, useState } from 'react'; import useDebounce from 'react-use/lib/useDebounce'; -import { ESFilter } from '../../../../../typings/elasticsearch'; +import { ESFilter } from '../../../../../src/core/types/elasticsearch'; import { createEsParams, useEsSearch } from './use_es_search'; export interface Props { diff --git a/x-pack/plugins/observability/public/pages/home/index.test.tsx b/x-pack/plugins/observability/public/pages/home/index.test.tsx index a2c784cb4b2de..60b3e809e7de9 100644 --- a/x-pack/plugins/observability/public/pages/home/index.test.tsx +++ b/x-pack/plugins/observability/public/pages/home/index.test.tsx @@ -24,32 +24,38 @@ describe('Home page', () => { }); it('renders loading component while requests are not returned', () => { - jest - .spyOn(hasData, 'useHasData') - .mockImplementation( - () => - ({ hasData: {}, hasAnyData: false, isAllRequestsComplete: false } as HasDataContextValue) - ); + jest.spyOn(hasData, 'useHasData').mockImplementation( + () => + ({ + hasDataMap: {}, + hasAnyData: false, + isAllRequestsComplete: false, + } as HasDataContextValue) + ); const { getByText } = render(); expect(getByText('Loading Observability')).toBeInTheDocument(); }); it('renders landing page', () => { - jest - .spyOn(hasData, 'useHasData') - .mockImplementation( - () => - ({ hasData: {}, hasAnyData: false, isAllRequestsComplete: true } as HasDataContextValue) - ); + jest.spyOn(hasData, 'useHasData').mockImplementation( + () => + ({ + hasDataMap: {}, + hasAnyData: false, + isAllRequestsComplete: true, + } as HasDataContextValue) + ); render(); expect(mockHistoryPush).toHaveBeenCalledWith({ pathname: '/landing' }); }); it('renders overview page', () => { - jest - .spyOn(hasData, 'useHasData') - .mockImplementation( - () => - ({ hasData: {}, hasAnyData: true, isAllRequestsComplete: false } as HasDataContextValue) - ); + jest.spyOn(hasData, 'useHasData').mockImplementation( + () => + ({ + hasDataMap: {}, + hasAnyData: true, + isAllRequestsComplete: false, + } as HasDataContextValue) + ); render(); expect(mockHistoryPush).toHaveBeenCalledWith({ pathname: '/overview' }); }); diff --git a/x-pack/plugins/observability/public/pages/overview/index.tsx b/x-pack/plugins/observability/public/pages/overview/index.tsx index 89398ad16f198..fdb52270befed 100644 --- a/x-pack/plugins/observability/public/pages/overview/index.tsx +++ b/x-pack/plugins/observability/public/pages/overview/index.tsx @@ -57,13 +57,13 @@ export function OverviewPage({ routeParams }: Props) { const { data: newsFeed } = useFetcher(() => getNewsFeed({ core }), [core]); - const { hasData, hasAnyData } = useHasData(); + const { hasDataMap, hasAnyData } = useHasData(); if (hasAnyData === undefined) { return ; } - const alerts = (hasData.alert?.hasData as Alert[]) || []; + const alerts = (hasDataMap.alert?.hasData as Alert[]) || []; const { refreshInterval = 10000, refreshPaused = true } = routeParams.query; diff --git a/x-pack/plugins/observability/public/pages/overview/overview.stories.tsx b/x-pack/plugins/observability/public/pages/overview/overview.stories.tsx index 2482ae7a8e7ab..dd424cf221d15 100644 --- a/x-pack/plugins/observability/public/pages/overview/overview.stories.tsx +++ b/x-pack/plugins/observability/public/pages/overview/overview.stories.tsx @@ -25,6 +25,7 @@ import { newsFeedFetchData } from './mock/news_feed.mock'; import { emptyResponse as emptyUptimeResponse, fetchUptimeData } from './mock/uptime.mock'; import { createObservabilityRuleTypeRegistryMock } from '../../rules/observability_rule_type_registry_mock'; import { KibanaPageTemplate } from '../../../../../../src/plugins/kibana_react/public'; +import { ApmIndicesConfig } from '../../../common/typings'; function unregisterAll() { unregisterDataHandler({ appName: 'apm' }); @@ -33,6 +34,11 @@ function unregisterAll() { unregisterDataHandler({ appName: 'synthetics' }); } +const sampleAPMIndices = { + // eslint-disable-next-line @typescript-eslint/naming-convention + 'apm_oss.transactionIndices': 'apm-*', +} as ApmIndicesConfig; + const withCore = makeDecorator({ name: 'withCore', parameterName: 'core', @@ -177,7 +183,7 @@ storiesOf('app/Overview', module) registerDataHandler({ appName: 'apm', fetchData: fetchApmData, - hasData: async () => false, + hasData: async () => ({ hasData: false, indices: sampleAPMIndices }), }); registerDataHandler({ appName: 'infra_logs', @@ -272,7 +278,7 @@ storiesOf('app/Overview', module) registerDataHandler({ appName: 'apm', fetchData: fetchApmData, - hasData: async () => true, + hasData: async () => ({ hasData: true, indices: sampleAPMIndices }), }); return ( @@ -289,7 +295,7 @@ storiesOf('app/Overview', module) registerDataHandler({ appName: 'apm', fetchData: fetchApmData, - hasData: async () => true, + hasData: async () => ({ hasData: true, indices: sampleAPMIndices }), }); registerDataHandler({ appName: 'infra_logs', @@ -321,7 +327,7 @@ storiesOf('app/Overview', module) registerDataHandler({ appName: 'apm', fetchData: fetchApmData, - hasData: async () => true, + hasData: async () => ({ hasData: true, indices: sampleAPMIndices }), }); registerDataHandler({ appName: 'infra_logs', @@ -355,7 +361,7 @@ storiesOf('app/Overview', module) registerDataHandler({ appName: 'apm', fetchData: fetchApmData, - hasData: async () => true, + hasData: async () => ({ hasData: true, indices: sampleAPMIndices }), }); registerDataHandler({ appName: 'infra_logs', @@ -386,7 +392,7 @@ storiesOf('app/Overview', module) registerDataHandler({ appName: 'apm', fetchData: async () => emptyAPMResponse, - hasData: async () => true, + hasData: async () => ({ hasData: true, indices: sampleAPMIndices }), }); registerDataHandler({ appName: 'infra_logs', @@ -420,7 +426,7 @@ storiesOf('app/Overview', module) fetchData: async () => { throw new Error('Error fetching APM data'); }, - hasData: async () => true, + hasData: async () => ({ hasData: true, indices: sampleAPMIndices }), }); registerDataHandler({ appName: 'infra_logs', diff --git a/x-pack/plugins/observability/public/typings/fetch_overview_data/index.ts b/x-pack/plugins/observability/public/typings/fetch_overview_data/index.ts index 6b69aa9888cf6..197a8c1060cdb 100644 --- a/x-pack/plugins/observability/public/typings/fetch_overview_data/index.ts +++ b/x-pack/plugins/observability/public/typings/fetch_overview_data/index.ts @@ -7,6 +7,8 @@ import { ObservabilityApp } from '../../../typings/common'; import { UXMetrics } from '../../components/shared/core_web_vitals'; +import { ApmIndicesConfig } from '../../../common/typings'; + export interface Stat { type: 'number' | 'percent' | 'bytesPerSecond'; value: number; @@ -34,11 +36,20 @@ export interface HasDataParams { export interface HasDataResponse { hasData: boolean; - indices: string; } export interface UXHasDataResponse extends HasDataResponse { - serviceName: string | number | undefined; + serviceName?: string | number; + indices?: string; +} + +export interface SyntheticsHasDataResponse extends HasDataResponse { + indices: string; +} + +export interface APMHasDataResponse { + hasData: boolean; + indices: ApmIndicesConfig; } export type FetchData = ( @@ -134,9 +145,9 @@ export interface ObservabilityFetchDataResponse { } export interface ObservabilityHasDataResponse { - apm: boolean; + apm: APMHasDataResponse; infra_metrics: boolean; infra_logs: boolean; - synthetics: HasDataResponse; + synthetics: SyntheticsHasDataResponse; ux: UXHasDataResponse; } diff --git a/x-pack/plugins/rule_registry/server/rule_data_client/types.ts b/x-pack/plugins/rule_registry/server/rule_data_client/types.ts index 348fca6a58188..d5ce022781b0d 100644 --- a/x-pack/plugins/rule_registry/server/rule_data_client/types.ts +++ b/x-pack/plugins/rule_registry/server/rule_data_client/types.ts @@ -9,7 +9,7 @@ import { ApiResponse } from '@elastic/elasticsearch'; import { BulkRequest, BulkResponse } from '@elastic/elasticsearch/api/types'; import { ElasticsearchClient } from 'kibana/server'; import { FieldDescriptor } from 'src/plugins/data/server'; -import { ESSearchRequest, ESSearchResponse } from 'typings/elasticsearch'; +import { ESSearchRequest, ESSearchResponse } from 'src/core/types/elasticsearch'; import { TechnicalRuleDataFieldName } from '../../common/technical_rule_data_field_names'; export interface RuleDataReader { diff --git a/x-pack/plugins/rule_registry/server/utils/create_persistence_rule_type_factory.ts b/x-pack/plugins/rule_registry/server/utils/create_persistence_rule_type_factory.ts index 0e244fbaa2ee3..3f50b78151e74 100644 --- a/x-pack/plugins/rule_registry/server/utils/create_persistence_rule_type_factory.ts +++ b/x-pack/plugins/rule_registry/server/utils/create_persistence_rule_type_factory.ts @@ -4,7 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { ESSearchRequest } from 'typings/elasticsearch'; +import { ESSearchRequest } from 'src/core/types/elasticsearch'; import v4 from 'uuid/v4'; import { Logger } from '@kbn/logging'; diff --git a/x-pack/plugins/security/common/model/user.ts b/x-pack/plugins/security/common/model/user.ts index 3f4787b207f88..2bcea659699cb 100644 --- a/x-pack/plugins/security/common/model/user.ts +++ b/x-pack/plugins/security/common/model/user.ts @@ -7,8 +7,8 @@ export interface User { username: string; - email: string; - full_name: string; + email?: string; + full_name?: string; roles: readonly string[]; enabled: boolean; metadata?: { diff --git a/x-pack/plugins/security/public/management/users/edit_user/user_form.tsx b/x-pack/plugins/security/public/management/users/edit_user/user_form.tsx index 29d87e31797cc..8101c09d64907 100644 --- a/x-pack/plugins/security/public/management/users/edit_user/user_form.tsx +++ b/x-pack/plugins/security/public/management/users/edit_user/user_form.tsx @@ -41,8 +41,8 @@ export const THROTTLE_USERS_WAIT = 10000; export interface UserFormValues { username?: string; - full_name: string; - email: string; + full_name?: string; + email?: string; password?: string; confirm_password?: string; roles: readonly string[]; diff --git a/x-pack/plugins/security/server/authentication/api_keys/api_keys.ts b/x-pack/plugins/security/server/authentication/api_keys/api_keys.ts index 4cbca1c70f507..1707ca710aaf8 100644 --- a/x-pack/plugins/security/server/authentication/api_keys/api_keys.ts +++ b/x-pack/plugins/security/server/authentication/api_keys/api_keys.ts @@ -224,7 +224,7 @@ export class APIKeys { try { result = ( await this.clusterClient.asInternalUser.security.grantApiKey({ - // @ts-expect-error @elastic/elasticsearch api_key.role_descriptors + // @ts-expect-error @elastic/elasticsearch api_key.role_descriptors doesn't support `Record` body: params, }) ).body; diff --git a/x-pack/plugins/security/server/authentication/providers/base.ts b/x-pack/plugins/security/server/authentication/providers/base.ts index c7c0edcf1e9e1..f6d9af24ee1ad 100644 --- a/x-pack/plugins/security/server/authentication/providers/base.ts +++ b/x-pack/plugins/security/server/authentication/providers/base.ts @@ -117,7 +117,7 @@ export abstract class BaseAuthenticationProvider { */ protected async getUser(request: KibanaRequest, authHeaders: Headers = {}) { return this.authenticationInfoToAuthenticatedUser( - // @ts-expect-error @elastic/elasticsearch `AuthenticateResponse` type doesn't define `authentication_type` and `enabled`. + // @ts-expect-error Metadata is defined as Record ( await this.options.client .asScoped({ headers: { ...request.headers, ...authHeaders } }) diff --git a/x-pack/plugins/security/server/authentication/providers/token.ts b/x-pack/plugins/security/server/authentication/providers/token.ts index 43338a8f6400f..fae0d7ca69038 100644 --- a/x-pack/plugins/security/server/authentication/providers/token.ts +++ b/x-pack/plugins/security/server/authentication/providers/token.ts @@ -84,7 +84,7 @@ export class TokenAuthenticationProvider extends BaseAuthenticationProvider { this.logger.debug('Get token API request to Elasticsearch successful'); return AuthenticationResult.succeeded( this.authenticationInfoToAuthenticatedUser( - // @ts-expect-error @elastic/elasticsearch GetUserAccessTokenResponse declares authentication: string, but expected AuthenticatedUser + // @ts-expect-error @elastic/elasticsearch metadata defined as Record; authenticationInfo as AuthenticationInfo ), { diff --git a/x-pack/plugins/security/server/authentication/tokens.ts b/x-pack/plugins/security/server/authentication/tokens.ts index 1adbb2dc66533..47051cc08da23 100644 --- a/x-pack/plugins/security/server/authentication/tokens.ts +++ b/x-pack/plugins/security/server/authentication/tokens.ts @@ -73,7 +73,7 @@ export class Tokens { return { accessToken, refreshToken, - // @ts-expect-error @elastic/elasticsearch declared GetUserAccessTokenResponse.authentication: string + // @ts-expect-error @elastic/elasticsearch user metadata defined as Record authenticationInfo: authenticationInfo as AuthenticationInfo, }; } catch (err) { diff --git a/x-pack/plugins/security/server/routes/authorization/roles/get.ts b/x-pack/plugins/security/server/routes/authorization/roles/get.ts index 01d32f7fb8233..075a8d133f1e6 100644 --- a/x-pack/plugins/security/server/routes/authorization/roles/get.ts +++ b/x-pack/plugins/security/server/routes/authorization/roles/get.ts @@ -32,7 +32,7 @@ export function defineGetRolesRoutes({ router, authz }: RouteDefinitionParams) { if (elasticsearchRole) { return response.ok({ body: transformElasticsearchRoleToRole( - // @ts-expect-error @elastic/elasticsearch `XPackRole` type doesn't define `applications` and `transient_metadata`. + // @ts-expect-error `SecurityIndicesPrivileges.names` expected to be `string[]` elasticsearchRole, request.params.name, authz.applicationName diff --git a/x-pack/plugins/security/server/routes/authorization/roles/get_all.ts b/x-pack/plugins/security/server/routes/authorization/roles/get_all.ts index 4d458be4e332f..be0880a06d59d 100644 --- a/x-pack/plugins/security/server/routes/authorization/roles/get_all.ts +++ b/x-pack/plugins/security/server/routes/authorization/roles/get_all.ts @@ -27,7 +27,7 @@ export function defineGetAllRolesRoutes({ router, authz }: RouteDefinitionParams body: Object.entries(elasticsearchRoles) .map(([roleName, elasticsearchRole]) => transformElasticsearchRoleToRole( - // @ts-expect-error @elastic/elasticsearch `XPackRole` type doesn't define `applications` and `transient_metadata`. + // @ts-expect-error @elastic/elasticsearch SecurityIndicesPrivileges.names expected to be string[] elasticsearchRole, roleName, authz.applicationName diff --git a/x-pack/plugins/security/server/routes/role_mapping/get.ts b/x-pack/plugins/security/server/routes/role_mapping/get.ts index 67cd8975b76eb..257b4210b13f7 100644 --- a/x-pack/plugins/security/server/routes/role_mapping/get.ts +++ b/x-pack/plugins/security/server/routes/role_mapping/get.ts @@ -36,7 +36,7 @@ export function defineRoleMappingGetRoutes(params: RouteDefinitionParams) { return { name, ...mapping, - // @ts-expect-error @elastic/elasticsearch `XPackRoleMapping` type doesn't define `role_templates` property. + // @ts-expect-error @elastic/elasticsearch `SecurityRoleMapping` doeesn't contain `role_templates` role_templates: (mapping.role_templates || []).map((entry: RoleTemplate) => { return { ...entry, diff --git a/x-pack/plugins/security_solution/public/common/components/endpoint/agent_status.tsx b/x-pack/plugins/security_solution/public/common/components/endpoint/agent_status.tsx new file mode 100644 index 0000000000000..f93721349fdac --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/endpoint/agent_status.tsx @@ -0,0 +1,30 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { EuiBadge } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { HostStatus } from '../../../../common/endpoint/types'; +import { HOST_STATUS_TO_BADGE_COLOR } from '../../../management/pages/endpoint_hosts/view/host_constants'; + +export const AgentStatus = React.memo(({ hostStatus }: { hostStatus: HostStatus }) => { + return ( + + + + ); +}); + +AgentStatus.displayName = 'AgentStatus'; diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/alert_summary_view.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/alert_summary_view.tsx index 5578264152c39..e229c0c6fae49 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/alert_summary_view.tsx +++ b/x-pack/plugins/security_solution/public/common/components/event_details/alert_summary_view.tsx @@ -12,7 +12,7 @@ import { EuiDescriptionListTitle, EuiSpacer, } from '@elastic/eui'; -import { get, getOr } from 'lodash/fp'; +import { get, getOr, find } from 'lodash/fp'; import React, { useMemo } from 'react'; import styled from 'styled-components'; @@ -53,6 +53,7 @@ const fields = [ { id: 'signal.rule.severity', label: ALERTS_HEADERS_SEVERITY }, { id: 'signal.rule.risk_score', label: ALERTS_HEADERS_RISK_SCORE }, { id: 'host.name' }, + { id: 'host.status' }, { id: 'user.name' }, { id: SOURCE_IP_FIELD_NAME, fieldType: IP_FIELD_TYPE }, { id: DESTINATION_IP_FIELD_NAME, fieldType: IP_FIELD_TYPE }, @@ -177,6 +178,24 @@ const AlertSummaryViewComponent: React.FC<{ timelineId, ]); + const agentId = useMemo(() => { + const findAgentId = find({ category: 'agent', field: 'agent.id' }, data)?.values; + return findAgentId ? findAgentId[0] : ''; + }, [data]); + + const agentStatusRow = { + title: i18n.AGENT_STATUS, + description: { + contextId: timelineId, + eventId, + fieldName: 'host.status', + value: agentId, + linkValue: undefined, + }, + }; + + const summaryRowsWithAgentStatus = [...summaryRows, agentStatusRow]; + const ruleId = useMemo(() => { const item = data.find((d) => d.field === 'signal.rule.id'); return Array.isArray(item?.originalValue) @@ -188,7 +207,11 @@ const AlertSummaryViewComponent: React.FC<{ return ( <> - + {maybeRule?.note && ( {i18n.INVESTIGATION_GUIDE} diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/translations.ts b/x-pack/plugins/security_solution/public/common/components/event_details/translations.ts index 1ff88d9c2018b..a28d1976ca940 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/translations.ts +++ b/x-pack/plugins/security_solution/public/common/components/event_details/translations.ts @@ -99,3 +99,7 @@ export const NESTED_COLUMN = (field: string) => defaultMessage: 'The {field} field is an object, and is broken down into nested fields which can be added as column', }); + +export const AGENT_STATUS = i18n.translate('xpack.securitySolution.detections.alerts.agentStatus', { + defaultMessage: 'Agent status', +}); diff --git a/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/index.ts b/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/index.ts index c744ace91f434..d5846a9f9ea50 100644 --- a/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/index.ts +++ b/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/index.ts @@ -12,7 +12,6 @@ import { getDefaultEuiMarkdownUiPlugins, } from '@elastic/eui'; // Remove after this issue is resolved: https://github.com/elastic/eui/issues/4688 -// eslint-disable-next-line import/no-extraneous-dependencies import { Options as Remark2RehypeOptions } from 'mdast-util-to-hast'; import { FunctionComponent } from 'react'; // eslint-disable-next-line import/no-extraneous-dependencies diff --git a/x-pack/plugins/security_solution/public/common/components/ml/hooks/use_installed_security_jobs.test.ts b/x-pack/plugins/security_solution/public/common/components/ml/hooks/use_installed_security_jobs.test.ts index a8f4d09cd7873..403b33d9c08f7 100644 --- a/x-pack/plugins/security_solution/public/common/components/ml/hooks/use_installed_security_jobs.test.ts +++ b/x-pack/plugins/security_solution/public/common/components/ml/hooks/use_installed_security_jobs.test.ts @@ -55,6 +55,7 @@ describe('useInstalledSecurityJobs', () => { id: 'siem-api-rare_process_linux_ecs', isSingleMetricViewerJob: true, jobState: 'closed', + jobTags: {}, latestTimestampMs: 1557434782207, memory_status: 'hard_limit', processed_record_count: 582251, diff --git a/x-pack/plugins/security_solution/public/common/components/ml_popover/api.mock.ts b/x-pack/plugins/security_solution/public/common/components/ml_popover/api.mock.ts index ac057dff15621..28d0ae179508d 100644 --- a/x-pack/plugins/security_solution/public/common/components/ml_popover/api.mock.ts +++ b/x-pack/plugins/security_solution/public/common/components/ml_popover/api.mock.ts @@ -48,6 +48,7 @@ export const mockOpenedJob: MlSummaryJob = { nodeName: 'siem-es', processed_record_count: 3425264, awaitingNodeAssignment: false, + jobTags: {}, }; export const mockJobsSummaryResponse: MlSummaryJob[] = [ @@ -67,6 +68,7 @@ export const mockJobsSummaryResponse: MlSummaryJob[] = [ earliestTimestampMs: 1554327458406, isSingleMetricViewerJob: true, awaitingNodeAssignment: false, + jobTags: {}, }, { id: 'siem-api-rare_process_linux_ecs', @@ -83,6 +85,7 @@ export const mockJobsSummaryResponse: MlSummaryJob[] = [ earliestTimestampMs: 1557353420495, isSingleMetricViewerJob: true, awaitingNodeAssignment: false, + jobTags: {}, }, { id: 'siem-api-rare_process_windows_ecs', @@ -97,6 +100,7 @@ export const mockJobsSummaryResponse: MlSummaryJob[] = [ datafeedState: 'stopped', isSingleMetricViewerJob: true, awaitingNodeAssignment: false, + jobTags: {}, }, { id: 'siem-api-suspicious_login_activity_ecs', @@ -111,6 +115,7 @@ export const mockJobsSummaryResponse: MlSummaryJob[] = [ datafeedState: 'stopped', isSingleMetricViewerJob: true, awaitingNodeAssignment: false, + jobTags: {}, }, ]; @@ -520,6 +525,7 @@ export const mockSecurityJobs: SecurityJob[] = [ isInstalled: true, isElasticJob: true, awaitingNodeAssignment: false, + jobTags: {}, }, { id: 'rare_process_by_host_linux_ecs', @@ -539,6 +545,7 @@ export const mockSecurityJobs: SecurityJob[] = [ isInstalled: true, isElasticJob: true, awaitingNodeAssignment: false, + jobTags: {}, }, { datafeedId: '', @@ -558,5 +565,6 @@ export const mockSecurityJobs: SecurityJob[] = [ isInstalled: false, isElasticJob: true, awaitingNodeAssignment: false, + jobTags: {}, }, ]; diff --git a/x-pack/plugins/security_solution/public/common/components/ml_popover/hooks/use_security_jobs.test.ts b/x-pack/plugins/security_solution/public/common/components/ml_popover/hooks/use_security_jobs.test.ts index 3731bebd92624..3c91baa920da7 100644 --- a/x-pack/plugins/security_solution/public/common/components/ml_popover/hooks/use_security_jobs.test.ts +++ b/x-pack/plugins/security_solution/public/common/components/ml_popover/hooks/use_security_jobs.test.ts @@ -62,6 +62,7 @@ describe('useSecurityJobs', () => { isInstalled: true, isSingleMetricViewerJob: true, jobState: 'closed', + jobTags: {}, latestTimestampMs: 1557434782207, memory_status: 'hard_limit', moduleId: '', diff --git a/x-pack/plugins/security_solution/public/common/components/ml_popover/hooks/use_security_jobs_helpers.test.tsx b/x-pack/plugins/security_solution/public/common/components/ml_popover/hooks/use_security_jobs_helpers.test.tsx index 8250807355648..7a488847cd583 100644 --- a/x-pack/plugins/security_solution/public/common/components/ml_popover/hooks/use_security_jobs_helpers.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/ml_popover/hooks/use_security_jobs_helpers.test.tsx @@ -44,6 +44,7 @@ describe('useSecurityJobsHelpers', () => { isInstalled: false, isSingleMetricViewerJob: false, jobState: '', + jobTags: {}, memory_status: '', moduleId: 'siem_auditbeat', processed_record_count: 0, diff --git a/x-pack/plugins/security_solution/public/common/components/ml_popover/hooks/use_security_jobs_helpers.tsx b/x-pack/plugins/security_solution/public/common/components/ml_popover/hooks/use_security_jobs_helpers.tsx index 70a2a7c87225f..fe3803c88e4f7 100644 --- a/x-pack/plugins/security_solution/public/common/components/ml_popover/hooks/use_security_jobs_helpers.tsx +++ b/x-pack/plugins/security_solution/public/common/components/ml_popover/hooks/use_security_jobs_helpers.tsx @@ -44,6 +44,7 @@ export const moduleToSecurityJob = ( isInstalled: false, isElasticJob: true, awaitingNodeAssignment: false, + jobTags: {}, }; }; diff --git a/x-pack/plugins/security_solution/public/common/components/ml_popover/jobs_table/__snapshots__/jobs_table.test.tsx.snap b/x-pack/plugins/security_solution/public/common/components/ml_popover/jobs_table/__snapshots__/jobs_table.test.tsx.snap index d64fb474c1fb3..2d2525b92deb1 100644 --- a/x-pack/plugins/security_solution/public/common/components/ml_popover/jobs_table/__snapshots__/jobs_table.test.tsx.snap +++ b/x-pack/plugins/security_solution/public/common/components/ml_popover/jobs_table/__snapshots__/jobs_table.test.tsx.snap @@ -46,6 +46,7 @@ exports[`JobsTableComponent renders correctly against snapshot 1`] = ` "isInstalled": true, "isSingleMetricViewerJob": true, "jobState": "closed", + "jobTags": Object {}, "latestResultsTimestampMs": 1571022900000, "latestTimestampMs": 1571022859393, "memory_status": "ok", @@ -73,6 +74,7 @@ exports[`JobsTableComponent renders correctly against snapshot 1`] = ` "isInstalled": true, "isSingleMetricViewerJob": true, "jobState": "closed", + "jobTags": Object {}, "memory_status": "ok", "moduleId": "siem_auditbeat", "processed_record_count": 0, @@ -96,6 +98,7 @@ exports[`JobsTableComponent renders correctly against snapshot 1`] = ` "isInstalled": false, "isSingleMetricViewerJob": false, "jobState": "", + "jobTags": Object {}, "memory_status": "", "moduleId": "siem_winlogbeat", "processed_record_count": 0, diff --git a/x-pack/plugins/security_solution/public/common/components/ml_popover/jobs_table/filters/__snapshots__/jobs_table_filters.test.tsx.snap b/x-pack/plugins/security_solution/public/common/components/ml_popover/jobs_table/filters/__snapshots__/jobs_table_filters.test.tsx.snap index 7367dbf7bdc0a..d66740ee5bb0e 100644 --- a/x-pack/plugins/security_solution/public/common/components/ml_popover/jobs_table/filters/__snapshots__/jobs_table_filters.test.tsx.snap +++ b/x-pack/plugins/security_solution/public/common/components/ml_popover/jobs_table/filters/__snapshots__/jobs_table_filters.test.tsx.snap @@ -49,6 +49,7 @@ exports[`JobsTableFilters renders correctly against snapshot 1`] = ` "isInstalled": true, "isSingleMetricViewerJob": true, "jobState": "closed", + "jobTags": Object {}, "latestResultsTimestampMs": 1571022900000, "latestTimestampMs": 1571022859393, "memory_status": "ok", @@ -76,6 +77,7 @@ exports[`JobsTableFilters renders correctly against snapshot 1`] = ` "isInstalled": true, "isSingleMetricViewerJob": true, "jobState": "closed", + "jobTags": Object {}, "memory_status": "ok", "moduleId": "siem_auditbeat", "processed_record_count": 0, @@ -99,6 +101,7 @@ exports[`JobsTableFilters renders correctly against snapshot 1`] = ` "isInstalled": false, "isSingleMetricViewerJob": false, "jobState": "", + "jobTags": Object {}, "memory_status": "", "moduleId": "siem_winlogbeat", "processed_record_count": 0, diff --git a/x-pack/plugins/security_solution/public/detections/components/host_isolation/index.tsx b/x-pack/plugins/security_solution/public/detections/components/host_isolation/index.tsx index 2ca8416841497..42d53f97d478b 100644 --- a/x-pack/plugins/security_solution/public/detections/components/host_isolation/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/host_isolation/index.tsx @@ -26,9 +26,9 @@ export const HostIsolationPanel = React.memo( cancelCallback: () => void; isolateAction: string; }) => { - const agentId = useMemo(() => { - const findAgentId = find({ category: 'agent', field: 'agent.id' }, details)?.values; - return findAgentId ? findAgentId[0] : ''; + const endpointId = useMemo(() => { + const findEndpointId = find({ category: 'agent', field: 'agent.id' }, details)?.values; + return findEndpointId ? findEndpointId[0] : ''; }, [details]); const hostName = useMemo(() => { @@ -87,7 +87,7 @@ export const HostIsolationPanel = React.memo( return isolateAction === 'isolateHost' ? ( ) : ( { const hostIsolated = await isolateHost(); diff --git a/x-pack/plugins/security_solution/public/detections/components/host_isolation/unisolate.tsx b/x-pack/plugins/security_solution/public/detections/components/host_isolation/unisolate.tsx index e72a0d2de61bc..71f7cadda2f68 100644 --- a/x-pack/plugins/security_solution/public/detections/components/host_isolation/unisolate.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/host_isolation/unisolate.tsx @@ -18,13 +18,13 @@ import { useHostUnisolation } from '../../containers/detection_engine/alerts/use export const UnisolateHost = React.memo( ({ - agentId, + endpointId, hostName, cases, caseIds, cancelCallback, }: { - agentId: string; + endpointId: string; hostName: string; cases: ReactNode; caseIds: string[]; @@ -33,7 +33,7 @@ export const UnisolateHost = React.memo( const [comment, setComment] = useState(''); const [isUnIsolated, setIsUnIsolated] = useState(false); - const { loading, unIsolateHost } = useHostUnisolation({ agentId, comment, caseIds }); + const { loading, unIsolateHost } = useHostUnisolation({ endpointId, comment, caseIds }); const confirmHostUnIsolation = useCallback(async () => { const hostIsolated = await unIsolateHost(); diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/api.test.ts b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/api.test.ts index 0c3159e0719e6..b944cb640b719 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/api.test.ts +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/api.test.ts @@ -178,19 +178,19 @@ describe('Detections Alerts API', () => { test('check parameter url', async () => { await createHostIsolation({ - agentId: 'fd8a122b-4c54-4c05-b295-e5f8381fc59d', + endpointId: 'fd8a122b-4c54-4c05-b295-e5f8381fc59d', comment: 'commento', caseIds: ['88c04a90-b19c-11eb-b838-bf3c7840b969'], }); expect(postMock).toHaveBeenCalledWith('/api/endpoint/isolate', { body: - '{"agent_ids":["fd8a122b-4c54-4c05-b295-e5f8381fc59d"],"comment":"commento","case_ids":["88c04a90-b19c-11eb-b838-bf3c7840b969"]}', + '{"endpoint_ids":["fd8a122b-4c54-4c05-b295-e5f8381fc59d"],"comment":"commento","case_ids":["88c04a90-b19c-11eb-b838-bf3c7840b969"]}', }); }); test('happy path', async () => { const hostIsolationResponse = await createHostIsolation({ - agentId: 'fd8a122b-4c54-4c05-b295-e5f8381fc59d', + endpointId: 'fd8a122b-4c54-4c05-b295-e5f8381fc59d', comment: 'commento', caseIds: ['88c04a90-b19c-11eb-b838-bf3c7840b969'], }); diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/api.ts b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/api.ts index 3baa6580b36fb..05706981a681d 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/api.ts +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/api.ts @@ -7,7 +7,7 @@ import { UpdateDocumentByQueryResponse } from 'elasticsearch'; import { getCasesFromAlertsUrl } from '../../../../../../cases/common'; -import { HostIsolationResponse, HostMetadataInfo } from '../../../../../common/endpoint/types'; +import { HostIsolationResponse, HostInfo } from '../../../../../common/endpoint/types'; import { DETECTION_ENGINE_QUERY_SIGNALS_URL, DETECTION_ENGINE_SIGNALS_STATUS_URL, @@ -118,16 +118,16 @@ export const createSignalIndex = async ({ signal }: BasicSignals): Promise => isolateHost({ - agent_ids: [agentId], + endpoint_ids: [endpointId], comment, case_ids: caseIds, }); @@ -142,16 +142,16 @@ export const createHostIsolation = async ({ * @throws An error if response is not OK */ export const createHostUnIsolation = async ({ - agentId, + endpointId, comment = '', caseIds, }: { - agentId: string; + endpointId: string; comment?: string; caseIds?: string[]; }): Promise => unIsolateHost({ - agent_ids: [agentId], + endpoint_ids: [endpointId], comment, case_ids: caseIds, }); @@ -178,12 +178,8 @@ export const getCaseIdsFromAlertId = async ({ * * @param host id */ -export const getHostMetadata = async ({ - agentId, -}: { - agentId: string; -}): Promise => - KibanaServices.get().http.fetch( +export const getHostMetadata = async ({ agentId }: { agentId: string }): Promise => + KibanaServices.get().http.fetch( resolvePathVariables(HOST_METADATA_GET_ROUTE, { id: agentId }), { method: 'get' } ); diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/use_host_isolation.tsx b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/use_host_isolation.tsx index ad3c6e91c03fe..12426e05ba528 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/use_host_isolation.tsx +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/use_host_isolation.tsx @@ -16,13 +16,13 @@ interface HostIsolationStatus { } interface UseHostIsolationProps { - agentId: string; + endpointId: string; comment: string; caseIds?: string[]; } export const useHostIsolation = ({ - agentId, + endpointId, comment, caseIds, }: UseHostIsolationProps): HostIsolationStatus => { @@ -32,7 +32,7 @@ export const useHostIsolation = ({ const isolateHost = useCallback(async () => { try { setLoading(true); - const isolationStatus = await createHostIsolation({ agentId, comment, caseIds }); + const isolationStatus = await createHostIsolation({ endpointId, comment, caseIds }); setLoading(false); return isolationStatus.action ? true : false; } catch (error) { @@ -40,6 +40,6 @@ export const useHostIsolation = ({ addError(error.message, { title: HOST_ISOLATION_FAILURE }); return false; } - }, [agentId, comment, caseIds, addError]); + }, [endpointId, comment, caseIds, addError]); return { loading, isolateHost }; }; diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/use_host_isolation_status.tsx b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/use_host_isolation_status.tsx index f7894d4764275..7419727fff6a2 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/use_host_isolation_status.tsx +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/use_host_isolation_status.tsx @@ -7,25 +7,27 @@ import { isEmpty } from 'lodash'; import { useEffect, useState } from 'react'; -import { Maybe } from '../../../../../../observability/common/typings'; import { useAppToasts } from '../../../../common/hooks/use_app_toasts'; import { getHostMetadata } from './api'; import { ISOLATION_STATUS_FAILURE } from './translations'; import { isEndpointHostIsolated } from '../../../../common/utils/validators'; +import { HostStatus } from '../../../../../common/endpoint/types'; interface HostIsolationStatusResponse { loading: boolean; - isIsolated: Maybe; + isIsolated: boolean; + agentStatus: HostStatus; } /* - * Retrieves the current isolation status of a host */ + * Retrieves the current isolation status of a host and the agent/host status */ export const useHostIsolationStatus = ({ agentId, }: { agentId: string; }): HostIsolationStatusResponse => { - const [isIsolated, setIsIsolated] = useState>(); + const [isIsolated, setIsIsolated] = useState(false); + const [agentStatus, setAgentStatus] = useState(HostStatus.UNHEALTHY); const [loading, setLoading] = useState(false); const { addError } = useAppToasts(); @@ -38,6 +40,7 @@ export const useHostIsolationStatus = ({ const metadataResponse = await getHostMetadata({ agentId }); if (isMounted) { setIsIsolated(isEndpointHostIsolated(metadataResponse.metadata)); + setAgentStatus(metadataResponse.host_status); } } catch (error) { addError(error.message, { title: ISOLATION_STATUS_FAILURE }); @@ -61,5 +64,5 @@ export const useHostIsolationStatus = ({ isMounted = false; }; }, [addError, agentId]); - return { loading, isIsolated }; + return { loading, isIsolated, agentStatus }; }; diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/use_host_unisolation.tsx b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/use_host_unisolation.tsx index 1a0ecb0d15878..55119f7122e12 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/use_host_unisolation.tsx +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/use_host_unisolation.tsx @@ -16,13 +16,13 @@ interface HostUnisolationStatus { } interface UseHostIsolationProps { - agentId: string; + endpointId: string; comment: string; caseIds?: string[]; } export const useHostUnisolation = ({ - agentId, + endpointId, comment, caseIds, }: UseHostIsolationProps): HostUnisolationStatus => { @@ -32,7 +32,7 @@ export const useHostUnisolation = ({ const unIsolateHost = useCallback(async () => { try { setLoading(true); - const isolationStatus = await createHostUnIsolation({ agentId, comment, caseIds }); + const isolationStatus = await createHostUnIsolation({ endpointId, comment, caseIds }); setLoading(false); return isolationStatus.action ? true : false; } catch (error) { @@ -40,6 +40,6 @@ export const useHostUnisolation = ({ addError(error.message, { title: HOST_ISOLATION_FAILURE }); return false; } - }, [agentId, comment, caseIds, addError]); + }, [endpointId, comment, caseIds, addError]); return { loading, unIsolateHost }; }; diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/endpoint_activity_log.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/endpoint_activity_log.tsx index 4395e3965ea00..55479845bce0a 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/endpoint_activity_log.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/endpoint_activity_log.tsx @@ -10,8 +10,6 @@ import React, { memo, useCallback } from 'react'; import { EuiButton, EuiEmptyPrompt, EuiLoadingContent, EuiSpacer } from '@elastic/eui'; import { useDispatch } from 'react-redux'; import { LogEntry } from './components/log_entry'; -import * as i18 from '../translations'; -import { SearchBar } from '../../../../components/search_bar'; import { Immutable, ActivityLog } from '../../../../../../common/endpoint/types'; import { AsyncResourceState } from '../../../../state'; import { useEndpointSelector } from '../hooks'; @@ -32,8 +30,6 @@ export const EndpointActivityLog = memo( const activityLogError = useEndpointSelector(getActivityLogError); const dispatch = useDispatch<(a: EndpointAction) => void>(); const { page, pageSize } = useEndpointSelector(getActivityLogDataPaging); - // TODO - const onSearch = useCallback(() => {}, []); const getActivityLog = useCallback(() => { dispatch({ @@ -57,7 +53,6 @@ export const EndpointActivityLog = memo( /> ) : ( <> - {activityLogLoading ? ( diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/agent_statuses.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/agent_statuses.tsx new file mode 100644 index 0000000000000..16f11809dd72b --- /dev/null +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/agent_statuses.tsx @@ -0,0 +1,56 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import { DefaultDraggable } from '../../../../../common/components/draggables'; +import { EndpointHostIsolationStatus } from '../../../../../common/components/endpoint/host_isolation'; +import { useHostIsolationStatus } from '../../../../../detections/containers/detection_engine/alerts/use_host_isolation_status'; +import { AgentStatus } from '../../../../../common/components/endpoint/agent_status'; + +export const AgentStatuses = React.memo( + ({ + fieldName, + contextId, + eventId, + value, + }: { + fieldName: string; + contextId: string; + eventId: string; + value: string; + }) => { + const { isIsolated, agentStatus } = useHostIsolationStatus({ agentId: value }); + const isolationFieldName = 'host.isolation'; + return ( + + + + + + + + + + + + + ); + } +); + +AgentStatuses.displayName = 'AgentStatuses'; diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/constants.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/constants.tsx index b1ef634fe052f..761d82b482af2 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/constants.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/constants.tsx @@ -16,3 +16,4 @@ export const REFERENCE_URL_FIELD_NAME = 'reference.url'; export const EVENT_URL_FIELD_NAME = 'event.url'; export const SIGNAL_RULE_NAME_FIELD_NAME = 'signal.rule.name'; export const SIGNAL_STATUS_FIELD_NAME = 'signal.status'; +export const HOST_STATUS_FIELD_NAME = 'host.status'; diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/formatted_field.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/formatted_field.tsx index 12effcd3fa81f..efb51916e3765 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/formatted_field.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/formatted_field.tsx @@ -5,6 +5,8 @@ * 2.0. */ +/* eslint-disable complexity */ + import { EuiFlexGroup, EuiFlexItem, EuiToolTip } from '@elastic/eui'; import { isNumber, isEmpty } from 'lodash/fp'; import React from 'react'; @@ -30,11 +32,13 @@ import { REFERENCE_URL_FIELD_NAME, EVENT_URL_FIELD_NAME, SIGNAL_STATUS_FIELD_NAME, + HOST_STATUS_FIELD_NAME, GEO_FIELD_TYPE, } from './constants'; import { RenderRuleName, renderEventModule, renderUrl } from './formatted_field_helpers'; import { RuleStatus } from './rule_status'; import { HostName } from './host_name'; +import { AgentStatuses } from './agent_statuses'; // simple black-list to prevent dragging and dropping fields such as message name const columnNamesNotDraggable = [MESSAGE_FIELD_NAME]; @@ -116,6 +120,15 @@ const FormattedFieldValueComponent: React.FC<{ return ( ); + } else if (fieldName === HOST_STATUS_FIELD_NAME) { + return ( + + ); } else if ( [ RULE_REFERENCE_FIELD_NAME, diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/resolver/tree/queries/stats.ts b/x-pack/plugins/security_solution/server/endpoint/routes/resolver/tree/queries/stats.ts index f21259980d464..b64390f4e382f 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/resolver/tree/queries/stats.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/resolver/tree/queries/stats.ts @@ -128,7 +128,7 @@ export class StatsQuery { index: this.indexPatterns, }); - // @ts-expect-error @elastic/elasticsearch no way to declare a type for aggregation in the search response + // @ts-expect-error declare aggegations type explicitly return response.body.aggregations?.ids?.buckets.reduce( (cummulative: Record, bucket: CategoriesAgg) => ({ ...cummulative, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/migrations/get_signal_versions_by_index.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/migrations/get_signal_versions_by_index.ts index 784164e430ff0..decde16d77a38 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/migrations/get_signal_versions_by_index.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/migrations/get_signal_versions_by_index.ts @@ -72,9 +72,8 @@ export const getSignalVersionsByIndex = async ({ }, }); - // @ts-expect-error @elastic/elasticsearch no way to declare a type for aggregation in the search response - const body = response.body as SignalVersionsAggResponse; - const indexBuckets = body.aggregations.signals_indices.buckets; + const aggs = response.body.aggregations as SignalVersionsAggResponse['aggregations']; + const indexBuckets = aggs.signals_indices.buckets; return index.reduce((agg, _index) => { const bucket = indexBuckets.find((ib) => ib.key === _index); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/migrations/get_signals_indices_in_range.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/migrations/get_signals_indices_in_range.ts index 3c9132fc81279..af236b10d0795 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/migrations/get_signals_indices_in_range.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/migrations/get_signals_indices_in_range.ts @@ -72,7 +72,6 @@ export const getSignalsIndicesInRange = async ({ }, }); - // @ts-expect-error @elastic/elasticsearch no way to declare a type for aggregation in the search response - const body = response.body as IndexesResponse; - return body.aggregations.indexes.buckets.map((bucket) => bucket.key); + const aggs = response.body.aggregations as IndexesResponse['aggregations']; + return aggs.indexes.buckets.map((bucket) => bucket.key); }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/reference_rules/query.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/reference_rules/query.ts index 04bfa78f883f0..39f325fd6cf8f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/reference_rules/query.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/reference_rules/query.ts @@ -8,7 +8,7 @@ import type { estypes } from '@elastic/elasticsearch'; import { schema } from '@kbn/config-schema'; import { Logger } from '@kbn/logging'; -import { ESSearchRequest } from 'typings/elasticsearch'; +import { ESSearchRequest } from 'src/core/types/elasticsearch'; import { buildEsQuery, IIndexPattern } from '../../../../../../../src/plugins/data/common'; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/eql.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/eql.test.ts index e7af3d484dfbd..a1d7d03f313db 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/eql.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/eql.test.ts @@ -78,6 +78,8 @@ describe('eql_executor', () => { logger, searchAfterSize, bulkCreate: jest.fn(), + wrapHits: jest.fn(), + wrapSequences: jest.fn(), }); expect(response.warningMessages.length).toEqual(1); }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/eql.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/eql.ts index a187b73069682..e08f519e9761a 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/eql.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/eql.ts @@ -21,18 +21,19 @@ import { isOutdated } from '../../migrations/helpers'; import { getIndexVersion } from '../../routes/index/get_index_version'; import { MIN_EQL_RULE_INDEX_VERSION } from '../../routes/index/get_signals_template'; import { EqlRuleParams } from '../../schemas/rule_schemas'; -import { buildSignalFromEvent, buildSignalGroupFromSequence } from '../build_bulk_body'; import { getInputIndex } from '../get_input_output_index'; -import { filterDuplicateSignals } from '../filter_duplicate_signals'; + import { AlertAttributes, BulkCreate, + WrapHits, + WrapSequences, EqlSignalSearchResponse, RuleRangeTuple, SearchAfterAndBulkCreateReturnType, - WrappedSignalHit, + SimpleHit, } from '../types'; -import { createSearchAfterReturnType, makeFloatString, wrapSignal } from '../utils'; +import { createSearchAfterReturnType, makeFloatString } from '../utils'; export const eqlExecutor = async ({ rule, @@ -43,6 +44,8 @@ export const eqlExecutor = async ({ logger, searchAfterSize, bulkCreate, + wrapHits, + wrapSequences, }: { rule: SavedObject>; tuple: RuleRangeTuple; @@ -52,6 +55,8 @@ export const eqlExecutor = async ({ logger: Logger; searchAfterSize: number; bulkCreate: BulkCreate; + wrapHits: WrapHits; + wrapSequences: WrapSequences; }): Promise => { const result = createSearchAfterReturnType(); const ruleParams = rule.attributes.params; @@ -104,27 +109,18 @@ export const eqlExecutor = async ({ const eqlSignalSearchEnd = performance.now(); const eqlSearchDuration = makeFloatString(eqlSignalSearchEnd - eqlSignalSearchStart); result.searchAfterTimes = [eqlSearchDuration]; - let newSignals: WrappedSignalHit[] | undefined; + let newSignals: SimpleHit[] | undefined; if (response.hits.sequences !== undefined) { - newSignals = response.hits.sequences.reduce( - (acc: WrappedSignalHit[], sequence) => - acc.concat(buildSignalGroupFromSequence(sequence, rule, ruleParams.outputIndex)), - [] - ); + newSignals = wrapSequences(response.hits.sequences); } else if (response.hits.events !== undefined) { - newSignals = filterDuplicateSignals( - rule.id, - response.hits.events.map((event) => - wrapSignal(buildSignalFromEvent(event, rule, true), ruleParams.outputIndex) - ) - ); + newSignals = wrapHits(response.hits.events); } else { throw new Error( 'eql query response should have either `sequences` or `events` but had neither' ); } - if (newSignals.length > 0) { + if (newSignals?.length) { const insertResult = await bulkCreate(newSignals); result.bulkCreateTimes.push(insertResult.bulkCreateDuration); result.createdSignalsCount += insertResult.createdItemsCount; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/filter_duplicate_signals.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/filter_duplicate_signals.test.ts index 5c4af83c3b03e..0098d50fc01ef 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/filter_duplicate_signals.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/filter_duplicate_signals.test.ts @@ -36,11 +36,13 @@ const mockSignals = [ ]; describe('filterDuplicateSignals', () => { - it('filters duplicate signals', () => { - expect(filterDuplicateSignals(mockRuleId1, mockSignals).length).toEqual(1); - }); + describe('detection engine implementation', () => { + it('filters duplicate signals', () => { + expect(filterDuplicateSignals(mockRuleId1, mockSignals, false).length).toEqual(1); + }); - it('does not filter non-duplicate signals', () => { - expect(filterDuplicateSignals(mockRuleId3, mockSignals).length).toEqual(2); + it('does not filter non-duplicate signals', () => { + expect(filterDuplicateSignals(mockRuleId3, mockSignals, false).length).toEqual(2); + }); }); }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/filter_duplicate_signals.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/filter_duplicate_signals.ts index a648c05306289..0b9859fad7688 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/filter_duplicate_signals.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/filter_duplicate_signals.ts @@ -5,10 +5,26 @@ * 2.0. */ -import { WrappedSignalHit } from './types'; +import { SimpleHit, WrappedSignalHit } from './types'; -export const filterDuplicateSignals = (ruleId: string, signals: WrappedSignalHit[]) => { - return signals.filter( - (doc) => !doc._source.signal?.ancestors.some((ancestor) => ancestor.rule === ruleId) - ); +const isWrappedSignalHit = ( + signals: SimpleHit[], + isRuleRegistryEnabled: boolean +): signals is WrappedSignalHit[] => { + return !isRuleRegistryEnabled; +}; + +export const filterDuplicateSignals = ( + ruleId: string, + signals: SimpleHit[], + isRuleRegistryEnabled: boolean +) => { + if (isWrappedSignalHit(signals, isRuleRegistryEnabled)) { + return signals.filter( + (doc) => !doc._source.signal?.ancestors.some((ancestor) => ancestor.rule === ruleId) + ); + } else { + // TODO: filter duplicate signals for RAC + return []; + } }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/search_after_bulk_create.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/search_after_bulk_create.test.ts index 184b49c2d6c7b..21c1402861e6e 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/search_after_bulk_create.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/search_after_bulk_create.test.ts @@ -66,7 +66,10 @@ describe('searchAfterAndBulkCreate', () => { buildRuleMessage, false ); - wrapHits = wrapHitsFactory({ ruleSO, signalsIndex: DEFAULT_SIGNALS_INDEX }); + wrapHits = wrapHitsFactory({ + ruleSO, + signalsIndex: DEFAULT_SIGNALS_INDEX, + }); }); test('should return success with number of searches less than max signals', async () => { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.ts index bb1e50c14d401..32bd6d71bfb1d 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.ts @@ -67,6 +67,7 @@ import { } from '../schemas/rule_schemas'; import { bulkCreateFactory } from './bulk_create_factory'; import { wrapHitsFactory } from './wrap_hits_factory'; +import { wrapSequencesFactory } from './wrap_sequences_factory'; export const signalRulesAlertType = ({ logger, @@ -233,6 +234,11 @@ export const signalRulesAlertType = ({ signalsIndex: params.outputIndex, }); + const wrapSequences = wrapSequencesFactory({ + ruleSO: savedObject, + signalsIndex: params.outputIndex, + }); + if (isMlRule(type)) { const mlRuleSO = asTypeSpecificSO(savedObject, machineLearningRuleParams); for (const tuple of tuples) { @@ -313,6 +319,8 @@ export const signalRulesAlertType = ({ searchAfterSize, bulkCreate, logger, + wrapHits, + wrapSequences, }); } } else { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threshold/bulk_create_threshold_signals.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threshold/bulk_create_threshold_signals.ts index 08fa2f14a0fd5..f56ed3a5e9eb4 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threshold/bulk_create_threshold_signals.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threshold/bulk_create_threshold_signals.ts @@ -4,7 +4,6 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ - import { get } from 'lodash/fp'; import set from 'set-value'; import { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threshold/get_threshold_bucket_filters.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threshold/get_threshold_bucket_filters.ts index 4dd21938690db..e6a188a20b5d5 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threshold/get_threshold_bucket_filters.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threshold/get_threshold_bucket_filters.ts @@ -6,7 +6,7 @@ */ import { Filter } from 'src/plugins/data/common'; -import { ESFilter } from '../../../../../../../../typings/elasticsearch'; +import { ESFilter } from '../../../../../../../../src/core/types/elasticsearch'; import { ThresholdSignalHistory, ThresholdSignalHistoryRecord } from '../types'; export const getThresholdBucketFilters = async ({ diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/types.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/types.ts index 8a6ce91b2575a..c399454b9888b 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/types.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/types.ts @@ -25,6 +25,7 @@ import { BaseHit, RuleAlertAction, SearchTypes, + EqlSequence, } from '../../../../common/detection_engine/types'; import { ListClient } from '../../../../../lists/server'; import { Logger, SavedObject } from '../../../../../../../src/core/server'; @@ -257,9 +258,11 @@ export type SignalsEnrichment = (signals: SignalSearchResponse) => Promise(docs: Array>) => Promise>; -export type WrapHits = ( - hits: Array> -) => Array>; +export type SimpleHit = BaseHit<{ '@timestamp': string }>; + +export type WrapHits = (hits: Array>) => SimpleHit[]; + +export type WrapSequences = (sequences: Array>) => SimpleHit[]; export interface SearchAfterAndBulkCreateParams { tuple: { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/wrap_hits_factory.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/wrap_hits_factory.ts index 3f3e4ef3631bd..d5c05bc890332 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/wrap_hits_factory.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/wrap_hits_factory.ts @@ -5,12 +5,7 @@ * 2.0. */ -import { - SearchAfterAndBulkCreateParams, - SignalSourceHit, - WrapHits, - WrappedSignalHit, -} from './types'; +import { SearchAfterAndBulkCreateParams, WrapHits, WrappedSignalHit } from './types'; import { generateId } from './utils'; import { buildBulkBody } from './build_bulk_body'; import { filterDuplicateSignals } from './filter_duplicate_signals'; @@ -25,11 +20,15 @@ export const wrapHitsFactory = ({ const wrappedDocs: WrappedSignalHit[] = events.flatMap((doc) => [ { _index: signalsIndex, - // TODO: bring back doc._version - _id: generateId(doc._index, doc._id, '', ruleSO.attributes.params.ruleId ?? ''), - _source: buildBulkBody(ruleSO, doc as SignalSourceHit), + _id: generateId( + doc._index, + doc._id, + String(doc._version), + ruleSO.attributes.params.ruleId ?? '' + ), + _source: buildBulkBody(ruleSO, doc), }, ]); - return filterDuplicateSignals(ruleSO.id, wrappedDocs); + return filterDuplicateSignals(ruleSO.id, wrappedDocs, false); }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/wrap_sequences_factory.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/wrap_sequences_factory.ts new file mode 100644 index 0000000000000..c53ea7b7ebe72 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/wrap_sequences_factory.ts @@ -0,0 +1,24 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { SearchAfterAndBulkCreateParams, WrappedSignalHit, WrapSequences } from './types'; +import { buildSignalGroupFromSequence } from './build_bulk_body'; + +export const wrapSequencesFactory = ({ + ruleSO, + signalsIndex, +}: { + ruleSO: SearchAfterAndBulkCreateParams['ruleSO']; + signalsIndex: string; +}): WrapSequences => (sequences) => + sequences.reduce( + (acc: WrappedSignalHit[], sequence) => [ + ...acc, + ...buildSignalGroupFromSequence(sequence, ruleSO, signalsIndex), + ], + [] + ); diff --git a/x-pack/plugins/security_solution/server/lib/machine_learning/index.test.ts b/x-pack/plugins/security_solution/server/lib/machine_learning/index.test.ts index 30dd5adf6123b..41f6833743797 100644 --- a/x-pack/plugins/security_solution/server/lib/machine_learning/index.test.ts +++ b/x-pack/plugins/security_solution/server/lib/machine_learning/index.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { ESFilter } from '../../../../../../typings/elasticsearch'; +import { ESFilter } from '../../../../../../src/core/types/elasticsearch'; import { getExceptionListItemSchemaMock } from '../../../../lists/common/schemas/response/exception_list_item_schema.mock'; import { getAnomalies, AnomaliesSearchParams } from '.'; diff --git a/x-pack/plugins/security_solution/server/plugin.ts b/x-pack/plugins/security_solution/server/plugin.ts index a0f466512cc1d..7e4d0989af413 100644 --- a/x-pack/plugins/security_solution/server/plugin.ts +++ b/x-pack/plugins/security_solution/server/plugin.ts @@ -208,8 +208,10 @@ export class Plugin implements IPlugin core.getStartServices().then(([coreStart]) => coreStart); @@ -293,7 +295,7 @@ export class Plugin implements IPlugin = { options: HostOverviewRequestOptions, response: IEsSearchResponse ): Promise => { - // @ts-expect-error @elastic/elasticsearch no way to declare type for aggregations + // @ts-expect-error specify aggregations type explicitly const aggregations: OverviewHostHit = get('aggregations', response.rawResponse) || {}; const inspect = { dsl: [inspectStringifyObject(buildOverviewHostQuery(options))], diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/overview/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/overview/index.ts index 1f85a119f3c8e..069125c6700eb 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/overview/index.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/overview/index.ts @@ -24,7 +24,7 @@ export const networkOverview: SecuritySolutionFactory = options: NetworkOverviewRequestOptions, response: IEsSearchResponse ): Promise => { - // @ts-expect-error @elastic/elasticsearch no way to declare type for aggregations + // @ts-expect-error specify aggregations type explicitly const aggregations: OverviewNetworkHit = get('aggregations', response.rawResponse) || {}; const inspect = { dsl: [inspectStringifyObject(buildOverviewNetworkQuery(options))], diff --git a/x-pack/plugins/snapshot_restore/server/routes/api/snapshots.ts b/x-pack/plugins/snapshot_restore/server/routes/api/snapshots.ts index 8ce33cb8cd05b..af9c08f76f6f7 100644 --- a/x-pack/plugins/snapshot_restore/server/routes/api/snapshots.ts +++ b/x-pack/plugins/snapshot_restore/server/routes/api/snapshots.ts @@ -73,12 +73,10 @@ export function registerSnapshotsRoutes({ ignore_unavailable: true, // Allow request to succeed even if some snapshots are unavailable. }); - const { responses: fetchedResponses } = response.body; + const { responses: fetchedResponses = [] } = response.body; // Decorate each snapshot with the repository with which it's associated. - // @ts-expect-error @elastic/elasticsearch related to above incorrect type from client - fetchedResponses.forEach(({ snapshots: fetchedSnapshots }) => { - // @ts-expect-error @elastic/elasticsearch related to above incorrect type from client + fetchedResponses.forEach(({ snapshots: fetchedSnapshots = [] }) => { fetchedSnapshots.forEach((snapshot) => { snapshots.push( deserializeSnapshotDetails( diff --git a/x-pack/plugins/stack_alerts/common/build_sorted_events_query.ts b/x-pack/plugins/stack_alerts/common/build_sorted_events_query.ts index 455307cb73a09..83421056229cf 100644 --- a/x-pack/plugins/stack_alerts/common/build_sorted_events_query.ts +++ b/x-pack/plugins/stack_alerts/common/build_sorted_events_query.ts @@ -5,7 +5,7 @@ * 2.0. */ import { estypes } from '@elastic/elasticsearch'; -import type { ESSearchRequest } from '../../../../typings/elasticsearch'; +import type { ESSearchRequest } from '../../../../src/core/types/elasticsearch'; interface BuildSortedEventsQueryOpts { aggs?: Record; diff --git a/x-pack/plugins/stack_alerts/server/alert_types/es_query/alert_type.test.ts b/x-pack/plugins/stack_alerts/server/alert_types/es_query/alert_type.test.ts index d195534d93f58..5b450ceba192a 100644 --- a/x-pack/plugins/stack_alerts/server/alert_types/es_query/alert_type.test.ts +++ b/x-pack/plugins/stack_alerts/server/alert_types/es_query/alert_type.test.ts @@ -17,7 +17,7 @@ import { loggingSystemMock } from '../../../../../../src/core/server/mocks'; import { getAlertType, ConditionMetAlertInstanceId, ActionGroupId } from './alert_type'; import { EsQueryAlertParams, EsQueryAlertState } from './alert_type_params'; import { ActionContext } from './action_context'; -import { ESSearchResponse, ESSearchRequest } from '../../../../../../typings/elasticsearch'; +import { ESSearchResponse, ESSearchRequest } from '../../../../../../src/core/types/elasticsearch'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { elasticsearchClientMock } from '../../../../../../src/core/server/elasticsearch/client/mocks'; diff --git a/x-pack/plugins/task_manager/server/monitoring/workload_statistics.test.ts b/x-pack/plugins/task_manager/server/monitoring/workload_statistics.test.ts index e88144f2b4a35..3fe003ebc6591 100644 --- a/x-pack/plugins/task_manager/server/monitoring/workload_statistics.test.ts +++ b/x-pack/plugins/task_manager/server/monitoring/workload_statistics.test.ts @@ -788,11 +788,11 @@ describe('padBuckets', () => { padBuckets(10, 3000, { key: '2020-10-02T19:47:28.128Z-2020-10-02T19:48:28.128Z', from: 1601668048128, - // @ts-expect-error @elastic/elasticsearch doesn't decalre from_as_string property from_as_string: '2020-10-02T19:47:28.128Z', to: 1601668108128, to_as_string: '2020-10-02T19:48:28.128Z', doc_count: 0, + // @ts-expect-error result type doesn't define histogram histogram: { buckets: [], }, @@ -805,11 +805,11 @@ describe('padBuckets', () => { padBuckets(10, 3000, { key: '2020-10-02T19:47:28.128Z-2020-10-02T19:48:28.128Z', from: 1601668046000, - // @ts-expect-error @elastic/elasticsearch doesn't decalre from_as_string property from_as_string: '2020-10-02T19:47:26.000Z', to: 1601668076000, to_as_string: '2020-10-02T19:47:56.000Z', doc_count: 3, + // @ts-expect-error result type doesn't define histogram histogram: { buckets: [ { @@ -883,11 +883,11 @@ describe('padBuckets', () => { padBuckets(10, 3000, { key: '2020-10-02T20:39:45.793Z-2020-10-02T20:40:14.793Z', from: 1601671183000, - // @ts-expect-error @elastic/elasticsearch doesn't decalre from_as_string property from_as_string: '2020-10-02T20:39:43.000Z', to: 1601671213000, to_as_string: '2020-10-02T20:40:13.000Z', doc_count: 2, + // @ts-expect-error result type doesn't define histogram histogram: { buckets: [ { @@ -913,11 +913,11 @@ describe('padBuckets', () => { padBuckets(20, 3000, { key: '2020-10-02T20:39:45.793Z-2020-10-02T20:40:14.793Z', from: 1601671185793, - // @ts-expect-error @elastic/elasticsearch doesn't decalre from_as_string property from_as_string: '2020-10-02T20:39:45.793Z', to: 1601671245793, to_as_string: '2020-10-02T20:40:45.793Z', doc_count: 2, + // @ts-expect-error result type doesn't define histogram histogram: { buckets: [ { @@ -943,11 +943,11 @@ describe('padBuckets', () => { padBuckets(20, 3000, { key: '2021-02-02T10:08:32.161Z-2021-02-02T10:09:32.161Z', from: 1612260512161, - // @ts-expect-error @elastic/elasticsearch doesn't decalre from_as_string property from_as_string: '2021-02-02T10:08:32.161Z', to: 1612260572161, to_as_string: '2021-02-02T10:09:32.161Z', doc_count: 2, + // @ts-expect-error result type doesn't define histogram histogram: { buckets: [ { diff --git a/x-pack/plugins/task_manager/server/monitoring/workload_statistics.ts b/x-pack/plugins/task_manager/server/monitoring/workload_statistics.ts index abd86be522f0c..64c1c66140196 100644 --- a/x-pack/plugins/task_manager/server/monitoring/workload_statistics.ts +++ b/x-pack/plugins/task_manager/server/monitoring/workload_statistics.ts @@ -13,7 +13,7 @@ import { keyBy, mapValues } from 'lodash'; import { estypes } from '@elastic/elasticsearch'; import { AggregatedStatProvider } from './runtime_statistics_aggregator'; import { parseIntervalAsSecond, asInterval, parseIntervalAsMillisecond } from '../lib/intervals'; -import { AggregationResultOf } from '../../../../../typings/elasticsearch'; +import { AggregationResultOf } from '../../../../../src/core/types/elasticsearch'; import { HealthStatus } from './monitoring_stats_stream'; import { TaskStore } from '../task_store'; import { createRunningAveragedStat } from './task_run_calcultors'; diff --git a/x-pack/plugins/task_manager/server/task_store.ts b/x-pack/plugins/task_manager/server/task_store.ts index ce01660134683..0a8335ebe98f3 100644 --- a/x-pack/plugins/task_manager/server/task_store.ts +++ b/x-pack/plugins/task_manager/server/task_store.ts @@ -319,9 +319,9 @@ export class TaskStore { return { docs: tasks - // @ts-expect-error @elastic/elasticsearch `Hid._id` expected to be `string` + // @ts-expect-error @elastic/elasticsearch _source is optional .filter((doc) => this.serializer.isRawSavedObject(doc)) - // @ts-expect-error @elastic/elasticsearch `Hid._id` expected to be `string` + // @ts-expect-error @elastic/elasticsearch _source is optional .map((doc) => this.serializer.rawToSavedObject(doc)) .map((doc) => omit(doc, 'namespace') as SavedObject) .map(savedObjectToConcreteTaskInstance), @@ -379,10 +379,8 @@ export class TaskStore { ); return { - // @ts-expect-error @elastic/elasticsearch declares UpdateByQueryResponse.total as optional - total, - // @ts-expect-error @elastic/elasticsearch declares UpdateByQueryResponse.total as optional - updated, + total: total || 0, + updated: updated || 0, version_conflicts: conflictsCorrectedForContinuation, }; } catch (e) { diff --git a/x-pack/plugins/transform/server/routes/api/transforms.ts b/x-pack/plugins/transform/server/routes/api/transforms.ts index ea2bb28776ac2..aa30a60b3421c 100644 --- a/x-pack/plugins/transform/server/routes/api/transforms.ts +++ b/x-pack/plugins/transform/server/routes/api/transforms.ts @@ -208,7 +208,7 @@ export function registerTransformsRoutes(routeDependencies: RouteDependencies) { await ctx.core.elasticsearch.client.asCurrentUser.transform .putTransform({ - // @ts-expect-error @elastic/elasticsearch max_page_search_size is required in TransformPivot + // @ts-expect-error @elastic/elasticsearch group_by is expected to be optional in TransformPivot body: req.body, transform_id: transformId, }) diff --git a/x-pack/plugins/triggers_actions_ui/server/data/lib/time_series_query.ts b/x-pack/plugins/triggers_actions_ui/server/data/lib/time_series_query.ts index a2ba8d43c9c60..6b2849b7b9670 100644 --- a/x-pack/plugins/triggers_actions_ui/server/data/lib/time_series_query.ts +++ b/x-pack/plugins/triggers_actions_ui/server/data/lib/time_series_query.ts @@ -165,7 +165,7 @@ export function getResultFromEs( delete aggregations.dateAgg; } - // @ts-expect-error @elastic/elasticsearch Aggregate does not specify buckets + // @ts-expect-error specify aggregations type explicitly const groupBuckets = aggregations.groupAgg?.buckets || []; const result: TimeSeriesResult = { results: [], diff --git a/x-pack/plugins/uptime/server/lib/lib.ts b/x-pack/plugins/uptime/server/lib/lib.ts index e79d3c28a7d3a..cf00841313536 100644 --- a/x-pack/plugins/uptime/server/lib/lib.ts +++ b/x-pack/plugins/uptime/server/lib/lib.ts @@ -17,7 +17,7 @@ import { UMBackendFrameworkAdapter } from './adapters'; import { UMLicenseCheck } from './domains'; import { UptimeRequests } from './requests'; import { savedObjectsAdapter } from './saved_objects'; -import { ESSearchResponse } from '../../../../../typings/elasticsearch'; +import { ESSearchResponse } from '../../../../../src/core/types/elasticsearch'; export interface UMDomainLibs { requests: UptimeRequests; diff --git a/x-pack/plugins/uptime/server/lib/requests/get_journey_failed_steps.ts b/x-pack/plugins/uptime/server/lib/requests/get_journey_failed_steps.ts index d14a723d27628..d98e235460167 100644 --- a/x-pack/plugins/uptime/server/lib/requests/get_journey_failed_steps.ts +++ b/x-pack/plugins/uptime/server/lib/requests/get_journey_failed_steps.ts @@ -6,7 +6,7 @@ */ import { QueryDslQueryContainer } from '@elastic/elasticsearch/api/types'; -import { SearchHit } from '../../../../../../typings/elasticsearch'; +import { SearchHit } from '../../../../../../src/core/types/elasticsearch'; import { asMutableArray } from '../../../common/utils/as_mutable_array'; import { UMElasticsearchQueryFn } from '../adapters/framework'; import { Ping } from '../../../common/runtime_types'; diff --git a/x-pack/plugins/uptime/server/lib/requests/get_journey_steps.ts b/x-pack/plugins/uptime/server/lib/requests/get_journey_steps.ts index 2e32670aea276..95aadc776fa76 100644 --- a/x-pack/plugins/uptime/server/lib/requests/get_journey_steps.ts +++ b/x-pack/plugins/uptime/server/lib/requests/get_journey_steps.ts @@ -6,7 +6,7 @@ */ import { QueryDslQueryContainer } from '@elastic/elasticsearch/api/types'; -import { SearchHit } from 'typings/elasticsearch/search'; +import { SearchHit } from 'src/core/types/elasticsearch/search'; import { asMutableArray } from '../../../common/utils/as_mutable_array'; import { UMElasticsearchQueryFn } from '../adapters/framework'; import { Ping } from '../../../common/runtime_types'; diff --git a/x-pack/plugins/uptime/server/lib/requests/get_snapshot_counts.ts b/x-pack/plugins/uptime/server/lib/requests/get_snapshot_counts.ts index 0e47f2a3d56c2..aef01f29f4d57 100644 --- a/x-pack/plugins/uptime/server/lib/requests/get_snapshot_counts.ts +++ b/x-pack/plugins/uptime/server/lib/requests/get_snapshot_counts.ts @@ -9,7 +9,7 @@ import { UMElasticsearchQueryFn } from '../adapters'; import { CONTEXT_DEFAULTS } from '../../../common/constants'; import { Snapshot } from '../../../common/runtime_types'; import { QueryContext } from './search'; -import { ESFilter } from '../../../../../../typings/elasticsearch'; +import { ESFilter } from '../../../../../../src/core/types/elasticsearch'; export interface GetSnapshotCountParams { dateRangeStart: string; diff --git a/x-pack/plugins/uptime/server/lib/requests/search/query_context.ts b/x-pack/plugins/uptime/server/lib/requests/search/query_context.ts index b54515e84289a..d443411ef4c6e 100644 --- a/x-pack/plugins/uptime/server/lib/requests/search/query_context.ts +++ b/x-pack/plugins/uptime/server/lib/requests/search/query_context.ts @@ -10,7 +10,7 @@ import { CursorPagination } from './types'; import { parseRelativeDate } from '../../helper'; import { CursorDirection, SortOrder } from '../../../../common/runtime_types'; import { UptimeESClient } from '../../lib'; -import { ESFilter } from '../../../../../../../typings/elasticsearch'; +import { ESFilter } from '../../../../../../../src/core/types/elasticsearch'; export class QueryContext { callES: UptimeESClient; diff --git a/x-pack/test/api_integration/apis/ml/modules/setup_module.ts b/x-pack/test/api_integration/apis/ml/modules/setup_module.ts index 30dc31ef460c6..6011c38255cdc 100644 --- a/x-pack/test/api_integration/apis/ml/modules/setup_module.ts +++ b/x-pack/test/api_integration/apis/ml/modules/setup_module.ts @@ -1146,8 +1146,8 @@ export default ({ getService }: FtrProviderContext) => { if (testData.requestBody.startDatafeed === true) { await ml.api.waitForADJobRecordCountToBePositive(job.jobId); } - await ml.api.waitForJobState(job.jobId, job.jobState); - await ml.api.waitForDatafeedState(datafeedId, job.datafeedState); + await ml.api.waitForDatafeedState(datafeedId, job.datafeedState, 4 * 60 * 1000); + await ml.api.waitForJobState(job.jobId, job.jobState, 4 * 60 * 1000); // model memory limit should be <= 99mb const { diff --git a/x-pack/test/case_api_integration/common/lib/utils.ts b/x-pack/test/case_api_integration/common/lib/utils.ts index 9ed5d84e54621..63be1736405fc 100644 --- a/x-pack/test/case_api_integration/common/lib/utils.ts +++ b/x-pack/test/case_api_integration/common/lib/utils.ts @@ -45,6 +45,7 @@ import { CasesStatusResponse, CasesConfigurationsResponse, CaseUserActionsResponse, + AlertResponse, } from '../../../../plugins/cases/common/api'; import { getPostCaseRequest, postCollectionReq, postCommentGenAlertReq } from './mock'; import { getCaseUserActionUrl, getSubCasesUrl } from '../../../../plugins/cases/common/api/helpers'; @@ -1102,3 +1103,22 @@ export const pushCase = async ({ return res; }; + +export const getAlertsAttachedToCase = async ({ + supertest, + caseId, + expectedHttpCode = 200, + auth = { user: superUser, space: null }, +}: { + supertest: st.SuperTest; + caseId: string; + expectedHttpCode?: number; + auth?: { user: User; space: string | null }; +}): Promise => { + const { body: theCase } = await supertest + .get(`${getSpaceUrlPrefix(auth?.space)}${CASES_URL}/${caseId}/alerts`) + .auth(auth.user.username, auth.user.password) + .expect(expectedHttpCode); + + return theCase; +}; diff --git a/x-pack/test/case_api_integration/security_and_spaces/tests/common/alerts/get_alerts_attached_to_case.ts b/x-pack/test/case_api_integration/security_and_spaces/tests/common/alerts/get_alerts_attached_to_case.ts new file mode 100644 index 0000000000000..d7b4e82c017db --- /dev/null +++ b/x-pack/test/case_api_integration/security_and_spaces/tests/common/alerts/get_alerts_attached_to_case.ts @@ -0,0 +1,215 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../../../common/ftr_provider_context'; + +import { getPostCaseRequest, postCommentAlertReq } from '../../../../common/lib/mock'; +import { + createCase, + createComment, + deleteAllCaseItems, + getAlertsAttachedToCase, +} from '../../../../common/lib/utils'; +import { + globalRead, + noKibanaPrivileges, + obsOnly, + obsOnlyRead, + obsSec, + obsSecRead, + secOnly, + secOnlyRead, + superUser, +} from '../../../../common/lib/authentication/users'; + +// eslint-disable-next-line import/no-default-export +export default ({ getService }: FtrProviderContext): void => { + const supertest = getService('supertest'); + const es = getService('es'); + + describe('get all alerts attach to a case', () => { + afterEach(async () => { + await deleteAllCaseItems(es); + }); + + it('should return all alerts for the specified case id', async () => { + const theCase = await createCase(supertest, getPostCaseRequest()); + + await createComment({ supertest, caseId: theCase.id, params: postCommentAlertReq }); + const updatedCase = await createComment({ + supertest, + caseId: theCase.id, + params: { ...postCommentAlertReq, alertId: 'test-id-2', index: 'test-index-2' }, + }); + + const alerts = await getAlertsAttachedToCase({ supertest, caseId: theCase.id }); + expect(alerts).to.eql([ + { + id: 'test-id', + index: 'test-index', + attached_at: updatedCase.comments![0].created_at, + }, + { + id: 'test-id-2', + index: 'test-index-2', + attached_at: updatedCase.comments![1].created_at, + }, + ]); + }); + + it('should return a 404 when case does not exist', async () => { + await getAlertsAttachedToCase({ supertest, caseId: 'not-exists', expectedHttpCode: 404 }); + }); + + it('should return a 404 when case id is empty', async () => { + await getAlertsAttachedToCase({ supertest, caseId: '', expectedHttpCode: 404 }); + }); + + describe('rbac', () => { + const supertestWithoutAuth = getService('supertestWithoutAuth'); + + it('should return the correct alert', async () => { + const secOnlyAuth = { user: secOnly, space: 'space1' }; + const obsOnlyAuth = { user: obsOnly, space: 'space1' }; + + const [case1, case2] = await Promise.all([ + createCase(supertestWithoutAuth, getPostCaseRequest(), 200, secOnlyAuth), + createCase( + supertestWithoutAuth, + getPostCaseRequest({ owner: 'observabilityFixture' }), + 200, + obsOnlyAuth + ), + ]); + + const [case2WithComments] = await Promise.all([ + createComment({ + supertest: supertestWithoutAuth, + caseId: case2.id, + params: { ...postCommentAlertReq, alertId: 'test-id-3', owner: 'observabilityFixture' }, + auth: obsOnlyAuth, + }), + createComment({ + supertest: supertestWithoutAuth, + caseId: case1.id, + params: postCommentAlertReq, + auth: secOnlyAuth, + }), + ]); + + // This call cannot be made inside the Promise.all call + // as there will be a race condition between the two calls + // and a 409 version conflict will be thrown + const case1WithComments = await createComment({ + supertest: supertestWithoutAuth, + caseId: case1.id, + params: { ...postCommentAlertReq, alertId: 'test-id-2' }, + auth: secOnlyAuth, + }); + + for (const scenario of [ + { + user: globalRead, + cases: [ + { theCase: case1WithComments, expectedAlerts: ['test-id', 'test-id-2'] }, + { theCase: case2WithComments, expectedAlerts: ['test-id-3'] }, + ], + }, + { + user: superUser, + cases: [ + { theCase: case1WithComments, expectedAlerts: ['test-id', 'test-id-2'] }, + { theCase: case2WithComments, expectedAlerts: ['test-id-3'] }, + ], + }, + { + user: secOnly, + cases: [{ theCase: case1WithComments, expectedAlerts: ['test-id', 'test-id-2'] }], + }, + { + user: secOnlyRead, + cases: [{ theCase: case1WithComments, expectedAlerts: ['test-id', 'test-id-2'] }], + }, + { + user: obsOnly, + cases: [{ theCase: case2WithComments, expectedAlerts: ['test-id-3'] }], + }, + { + user: obsOnlyRead, + cases: [{ theCase: case2WithComments, expectedAlerts: ['test-id-3'] }], + }, + { + user: obsSecRead, + cases: [ + { theCase: case1WithComments, expectedAlerts: ['test-id', 'test-id-2'] }, + { theCase: case2WithComments, expectedAlerts: ['test-id-3'] }, + ], + }, + { + user: obsSec, + cases: [ + { theCase: case1WithComments, expectedAlerts: ['test-id', 'test-id-2'] }, + { theCase: case2WithComments, expectedAlerts: ['test-id-3'] }, + ], + }, + ]) { + for (const theCase of scenario.cases) { + const res = await getAlertsAttachedToCase({ + supertest: supertestWithoutAuth, + caseId: theCase.theCase.id, + auth: { + user: scenario.user, + space: 'space1', + }, + }); + + expect(res.length).to.eql(theCase.expectedAlerts.length); + + for (const [index, alertId] of theCase.expectedAlerts.entries()) { + expect(res[index]).to.eql({ + id: alertId, + index: 'test-index', + attached_at: theCase.theCase.comments![index].created_at, + }); + } + } + } + }); + + for (const scenario of [ + { user: noKibanaPrivileges, space: 'space1' }, + { user: obsOnly, space: 'space1' }, + { user: obsOnlyRead, space: 'space1' }, + { user: secOnly, space: 'space2' }, + ]) { + it(`User ${scenario.user.username} with role(s) ${scenario.user.roles.join()} and space ${ + scenario.space + } - should not get alerts`, async () => { + const caseInfo = await createCase(supertest, getPostCaseRequest(), 200, { + user: superUser, + space: scenario.space, + }); + + await createComment({ + supertest: supertestWithoutAuth, + caseId: caseInfo.id, + params: postCommentAlertReq, + auth: { user: superUser, space: scenario.space }, + }); + + await getAlertsAttachedToCase({ + supertest: supertestWithoutAuth, + caseId: caseInfo.id, + auth: { user: scenario.user, space: scenario.space }, + expectedHttpCode: 403, + }); + }); + } + }); + }); +}; diff --git a/x-pack/test/case_api_integration/security_and_spaces/tests/common/index.ts b/x-pack/test/case_api_integration/security_and_spaces/tests/common/index.ts index 9d35d5ec82fc5..9b24de26245f4 100644 --- a/x-pack/test/case_api_integration/security_and_spaces/tests/common/index.ts +++ b/x-pack/test/case_api_integration/security_and_spaces/tests/common/index.ts @@ -18,6 +18,7 @@ export default ({ loadTestFile }: FtrProviderContext): void => { loadTestFile(require.resolve('./comments/patch_comment')); loadTestFile(require.resolve('./comments/post_comment')); loadTestFile(require.resolve('./alerts/get_cases')); + loadTestFile(require.resolve('./alerts/get_alerts_attached_to_case')); loadTestFile(require.resolve('./cases/delete_cases')); loadTestFile(require.resolve('./cases/find_cases')); loadTestFile(require.resolve('./cases/get_case')); diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/generating_signals.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/generating_signals.ts index c3dbd24ae9f04..a1a97ac8bfd35 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/generating_signals.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/generating_signals.ts @@ -227,7 +227,7 @@ export default ({ getService }: FtrProviderContext) => { parents: [ { rule: signalNoRule.parents[0].rule, // rule id is always changing so skip testing it - id: 'acf538fc082adf970012be166527c4d9fc120f0015f145e0a466a3ceb32db606', + id: '82421e2f4e96058baaa2ed87abbe565403b45edf36348c2b79a4f0e8cc1cd055', type: 'signal', index: '.siem-signals-default-000001', depth: 1, @@ -242,7 +242,7 @@ export default ({ getService }: FtrProviderContext) => { }, { rule: signalNoRule.ancestors[1].rule, // rule id is always changing so skip testing it - id: 'acf538fc082adf970012be166527c4d9fc120f0015f145e0a466a3ceb32db606', + id: '82421e2f4e96058baaa2ed87abbe565403b45edf36348c2b79a4f0e8cc1cd055', type: 'signal', index: '.siem-signals-default-000001', depth: 1, @@ -252,7 +252,7 @@ export default ({ getService }: FtrProviderContext) => { depth: 2, parent: { rule: signalNoRule.parent?.rule, // parent.rule is always changing so skip testing it - id: 'acf538fc082adf970012be166527c4d9fc120f0015f145e0a466a3ceb32db606', + id: '82421e2f4e96058baaa2ed87abbe565403b45edf36348c2b79a4f0e8cc1cd055', type: 'signal', index: '.siem-signals-default-000001', depth: 1, @@ -1265,7 +1265,7 @@ export default ({ getService }: FtrProviderContext) => { parents: [ { rule: signalNoRule.parents[0].rule, // rule id is always changing so skip testing it - id: 'b63bcc90b9393f94899991397a3c2df2f3f5c6ebf56440434500f1e1419df7c9', + id: 'c4db4921f2d9152865fd6518c2a2ef3471738e49f607a21319048c69a303f83f', type: 'signal', index: '.siem-signals-default-000001', depth: 1, @@ -1280,7 +1280,7 @@ export default ({ getService }: FtrProviderContext) => { }, { rule: signalNoRule.ancestors[1].rule, // rule id is always changing so skip testing it - id: 'b63bcc90b9393f94899991397a3c2df2f3f5c6ebf56440434500f1e1419df7c9', + id: 'c4db4921f2d9152865fd6518c2a2ef3471738e49f607a21319048c69a303f83f', type: 'signal', index: '.siem-signals-default-000001', depth: 1, @@ -1290,7 +1290,7 @@ export default ({ getService }: FtrProviderContext) => { depth: 2, parent: { rule: signalNoRule.parent?.rule, // parent.rule is always changing so skip testing it - id: 'b63bcc90b9393f94899991397a3c2df2f3f5c6ebf56440434500f1e1419df7c9', + id: 'c4db4921f2d9152865fd6518c2a2ef3471738e49f607a21319048c69a303f83f', type: 'signal', index: '.siem-signals-default-000001', depth: 1, @@ -1423,7 +1423,7 @@ export default ({ getService }: FtrProviderContext) => { parents: [ { rule: signalNoRule.parents[0].rule, // rule id is always changing so skip testing it - id: 'd2114ed6553816f87d6707b5bc50b88751db73b0f4930433d0890474804aa179', + id: '0733d5d2eaed77410a65eec95cfb2df099abc97289b78e2b0b406130e2dbdb33', type: 'signal', index: '.siem-signals-default-000001', depth: 1, @@ -1438,7 +1438,7 @@ export default ({ getService }: FtrProviderContext) => { }, { rule: signalNoRule.ancestors[1].rule, // rule id is always changing so skip testing it - id: 'd2114ed6553816f87d6707b5bc50b88751db73b0f4930433d0890474804aa179', + id: '0733d5d2eaed77410a65eec95cfb2df099abc97289b78e2b0b406130e2dbdb33', type: 'signal', index: '.siem-signals-default-000001', depth: 1, @@ -1448,7 +1448,7 @@ export default ({ getService }: FtrProviderContext) => { depth: 2, parent: { rule: signalNoRule.parent?.rule, // parent.rule is always changing so skip testing it - id: 'd2114ed6553816f87d6707b5bc50b88751db73b0f4930433d0890474804aa179', + id: '0733d5d2eaed77410a65eec95cfb2df099abc97289b78e2b0b406130e2dbdb33', type: 'signal', index: '.siem-signals-default-000001', depth: 1, diff --git a/x-pack/test/functional/apps/dashboard_mode/dashboard_empty_screen.js b/x-pack/test/functional/apps/dashboard_mode/dashboard_empty_screen.js index 96c40bc4cf409..2adf13db26250 100644 --- a/x-pack/test/functional/apps/dashboard_mode/dashboard_empty_screen.js +++ b/x-pack/test/functional/apps/dashboard_mode/dashboard_empty_screen.js @@ -13,7 +13,8 @@ export default function ({ getPageObjects, getService }) { const dashboardPanelActions = getService('dashboardPanelActions'); const PageObjects = getPageObjects(['common', 'dashboard', 'visualize', 'lens']); - describe('empty dashboard', function () { + // FLAKY: https://github.com/elastic/kibana/issues/102366 + describe.skip('empty dashboard', function () { before(async () => { await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/logstash_functional'); await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/lens/basic'); diff --git a/x-pack/test/functional/apps/ingest_pipelines/feature_controls/ingest_pipelines_security.ts b/x-pack/test/functional/apps/ingest_pipelines/feature_controls/ingest_pipelines_security.ts index ea3781de58f15..aed73d6c9858d 100644 --- a/x-pack/test/functional/apps/ingest_pipelines/feature_controls/ingest_pipelines_security.ts +++ b/x-pack/test/functional/apps/ingest_pipelines/feature_controls/ingest_pipelines_security.ts @@ -60,7 +60,9 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { it('should render the "Ingest" section with ingest pipelines', async () => { await PageObjects.common.navigateToApp('management'); const sections = await managementMenu.getSections(); - expect(sections).to.have.length(1); + // We gave the ingest node pipelines user access to advanced settings to allow them to use ingest node pipelines. + // See https://github.com/elastic/kibana/pull/102409/ + expect(sections).to.have.length(2); expect(sections[0]).to.eql({ sectionId: 'ingest', sectionLinks: ['ingest_pipelines'], diff --git a/x-pack/test/functional/apps/ingest_pipelines/ingest_pipelines.ts b/x-pack/test/functional/apps/ingest_pipelines/ingest_pipelines.ts index 3c0cdf4c8060c..02819cd261534 100644 --- a/x-pack/test/functional/apps/ingest_pipelines/ingest_pipelines.ts +++ b/x-pack/test/functional/apps/ingest_pipelines/ingest_pipelines.ts @@ -18,10 +18,12 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { const pageObjects = getPageObjects(['common', 'ingestPipelines']); const log = getService('log'); const es = getService('es'); + const security = getService('security'); describe('Ingest Pipelines', function () { this.tags('smoke'); before(async () => { + await security.testUser.setRoles(['ingest_pipelines_user']); await pageObjects.common.navigateToApp('ingestPipelines'); }); @@ -46,6 +48,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { after(async () => { // Delete the pipeline that was created await es.ingest.deletePipeline({ id: PIPELINE.name }); + await security.testUser.restoreDefaults(); }); }); }; diff --git a/x-pack/test/functional/apps/lens/formula.ts b/x-pack/test/functional/apps/lens/formula.ts index db7c680ac20af..d1376731886eb 100644 --- a/x-pack/test/functional/apps/lens/formula.ts +++ b/x-pack/test/functional/apps/lens/formula.ts @@ -15,7 +15,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const browser = getService('browser'); const testSubjects = getService('testSubjects'); - describe('lens formula', () => { + // FLAKY: https://github.com/elastic/kibana/issues/102183 + describe.skip('lens formula', () => { it('should transition from count to formula', async () => { await PageObjects.visualize.gotoVisualizationLandingPage(); await listingTable.searchForItemWithName('lnsXYvis'); diff --git a/x-pack/test/functional/apps/ml/anomaly_detection/date_nanos_job.ts b/x-pack/test/functional/apps/ml/anomaly_detection/date_nanos_job.ts index 024735e939024..d351e8f7057e4 100644 --- a/x-pack/test/functional/apps/ml/anomaly_detection/date_nanos_job.ts +++ b/x-pack/test/functional/apps/ml/anomaly_detection/date_nanos_job.ts @@ -123,8 +123,6 @@ export default function ({ getService }: FtrProviderContext) { '@timestamp' ); await ml.testResources.setKibanaTimeZoneToUTC(); - - await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/ml/event_rate_nanos'); await ml.securityUI.loginAsMlPowerUser(); }); diff --git a/x-pack/test/functional/apps/reporting/reporting.ts b/x-pack/test/functional/apps/reporting/reporting.ts index bd1626ec32380..9896e3371a282 100644 --- a/x-pack/test/functional/apps/reporting/reporting.ts +++ b/x-pack/test/functional/apps/reporting/reporting.ts @@ -13,7 +13,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const es = getService('es'); const esArchiver = getService('esArchiver'); - describe('Reporting', function () { + // FLAKY: https://github.com/elastic/kibana/issues/102722 + describe.skip('Reporting', function () { this.tags(['smoke', 'ciGroup2']); before(async () => { await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/packaging'); diff --git a/x-pack/test/functional/config.js b/x-pack/test/functional/config.js index 2679bb55ad341..cf05bd6e15898 100644 --- a/x-pack/test/functional/config.js +++ b/x-pack/test/functional/config.js @@ -514,6 +514,14 @@ export default async function ({ readConfigFile }) { elasticsearch: { cluster: ['manage_pipeline', 'cluster:monitor/nodes/info'], }, + kibana: [ + { + feature: { + advancedSettings: ['read'], + }, + spaces: ['*'], + }, + ], }, license_management_user: { diff --git a/x-pack/test/functional/services/ml/api.ts b/x-pack/test/functional/services/ml/api.ts index 317f2dfe60514..728e3ff8fc8e6 100644 --- a/x-pack/test/functional/services/ml/api.ts +++ b/x-pack/test/functional/services/ml/api.ts @@ -183,19 +183,19 @@ export function MachineLearningAPIProvider({ getService }: FtrProviderContext) { return jobStats; }, - async waitForJobState(jobId: string, expectedJobState: JOB_STATE) { - await retry.waitForWithTimeout( - `job state to be ${expectedJobState}`, - 2 * 60 * 1000, - async () => { - const state = await this.getJobState(jobId); - if (state === expectedJobState) { - return true; - } else { - throw new Error(`expected job state to be ${expectedJobState} but got ${state}`); - } + async waitForJobState( + jobId: string, + expectedJobState: JOB_STATE, + timeout: number = 2 * 60 * 1000 + ) { + await retry.waitForWithTimeout(`job state to be ${expectedJobState}`, timeout, async () => { + const state = await this.getJobState(jobId); + if (state === expectedJobState) { + return true; + } else { + throw new Error(`expected job state to be ${expectedJobState} but got ${state}`); } - ); + }); }, async getDatafeedState(datafeedId: string): Promise { @@ -214,10 +214,14 @@ export function MachineLearningAPIProvider({ getService }: FtrProviderContext) { return state; }, - async waitForDatafeedState(datafeedId: string, expectedDatafeedState: DATAFEED_STATE) { + async waitForDatafeedState( + datafeedId: string, + expectedDatafeedState: DATAFEED_STATE, + timeout: number = 2 * 60 * 1000 + ) { await retry.waitForWithTimeout( `datafeed state to be ${expectedDatafeedState}`, - 2 * 60 * 1000, + timeout, async () => { const state = await this.getDatafeedState(datafeedId); if (state === expectedDatafeedState) { diff --git a/x-pack/test/saved_object_api_integration/common/suites/export.ts b/x-pack/test/saved_object_api_integration/common/suites/export.ts index d9ebbac810231..ea2f321458c22 100644 --- a/x-pack/test/saved_object_api_integration/common/suites/export.ts +++ b/x-pack/test/saved_object_api_integration/common/suites/export.ts @@ -21,12 +21,15 @@ const { export interface ExportTestDefinition extends TestDefinition { request: ReturnType; } + export type ExportTestSuite = TestSuite; + interface SuccessResult { type: string; id: string; originId?: string; } + export interface ExportTestCase { title: string; type: string; @@ -135,7 +138,13 @@ export const createRequest = ({ type, id }: ExportTestCase) => const getTestTitle = ({ failure, title }: ExportTestCase) => `${failure?.reason || 'success'} ["${title}"]`; -const EMPTY_RESULT = { exportedCount: 0, missingRefCount: 0, missingReferences: [] }; +const EMPTY_RESULT = { + excludedObjects: [], + excludedObjectsCount: 0, + exportedCount: 0, + missingRefCount: 0, + missingReferences: [], +}; export function exportTestSuiteFactory(esArchiver: any, supertest: SuperTest) { const expectSavedObjectForbiddenBulkGet = expectResponses.forbiddenTypes('bulk_get'); @@ -189,6 +198,8 @@ export function exportTestSuiteFactory(esArchiver: any, supertest: SuperTest + >; return { - // @ts-expect-error @elastic/elasticsearch doesn't defined `count.buckets`. - buckets: response.aggregations?.count.buckets as SpaceBucket[], + buckets: aggs.count.buckets, }; }; diff --git a/x-pack/test/stack_functional_integration/apps/metricbeat/_metricbeat_dashboard.js b/x-pack/test/stack_functional_integration/apps/metricbeat/_metricbeat_dashboard.js index 502c950d2b113..2a04d40d0b727 100644 --- a/x-pack/test/stack_functional_integration/apps/metricbeat/_metricbeat_dashboard.js +++ b/x-pack/test/stack_functional_integration/apps/metricbeat/_metricbeat_dashboard.js @@ -49,7 +49,9 @@ export default function ({ getService, getPageObjects, updateBaselines }) { await PageObjects.common.sleep(2000); await find.clickByButtonText('Dismiss'); await PageObjects.dashboard.waitForRenderComplete(); - await browser.setScreenshotSize(1000, 1000); + await PageObjects.common.sleep(2000); + await browser.setScreenshotSize(1000, 1337); + await PageObjects.common.sleep(2000); }); after(async function () { diff --git a/yarn.lock b/yarn.lock index d0531ebfbaede..c9e139e68b592 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2792,7 +2792,7 @@ version "0.0.0" uid "" -"@kbn/ui-shared-deps@link:packages/kbn-ui-shared-deps": +"@kbn/ui-shared-deps@link:bazel-bin/packages/kbn-ui-shared-deps": version "0.0.0" uid ""