[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
<details>
<summary>Nginx (including in a different space)</summary>


![image](a2985314-5a08-45fb-9bce-8a4283464cd8)


![image](97981e0c-3149-4629-83ec-3c718a393635)
</details>

<details>
<summary>Security Posture Management</summary>


![image](93314f9f-6797-4871-927a-ffe11f11f32f)
</details>

<details>
<summary>Rapid7 Threat Command</summary>


![image](d31578c6-711a-4d52-9b85-2f60267e41ba)
</details>

<details>
<summary>Lateral Movement Detection</summary>


![image](6720eceb-9e42-4024-8ab5-efef6553c3b7)
</details>

### 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>
This commit is contained in:
Jen Huang 2024-05-01 15:03:45 -07:00 committed by GitHub
parent c5c40980cb
commit 980d8bf301
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
21 changed files with 617 additions and 549 deletions

View file

@ -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<string>(allowedAssetTypes);
export const displayedAssetTypesLookup = new Set<string>(displayedAssetTypes);

View file

@ -195,7 +195,7 @@ export type {
FleetServerAgentComponentStatus,
AssetSOObject,
SimpleSOAssetType,
AllowedAssetTypes,
DisplayedAssetTypes,
} from './types';
export { ElasticsearchAssetType } from './types';

View file

@ -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"
}
}
}

View file

@ -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:

View file

@ -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

View file

@ -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;
};

View file

@ -208,7 +208,7 @@ export interface GetBulkAssetsRequest {
}
export interface GetBulkAssetsResponse {
items: SimpleSOAssetType[];
items: Array<SimpleSOAssetType & { appLink?: string }>;
}
export interface GetInputsTemplatesRequest {

View file

@ -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 (
<div style={{ width }}>
<Component
assets={{
kibana: {
csp_rule_template: [],
dashboard: [],
visualization: [],
index_pattern: [],
search: [],
map: [],
lens: [],
security_rule: [],
ml_module: [],
tag: [],
osquery_pack_asset: [],
osquery_saved_query: [],
},
elasticsearch: {
component_template: [],
data_stream_ilm_policy: [],
ilm_policy: [],
index_template: [],
ingest_pipeline: [],
transform: [],
ml_model: [],
},
}}
/>
</div>
);
};
AssetsFacetGroup.args = args;

View file

@ -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 (
<Fragment>
{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 (
<Fragment key={service}>
<Header gutterSize="s" alignItems="center">
<EuiFlexItem grow={false}>
<EuiIcon type={ServiceIcons[service]} />
</EuiFlexItem>
<EuiFlexItem>
<EuiTitle key={service} size="xs">
<EuiText>
<h4>
<FormattedMessage
id="xpack.fleet.epm.assetGroupTitle"
defaultMessage="{assetType} assets"
values={{
assetType: ServiceTitleMap[service],
}}
/>
</h4>
</EuiText>
</EuiTitle>
</EuiFlexItem>
</Header>
<FacetGroup>
{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 ? <EuiIcon type={iconType} size="s" /> : '';
return (
<FacetButton key={type} quantity={parts.length} icon={iconNode} isDisabled={true}>
<EuiTextColor color="subdued">{AssetTitleMap[type]}</EuiTextColor>
</FacetButton>
);
})}
</FacetGroup>
</Fragment>
);
})}
</Fragment>
);
}

View file

@ -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<Extract<ServiceName, 'kibana'>, KibanaAssetType[]> &
Record<Extract<ServiceName, 'elasticsearch'>, 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<DisplayedAssetType, string> = {
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<DisplayedAssetType, string> = {
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<ServiceName, string> = {
kibana: 'Kibana',
elasticsearch: 'Elasticsearch',
};
export const AssetIcons: Record<KibanaAssetType, IconType> = {
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<ServiceName, IconType> = {
elasticsearch: 'logoElasticsearch',
kibana: 'logoKibana',
};

View file

@ -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<boolean>(true);
const [assetSavedObjects, setAssetsSavedObjects] = useState<undefined | SimpleSOAssetType[]>();
const [deferredInstallations, setDeferredInstallations] = useState<EsAssetReference[]>();
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<string, Record<string, SimpleSOAssetType & { appLink?: string }>>
>({});
const [deferredInstallations, setDeferredInstallations] = useState<EsAssetReference[]>();
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<string, Array<EsAssetReference | KibanaAssetReference>>),
[pkgAssets]
);
const [fetchError, setFetchError] = useState<undefined | Error>();
const [isLoading, setIsLoading] = useState<boolean>(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 (!pkgInstallationInfo) {
setIsLoading(false);
return;
}
// 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 (pkgAssets.length === 0) {
setIsLoading(false);
return;
}
const pkgInstallationInfo = packageInfo.installationInfo;
if (pkgAssets.length > 0) {
const deferredAssets = pkgAssets.filter((asset): asset is EsAssetReference => {
return 'deferred' in asset && asset.deferred === true;
});
setDeferredInstallations(deferredAssets);
}
if (
pkgInstallationInfo?.installed_es &&
Array.isArray(pkgInstallationInfo.installed_es) &&
pkgInstallationInfo.installed_es.length > 0
) {
const deferredAssets = pkgInstallationInfo.installed_es.filter(
(asset) => asset.deferred === true
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)
);
setDeferredInstallations(deferredAssets);
}
const authorizedTransforms = (pkgInstallationInfo?.installed_es || []).filter(
(asset) => asset.type === ElasticsearchAssetType.transform && !asset.deferred
);
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);
}
} 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 <Redirect to={getPath('integration_details_overview', { pkgkey })} />;
}
const showDeferredInstallations =
const hasDeferredInstallations =
Array.isArray(deferredInstallations) && deferredInstallations.length > 0;
let content: JSX.Element | Array<JSX.Element | null> | null;
if (isLoading) {
content = <Loading />;
@ -158,58 +176,18 @@ export const AssetsPage = ({ packageInfo, refetchPackageInfo }: AssetsPanelProps
/>
</EuiCallOut>
);
} else if (fetchError) {
content = (
<Error
title={
<FormattedMessage
id="xpack.fleet.epm.packageDetails.assets.fetchAssetsErrorTitle"
defaultMessage="Error loading assets"
/>
}
error={fetchError}
/>
);
} else if (!assetsInstalledInCurrentSpace) {
content = (
<EuiCallOut
heading="h2"
title={
<FormattedMessage
id="xpack.fleet.epm.packageDetails.assets.assetsNotAvailableInCurrentSpaceTitle"
defaultMessage="Assets not available in this space"
/>
}
>
<p>
<FormattedMessage
id="xpack.fleet.epm.packageDetails.assets.assetsNotAvailableInCurrentSpaceBody"
defaultMessage="This integration is installed, but no assets are available in this space. {learnMoreLink}."
values={{
learnMoreLink: (
<EuiLink href={docLinks.links.fleet.installAndUninstallIntegrationAssets} external>
<FormattedMessage
id="xpack.fleet.epm.packageDetails.assets.assetsNotAvailableInCurrentSpace.learnMore"
defaultMessage="Learn more"
/>
</EuiLink>
),
}}
/>
</p>
</EuiCallOut>
);
} 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 = (
<ExtensionWrapper>
<customAssetsExtension.Component />
<EuiSpacer size="l" />
</ExtensionWrapper>
);
} else {
content = !showDeferredInstallations ? (
content = !hasDeferredInstallations ? (
<EuiTitle>
<h2>
<FormattedMessage
@ -222,33 +200,76 @@ export const AssetsPage = ({ packageInfo, refetchPackageInfo }: AssetsPanelProps
}
} else {
content = [
...allowedAssetTypes.map((assetType) => {
const sectionAssetSavedObjects = assetSavedObjects.filter((so) => so.type === assetType);
// Show callout if Kibana assets are installed in a different space
!assetsInstalledInCurrentSpace ? (
<>
<EuiCallOut
heading="h2"
title={
<FormattedMessage
id="xpack.fleet.epm.packageDetails.assets.assetsNotAvailableInCurrentSpaceTitle"
defaultMessage="Kibana assets not available in this space"
/>
}
>
<p>
<FormattedMessage
id="xpack.fleet.epm.packageDetails.assets.assetsNotAvailableInCurrentSpaceBody"
defaultMessage="This integration is installed, but Kibana assets are not available in this space. {learnMoreLink}."
values={{
learnMoreLink: (
<EuiLink
href={docLinks.links.fleet.installAndUninstallIntegrationAssets}
external
>
<FormattedMessage
id="xpack.fleet.epm.packageDetails.assets.assetsNotAvailableInCurrentSpace.learnMore"
defaultMessage="Learn more"
/>
</EuiLink>
),
}}
/>
</p>
</EuiCallOut>
if (!sectionAssetSavedObjects.length) {
<EuiSpacer size="m" />
</>
) : null,
// Ensure we add any custom assets provided via UI extension to the before other assets
customAssetsExtension ? (
<ExtensionWrapper>
<customAssetsExtension.Component />
<EuiSpacer size="l" />
</ExtensionWrapper>
) : 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 (
<Fragment key={assetType}>
<AssetsAccordion
savedObjects={sectionAssetSavedObjects}
type={assetType}
key={assetType}
/>
<AssetsAccordion savedObjects={finalAssets} type={assetType} key={assetType} />
<EuiSpacer size="l" />
</Fragment>
);
}),
// Ensure we add any custom assets provided via UI extension to the end of the list of other assets
customAssetsExtension ? (
<ExtensionWrapper>
<customAssetsExtension.Component />
</ExtensionWrapper>
) : null,
];
}
const deferredInstallationsContent = showDeferredInstallations ? (
const deferredInstallationsContent = hasDeferredInstallations ? (
<>
<DeferredAssetsSection
deferredInstallations={deferredInstallations}
@ -263,6 +284,20 @@ export const AssetsPage = ({ packageInfo, refetchPackageInfo }: AssetsPanelProps
<EuiFlexGroup alignItems="flexStart">
<EuiFlexItem grow={1} />
<EuiFlexItem grow={6}>
{fetchError && (
<>
<Error
title={
<FormattedMessage
id="xpack.fleet.epm.packageDetails.assets.fetchAssetsErrorTitle"
defaultMessage="Error loading complete asset information"
/>
}
error={fetchError}
/>
<EuiSpacer size="m" />
</>
)}
{deferredInstallationsContent}
{content}
</EuiFlexItem>

View file

@ -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<Props> = ({ 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<Props> = ({ savedObjects, type }
<>
<EuiSpacer size="m" />
<EuiSplitPanel.Outer hasBorder hasShadow={false}>
{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 (
<Fragment key={id}>
<EuiSplitPanel.Inner grow={false} key={idx}>
<EuiText size="m">
<p>
{pathToObjectInApp ? (
<EuiLink href={pathToObjectInApp}>{title}</EuiLink>
{appLink ? (
<EuiLink href={http.basePath.prepend(appLink)}>{title}</EuiLink>
) : (
title
)}

View file

@ -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<Props> = 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;
},
{}

View file

@ -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<string | undefined>();
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 (

View file

@ -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
*

View file

@ -237,10 +237,16 @@ export const getBulkAssetsHandler: FleetRequestHandler<
undefined,
TypeOf<typeof GetBulkAssetsRequestSchema.body>
> = 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,

View file

@ -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 { allowedAssetTypesLookup } from '../../../../common/constants';
import type { AssetSOObject, KibanaSavedObjectType, SimpleSOAssetType } from '../../../../common';
import { ElasticsearchAssetType } from '../../../../common';
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<SimpleSOAssetAttributes>(
assetIds
);
const types: Record<string, SavedObjectsType | undefined> = {};
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;

View file

@ -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'];

View file

@ -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",
},
]
`;

View file

@ -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();
});
});
});

View file

@ -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: [
{