diff --git a/x-pack/plugins/fleet/common/constants/epm.ts b/x-pack/plugins/fleet/common/constants/epm.ts index 3909605e5b71b..562f099fdd3ca 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 9ac36d753c4e8..0c729823f3391 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 1e21387552120..2c2f68bc1adf1 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 c510e2b1812e1..b9f4a447ae2d7 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 2afc85d0f9849..6ec41325cf6e7 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 a62833dfdcfb5..738689fdaa2cd 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 4882c1c0652e6..58f42b08e0612 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 f76a1f85772be..0000000000000 --- 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 3bed1f7b5f84d..0000000000000 --- 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 eea79a8f9df6b..1a1fba813bd8c 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 9c51527c4a2de..5c03cd45b32d2 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 4d52ce96d638c..dcabd24261e87 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 ed91c37408b5d..28903bbd87a8d 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 d10afc4d1806a..b05b3a1abc049 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 739fd078fe4db..0532f1583b5f5 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 47324cfc493f1..a58f83353f3f7 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 8e66dc904dbf2..8171ee33f22f7 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 b05b95e0f4f70..df7bf4275b58d 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 0000000000000..de3f68ba26115 --- /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 94bc4d621e6eb..e6ac44b557d14 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 cd3898a58c6a7..a932172cf0960 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: [ {