From 980d8bf3012265c2c2da6d695ace343d3d1f8d80 Mon Sep 17 00:00:00 2001 From: Jen Huang Date: Wed, 1 May 2024 15:03:45 -0700 Subject: [PATCH] [Fleet] Show all integration assets on detail page (#182180) ## Summary Resolves https://github.com/elastic/kibana/issues/160555. This PR allows all assets to be shown on Integration details > Assets tab and links them to other apps in Kibana whenever possible for viewing. Previously, only dashboards, visualizations, and saved searches were shown in this view. Now all Kibana and Elasticsearch assets are shown if the integration was installed in the user's current space. If an integration was installed in a different space, only ES assets will be shown. #### Caveats 1) This page lists all assets tracked on the package installation saved object *after* the package is installed (`installed_es` and `installed_kibana`). This list differs from the summary of assets shown on the Overview tab because it includes Fleet-installed assets that are not part of the package, notably index templates, component templates, and default ingest pipelines. https://github.com/elastic/kibana/issues/182197 was created to decide how to resolve this asset count discrepency. 2) ML and Security assets are shown but not linked. The following issues have been created for downstream teams to decide where their assets should link to: #182199, #182200 ### Screenshots
Nginx (including in a different space) ![image](https://github.com/elastic/kibana/assets/1965714/a2985314-5a08-45fb-9bce-8a4283464cd8) ![image](https://github.com/elastic/kibana/assets/1965714/97981e0c-3149-4629-83ec-3c718a393635)
Security Posture Management ![image](https://github.com/elastic/kibana/assets/1965714/93314f9f-6797-4871-927a-ffe11f11f32f)
Rapid7 Threat Command ![image](https://github.com/elastic/kibana/assets/1965714/d31578c6-711a-4d52-9b85-2f60267e41ba)
Lateral Movement Detection ![image](https://github.com/elastic/kibana/assets/1965714/6720eceb-9e42-4024-8ab5-efef6553c3b7)
### Checklist Delete any items that are not applicable to this PR. - [x] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md) - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios --------- Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- x-pack/plugins/fleet/common/constants/epm.ts | 16 +- x-pack/plugins/fleet/common/index.ts | 2 +- .../plugins/fleet/common/openapi/bundled.json | 46 +-- .../plugins/fleet/common/openapi/bundled.yaml | 36 +-- .../schemas/get_bulk_assets_response.yaml | 36 +-- .../plugins/fleet/common/types/models/epm.ts | 22 +- .../fleet/common/types/rest_spec/epm.ts | 2 +- .../components/assets_facet_group.stories.tsx | 59 ---- .../epm/components/assets_facet_group.tsx | 115 -------- .../integrations/sections/epm/constants.tsx | 130 +++++---- .../epm/screens/detail/assets/assets.tsx | 271 ++++++++++-------- .../detail/assets/assets_accordion.tsx | 32 +-- .../epm/screens/detail/overview/details.tsx | 4 +- .../fleet/public/hooks/use_fleet_status.tsx | 16 +- .../fleet/public/hooks/use_kibana_link.ts | 40 --- .../fleet/server/routes/epm/handlers.ts | 10 +- .../services/epm/packages/get_bulk_assets.ts | 79 ++++- .../fleet/server/types/so_attributes.ts | 6 +- .../epm/__snapshots__/bulk_get_assets.snap | 188 ++++++++++++ .../apis/epm/bulk_get_assets.ts | 38 +-- .../apis/epm/update_assets.ts | 20 +- 21 files changed, 618 insertions(+), 550 deletions(-) delete mode 100644 x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/assets_facet_group.stories.tsx delete mode 100644 x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/assets_facet_group.tsx create mode 100644 x-pack/test/fleet_api_integration/apis/epm/__snapshots__/bulk_get_assets.snap diff --git a/x-pack/plugins/fleet/common/constants/epm.ts b/x-pack/plugins/fleet/common/constants/epm.ts index 3909605e5b71be..562f099fdd3ca0 100644 --- a/x-pack/plugins/fleet/common/constants/epm.ts +++ b/x-pack/plugins/fleet/common/constants/epm.ts @@ -4,8 +4,8 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import type { AllowedAssetTypes } from '../types/models'; -import { ElasticsearchAssetType, KibanaAssetType } from '../types/models'; +import type { DisplayedAssetTypes } from '../types/models'; +import { ElasticsearchAssetType, KibanaSavedObjectType } from '../types/models'; export const PACKAGES_SAVED_OBJECT_TYPE = 'epm-packages'; export const ASSETS_SAVED_OBJECT_TYPE = 'epm-packages-assets'; @@ -87,11 +87,11 @@ export const installationStatuses = { NotInstalled: 'not_installed', } as const; -export const allowedAssetTypes: AllowedAssetTypes = [ - KibanaAssetType.dashboard, - KibanaAssetType.search, - KibanaAssetType.visualization, - ElasticsearchAssetType.transform, +// These asset types are allowed to be shown on Integration details > Assets tab +// This array also controls the order in which the asset types are displayed +export const displayedAssetTypes: DisplayedAssetTypes = [ + ...Object.values(KibanaSavedObjectType), + ...Object.values(ElasticsearchAssetType), ]; -export const allowedAssetTypesLookup = new Set(allowedAssetTypes); +export const displayedAssetTypesLookup = new Set(displayedAssetTypes); diff --git a/x-pack/plugins/fleet/common/index.ts b/x-pack/plugins/fleet/common/index.ts index 9ac36d753c4e83..0c729823f33912 100644 --- a/x-pack/plugins/fleet/common/index.ts +++ b/x-pack/plugins/fleet/common/index.ts @@ -195,7 +195,7 @@ export type { FleetServerAgentComponentStatus, AssetSOObject, SimpleSOAssetType, - AllowedAssetTypes, + DisplayedAssetTypes, } from './types'; export { ElasticsearchAssetType } from './types'; diff --git a/x-pack/plugins/fleet/common/openapi/bundled.json b/x-pack/plugins/fleet/common/openapi/bundled.json index 1e213875521201..2c2f68bc1adf18 100644 --- a/x-pack/plugins/fleet/common/openapi/bundled.json +++ b/x-pack/plugins/fleet/common/openapi/bundled.json @@ -6295,33 +6295,33 @@ "type": "object", "deprecated": true, "properties": { - "response": { + "items": { "type": "array", "items": { - "type": "array", - "items": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "type": { - "$ref": "#/components/schemas/saved_object_type" - }, - "updatedAt": { - "type": "string" - }, - "attributes": { - "type": "object", - "properties": { - "title": { - "type": "string" - }, - "description": { - "type": "string" - } + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "type": { + "$ref": "#/components/schemas/saved_object_type" + }, + "updatedAt": { + "type": "string" + }, + "attributes": { + "type": "object", + "properties": { + "title": { + "type": "string" + }, + "description": { + "type": "string" } } + }, + "appLink": { + "type": "string" } } } diff --git a/x-pack/plugins/fleet/common/openapi/bundled.yaml b/x-pack/plugins/fleet/common/openapi/bundled.yaml index c510e2b1812e17..b9f4a447ae2d73 100644 --- a/x-pack/plugins/fleet/common/openapi/bundled.yaml +++ b/x-pack/plugins/fleet/common/openapi/bundled.yaml @@ -3971,26 +3971,26 @@ components: type: object deprecated: true properties: - response: + items: type: array items: - type: array - items: - type: object - properties: - id: - type: string - type: - $ref: '#/components/schemas/saved_object_type' - updatedAt: - type: string - attributes: - type: object - properties: - title: - type: string - description: - type: string + type: object + properties: + id: + type: string + type: + $ref: '#/components/schemas/saved_object_type' + updatedAt: + type: string + attributes: + type: object + properties: + title: + type: string + description: + type: string + appLink: + type: string required: - items get_categories_response: diff --git a/x-pack/plugins/fleet/common/openapi/components/schemas/get_bulk_assets_response.yaml b/x-pack/plugins/fleet/common/openapi/components/schemas/get_bulk_assets_response.yaml index 2afc85d0f98494..6ec41325cf6e72 100644 --- a/x-pack/plugins/fleet/common/openapi/components/schemas/get_bulk_assets_response.yaml +++ b/x-pack/plugins/fleet/common/openapi/components/schemas/get_bulk_assets_response.yaml @@ -2,25 +2,25 @@ title: Bulk get assets response type: object deprecated: true properties: - response: + items: type: array items: - type: array - items: - type: object - properties: - id: - type: string - type: - $ref: ./saved_object_type.yaml - updatedAt: - type: string - attributes: - type: object - properties: - title: - type: string - description: - type: string + type: object + properties: + id: + type: string + type: + $ref: ./saved_object_type.yaml + updatedAt: + type: string + attributes: + type: object + properties: + title: + type: string + description: + type: string + appLink: + type: string required: - items diff --git a/x-pack/plugins/fleet/common/types/models/epm.ts b/x-pack/plugins/fleet/common/types/models/epm.ts index a62833dfdcfb5f..738689fdaa2cdc 100644 --- a/x-pack/plugins/fleet/common/types/models/epm.ts +++ b/x-pack/plugins/fleet/common/types/models/epm.ts @@ -53,17 +53,17 @@ export type AssetType = */ export enum KibanaAssetType { dashboard = 'dashboard', + lens = 'lens', visualization = 'visualization', search = 'search', indexPattern = 'index_pattern', map = 'map', - lens = 'lens', + mlModule = 'ml_module', securityRule = 'security_rule', cloudSecurityPostureRuleTemplate = 'csp_rule_template', - mlModule = 'ml_module', - tag = 'tag', osqueryPackAsset = 'osquery_pack_asset', osquerySavedQuery = 'osquery_saved_query', + tag = 'tag', } /* @@ -71,27 +71,27 @@ export enum KibanaAssetType { */ export enum KibanaSavedObjectType { dashboard = 'dashboard', + lens = 'lens', visualization = 'visualization', search = 'search', indexPattern = 'index-pattern', map = 'map', - lens = 'lens', mlModule = 'ml-module', securityRule = 'security-rule', cloudSecurityPostureRuleTemplate = 'csp-rule-template', - tag = 'tag', osqueryPackAsset = 'osquery-pack-asset', osquerySavedQuery = 'osquery-saved-query', + tag = 'tag', } export enum ElasticsearchAssetType { index = 'index', + indexTemplate = 'index_template', componentTemplate = 'component_template', ingestPipeline = 'ingest_pipeline', - indexTemplate = 'index_template', ilmPolicy = 'ilm_policy', - transform = 'transform', dataStreamIlmPolicy = 'data_stream_ilm_policy', + transform = 'transform', mlModel = 'ml_model', } export type FleetElasticsearchAssetType = Exclude< @@ -99,12 +99,7 @@ export type FleetElasticsearchAssetType = Exclude< ElasticsearchAssetType.index >; -export type AllowedAssetTypes = [ - KibanaAssetType.dashboard, - KibanaAssetType.search, - KibanaAssetType.visualization, - ElasticsearchAssetType.transform -]; +export type DisplayedAssetTypes = Array<`${KibanaSavedObjectType | ElasticsearchAssetType}`>; // Defined as part of the removing public references to saved object schemas export interface SimpleSOAssetType { @@ -112,6 +107,7 @@ export interface SimpleSOAssetType { type: ElasticsearchAssetType | KibanaSavedObjectType; updatedAt?: string; attributes: { + service?: string; title?: string; description?: string; }; diff --git a/x-pack/plugins/fleet/common/types/rest_spec/epm.ts b/x-pack/plugins/fleet/common/types/rest_spec/epm.ts index 4882c1c0652e63..58f42b08e06121 100644 --- a/x-pack/plugins/fleet/common/types/rest_spec/epm.ts +++ b/x-pack/plugins/fleet/common/types/rest_spec/epm.ts @@ -208,7 +208,7 @@ export interface GetBulkAssetsRequest { } export interface GetBulkAssetsResponse { - items: SimpleSOAssetType[]; + items: Array; } export interface GetInputsTemplatesRequest { diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/assets_facet_group.stories.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/assets_facet_group.stories.tsx deleted file mode 100644 index f76a1f85772bed..00000000000000 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/assets_facet_group.stories.tsx +++ /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; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React from 'react'; - -import { AssetsFacetGroup as Component } from './assets_facet_group'; - -export default { - component: Component, - title: 'Sections/EPM/Assets Facet Group', -}; - -interface Args { - width: number; -} - -const args: Args = { - width: 250, -}; - -export const AssetsFacetGroup = ({ width }: Args) => { - return ( -
- -
- ); -}; - -AssetsFacetGroup.args = args; diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/assets_facet_group.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/assets_facet_group.tsx deleted file mode 100644 index 3bed1f7b5f84de..00000000000000 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/assets_facet_group.tsx +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React, { Fragment } from 'react'; -import { - EuiFacetButton, - EuiFacetGroup, - EuiFlexGroup, - EuiFlexItem, - EuiIcon, - EuiText, - EuiTextColor, - EuiTitle, -} from '@elastic/eui'; -import styled from 'styled-components'; -import { FormattedMessage } from '@kbn/i18n-react'; - -import type { - AssetsGroupedByServiceByType, - AssetTypeToParts, - KibanaAssetType, -} from '../../../types'; -import { entries } from '../../../types'; -import { - AssetIcons, - AssetTitleMap, - DisplayedAssets, - ServiceIcons, - ServiceTitleMap, -} from '../constants'; - -const FirstHeaderRow = styled(EuiFlexGroup)` - padding: 0 0 ${(props) => props.theme.eui.euiSizeM} 0; -`; - -const HeaderRow = styled(EuiFlexGroup)` - padding: ${(props) => props.theme.eui.euiSizeM} 0; -`; - -const FacetGroup = styled(EuiFacetGroup)` - flex-grow: 0; -`; - -const FacetButton = styled(EuiFacetButton)` - &&& { - .euiFacetButton__icon, - .euiFacetButton__quantity { - opacity: 1; - } - .euiFacetButton__text { - color: ${(props) => props.theme.eui.euiTextColor}; - } - } -`; - -export function AssetsFacetGroup({ assets }: { assets: AssetsGroupedByServiceByType }) { - return ( - - {entries(assets).map(([service, typeToParts], index) => { - const Header = index === 0 ? FirstHeaderRow : HeaderRow; - // filter out assets we are not going to display - const filteredTypes: AssetTypeToParts = entries(typeToParts).reduce( - (acc: any, [asset, value]) => { - if (DisplayedAssets[service].includes(asset)) acc[asset] = value; - return acc; - }, - {} - ); - return ( - -
- - - - - - - -

- -

-
-
-
-
- - - {entries(filteredTypes).map(([_type, parts]) => { - const type = _type as KibanaAssetType; - // only kibana assets have icons - const iconType = type in AssetIcons && AssetIcons[type]; - const iconNode = iconType ? : ''; - return ( - - {AssetTitleMap[type]} - - ); - })} - -
- ); - })} -
- ); -} diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/constants.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/constants.tsx index eea79a8f9df6b7..1a1fba813bd8ca 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/constants.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/constants.tsx @@ -5,27 +5,89 @@ * 2.0. */ -import type { IconType } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import type { ServiceName } from '../../types'; +import type { ServiceName, KibanaSavedObjectType } from '../../types'; import { ElasticsearchAssetType, KibanaAssetType } from '../../types'; // only allow Kibana assets for the kibana key, ES assets for elasticsearch, etc type ServiceNameToAssetTypes = Record, KibanaAssetType[]> & Record, ElasticsearchAssetType[]>; -export const DisplayedAssets: ServiceNameToAssetTypes = { +export const DisplayedAssetsFromPackageInfo: ServiceNameToAssetTypes = { kibana: Object.values(KibanaAssetType), elasticsearch: Object.values(ElasticsearchAssetType), }; -export type DisplayedAssetType = ElasticsearchAssetType | KibanaAssetType | 'view'; - -export const AssetTitleMap: Record = { +export const AssetTitleMap: Record< + KibanaSavedObjectType | KibanaAssetType | ElasticsearchAssetType | 'view', + string +> = { + // Kibana + // Duplication is because some assets are listed from package paths (snake cased) + // and some are from saved objects (kebab cased) dashboard: i18n.translate('xpack.fleet.epm.assetTitles.dashboards', { defaultMessage: 'Dashboards', }), + lens: i18n.translate('xpack.fleet.epm.assetTitles.lens', { + defaultMessage: 'Lens', + }), + visualization: i18n.translate('xpack.fleet.epm.assetTitles.visualizations', { + defaultMessage: 'Visualizations', + }), + search: i18n.translate('xpack.fleet.epm.assetTitles.savedSearches', { + defaultMessage: 'Saved searches', + }), + 'index-pattern': i18n.translate('xpack.fleet.epm.assetTitles.indexPatterns', { + defaultMessage: 'Data views', + }), + index_pattern: i18n.translate('xpack.fleet.epm.assetTitles.indexPatterns', { + defaultMessage: 'Data views', + }), + map: i18n.translate('xpack.fleet.epm.assetTitles.maps', { + defaultMessage: 'Maps', + }), + 'security-rule': i18n.translate('xpack.fleet.epm.assetTitles.securityRules', { + defaultMessage: 'Security rules', + }), + security_rule: i18n.translate('xpack.fleet.epm.assetTitles.securityRules', { + defaultMessage: 'Security rules', + }), + 'csp-rule-template': i18n.translate( + 'xpack.fleet.epm.assetTitles.cloudSecurityPostureRuleTemplate', + { + defaultMessage: 'Benchmark rules', + } + ), + csp_rule_template: i18n.translate( + 'xpack.fleet.epm.assetTitles.cloudSecurityPostureRuleTemplate', + { + defaultMessage: 'Benchmark rules', + } + ), + 'ml-module': i18n.translate('xpack.fleet.epm.assetTitles.mlModules', { + defaultMessage: 'ML modules', + }), + ml_module: i18n.translate('xpack.fleet.epm.assetTitles.mlModules', { + defaultMessage: 'ML modules', + }), + tag: i18n.translate('xpack.fleet.epm.assetTitles.tag', { + defaultMessage: 'Tags', + }), + 'osquery-pack-asset': i18n.translate('xpack.fleet.epm.assetTitles.osqueryPackAssets', { + defaultMessage: 'Osquery packs', + }), + osquery_pack_asset: i18n.translate('xpack.fleet.epm.assetTitles.osqueryPackAssets', { + defaultMessage: 'Osquery packs', + }), + 'osquery-saved-query': i18n.translate('xpack.fleet.epm.assetTitles.osquerySavedQuery', { + defaultMessage: 'Osquery saved queries', + }), + osquery_saved_query: i18n.translate('xpack.fleet.epm.assetTitles.osquerySavedQuery', { + defaultMessage: 'Osquery saved queries', + }), + + // ES ilm_policy: i18n.translate('xpack.fleet.epm.assetTitles.ilmPolicies', { defaultMessage: 'ILM policies', }), @@ -38,80 +100,24 @@ export const AssetTitleMap: Record = { index: i18n.translate('xpack.fleet.epm.assetTitles.indices', { defaultMessage: 'Indices', }), - index_pattern: i18n.translate('xpack.fleet.epm.assetTitles.indexPatterns', { - defaultMessage: 'Index patterns', - }), index_template: i18n.translate('xpack.fleet.epm.assetTitles.indexTemplates', { defaultMessage: 'Index templates', }), component_template: i18n.translate('xpack.fleet.epm.assetTitles.componentTemplates', { defaultMessage: 'Component templates', }), - search: i18n.translate('xpack.fleet.epm.assetTitles.savedSearches', { - defaultMessage: 'Saved searches', - }), - visualization: i18n.translate('xpack.fleet.epm.assetTitles.visualizations', { - defaultMessage: 'Visualizations', - }), - map: i18n.translate('xpack.fleet.epm.assetTitles.maps', { - defaultMessage: 'Maps', - }), data_stream_ilm_policy: i18n.translate('xpack.fleet.epm.assetTitles.dataStreamILM', { defaultMessage: 'Data stream ILM policies', }), - lens: i18n.translate('xpack.fleet.epm.assetTitles.lens', { - defaultMessage: 'Lens', - }), - security_rule: i18n.translate('xpack.fleet.epm.assetTitles.securityRules', { - defaultMessage: 'Security rules', - }), - osquery_pack_asset: i18n.translate('xpack.fleet.epm.assetTitles.osqueryPackAssets', { - defaultMessage: 'Osquery packs', - }), - osquery_saved_query: i18n.translate('xpack.fleet.epm.assetTitles.osquerySavedQuery', { - defaultMessage: 'Osquery saved queries', - }), - ml_module: i18n.translate('xpack.fleet.epm.assetTitles.mlModules', { - defaultMessage: 'ML modules', - }), ml_model: i18n.translate('xpack.fleet.epm.assetTitles.mlModels', { defaultMessage: 'ML models', }), view: i18n.translate('xpack.fleet.epm.assetTitles.views', { defaultMessage: 'Views', }), - tag: i18n.translate('xpack.fleet.epm.assetTitles.tag', { - defaultMessage: 'Tag', - }), - csp_rule_template: i18n.translate( - 'xpack.fleet.epm.assetTitles.cloudSecurityPostureRuleTemplate', - { - defaultMessage: 'Benchmark rules', - } - ), }; export const ServiceTitleMap: Record = { kibana: 'Kibana', elasticsearch: 'Elasticsearch', }; - -export const AssetIcons: Record = { - dashboard: 'dashboardApp', - index_pattern: 'indexPatternApp', - search: 'searchProfilerApp', - visualization: 'visualizeApp', - map: 'emsApp', - lens: 'lensApp', - security_rule: 'securityApp', - csp_rule_template: 'securityApp', // TODO ICON - ml_module: 'mlApp', - tag: 'tagApp', - osquery_pack_asset: 'osqueryApp', - osquery_saved_query: 'osqueryApp', -}; - -export const ServiceIcons: Record = { - elasticsearch: 'logoElasticsearch', - kibana: 'logoKibana', -}; diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/assets/assets.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/assets/assets.tsx index 9c51527c4a2ded..5c03cd45b32d21 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/assets/assets.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/assets/assets.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { Fragment, useEffect, useState, useCallback } from 'react'; +import React, { Fragment, useEffect, useState, useCallback, useMemo } from 'react'; import { Redirect } from 'react-router-dom'; import { FormattedMessage } from '@kbn/i18n-react'; import { EuiFlexGroup, EuiFlexItem, EuiLink, EuiSpacer, EuiTitle, EuiCallOut } from '@elastic/eui'; @@ -13,14 +13,15 @@ import { EuiFlexGroup, EuiFlexItem, EuiLink, EuiSpacer, EuiTitle, EuiCallOut } f import type { EsAssetReference, AssetSOObject, + KibanaAssetReference, SimpleSOAssetType, } from '../../../../../../../../common'; -import { allowedAssetTypes } from '../../../../../../../../common/constants'; +import { displayedAssetTypes } from '../../../../../../../../common/constants'; import { Error, ExtensionWrapper, Loading } from '../../../../../components'; import type { PackageInfo } from '../../../../../types'; -import { ElasticsearchAssetType, InstallStatus } from '../../../../../types'; +import { InstallStatus } from '../../../../../types'; import { useGetPackageInstallStatus, @@ -28,6 +29,7 @@ import { useStartServices, useUIExtension, useAuthz, + useFleetStatus, } from '../../../../../hooks'; import { sendGetBulkAssets } from '../../../../../hooks'; @@ -45,7 +47,9 @@ export const AssetsPage = ({ packageInfo, refetchPackageInfo }: AssetsPanelProps const { name, version } = packageInfo; const pkgkey = `${name}-${version}`; - const { spaces, docLinks } = useStartServices(); + const { docLinks } = useStartServices(); + const { spaceId } = useFleetStatus(); + const customAssetsExtension = useUIExtension(packageInfo.name, 'package-detail-assets'); const canReadPackageSettings = useAuthz().integrations.readPackageInfo; @@ -54,11 +58,38 @@ export const AssetsPage = ({ packageInfo, refetchPackageInfo }: AssetsPanelProps const getPackageInstallStatus = useGetPackageInstallStatus(); const packageInstallStatus = getPackageInstallStatus(packageInfo.name); - // assume assets are installed in this space until we find otherwise - const [assetsInstalledInCurrentSpace, setAssetsInstalledInCurrentSpace] = useState(true); - const [assetSavedObjects, setAssetsSavedObjects] = useState(); - const [deferredInstallations, setDeferredInstallations] = useState(); + const pkgInstallationInfo = + 'installationInfo' in packageInfo ? packageInfo.installationInfo : undefined; + const installedSpaceId = pkgInstallationInfo?.installed_kibana_space_id; + const assetsInstalledInCurrentSpace = !installedSpaceId || installedSpaceId === spaceId; + + const [assetSavedObjectsByType, setAssetsSavedObjectsByType] = useState< + Record> + >({}); + const [deferredInstallations, setDeferredInstallations] = useState(); + const pkgAssets = useMemo( + () => [ + ...(assetsInstalledInCurrentSpace ? pkgInstallationInfo?.installed_kibana || [] : []), + ...(pkgInstallationInfo?.installed_es || []), + ], + [ + assetsInstalledInCurrentSpace, + pkgInstallationInfo?.installed_es, + pkgInstallationInfo?.installed_kibana, + ] + ); + const pkgAssetsByType = useMemo( + () => + pkgAssets.reduce((acc, asset) => { + if (!acc[asset.type] && displayedAssetTypes.includes(asset.type)) { + acc[asset.type] = []; + } + acc[asset.type].push(asset); + return acc; + }, {} as Record>), + [pkgAssets] + ); const [fetchError, setFetchError] = useState(); const [isLoading, setIsLoading] = useState(true); @@ -70,65 +101,51 @@ export const AssetsPage = ({ packageInfo, refetchPackageInfo }: AssetsPanelProps useEffect(() => { const fetchAssetSavedObjects = async () => { - if ('installationInfo' in packageInfo) { - if (spaces) { - const { id: spaceId } = await spaces.getActiveSpace(); - const assetInstallSpaceId = packageInfo.installationInfo?.installed_kibana_space_id; - - // if assets are installed in a different space no need to attempt to load them. - if (assetInstallSpaceId && assetInstallSpaceId !== spaceId) { - setAssetsInstalledInCurrentSpace(false); - setIsLoading(false); - return; - } - } + if (!pkgInstallationInfo) { + setIsLoading(false); + return; + } - const pkgInstallationInfo = packageInfo.installationInfo; + if (pkgAssets.length === 0) { + setIsLoading(false); + return; + } - if ( - pkgInstallationInfo?.installed_es && - Array.isArray(pkgInstallationInfo.installed_es) && - pkgInstallationInfo.installed_es.length > 0 - ) { - const deferredAssets = pkgInstallationInfo.installed_es.filter( - (asset) => asset.deferred === true - ); - setDeferredInstallations(deferredAssets); - } - const authorizedTransforms = (pkgInstallationInfo?.installed_es || []).filter( - (asset) => asset.type === ElasticsearchAssetType.transform && !asset.deferred - ); + if (pkgAssets.length > 0) { + const deferredAssets = pkgAssets.filter((asset): asset is EsAssetReference => { + return 'deferred' in asset && asset.deferred === true; + }); + setDeferredInstallations(deferredAssets); + } - if ( - authorizedTransforms?.length === 0 && - (!pkgInstallationInfo?.installed_kibana || - pkgInstallationInfo.installed_kibana.length === 0) - ) { - setIsLoading(false); - return; - } - try { - const assetIds: AssetSOObject[] = [ - ...authorizedTransforms, - ...(pkgInstallationInfo?.installed_kibana || []), - ].map(({ id, type }) => ({ - id, - type, - })); - - const response = await sendGetBulkAssets({ assetIds }); - setAssetsSavedObjects(response.data?.items); - } catch (e) { - setFetchError(e); - } finally { - setIsLoading(false); + try { + const assetIds: AssetSOObject[] = pkgAssets.map(({ id, type }) => ({ + id, + type, + })); + + const { data, error } = await sendGetBulkAssets({ assetIds }); + if (error) { + setFetchError(error); + } else { + setAssetsSavedObjectsByType( + (data?.items || []).reduce((acc, asset) => { + if (!acc[asset.type]) { + acc[asset.type] = {}; + } + acc[asset.type][asset.id] = asset; + return acc; + }, {} as typeof assetSavedObjectsByType) + ); } - } else { + } catch (e) { + setFetchError(e); + } finally { setIsLoading(false); } }; fetchAssetSavedObjects(); - }, [packageInfo, spaces]); + }, [packageInfo, pkgAssets, pkgInstallationInfo]); // if they arrive at this page and the package is not installed, send them to overview // this happens if they arrive with a direct url or they uninstall while on this tab @@ -136,8 +153,9 @@ export const AssetsPage = ({ packageInfo, refetchPackageInfo }: AssetsPanelProps return ; } - const showDeferredInstallations = + const hasDeferredInstallations = Array.isArray(deferredInstallations) && deferredInstallations.length > 0; + let content: JSX.Element | Array | null; if (isLoading) { content = ; @@ -158,58 +176,18 @@ export const AssetsPage = ({ packageInfo, refetchPackageInfo }: AssetsPanelProps /> ); - } else if (fetchError) { - content = ( - - } - error={fetchError} - /> - ); - } else if (!assetsInstalledInCurrentSpace) { - content = ( - - } - > -

- - - - ), - }} - /> -

-
- ); - } else if (assetSavedObjects === undefined || assetSavedObjects.length === 0) { + } else if (pkgAssets.length === 0) { if (customAssetsExtension) { // If a UI extension for custom asset entries is defined, render the custom component here despite // there being no saved objects found content = ( + ); } else { - content = !showDeferredInstallations ? ( + content = !hasDeferredInstallations ? (

{ - const sectionAssetSavedObjects = assetSavedObjects.filter((so) => so.type === assetType); + // Show callout if Kibana assets are installed in a different space + !assetsInstalledInCurrentSpace ? ( + <> + + } + > +

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

+
- if (!sectionAssetSavedObjects.length) { + + + ) : null, + + // Ensure we add any custom assets provided via UI extension to the before other assets + customAssetsExtension ? ( + + + + + ) : null, + + // List all assets by order of `displayedAssetTypes` + ...displayedAssetTypes.map((assetType) => { + const assets = pkgAssetsByType[assetType] || []; + const soAssets = assetSavedObjectsByType[assetType] || {}; + const finalAssets = assets.map((asset) => { + return { + ...asset, + ...soAssets[asset.id], + }; + }); + + if (!finalAssets.length) { return null; } return ( - + ); }), - // Ensure we add any custom assets provided via UI extension to the end of the list of other assets - customAssetsExtension ? ( - - - - ) : null, ]; } - const deferredInstallationsContent = showDeferredInstallations ? ( + const deferredInstallationsContent = hasDeferredInstallations ? ( <> + {fetchError && ( + <> + + } + error={fetchError} + /> + + + )} {deferredInstallationsContent} {content} diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/assets/assets_accordion.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/assets/assets_accordion.tsx index 4d52ce96d638c9..dcabd24261e87d 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/assets/assets_accordion.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/assets/assets_accordion.tsx @@ -22,20 +22,16 @@ import { } from '@elastic/eui'; import { AssetTitleMap } from '../../../constants'; +import type { DisplayedAssetTypes, GetBulkAssetsResponse } from '../../../../../../../../common'; +import { useStartServices } from '../../../../../hooks'; +import { KibanaAssetType } from '../../../../../types'; -import type { SimpleSOAssetType, AllowedAssetTypes } from '../../../../../../../../common'; +export type DisplayedAssetType = DisplayedAssetTypes[number] | 'view'; -import { getHrefToObjectInKibanaApp, useStartServices } from '../../../../../hooks'; - -import { ElasticsearchAssetType, KibanaAssetType } from '../../../../../types'; - -export type AllowedAssetType = AllowedAssetTypes[number] | 'view'; -interface Props { - type: AllowedAssetType; - savedObjects: SimpleSOAssetType[]; -} - -export const AssetsAccordion: FunctionComponent = ({ savedObjects, type }) => { +export const AssetsAccordion: FunctionComponent<{ + type: DisplayedAssetType; + savedObjects: GetBulkAssetsResponse['items']; +}> = ({ savedObjects, type }) => { const { http } = useStartServices(); const isDashboard = type === KibanaAssetType.dashboard; @@ -62,25 +58,21 @@ export const AssetsAccordion: FunctionComponent = ({ savedObjects, type } <> - {savedObjects.map(({ id, attributes: { title: soTitle, description } }, idx) => { + {savedObjects.map(({ id, attributes, appLink }, idx) => { + const { title: soTitle, description } = attributes || {}; // Ignore custom asset views or if not a Kibana asset if (type === 'view') { return; } - const pathToObjectInApp = getHrefToObjectInKibanaApp({ - http, - id, - type: type === ElasticsearchAssetType.transform ? undefined : type, - }); const title = soTitle ?? id; return (

- {pathToObjectInApp ? ( - {title} + {appLink ? ( + {title} ) : ( title )} diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/overview/details.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/overview/details.tsx index ed91c37408b5da..28903bbd87a8dd 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/overview/details.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/overview/details.tsx @@ -33,7 +33,7 @@ import type { } from '../../../../../types'; import { entries } from '../../../../../types'; import { useGetCategoriesQuery } from '../../../../../hooks'; -import { AssetTitleMap, DisplayedAssets, ServiceTitleMap } from '../../../constants'; +import { AssetTitleMap, DisplayedAssetsFromPackageInfo, ServiceTitleMap } from '../../../constants'; import { ChangelogModal } from '../settings/changelog_modal'; @@ -133,7 +133,7 @@ export const Details: React.FC = memo(({ packageInfo, integrationInfo }) // (currently we only display Kibana and Elasticsearch assets) const filteredTypes: AssetTypeToParts = entries(typeToParts).reduce( (acc: any, [asset, value]) => { - if (DisplayedAssets[service].includes(asset)) acc[asset] = value; + if (DisplayedAssetsFromPackageInfo[service].includes(asset)) acc[asset] = value; return acc; }, {} diff --git a/x-pack/plugins/fleet/public/hooks/use_fleet_status.tsx b/x-pack/plugins/fleet/public/hooks/use_fleet_status.tsx index d10afc4d1806ac..b05b3a1abc049d 100644 --- a/x-pack/plugins/fleet/public/hooks/use_fleet_status.tsx +++ b/x-pack/plugins/fleet/public/hooks/use_fleet_status.tsx @@ -5,10 +5,11 @@ * 2.0. */ -import React, { useContext, useState } from 'react'; +import React, { useContext, useState, useEffect } from 'react'; import type { GetFleetStatusResponse } from '../types'; +import { useStartServices } from './use_core'; import { useConfig } from './use_config'; import { useGetFleetStatusQuery } from './use_request'; @@ -20,6 +21,7 @@ export interface FleetStatusProviderProps { missingRequirements?: GetFleetStatusResponse['missing_requirements']; missingOptionalFeatures?: GetFleetStatusResponse['missing_optional_features']; isSecretsStorageEnabled?: GetFleetStatusResponse['is_secrets_storage_enabled']; + spaceId?: string; } interface FleetStatus extends FleetStatusProviderProps { @@ -39,9 +41,20 @@ export const FleetStatusProvider: React.FC<{ defaultFleetStatus?: FleetStatusProviderProps; }> = ({ defaultFleetStatus, children }) => { const config = useConfig(); + const { spaces } = useStartServices(); + const [spaceId, setSpaceId] = useState(); const [forceDisplayInstructions, setForceDisplayInstructions] = useState(false); const { data, isLoading, refetch } = useGetFleetStatusQuery(); + useEffect(() => { + const getSpace = async () => { + if (spaces) { + const space = await spaces.getActiveSpace(); + setSpaceId(space.id); + } + }; + getSpace(); + }, [spaces]); const state = { ...defaultFleetStatus, @@ -51,6 +64,7 @@ export const FleetStatusProvider: React.FC<{ missingRequirements: data?.missing_requirements, missingOptionalFeatures: data?.missing_optional_features, isSecretsStorageEnabled: data?.is_secrets_storage_enabled, + spaceId, }; return ( diff --git a/x-pack/plugins/fleet/public/hooks/use_kibana_link.ts b/x-pack/plugins/fleet/public/hooks/use_kibana_link.ts index 739fd078fe4db4..0532f1583b5f52 100644 --- a/x-pack/plugins/fleet/public/hooks/use_kibana_link.ts +++ b/x-pack/plugins/fleet/public/hooks/use_kibana_link.ts @@ -6,8 +6,6 @@ */ import type { HttpStart } from '@kbn/core/public'; -import { KibanaAssetType } from '../types'; - import { useStartServices } from '.'; const KIBANA_BASE_PATH = '/app/kibana'; @@ -16,44 +14,6 @@ const getKibanaLink = (http: HttpStart, path: string) => { return http.basePath.prepend(`${KIBANA_BASE_PATH}#${path}`); }; -/** - * TODO: This is a temporary solution for getting links to various assets. It is very risky because: - * - * 1. The plugin might not exist/be enabled - * 2. URLs and paths might not always be supported - * - * We should migrate to using the new URL service locators. - * - * @deprecated {@link Locators} from the new URL service need to be used instead. - - */ -export const getHrefToObjectInKibanaApp = ({ - type, - id, - http, -}: { - type: KibanaAssetType | undefined; - id: string; - http: HttpStart; -}): undefined | string => { - let kibanaAppPath: undefined | string; - switch (type) { - case KibanaAssetType.dashboard: - kibanaAppPath = `/dashboard/${id}`; - break; - case KibanaAssetType.search: - kibanaAppPath = `/discover/${id}`; - break; - case KibanaAssetType.visualization: - kibanaAppPath = `/visualize/edit/${id}`; - break; - default: - return undefined; - } - - return getKibanaLink(http, kibanaAppPath); -}; - /** * TODO: This functionality needs to be replaced with use of the new URL service locators * diff --git a/x-pack/plugins/fleet/server/routes/epm/handlers.ts b/x-pack/plugins/fleet/server/routes/epm/handlers.ts index 47324cfc493f15..a58f83353f3f72 100644 --- a/x-pack/plugins/fleet/server/routes/epm/handlers.ts +++ b/x-pack/plugins/fleet/server/routes/epm/handlers.ts @@ -237,10 +237,16 @@ export const getBulkAssetsHandler: FleetRequestHandler< undefined, TypeOf > = async (context, request, response) => { + const coreContext = await context.core; try { const { assetIds } = request.body; - const savedObjectsClient = (await context.fleet).internalSoClient; - const assets = await getBulkAssets(savedObjectsClient, assetIds as AssetSOObject[]); + const savedObjectsClient = coreContext.savedObjects.client; + const savedObjectsTypeRegistry = coreContext.savedObjects.typeRegistry; + const assets = await getBulkAssets( + savedObjectsClient, + savedObjectsTypeRegistry, + assetIds as AssetSOObject[] + ); const body: GetBulkAssetsResponse = { items: assets, diff --git a/x-pack/plugins/fleet/server/services/epm/packages/get_bulk_assets.ts b/x-pack/plugins/fleet/server/services/epm/packages/get_bulk_assets.ts index 8e66dc904dbf23..8171ee33f22f72 100644 --- a/x-pack/plugins/fleet/server/services/epm/packages/get_bulk_assets.ts +++ b/x-pack/plugins/fleet/server/services/epm/packages/get_bulk_assets.ts @@ -5,33 +5,87 @@ * 2.0. */ -import type { SavedObjectsClientContract } from '@kbn/core/server'; - import type { - AssetSOObject, - ElasticsearchAssetType, - KibanaSavedObjectType, - SimpleSOAssetType, -} from '../../../../common'; + SavedObjectsClientContract, + ISavedObjectTypeRegistry, + SavedObjectsType, +} from '@kbn/core/server'; + +import type { AssetSOObject, KibanaSavedObjectType, SimpleSOAssetType } from '../../../../common'; +import { ElasticsearchAssetType } from '../../../../common'; -import { allowedAssetTypesLookup } from '../../../../common/constants'; +import { displayedAssetTypesLookup } from '../../../../common/constants'; import type { SimpleSOAssetAttributes } from '../../../types'; +const getKibanaLinkForESAsset = (type: ElasticsearchAssetType, id: string): string => { + switch (type) { + case 'index': + return `/app/management/data/index_management/indices/index_details?indexName=${id}`; + case 'index_template': + return `/app/management/data/index_management/templates/${id}`; + case 'component_template': + return `/app/management/data/index_management/component_templates/${id}`; + case 'ingest_pipeline': + return `/app/management/ingest/ingest_pipelines/?pipeline=${id}`; + case 'ilm_policy': + return `/app/management/data/index_lifecycle_management/policies/edit/${id}`; + case 'data_stream_ilm_policy': + return `/app/management/data/index_lifecycle_management/policies/edit/${id}`; + case 'transform': + // TODO: Confirm link for transforms + return ''; + case 'ml_model': + // TODO: Confirm link for ml models + return ''; + default: + return ''; + } +}; + export async function getBulkAssets( soClient: SavedObjectsClientContract, + soTypeRegistry: ISavedObjectTypeRegistry, assetIds: AssetSOObject[] ) { const { resolved_objects: resolvedObjects } = await soClient.bulkResolve( assetIds ); + const types: Record = {}; + const res: SimpleSOAssetType[] = resolvedObjects .map(({ saved_object: savedObject }) => savedObject) - .filter( - (savedObject) => - savedObject?.error?.statusCode !== 404 && allowedAssetTypesLookup.has(savedObject.type) - ) + .filter((savedObject) => displayedAssetTypesLookup.has(savedObject.type)) .map((obj) => { + // Kibana SOs are registered with an app URL getter, so try to use that + // for retrieving links to assets whenever possible + if (!types[obj.type]) { + types[obj.type] = soTypeRegistry.getType(obj.type); + } + let appLink: string = ''; + try { + if (types[obj.type]?.management?.getInAppUrl) { + appLink = types[obj.type]!.management!.getInAppUrl!(obj)?.path || ''; + } + } catch (e) { + // Ignore errors from `getInAppUrl()` + // This can happen if user can't access the saved object (i.e. in a different space) + } + + // TODO: Ask for Kibana SOs to have `getInAppUrl()` registered so that the above works safely: + // ml-module + // security-rule + // csp-rule-template + // osquery-pack-asset + // osquery-saved-query + + // If we still don't have an app link at this point, manually map them (only ES types) + if (!appLink) { + if (Object.values(ElasticsearchAssetType).includes(obj.type as ElasticsearchAssetType)) { + appLink = getKibanaLinkForESAsset(obj.type as ElasticsearchAssetType, obj.id); + } + } + return { id: obj.id, type: obj.type as unknown as ElasticsearchAssetType | KibanaSavedObjectType, @@ -40,6 +94,7 @@ export async function getBulkAssets( title: obj.attributes?.title, description: obj.attributes?.description, }, + appLink, }; }); return res; diff --git a/x-pack/plugins/fleet/server/types/so_attributes.ts b/x-pack/plugins/fleet/server/types/so_attributes.ts index b05b95e0f4f705..df7bf4275b58db 100644 --- a/x-pack/plugins/fleet/server/types/so_attributes.ts +++ b/x-pack/plugins/fleet/server/types/so_attributes.ts @@ -32,6 +32,7 @@ import type { KafkaPartitionType, KafkaSaslMechanism, KafkaTopicWhenType, + SimpleSOAssetType, } from '../../common/types'; export type AgentPolicyStatus = typeof agentPolicyStatuses; @@ -241,7 +242,4 @@ export interface DownloadSourceSOAttributes { source_id?: string; proxy_id?: string | null; } -export interface SimpleSOAssetAttributes { - title?: string; - description?: string; -} +export type SimpleSOAssetAttributes = SimpleSOAssetType['attributes']; diff --git a/x-pack/test/fleet_api_integration/apis/epm/__snapshots__/bulk_get_assets.snap b/x-pack/test/fleet_api_integration/apis/epm/__snapshots__/bulk_get_assets.snap new file mode 100644 index 00000000000000..de3f68ba261153 --- /dev/null +++ b/x-pack/test/fleet_api_integration/apis/epm/__snapshots__/bulk_get_assets.snap @@ -0,0 +1,188 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`EPM Endpoints Bulk get assets installs all assets when installing a package for the first time should get the assets based on the required objects 1`] = ` +Array [ + Object { + "appLink": "/app/management/data/index_lifecycle_management/policies/edit/all_assets", + "attributes": Object {}, + "id": "all_assets", + "type": "ilm_policy", + }, + Object { + "appLink": "/app/management/data/index_lifecycle_management/policies/edit/logs-all_assets.test_logs-all_assets", + "attributes": Object {}, + "id": "logs-all_assets.test_logs-all_assets", + "type": "data_stream_ilm_policy", + }, + Object { + "appLink": "/app/management/data/index_lifecycle_management/policies/edit/metrics-all_assets.test_metrics-all_assets", + "attributes": Object {}, + "id": "metrics-all_assets.test_metrics-all_assets", + "type": "data_stream_ilm_policy", + }, + Object { + "appLink": "", + "attributes": Object {}, + "id": "default", + "type": "ml_model", + }, + Object { + "appLink": "/app/management/ingest/ingest_pipelines/?pipeline=logs-all_assets.test_logs-0.1.0", + "attributes": Object {}, + "id": "logs-all_assets.test_logs-0.1.0", + "type": "ingest_pipeline", + }, + Object { + "appLink": "/app/management/ingest/ingest_pipelines/?pipeline=logs-all_assets.test_logs-0.1.0-pipeline1", + "attributes": Object {}, + "id": "logs-all_assets.test_logs-0.1.0-pipeline1", + "type": "ingest_pipeline", + }, + Object { + "appLink": "/app/management/ingest/ingest_pipelines/?pipeline=logs-all_assets.test_logs-0.1.0-pipeline2", + "attributes": Object {}, + "id": "logs-all_assets.test_logs-0.1.0-pipeline2", + "type": "ingest_pipeline", + }, + Object { + "appLink": "/app/management/ingest/ingest_pipelines/?pipeline=metrics-all_assets.test_metrics-0.1.0", + "attributes": Object {}, + "id": "metrics-all_assets.test_metrics-0.1.0", + "type": "ingest_pipeline", + }, + Object { + "appLink": "/app/management/data/index_management/templates/logs-all_assets.test_logs", + "attributes": Object {}, + "id": "logs-all_assets.test_logs", + "type": "index_template", + }, + Object { + "appLink": "/app/management/data/index_management/component_templates/logs-all_assets.test_logs@package", + "attributes": Object {}, + "id": "logs-all_assets.test_logs@package", + "type": "component_template", + }, + Object { + "appLink": "/app/management/data/index_management/component_templates/logs-all_assets.test_logs@custom", + "attributes": Object {}, + "id": "logs-all_assets.test_logs@custom", + "type": "component_template", + }, + Object { + "appLink": "/app/management/data/index_management/templates/metrics-all_assets.test_metrics", + "attributes": Object {}, + "id": "metrics-all_assets.test_metrics", + "type": "index_template", + }, + Object { + "appLink": "/app/management/data/index_management/component_templates/metrics-all_assets.test_metrics@package", + "attributes": Object {}, + "id": "metrics-all_assets.test_metrics@package", + "type": "component_template", + }, + Object { + "appLink": "/app/management/data/index_management/component_templates/metrics-all_assets.test_metrics@custom", + "attributes": Object {}, + "id": "metrics-all_assets.test_metrics@custom", + "type": "component_template", + }, + Object { + "appLink": "/app/dashboards#/view/sample_dashboard", + "attributes": Object { + "description": "Sample dashboard", + "title": "[Logs Sample] Overview ECS", + }, + "id": "sample_dashboard", + "type": "dashboard", + }, + Object { + "appLink": "/app/dashboards#/view/sample_dashboard2", + "attributes": Object { + "description": "Sample dashboard 2", + "title": "[Logs Sample2] Overview ECS", + }, + "id": "sample_dashboard2", + "type": "dashboard", + }, + Object { + "appLink": "/app/lens#/edit/sample_lens", + "attributes": Object { + "description": "", + "title": "sample-lens", + }, + "id": "sample_lens", + "type": "lens", + }, + Object { + "appLink": "/app/visualize#/edit/sample_visualization", + "attributes": Object { + "description": "sample visualization update", + "title": "sample vis title", + }, + "id": "sample_visualization", + "type": "visualization", + }, + Object { + "appLink": "/app/discover#/view/sample_search", + "attributes": Object { + "description": "", + "title": "All logs [Logs Kafka] ECS", + }, + "id": "sample_search", + "type": "search", + }, + Object { + "appLink": "/app/management/kibana/dataViews/dataView/test-*", + "attributes": Object { + "title": "test-*", + }, + "id": "test-*", + "type": "index-pattern", + }, + Object { + "appLink": "", + "attributes": Object { + "description": "Find unusual activity in HTTP access logs from filebeat (ECS).", + "title": "Nginx access logs", + }, + "id": "sample_ml_module", + "type": "ml-module", + }, + Object { + "appLink": "", + "attributes": Object { + "description": "Identifies a suspicious parent child process relationship with cmd.exe descending from svchost.exe", + }, + "id": "sample_security_rule", + "type": "security-rule", + }, + Object { + "appLink": "", + "attributes": Object {}, + "id": "sample_csp_rule_template", + "type": "csp-rule-template", + }, + Object { + "appLink": "", + "attributes": Object {}, + "id": "sample_osquery_pack_asset", + "type": "osquery-pack-asset", + }, + Object { + "appLink": "/app/osquery/saved_queries/sample_osquery_saved_query", + "attributes": Object { + "description": "Test saved query description", + }, + "id": "sample_osquery_saved_query", + "type": "osquery-saved-query", + }, + Object { + "appLink": "", + "attributes": Object { + "description": "", + }, + "id": "sample_tag", + "type": "tag", + }, +] +`; diff --git a/x-pack/test/fleet_api_integration/apis/epm/bulk_get_assets.ts b/x-pack/test/fleet_api_integration/apis/epm/bulk_get_assets.ts index 94bc4d621e6eba..e6ac44b557d144 100644 --- a/x-pack/test/fleet_api_integration/apis/epm/bulk_get_assets.ts +++ b/x-pack/test/fleet_api_integration/apis/epm/bulk_get_assets.ts @@ -5,7 +5,6 @@ * 2.0. */ -import expect from '@kbn/expect'; import { GetBulkAssetsResponse } from '@kbn/fleet-plugin/common'; import { FtrProviderContext } from '../../../api_integration/ftr_provider_context'; import { skipIfNoDockerRegistry } from '../../helpers'; @@ -44,37 +43,30 @@ export default function (providerContext: FtrProviderContext) { }); it('should get the assets based on the required objects', async () => { + const packageInfo = await supertest + .get(`/api/fleet/epm/packages/${pkgName}/${pkgVersion}`) + .expect(200); + const packageSOAttributes = packageInfo.body.item.savedObject.attributes; const { body }: { body: GetBulkAssetsResponse } = await supertest .post(`/api/fleet/epm/bulk_assets`) .set('kbn-xsrf', 'xxxx') .send({ assetIds: [ - { - type: 'dashboard', - id: 'sample_dashboard', - }, - { - id: 'sample_visualization', - type: 'visualization', - }, + ...packageSOAttributes.installed_es, + ...packageSOAttributes.installed_kibana, ], }) .expect(200); - const asset1 = body.items[0]; - expect(asset1.id).to.equal('sample_dashboard'); - expect(asset1.type).to.equal('dashboard'); - expect(asset1.attributes).to.eql({ - title: '[Logs Sample] Overview ECS', - description: 'Sample dashboard', - }); - const asset2 = body.items[1]; - expect(asset2.id).to.equal('sample_visualization'); - expect(asset2.type).to.equal('visualization'); - expect(asset2.attributes).to.eql({ - title: 'sample vis title', - description: 'sample visualization update', - }); + // check overall list of assets and app links + expectSnapshot( + body.items.map((item) => ({ + type: item.type, + id: item.id, + appLink: item.appLink, + attributes: item.attributes, + })) + ).toMatch(); }); }); }); diff --git a/x-pack/test/fleet_api_integration/apis/epm/update_assets.ts b/x-pack/test/fleet_api_integration/apis/epm/update_assets.ts index cd3898a58c6a78..a932172cf09601 100644 --- a/x-pack/test/fleet_api_integration/apis/epm/update_assets.ts +++ b/x-pack/test/fleet_api_integration/apis/epm/update_assets.ts @@ -343,6 +343,10 @@ export default function (providerContext: FtrProviderContext) { id: 'sample_dashboard', type: 'dashboard', }, + { + id: 'sample_lens', + type: 'lens', + }, { id: 'sample_visualization', type: 'visualization', @@ -352,8 +356,8 @@ export default function (providerContext: FtrProviderContext) { type: 'search', }, { - id: 'sample_lens', - type: 'lens', + id: 'sample_ml_module', + type: 'ml-module', }, { id: 'sample_security_rule', @@ -363,14 +367,6 @@ export default function (providerContext: FtrProviderContext) { id: 'sample_csp_rule_template2', type: 'csp-rule-template', }, - { - id: 'sample_ml_module', - type: 'ml-module', - }, - { - id: 'sample_tag', - type: 'tag', - }, { id: 'sample_osquery_pack_asset', type: 'osquery-pack-asset', @@ -379,6 +375,10 @@ export default function (providerContext: FtrProviderContext) { id: 'sample_osquery_saved_query', type: 'osquery-saved-query', }, + { + id: 'sample_tag', + type: 'tag', + }, ], installed_es: [ {