[Fleet] Remove dependency on public saved objects HTTP APIs (#158350)

## Summary

Remove dependency on public saved objects HTTP APIs

- [x] Removed usage of `SimpleSavedObject` type used for integration
assets
[accordion](d694a0d75f/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/assets/assets_accordion.tsx)
- [x] Removed usage of `savedObjectsClient` and
`ResolvedSimpleSavedObject` on integration assets page. Added a new
Fleet API endpoint to retrieve the necessary assets information and
related frontend hook `sendGetBulkAssets` to replace the savedObject
client
```
POST kbn:/api/fleet/epm/bulk_assets
{
  assetIds: [
              {
                type: 'dashboard',
                id: 'sample_dashboard',
              },
              {
                type: 'visualization',
                id: 'sample_visualization', 
              },
            ],
}
```
- [x] Removed deprecated public saved objects typings in `EPM` and
`Settings` types. These types are currently used in the following
endpoints, they both make use of `savedObject` field:
- GET `api/fleet/epm/packages/{pkgName}/{version}`: response schema uses
[PackageInfo](ff8cebb407/x-pack/plugins/fleet/common/types/models/epm.ts (L466-L468))
type.
- GET `api/fleet/epm/packages`: response schema uses
[PackageList](8d1afc5c97/x-pack/plugins/fleet/common/types/models/epm.ts (L438-L442))
type

My proposed solution is to remove the types imported from `core` and
mark the use of `savedObject` field as a deprecation, since they are
currently exposed through those endpoints (and used in lots of places in
the code).



### Checklist

Delete any items that are not applicable to this PR.

- [ ]
[Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html)
was added for features that require explanation or tutorials
- [ ] [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:
Cristina Amico 2023-06-07 16:19:33 +02:00 committed by GitHub
parent 005108684b
commit 177914bcaf
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
51 changed files with 720 additions and 248 deletions

View file

@ -4,6 +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';
export const PACKAGES_SAVED_OBJECT_TYPE = 'epm-packages';
export const ASSETS_SAVED_OBJECT_TYPE = 'epm-packages-assets';
@ -79,3 +81,12 @@ export const installationStatuses = {
InstallFailed: 'install_failed',
NotInstalled: 'not_installed',
} as const;
export const allowedAssetTypes: AllowedAssetTypes = [
KibanaAssetType.dashboard,
KibanaAssetType.search,
KibanaAssetType.visualization,
ElasticsearchAssetType.transform,
];
export const allowedAssetTypesLookup = new Set<string>(allowedAssetTypes);

View file

@ -39,6 +39,7 @@ export const EPM_API_ROUTES = {
CATEGORIES_PATTERN: `${EPM_API_ROOT}/categories`,
VERIFICATION_KEY_ID: `${EPM_API_ROOT}/verification_key_id`,
STATS_PATTERN: `${EPM_PACKAGES_MANY}/{pkgName}/stats`,
BULK_ASSETS_PATTERN: `${EPM_API_ROOT}/bulk_assets`,
INFO_PATTERN_DEPRECATED: EPM_PACKAGES_ONE_DEPRECATED,
INSTALL_FROM_REGISTRY_PATTERN_DEPRECATED: EPM_PACKAGES_ONE_DEPRECATED,

View file

@ -115,6 +115,7 @@ export type {
UpgradePackagePolicyDryRunResponseItem,
BulkGetPackagePoliciesResponse,
BulkGetAgentPoliciesResponse,
GetBulkAssetsResponse,
// Models
Agent,
AgentStatus,
@ -191,6 +192,9 @@ export type {
// Fleet server models
FleetServerAgent,
FleetServerAgentComponentStatus,
AssetSOObject,
SimpleSOAssetType,
AllowedAssetTypes,
} from './types';
export { ElasticsearchAssetType } from './types';

View file

@ -307,6 +307,59 @@
},
"parameters": []
},
"/epm/bulk_assets": {
"post": {
"summary": "Bulk get assets",
"tags": [
"Elastic Package Manager (EPM)"
],
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/get_bulk_assets_response"
}
}
}
},
"400": {
"$ref": "#/components/responses/error"
}
},
"operationId": "bulk-get-assets",
"requestBody": {
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"assetIds": {
"type": "array",
"items": {
"type": "object",
"properties": {
"type": {
"type": "string"
},
"id": {
"type": "string"
}
}
},
"description": "list of items necessary to fetch assets"
}
},
"required": [
"assetIds"
]
}
}
}
}
}
},
"/epm/categories": {
"get": {
"summary": "List package categories",
@ -5557,6 +5610,82 @@
"item"
]
},
"saved_object_type": {
"title": "Saved Object type",
"oneOf": [
{
"type": "string",
"enum": [
"dashboard",
"visualization",
"search",
"index_pattern",
"map",
"lens",
"security_rule",
"csp_rule_template",
"ml_module",
"tag",
"osquery_pack_asset",
"osquery_saved_query"
]
},
{
"type": "string",
"enum": [
"index",
"component_template",
"ingest_pipeline",
"index_template",
"ilm_policy",
"transform",
"data_stream_ilm_policy",
"ml_model"
]
}
]
},
"get_bulk_assets_response": {
"title": "Bulk get assets response",
"type": "object",
"deprecated": true,
"properties": {
"response": {
"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"
}
}
}
}
}
}
}
},
"required": [
"items"
]
},
"get_categories_response": {
"title": "Get categories response",
"type": "object",

View file

@ -191,6 +191,39 @@ paths:
$ref: '#/components/responses/error'
operationId: packages-get-verification-key-id
parameters: []
/epm/bulk_assets:
post:
summary: Bulk get assets
tags:
- Elastic Package Manager (EPM)
responses:
'200':
description: OK
content:
application/json:
schema:
$ref: '#/components/schemas/get_bulk_assets_response'
'400':
$ref: '#/components/responses/error'
operationId: bulk-get-assets
requestBody:
content:
application/json:
schema:
type: object
properties:
assetIds:
type: array
items:
type: object
properties:
type:
type: string
id:
type: string
description: list of items necessary to fetch assets
required:
- assetIds
/epm/categories:
get:
summary: List package categories
@ -3478,6 +3511,60 @@ components:
$ref: '#/components/schemas/settings'
required:
- item
saved_object_type:
title: Saved Object type
oneOf:
- type: string
enum:
- dashboard
- visualization
- search
- index_pattern
- map
- lens
- security_rule
- csp_rule_template
- ml_module
- tag
- osquery_pack_asset
- osquery_saved_query
- type: string
enum:
- index
- component_template
- ingest_pipeline
- index_template
- ilm_policy
- transform
- data_stream_ilm_policy
- ml_model
get_bulk_assets_response:
title: Bulk get assets response
type: object
deprecated: true
properties:
response:
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
required:
- items
get_categories_response:
title: Get categories response
type: object

View file

@ -0,0 +1,26 @@
title: Bulk get assets response
type: object
deprecated: true
properties:
response:
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
required:
- items

View file

@ -0,0 +1,26 @@
title: Saved Object type
oneOf:
- type: string
enum:
- dashboard
- visualization
- search
- index_pattern
- map
- lens
- security_rule
- csp_rule_template
- ml_module
- tag
- osquery_pack_asset
- osquery_saved_query
- type: string
enum:
- index
- component_template
- ingest_pipeline
- index_template
- ilm_policy
- transform
- data_stream_ilm_policy
- ml_model

View file

@ -28,6 +28,8 @@ paths:
# EPM / integrations endpoints
/epm/verification_key_id:
$ref: paths/epm@verification_key_id.yaml
/epm/bulk_assets:
$ref: paths/epm@bulk_assets.yaml
/epm/categories:
$ref: paths/epm@categories.yaml
/epm/packages/limited:

View file

@ -0,0 +1,32 @@
post:
summary: Bulk get assets
tags:
- Elastic Package Manager (EPM)
responses:
'200':
description: OK
content:
application/json:
schema:
$ref: ../components/schemas/get_bulk_assets_response.yaml
'400':
$ref: ../components/responses/error.yaml
operationId: bulk-get-assets
requestBody:
content:
application/json:
schema:
type: object
properties:
assetIds:
type: array
items:
type: object
properties:
type:
type: string
id:
type: string
description: list of items necessary to fetch assets
required:
- assetIds

View file

@ -89,6 +89,9 @@ export const epmRouteService = {
.replace('{pkgVersion}', pkgVersion)
.replace(/\/$/, ''); // trim trailing slash
},
getBulkAssetsPath: () => {
return EPM_API_ROUTES.BULK_ASSETS_PATTERN;
},
};
export const packagePolicyRouteService = {

View file

@ -14,6 +14,3 @@ export interface DownloadSourceBase {
export type DownloadSource = DownloadSourceBase & {
id: string;
};
export type DownloadSourceAttributes = DownloadSourceBase & {
source_id?: string;
};

View file

@ -6,11 +6,6 @@
*/
import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
// Follow pattern from https://github.com/elastic/kibana/pull/52447
// TODO: Update when https://github.com/elastic/kibana/issues/53021 is closed
import type { SavedObject, SavedObjectAttributes, SavedObjectReference } from '@kbn/core/public';
import type { CustomIntegrationIcon } from '@kbn/custom-integrations-plugin/common';
import type {
ASSETS_SAVED_OBJECT_TYPE,
@ -21,12 +16,7 @@ import type {
} from '../../constants';
import type { ValueOf } from '..';
import type {
PackageSpecManifest,
PackageSpecIcon,
PackageSpecScreenshot,
PackageSpecCategory,
} from './package_spec';
import type { PackageSpecManifest, PackageSpecIcon, PackageSpecCategory } from './package_spec';
export type InstallationStatus = typeof installationStatuses;
@ -48,13 +38,6 @@ export type InstallSource = 'registry' | 'upload' | 'bundled';
export type EpmPackageInstallStatus = 'installed' | 'installing' | 'install_failed';
export type DetailViewPanelName =
| 'overview'
| 'policies'
| 'assets'
| 'settings'
| 'custom'
| 'api-reference';
export type ServiceName = 'kibana' | 'elasticsearch';
export type AgentAssetType = typeof agentAssetTypes;
export type DocAssetType = 'doc' | 'notice' | 'license';
@ -115,6 +98,29 @@ export type FleetElasticsearchAssetType = Exclude<
ElasticsearchAssetType.index
>;
export type AllowedAssetTypes = [
KibanaAssetType.dashboard,
KibanaAssetType.search,
KibanaAssetType.visualization,
ElasticsearchAssetType.transform
];
// Defined as part of the removing public references to saved object schemas
export interface SimpleSOAssetType {
id: string;
type: ElasticsearchAssetType | KibanaSavedObjectType;
updatedAt?: string;
attributes: {
title?: string;
description?: string;
};
}
export interface AssetSOObject {
id: string;
type: ElasticsearchAssetType | KibanaSavedObjectType;
}
export type DataType = typeof dataTypes;
export type MonitoringType = typeof monitoringTypes;
export type InstallablePackage = RegistryPackage | ArchivePackage;
@ -251,12 +257,6 @@ export interface RegistryStream {
export type RegistryStreamWithDataStream = RegistryStream & { data_stream: RegistryDataStream };
export type RequirementVersion = string;
export type RequirementVersionRange = string;
export interface ServiceRequirements {
versions: RequirementVersionRange;
}
// Registry's response types
// from /search
// https://github.com/elastic/package-registry/blob/master/docs/api/search.json
@ -279,8 +279,6 @@ export type RegistrySearchResult = Pick<
| 'categories'
>;
export type ScreenshotItem = RegistryImage | PackageSpecScreenshot;
// from /categories
// https://github.com/elastic/package-registry/blob/master/docs/api/categories.json
export type CategorySummaryList = CategorySummaryItem[];
@ -420,6 +418,34 @@ export interface RegistryVarsEntry {
};
}
// Deprecated as part of the removing public references to saved object schemas
// See https://github.com/elastic/kibana/issues/149098
/**
* @deprecated
*/
export interface InstallableSavedObject {
type: string;
id: string;
attributes: Installation;
references?: SOReference[];
created_at?: string;
updated_at?: string;
version?: string;
coreMigrationVersion?: string;
namespaces?: string[];
}
// Deprecated as part of the removing public references to saved object schemas
// See https://github.com/elastic/kibana/issues/149098
/**
* @deprecated
*/
interface SOReference {
name: string;
type: string;
id: string;
}
// some properties are optional in Registry responses but required in EPM
// internal until we need them
export interface EpmPackageAdditions {
@ -437,36 +463,19 @@ type Merge<FirstType, SecondType> = Omit<FirstType, Extract<keyof FirstType, key
// Managers public HTTP response types
export type PackageList = PackageListItem[];
export type PackageListItem = Installable<RegistrySearchResult> & {
id: string;
integration?: string;
id: string;
savedObject?: InstallableSavedObject;
};
export type IntegrationCardReleaseLabel = 'beta' | 'preview' | 'ga' | 'rc';
export interface IntegrationCardItem {
url: string;
release?: IntegrationCardReleaseLabel;
description: string;
name: string;
title: string;
version: string;
icons: Array<PackageSpecIcon | CustomIntegrationIcon>;
integration: string;
id: string;
categories: string[];
fromIntegrations?: string;
isReauthorizationRequired?: boolean;
isUnverified?: boolean;
isUpdateAvailable?: boolean;
showLabels?: boolean;
}
export type PackageVerificationStatus = 'verified' | 'unverified' | 'unknown';
export type PackagesGroupedByStatus = Record<ValueOf<InstallationStatus>, PackageList>;
export type PackageInfo =
| Installable<Merge<RegistryPackage, EpmPackageAdditions>>
| Installable<Merge<ArchivePackage, EpmPackageAdditions>>;
export type IntegrationCardReleaseLabel = 'beta' | 'preview' | 'ga' | 'rc';
export type PackageVerificationStatus = 'verified' | 'unverified' | 'unknown';
// TODO - Expand this with other experimental indexing types
export type ExperimentalIndexingFeature =
| 'synthetic_source'
@ -479,7 +488,7 @@ export interface ExperimentalDataStreamFeature {
features: Partial<Record<ExperimentalIndexingFeature, boolean>>;
}
export interface Installation extends SavedObjectAttributes {
export interface Installation {
installed_kibana: KibanaAssetReference[];
installed_es: EsAssetReference[];
package_assets?: PackageAssetReference[];
@ -500,8 +509,9 @@ export interface Installation extends SavedObjectAttributes {
data_stream: string;
features: Partial<Record<ExperimentalIndexingFeature, boolean>>;
}>;
internal?: boolean;
removable?: boolean;
}
export interface PackageUsageStats {
agent_policy_count: number;
}
@ -519,12 +529,12 @@ export type InstallStatusExcluded<T = {}> = T & {
export type InstalledRegistry<T = {}> = T & {
status: InstallationStatus['Installed'];
savedObject: SavedObject<Installation>;
savedObject?: InstallableSavedObject;
};
export type Installing<T = {}> = T & {
status: InstallationStatus['Installing'];
savedObject: SavedObject<Installation>;
savedObject?: InstallableSavedObject;
};
export type NotInstalled<T = {}> = T & {
@ -537,17 +547,20 @@ export type InstallFailed<T = {}> = T & {
export type AssetReference = KibanaAssetReference | EsAssetReference;
export type KibanaAssetReference = Pick<SavedObjectReference, 'id'> & {
export interface KibanaAssetReference {
id: string;
type: KibanaSavedObjectType;
};
export type EsAssetReference = Pick<SavedObjectReference, 'id'> & {
}
export interface EsAssetReference {
id: string;
type: ElasticsearchAssetType;
deferred?: boolean;
};
}
export type PackageAssetReference = Pick<SavedObjectReference, 'id'> & {
export interface PackageAssetReference {
id: string;
type: typeof ASSETS_SAVED_OBJECT_TYPE;
};
}
export interface IndexTemplateMappings {
properties: any;

View file

@ -16,6 +16,8 @@ import type {
InstallType,
InstallSource,
EpmPackageInstallStatus,
SimpleSOAssetType,
AssetSOObject,
} from '../models/epm';
export interface GetCategoriesRequest {
@ -197,3 +199,13 @@ export interface DeletePackageResponse {
export interface GetVerificationKeyIdResponse {
id: string | null;
}
export interface GetBulkAssetsRequest {
body: {
assetIds: AssetSOObject[];
};
}
export interface GetBulkAssetsResponse {
items: SimpleSOAssetType[];
}

View file

@ -8,7 +8,7 @@
import { Search as LocalSearch, PrefixIndexStrategy } from 'js-search';
import { useRef } from 'react';
import type { IntegrationCardItem } from '../../../../common/types/models';
import type { IntegrationCardItem } from '../sections/epm/screens/home';
export const searchIdField = 'id';
export const fieldsToSearch = ['name', 'title', 'description'];

View file

@ -19,7 +19,7 @@ import {
} from '../screens/detail/assets/deferred_assets_warning';
import { CardIcon } from '../../../../../components/package_icon';
import type { IntegrationCardItem } from '../../../../../../common/types/models/epm';
import type { IntegrationCardItem } from '../screens/home';
import { InlineReleaseBadge, WithGuidedOnboardingTour } from '../../../components';
import { useStartServices, useIsGuidedOnboardingActive } from '../../../hooks';

View file

@ -23,7 +23,7 @@ import { FormattedMessage } from '@kbn/i18n-react';
import { Loading } from '../../../../components';
import type { IntegrationCardItem } from '../../../../../../../common/types/models';
import type { IntegrationCardItem } from '../../screens/home';
import type { ExtendedIntegrationCategory } from '../../screens/home/category_facets';

View file

@ -24,7 +24,7 @@ import { FormattedMessage } from '@kbn/i18n-react';
import { useLocalSearch, searchIdField } from '../../../../hooks';
import type { IntegrationCardItem } from '../../../../../../../common/types/models';
import type { IntegrationCardItem } from '../../screens/home';
import type {
ExtendedIntegrationCategory,

View file

@ -4,7 +4,7 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import type { IntegrationCardItem } from '../../../../../../../common/types';
import type { IntegrationCardItem } from '../../screens/home';
import { _promoteFeaturedIntegrations } from './promote_featured_integrations';

View file

@ -8,7 +8,8 @@
import React from 'react';
import styled from 'styled-components';
import type { RequirementVersion } from '../../../types';
export type RequirementVersion = string;
export type RequirementVersionRange = string;
const CodeText = styled.span`
font-family: ${(props) => props.theme.eui.euiCodeFontFamily};

View file

@ -8,12 +8,14 @@
import React, { Fragment, useEffect, useState } from 'react';
import { Redirect } from 'react-router-dom';
import { FormattedMessage } from '@kbn/i18n-react';
import { EuiCallOut, EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiTitle } from '@elastic/eui';
import { groupBy } from 'lodash';
import { EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiTitle, EuiCallOut } from '@elastic/eui';
import type { ResolvedSimpleSavedObject } from '@kbn/core/public';
import type { EsAssetReference } from '../../../../../../../../common';
import type {
EsAssetReference,
AssetSOObject,
SimpleSOAssetType,
} from '../../../../../../../../common';
import { allowedAssetTypes } from '../../../../../../../../common/constants';
import { Error, ExtensionWrapper, Loading } from '../../../../../components';
@ -25,16 +27,15 @@ import {
useLink,
useStartServices,
useUIExtension,
useAuthz,
} from '../../../../../hooks';
import { sendGetBulkAssets } from '../../../../../hooks';
import { DeferredAssetsSection } from './deferred_assets_accordion';
import type { AssetSavedObject } from './types';
import { allowedAssetTypes } from './constants';
import { AssetsAccordion } from './assets_accordion';
const allowedAssetTypesLookup = new Set<string>(allowedAssetTypes);
interface AssetsPanelProps {
packageInfo: PackageInfo;
}
@ -42,31 +43,30 @@ interface AssetsPanelProps {
export const AssetsPage = ({ packageInfo }: AssetsPanelProps) => {
const { name, version } = packageInfo;
const pkgkey = `${name}-${version}`;
const {
spaces,
savedObjects: { client: savedObjectsClient },
} = useStartServices();
const { spaces } = useStartServices();
const customAssetsExtension = useUIExtension(packageInfo.name, 'package-detail-assets');
const canReadPackageSettings = useAuthz().integrations.readPackageSettings;
const { getPath } = useLink();
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 | AssetSavedObject[]>();
const [assetSavedObjects, setAssetsSavedObjects] = useState<undefined | SimpleSOAssetType[]>();
const [deferredInstallations, setDeferredInstallations] = useState<EsAssetReference[]>();
const [fetchError, setFetchError] = useState<undefined | Error>();
const [isLoading, setIsLoading] = useState<boolean>(true);
const [hasPermissionError, setHasPermissionError] = useState<boolean>(false);
useEffect(() => {
const fetchAssetSavedObjects = async () => {
if ('savedObject' in packageInfo) {
if (spaces) {
const { id: spaceId } = await spaces.getActiveSpace();
const assetInstallSpaceId = packageInfo.savedObject.attributes.installed_kibana_space_id;
const assetInstallSpaceId = packageInfo.savedObject?.attributes.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);
@ -75,71 +75,40 @@ export const AssetsPage = ({ packageInfo }: AssetsPanelProps) => {
}
}
const {
savedObject: { attributes: packageAttributes },
} = packageInfo;
const packageAttributes = packageInfo.savedObject?.attributes;
if (
packageAttributes?.installed_es &&
Array.isArray(packageAttributes.installed_es) &&
packageAttributes.installed_es?.length > 0
packageAttributes.installed_es.length > 0
) {
const deferredAssets = packageAttributes.installed_es.filter(
(asset) => asset.deferred === true
);
setDeferredInstallations(deferredAssets);
}
const authorizedTransforms = packageAttributes.installed_es.filter(
const authorizedTransforms = (packageAttributes?.installed_es || []).filter(
(asset) => asset.type === ElasticsearchAssetType.transform && !asset.deferred
);
if (
authorizedTransforms.length === 0 &&
(!packageAttributes.installed_kibana || packageAttributes.installed_kibana.length === 0)
authorizedTransforms?.length === 0 &&
(!packageAttributes?.installed_kibana || packageAttributes.installed_kibana.length === 0)
) {
setIsLoading(false);
return;
}
try {
const objectsToGet = [...authorizedTransforms, ...packageAttributes.installed_kibana].map(
({ id, type }) => ({
id,
type,
})
);
// We don't have an API to know which SO types a user has access to, so instead we make a request for each
// SO type and ignore the 403 errors
const objectsByType = await Promise.all(
Object.entries(groupBy(objectsToGet, 'type')).map(([type, objects]) =>
savedObjectsClient
.bulkResolve(objects)
// Ignore privilege errors
.catch((e: any) => {
if (e?.body?.statusCode === 403) {
setHasPermissionError(true);
return { resolved_objects: [] };
} else {
throw e;
}
})
.then(
({
resolved_objects: resolvedObjects,
}: {
resolved_objects: ResolvedSimpleSavedObject[];
}) => {
return resolvedObjects
.map(({ saved_object: savedObject }) => savedObject)
.filter(
(savedObject) =>
savedObject?.error?.statusCode !== 404 &&
allowedAssetTypesLookup.has(savedObject.type)
) as AssetSavedObject[];
}
)
)
);
setAssetsSavedObjects([...objectsByType.flat()]);
const assetIds: AssetSOObject[] = [
...authorizedTransforms,
...(packageAttributes?.installed_kibana || []),
].map(({ id, type }) => ({
id,
type,
}));
const response = await sendGetBulkAssets({ assetIds });
setAssetsSavedObjects(response.data?.items);
} catch (e) {
setFetchError(e);
} finally {
@ -150,7 +119,7 @@ export const AssetsPage = ({ packageInfo }: AssetsPanelProps) => {
}
};
fetchAssetSavedObjects();
}, [savedObjectsClient, packageInfo, spaces]);
}, [packageInfo, spaces]);
// 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
@ -160,10 +129,26 @@ export const AssetsPage = ({ packageInfo }: AssetsPanelProps) => {
const showDeferredInstallations =
Array.isArray(deferredInstallations) && deferredInstallations.length > 0;
let content: JSX.Element | Array<JSX.Element | null> | null;
if (isLoading) {
content = <Loading />;
} else if (!canReadPackageSettings) {
content = (
<EuiCallOut
color="warning"
title={
<FormattedMessage
id="xpack.fleet.epm.packageDetails.assets.assetsPermissionErrorTitle"
defaultMessage="Permission error"
/>
}
>
<FormattedMessage
id="xpack.fleet.epm.packageDetails.assets.assetsPermissionError"
defaultMessage="You do not have permission to retrieve the Kibana saved object for that integration. Contact your administrator."
/>
</EuiCallOut>
);
} else if (fetchError) {
content = (
<Error
@ -187,23 +172,6 @@ export const AssetsPage = ({ packageInfo }: AssetsPanelProps) => {
</h2>
</EuiTitle>
);
} else if (hasPermissionError) {
content = (
<EuiCallOut
color="warning"
title={
<FormattedMessage
id="xpack.fleet.epm.packageDetails.assets.assetsPermissionErrorTitle"
defaultMessage="Permission errors"
/>
}
>
<FormattedMessage
id="xpack.fleet.epm.packageDetails.assets.assetsPermissionError"
defaultMessage="You do not have permission to retrieve the Kibana saved object for that integration. Contact your administrator."
/>
</EuiCallOut>
);
} else if (assetSavedObjects === undefined || assetSavedObjects.length === 0) {
if (customAssetsExtension) {
// If a UI extension for custom asset entries is defined, render the custom component here despite

View file

@ -23,15 +23,16 @@ import {
import { AssetTitleMap } from '../../../constants';
import type { SimpleSOAssetType, AllowedAssetTypes } from '../../../../../../../../common';
import { getHrefToObjectInKibanaApp, useStartServices } from '../../../../../hooks';
import { ElasticsearchAssetType, KibanaAssetType } from '../../../../../types';
import type { AllowedAssetType, AssetSavedObject } from './types';
export type AllowedAssetType = AllowedAssetTypes[number] | 'view';
interface Props {
type: AllowedAssetType;
savedObjects: AssetSavedObject[];
savedObjects: SimpleSOAssetType[];
}
export const AssetsAccordion: FunctionComponent<Props> = ({ savedObjects, type }) => {

View file

@ -1,17 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { ElasticsearchAssetType, KibanaAssetType } from '../../../../../types';
import type { AllowedAssetTypes } from './types';
export const allowedAssetTypes: AllowedAssetTypes = [
KibanaAssetType.dashboard,
KibanaAssetType.search,
KibanaAssetType.visualization,
ElasticsearchAssetType.transform,
];

View file

@ -1,22 +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 type { SimpleSavedObject } from '@kbn/core/public';
import type { KibanaAssetType } from '../../../../../types';
import type { ElasticsearchAssetType } from '../../../../../types';
export type AssetSavedObject = SimpleSavedObject<{ title: string; description?: string }>;
export type AllowedAssetTypes = [
KibanaAssetType.dashboard,
KibanaAssetType.search,
KibanaAssetType.visualization,
ElasticsearchAssetType.transform
];
export type AllowedAssetType = AllowedAssetTypes[number] | 'view';

View file

@ -20,10 +20,7 @@ import type {
GetSettingsResponse,
GetVerificationKeyIdResponse,
} from '../../../../../../../common/types/rest_spec';
import type {
DetailViewPanelName,
KibanaAssetType,
} from '../../../../../../../common/types/models';
import type { KibanaAssetType } from '../../../../../../../common/types/models';
import {
agentPolicyRouteService,
appRoutesService,
@ -36,9 +33,11 @@ import { createIntegrationsTestRendererMock } from '../../../../../../mock';
import { ExperimentalFeaturesService } from '../../../../services';
import type { DetailViewPanelName } from '.';
import { Detail } from '.';
// @ts-ignore this saves us having to define all experimental features
ExperimentalFeaturesService.init({});
import { Detail } from '.';
describe('when on integration detail', () => {
const pkgkey = 'nginx-0.3.7';

View file

@ -56,7 +56,7 @@ import {
useIsGuidedOnboardingActive,
} from '../../../../hooks';
import { pkgKeyFromPackageInfo } from '../../../../services';
import type { DetailViewPanelName, PackageInfo } from '../../../../types';
import type { PackageInfo } from '../../../../types';
import { InstallStatus } from '../../../../types';
import {
Error,
@ -86,6 +86,14 @@ import { DocumentationPage } from './documentation';
import './index.scss';
export type DetailViewPanelName =
| 'overview'
| 'policies'
| 'assets'
| 'settings'
| 'custom'
| 'api-reference';
export interface DetailParams {
pkgkey: string;
panel?: DetailViewPanelName;
@ -249,7 +257,7 @@ export function Detail() {
let installedVersion;
const { name } = packageInfoData.item;
if ('savedObject' in packageInfoResponse) {
installedVersion = packageInfoResponse.savedObject.attributes.version;
installedVersion = packageInfoResponse.savedObject?.attributes.version;
}
const status: InstallStatus = packageInfoResponse?.status as any;
if (name) {

View file

@ -10,9 +10,10 @@ import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n-react';
import { EuiFlexGroup, EuiFlexItem, EuiImage, EuiText, EuiPagination } from '@elastic/eui';
import type { ScreenshotItem } from '../../../../../types';
import type { RegistryImage, PackageSpecScreenshot } from '../../../../../../../../common/types';
import { useLinks } from '../../../../../hooks';
type ScreenshotItem = RegistryImage | PackageSpecScreenshot;
interface ScreenshotProps {
images: ScreenshotItem[];
packageName: string;

View file

@ -455,7 +455,8 @@ export const SettingsPage: React.FC<Props> = memo(({ packageInfo, theme$ }: Prop
<ReinstallButton
{...packageInfo}
installSource={
'savedObject' in packageInfo
'savedObject' in packageInfo &&
packageInfo.savedObject?.attributes.install_source
? packageInfo.savedObject.attributes.install_source
: ''
}

View file

@ -28,7 +28,7 @@ import {
isIntegrationPolicyTemplate,
} from '../../../../../../../../common/services';
import type { IntegrationCardItem } from '../../../../../../../../common/types/models';
import type { IntegrationCardItem } from '..';
import { ALL_CATEGORY } from '../category_facets';
import type { CategoryFacet } from '../category_facets';

View file

@ -9,12 +9,19 @@ import React, { useState } from 'react';
import { Switch } from 'react-router-dom';
import { Route } from '@kbn/shared-ux-router';
import type { CustomIntegration } from '@kbn/custom-integrations-plugin/common';
import type {
CustomIntegration,
CustomIntegrationIcon,
} from '@kbn/custom-integrations-plugin/common';
import { hasDeferredInstallations } from '../../../../../../services/has_deferred_installations';
import { getPackageReleaseLabel } from '../../../../../../../common/services';
import { installationStatuses } from '../../../../../../../common/constants';
import type {
PackageSpecIcon,
IntegrationCardReleaseLabel,
} from '../../../../../../../common/types';
import type { DynamicPage, DynamicPagePathValues, StaticPage } from '../../../../constants';
import { INTEGRATIONS_ROUTING_PATHS, INTEGRATIONS_SEARCH_QUERYPARAM } from '../../../../constants';
@ -23,11 +30,6 @@ import { isPackageUnverified, isPackageUpdatable } from '../../../../services';
import type { PackageListItem } from '../../../../types';
import type {
IntegrationCardItem,
IntegrationCardReleaseLabel,
} from '../../../../../../../common/types/models';
import { useGetPackagesQuery } from '../../../../hooks';
import type { CategoryFacet, ExtendedIntegrationCategory } from './category_facets';
@ -40,6 +42,24 @@ export interface CategoryParams {
subcategory?: string;
}
export interface IntegrationCardItem {
url: string;
release?: IntegrationCardReleaseLabel;
description: string;
name: string;
title: string;
version: string;
icons: Array<PackageSpecIcon | CustomIntegrationIcon>;
integration: string;
id: string;
categories: string[];
fromIntegrations?: string;
isReauthorizationRequired?: boolean;
isUnverified?: boolean;
isUpdateAvailable?: boolean;
showLabels?: boolean;
}
export const getParams = (params: CategoryParams, search: string) => {
const { category, subcategory } = params;
const selectedCategory: ExtendedIntegrationCategory = category || '';
@ -81,7 +101,7 @@ export const mapToCard = ({
: item.uiExternalLink || getAbsolutePath(item.uiInternalPath);
} else {
let urlVersion = item.version;
if ('savedObject' in item) {
if ('savedObject' in item && item?.savedObject?.attributes.version) {
urlVersion = item.savedObject.attributes.version || item.version;
isUnverified = isPackageUnverified(item, packageVerificationKeyId);
isUpdateAvailable = isPackageUpdatable(item);
@ -130,7 +150,10 @@ export const EPMHomePage: React.FC = () => {
);
const unverifiedPackageCount = installedPackages.filter(
(pkg) => 'savedObject' in pkg && pkg.savedObject.attributes.verification_status === 'unverified'
(pkg) =>
'savedObject' in pkg &&
pkg.savedObject?.attributes.verification_status &&
pkg.savedObject.attributes.verification_status === 'unverified'
).length;
const upgradeablePackageCount = installedPackages.filter(isPackageUpdatable).length;

View file

@ -171,7 +171,8 @@ export const InstalledPackages: React.FC<{
() =>
installedPackages.filter(
(item) =>
'savedObject' in item && semverLt(item.savedObject.attributes.version, item.version)
item.savedObject?.attributes.version &&
semverLt(item.savedObject.attributes.version, item.version)
),
[installedPackages]
);

View file

@ -8,7 +8,7 @@
import type { IntegrationCategory } from '@kbn/custom-integrations-plugin/common';
import { INTEGRATION_CATEGORY_DISPLAY } from '@kbn/custom-integrations-plugin/common';
import type { IntegrationCardItem } from '../../../../../../../common/types/models';
import type { IntegrationCardItem } from '.';
import type { CategoryFacet } from './category_facets';

View file

@ -31,11 +31,10 @@ export const useIsPackagePolicyUpgradable = () => {
const { name, version } = pkgPolicy.package;
const installedPackage = allInstalledPackages.find(
(installedPkg) =>
'savedObject' in installedPkg && installedPkg.savedObject.attributes.name === name
'savedObject' in installedPkg && installedPkg.savedObject?.attributes.name === name
);
if (
installedPackage &&
'savedObject' in installedPackage &&
installedPackage?.savedObject?.attributes.version &&
semverLt(version, installedPackage.savedObject.attributes.version)
) {
return true;

View file

@ -44,7 +44,9 @@ export const usePackageInstallationsQuery = () => {
() =>
allInstalledPackages.filter(
(item) =>
'savedObject' in item && semverLt(item.savedObject.attributes.version, item.version)
'savedObject' in item &&
item.savedObject?.attributes.version &&
semverLt(item.savedObject.attributes.version, item.version)
),
[allInstalledPackages]
);
@ -57,11 +59,12 @@ export const usePackageInstallationsQuery = () => {
const { name, version } = pkgPolicy.package;
const installedPackage = allInstalledPackages.find(
(installedPkg) =>
'savedObject' in installedPkg && installedPkg.savedObject.attributes.name === name
'savedObject' in installedPkg && installedPkg?.savedObject?.attributes.name === name
);
if (
installedPackage &&
'savedObject' in installedPackage &&
installedPackage?.savedObject?.attributes?.version &&
semverLt(version, installedPackage.savedObject.attributes.version)
) {
const packageData = result.get(name) ?? {

View file

@ -24,6 +24,8 @@ import type {
DeletePackageResponse,
UpdatePackageRequest,
UpdatePackageResponse,
GetBulkAssetsRequest,
GetBulkAssetsResponse,
GetVerificationKeyIdResponse,
} from '../../types';
import type { FleetErrorResponse, GetStatsResponse } from '../../../common/types';
@ -266,3 +268,11 @@ export const sendUpdatePackage = (
body,
});
};
export const sendGetBulkAssets = (body: GetBulkAssetsRequest['body']) => {
return sendRequest<GetBulkAssetsResponse>({
path: epmRouteService.getBulkAssetsPath(),
method: 'post',
body,
});
};

View file

@ -8,6 +8,8 @@
import type { PackageInfo, PackageListItem } from '../../common';
export const getDeferredInstallationsCnt = (pkg?: PackageInfo | PackageListItem | null): number => {
if (!pkg) return 0;
return pkg && 'savedObject' in pkg && pkg.savedObject
? pkg.savedObject.attributes?.installed_es?.filter((d) => d.deferred).length
: 0;

View file

@ -6,9 +6,9 @@
*/
import semverLt from 'semver/functions/lt';
import type { PackageInfo, PackageListItem } from '../types';
import type { PackageListItem } from '../types';
export const isPackageUpdatable = (pkg: PackageInfo | PackageListItem): boolean =>
'savedObject' in pkg &&
pkg.savedObject &&
semverLt(pkg.savedObject.attributes.version, pkg.version);
export const isPackageUpdatable = (pkg: PackageListItem): boolean =>
'savedObject' in pkg && pkg.savedObject?.attributes.version
? semverLt(pkg.savedObject.attributes.version, pkg.version)
: false;

View file

@ -17,10 +17,10 @@ export function isPackageUnverified(
pkg: PackageInfo | PackageListItem,
packageVerificationKeyId?: string
) {
if (!('savedObject' in pkg)) return false;
if (!('savedObject' in pkg) || !pkg.savedObject?.attributes) return false;
const { verification_status: verificationStatus, verification_key_id: verificationKeyId } =
pkg.savedObject.attributes;
pkg?.savedObject?.attributes;
const { packageVerification: isPackageVerificationEnabled } = ExperimentalFeaturesService.get();
const isKeyOutdated = !!verificationKeyId && verificationKeyId !== packageVerificationKeyId;

View file

@ -108,8 +108,6 @@ export type {
PackageListItem,
PackagesGroupedByStatus,
RequirementsByServiceName,
RequirementVersion,
ScreenshotItem,
ServiceName,
GetCategoriesRequest,
GetCategoriesResponse,
@ -120,7 +118,6 @@ export type {
GetInfoResponse,
InstallPackageResponse,
DeletePackageResponse,
DetailViewPanelName,
InstallationStatus,
Installable,
RegistryRelease,
@ -135,6 +132,8 @@ export type {
PostHealthCheckResponse,
PostRetrieveAgentsByActionsRequest,
PostRetrieveAgentsByActionsResponse,
GetBulkAssetsRequest,
GetBulkAssetsResponse,
} from '../../common/types';
export {
entries,

View file

@ -13,7 +13,6 @@ import semverValid from 'semver/functions/valid';
import type { ResponseHeaders, KnownHeaders, HttpResponseOptions } from '@kbn/core/server';
import { HTTPAuthorizationHeader } from '../../../common/http_authorization_header';
import { generateTransformSecondaryAuthHeaders } from '../../services/api_keys/transform_api_keys';
import { handleTransformReauthorizeAndStart } from '../../services/epm/elasticsearch/transform/reauthorize';
@ -30,6 +29,8 @@ import type {
GetStatsResponse,
UpdatePackageResponse,
GetVerificationKeyIdResponse,
GetBulkAssetsResponse,
SimpleSOAssetType,
GetInstalledPackagesResponse,
GetEpmDataStreamsResponse,
} from '../../../common/types';
@ -48,6 +49,7 @@ import type {
FleetRequestHandler,
UpdatePackageRequestSchema,
GetLimitedPackagesRequestSchema,
GetBulkAssetsRequestSchema,
} from '../../types';
import {
bulkInstallPackages,
@ -70,8 +72,10 @@ import { getAsset } from '../../services/epm/archive/storage';
import { getPackageUsageStats } from '../../services/epm/packages/get';
import { updatePackage } from '../../services/epm/packages/update';
import { getGpgKeyIdOrUndefined } from '../../services/epm/packages/package_verification';
import type { ReauthorizeTransformRequestSchema } from '../../types';
import type { ReauthorizeTransformRequestSchema, SimpleSOAssetAttributes } from '../../types';
import type { KibanaSavedObjectType, ElasticsearchAssetType } from '../../../common/types/models';
import { getDataStreams } from '../../services/epm/data_streams';
import { allowedAssetTypesLookup } from '../../../common/constants';
const CACHE_CONTROL_10_MINUTES_HEADER: HttpResponseOptions['headers'] = {
'cache-control': 'max-age=600',
@ -287,6 +291,7 @@ export const getInfoHandler: FleetRequestHandler<
ignoreUnverified,
prerelease,
});
const body: GetInfoResponse = {
item: res,
};
@ -296,6 +301,43 @@ export const getInfoHandler: FleetRequestHandler<
}
};
export const getBulkAssetsHandler: FleetRequestHandler<
undefined,
undefined,
TypeOf<typeof GetBulkAssetsRequestSchema.body>
> = async (context, request, response) => {
try {
const savedObjectsClient = (await context.fleet).internalSoClient;
const { assetIds } = request.body;
const { resolved_objects: resolvedObjects } =
await savedObjectsClient.bulkResolve<SimpleSOAssetAttributes>(assetIds);
const res: SimpleSOAssetType[] = resolvedObjects
.map(({ saved_object: savedObject }) => savedObject)
.filter(
(savedObject) =>
savedObject?.error?.statusCode !== 404 && allowedAssetTypesLookup.has(savedObject.type)
)
.map((obj) => {
return {
id: obj.id,
type: obj.type as unknown as ElasticsearchAssetType | KibanaSavedObjectType,
updatedAt: obj.updated_at,
attributes: {
title: obj.attributes.title,
description: obj.attributes.description,
},
};
});
const body: GetBulkAssetsResponse = {
items: res,
};
return response.ok({ body });
} catch (error) {
return defaultFleetErrorHandler({ error, response });
}
};
export const updatePackageHandler: FleetRequestHandler<
TypeOf<typeof UpdatePackageRequestSchema.params>,
unknown,

View file

@ -31,6 +31,7 @@ import {
GetFileRequestSchema,
GetInfoRequestSchema,
GetInfoRequestSchemaDeprecated,
GetBulkAssetsRequestSchema,
InstallPackageFromRegistryRequestSchema,
InstallPackageFromRegistryRequestSchemaDeprecated,
InstallPackageByUploadRequestSchema,
@ -51,6 +52,7 @@ import {
getLimitedListHandler,
getFileHandler,
getInfoHandler,
getBulkAssetsHandler,
installPackageFromRegistryHandler,
installPackageByUploadHandler,
deletePackageHandler,
@ -227,6 +229,17 @@ export const registerRoutes = (router: FleetAuthzRouter) => {
getDataStreamsHandler
);
router.post(
{
path: EPM_API_ROUTES.BULK_ASSETS_PATTERN,
validate: GetBulkAssetsRequestSchema,
fleetAuthz: {
integrations: { readPackageInfo: true },
},
},
getBulkAssetsHandler
);
// deprecated since 8.0
router.get(
{

View file

@ -9,7 +9,7 @@ import { savedObjectsClientMock } from '@kbn/core/server/mocks';
import { securityMock } from '@kbn/security-plugin/server/mocks';
import type { DownloadSourceAttributes } from '../types';
import type { DownloadSourceSOAttributes } from '../types';
import { DOWNLOAD_SOURCE_SAVED_OBJECT_TYPE } from '../constants';
@ -158,7 +158,7 @@ describe('Download Service', () => {
// ID should always be the same for a predefined id
expect(soClient.create.mock.calls[0][2]?.id).toEqual('download-source-test');
expect((soClient.create.mock.calls[0][1] as DownloadSourceAttributes).source_id).toEqual(
expect((soClient.create.mock.calls[0][1] as DownloadSourceSOAttributes).source_id).toEqual(
'download-source-test'
);
});

View file

@ -12,7 +12,7 @@ import {
DEFAULT_DOWNLOAD_SOURCE_ID,
} from '../constants';
import type { DownloadSource, DownloadSourceAttributes, DownloadSourceBase } from '../types';
import type { DownloadSource, DownloadSourceSOAttributes, DownloadSourceBase } from '../types';
import { DownloadSourceError, FleetError } from '../errors';
import { SO_SEARCH_LIMIT } from '../../common';
@ -20,7 +20,7 @@ import { agentPolicyService } from './agent_policy';
import { appContextService } from './app_context';
import { escapeSearchQueryPhrase } from './saved_object';
function savedObjectToDownloadSource(so: SavedObject<DownloadSourceAttributes>) {
function savedObjectToDownloadSource(so: SavedObject<DownloadSourceSOAttributes>) {
const { source_id: sourceId, ...attributes } = so.attributes;
return {
@ -31,7 +31,7 @@ function savedObjectToDownloadSource(so: SavedObject<DownloadSourceAttributes>)
class DownloadSourceService {
public async get(soClient: SavedObjectsClientContract, id: string): Promise<DownloadSource> {
const soResponse = await soClient.get<DownloadSourceAttributes>(
const soResponse = await soClient.get<DownloadSourceSOAttributes>(
DOWNLOAD_SOURCE_SAVED_OBJECT_TYPE,
id
);
@ -44,7 +44,7 @@ class DownloadSourceService {
}
public async list(soClient: SavedObjectsClientContract) {
const downloadSources = await soClient.find<DownloadSourceAttributes>({
const downloadSources = await soClient.find<DownloadSourceSOAttributes>({
type: DOWNLOAD_SOURCE_SAVED_OBJECT_TYPE,
page: 1,
perPage: SO_SEARCH_LIMIT,
@ -65,7 +65,7 @@ class DownloadSourceService {
downloadSource: DownloadSourceBase,
options?: { id?: string; overwrite?: boolean }
): Promise<DownloadSource> {
const data: DownloadSourceAttributes = downloadSource;
const data: DownloadSourceSOAttributes = downloadSource;
await this.requireUniqueName(soClient, {
name: downloadSource.name,
@ -84,7 +84,7 @@ class DownloadSourceService {
data.source_id = options?.id;
}
const newSo = await soClient.create<DownloadSourceAttributes>(
const newSo = await soClient.create<DownloadSourceSOAttributes>(
DOWNLOAD_SOURCE_SAVED_OBJECT_TYPE,
data,
{
@ -101,7 +101,7 @@ class DownloadSourceService {
id: string,
newData: Partial<DownloadSource>
) {
const updateData: Partial<DownloadSourceAttributes> = newData;
const updateData: Partial<DownloadSourceSOAttributes> = newData;
if (newData.name) {
await this.requireUniqueName(soClient, {
@ -117,7 +117,7 @@ class DownloadSourceService {
await this.update(soClient, defaultDownloadSourceId, { is_default: false });
}
}
const soResponse = await soClient.update<DownloadSourceAttributes>(
const soResponse = await soClient.update<DownloadSourceSOAttributes>(
DOWNLOAD_SOURCE_SAVED_OBJECT_TYPE,
id,
updateData
@ -183,7 +183,7 @@ class DownloadSourceService {
soClient: SavedObjectsClientContract,
downloadSource: { name: string; id?: string }
) {
const results = await soClient.find<DownloadSourceAttributes>({
const results = await soClient.find<DownloadSourceSOAttributes>({
type: DOWNLOAD_SOURCE_SAVED_OBJECT_TYPE,
searchFields: ['name'],
search: escapeSearchQueryPhrase(downloadSource.name),
@ -205,7 +205,7 @@ class DownloadSourceService {
}
private async _getDefaultDownloadSourceSO(soClient: SavedObjectsClientContract) {
return await soClient.find<DownloadSourceAttributes>({
return await soClient.find<DownloadSourceSOAttributes>({
type: DOWNLOAD_SOURCE_SAVED_OBJECT_TYPE,
searchFields: ['is_default'],
search: 'true',

View file

@ -27,6 +27,7 @@ import type {
PackageUsageStats,
Installable,
PackageDataStreamTypes,
PackageList,
} from '../../../../common/types';
import { PACKAGES_SAVED_OBJECT_TYPE } from '../../../constants';
import type {
@ -146,7 +147,7 @@ export async function getPackages(
return newPkg;
});
return packageListWithoutStatus;
return packageListWithoutStatus as PackageList;
}
interface GetInstalledPackagesOptions {

View file

@ -17,7 +17,6 @@ const mockInstallation: SavedObject<Installation> = {
references: [],
type: 'epm-packages',
attributes: {
id: 'test-pkg',
installed_kibana_space_id: 'default',
installed_kibana: [{ type: KibanaSavedObjectType.dashboard, id: 'dashboard-1' }],
installed_es: [{ type: ElasticsearchAssetType.ingestPipeline, id: 'pipeline' }],
@ -38,7 +37,6 @@ const mockInstallationUpdateFail: SavedObject<Installation> = {
references: [],
type: 'epm-packages',
attributes: {
id: 'test-pkg',
installed_kibana_space_id: 'default',
installed_kibana: [{ type: KibanaSavedObjectType.dashboard, id: 'dashboard-1' }],
installed_es: [{ type: ElasticsearchAssetType.ingestPipeline, id: 'pipeline' }],

View file

@ -78,7 +78,6 @@ export type {
FullAgentPolicyInputStream,
DownloadSourceBase,
DownloadSource,
DownloadSourceAttributes,
PackageVerificationStatus,
BulkInstallPackageInfo,
PackageAssetReference,

View file

@ -89,6 +89,12 @@ export const GetInfoRequestSchema = {
}),
};
export const GetBulkAssetsRequestSchema = {
body: schema.object({
assetIds: schema.arrayOf(schema.object({ id: schema.string(), type: schema.string() })),
}),
};
export const GetInfoRequestSchemaDeprecated = {
params: schema.object({
pkgkey: schema.string(),

View file

@ -137,7 +137,18 @@ export interface OutputSOAttributes {
}
export interface SettingsSOAttributes {
prerelease_integrations_enabled: boolean;
has_seen_add_data_notice?: boolean;
fleet_server_hosts?: string[];
prerelease_integrations_enabled: boolean;
}
export interface DownloadSourceSOAttributes {
name: string;
host: string;
is_default: boolean;
source_id?: string;
}
export interface SimpleSOAssetAttributes {
title?: string;
description?: string;
}

View file

@ -49,7 +49,7 @@ export const createInstalledIntegrationSet = (): IInstalledIntegrationSet => {
}
// Actual `installed_version` is buried in SO, root `version` is latest package version available
const installedPackageVersion = fleetPackage.savedObject.attributes.install_version;
const installedPackageVersion = fleetPackage.savedObject?.attributes.install_version || '';
// Policy templates correspond to package's integrations.
const packagePolicyTemplates = fleetPackage.policy_templates ?? [];

View file

@ -0,0 +1,81 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import expect from '@kbn/expect';
import { GetBulkAssetsResponse } from '@kbn/fleet-plugin/common';
import { FtrProviderContext } from '../../../api_integration/ftr_provider_context';
import { skipIfNoDockerRegistry } from '../../helpers';
import { setupFleetAndAgents } from '../agents/services';
export default function (providerContext: FtrProviderContext) {
const { getService } = providerContext;
const supertest = getService('supertest');
const dockerServers = getService('dockerServers');
const server = dockerServers.get('registry');
const pkgName = 'all_assets';
const pkgVersion = '0.1.0';
const uninstallPackage = async (pkg: string, version: string) => {
await supertest.delete(`/api/fleet/epm/packages/${pkg}/${version}`).set('kbn-xsrf', 'xxxx');
};
const installPackage = async (pkg: string, version: string) => {
await supertest
.post(`/api/fleet/epm/packages/${pkg}/${version}`)
.set('kbn-xsrf', 'xxxx')
.send({ force: true });
};
describe('Bulk get assets', async () => {
skipIfNoDockerRegistry(providerContext);
setupFleetAndAgents(providerContext);
describe('installs all assets when installing a package for the first time', async () => {
before(async () => {
if (!server.enabled) return;
await installPackage(pkgName, pkgVersion);
});
after(async () => {
if (!server.enabled) return;
await uninstallPackage(pkgName, pkgVersion);
});
it('should get the assets based on the required objects', async () => {
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',
},
],
})
.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',
});
});
});
});
}

View file

@ -41,5 +41,6 @@ export default function loadTests({ loadTestFile, getService }) {
loadTestFile(require.resolve('./verification_key_id'));
loadTestFile(require.resolve('./install_integration_in_multiple_spaces.ts'));
loadTestFile(require.resolve('./install_hidden_datastreams'));
loadTestFile(require.resolve('./bulk_get_assets'));
});
}

View file

@ -58,7 +58,7 @@ export default function (providerContext: FtrProviderContext) {
.get(`/api/fleet/epm/packages/endpoint/${latestEndpointVersion}`)
.expect(200));
expect(body.item).to.have.property('savedObject');
expect((body.item as InstalledRegistry).savedObject.attributes.install_version).to.eql(
expect((body.item as InstalledRegistry).savedObject?.attributes.install_version).to.eql(
latestEndpointVersion
);
});