From ffd7e78a49b6dabb40c0c7a8ca9cf4bc10be619a Mon Sep 17 00:00:00 2001 From: Kerry Gallagher Date: Wed, 12 Jul 2023 12:34:37 +0100 Subject: [PATCH 1/4] Add additional properties to installed packages API --- .../common/datasets/models/integration.ts | 6 + .../common/datasets/types.ts | 13 ++ .../components/dataset_selector/utils.tsx | 16 +- .../fleet/common/types/rest_spec/epm.ts | 2 +- .../server/services/epm/packages/get.test.ts | 142 +++++++++++++----- .../fleet/server/services/epm/packages/get.ts | 61 +++++++- 6 files changed, 196 insertions(+), 44 deletions(-) diff --git a/x-pack/plugins/discover_log_explorer/common/datasets/models/integration.ts b/x-pack/plugins/discover_log_explorer/common/datasets/models/integration.ts index 764eeeb40b9a0..2c5e6f077c918 100644 --- a/x-pack/plugins/discover_log_explorer/common/datasets/models/integration.ts +++ b/x-pack/plugins/discover_log_explorer/common/datasets/models/integration.ts @@ -11,6 +11,9 @@ import { IntegrationId, IntegrationType } from '../types'; export class Integration { id: IntegrationId; name: IntegrationType['name']; + title: IntegrationType['title']; + description: IntegrationType['description']; + icons: IntegrationType['icons']; status: IntegrationType['status']; version: IntegrationType['version']; datasets: Dataset[]; @@ -18,6 +21,9 @@ export class Integration { private constructor(integration: Integration) { this.id = integration.id; this.name = integration.name; + this.title = integration.title; + this.description = integration.description; + this.icons = integration.icons; this.status = integration.status; this.version = integration.version; this.datasets = integration.datasets; diff --git a/x-pack/plugins/discover_log_explorer/common/datasets/types.ts b/x-pack/plugins/discover_log_explorer/common/datasets/types.ts index a770aee308423..22881522928f5 100644 --- a/x-pack/plugins/discover_log_explorer/common/datasets/types.ts +++ b/x-pack/plugins/discover_log_explorer/common/datasets/types.ts @@ -27,6 +27,19 @@ const integrationStatusRT = rt.keyof({ export const integrationRT = rt.type({ name: rt.string, + title: rt.union([rt.string, rt.undefined]), + description: rt.union([rt.string, rt.undefined]), + icons: rt.union([ + rt.array( + rt.type({ + src: rt.string, + title: rt.string, + size: rt.string, + type: rt.string, + }) + ), + rt.undefined, + ]), status: integrationStatusRT, version: rt.string, dataStreams: rt.array(datasetRT), diff --git a/x-pack/plugins/discover_log_explorer/public/components/dataset_selector/utils.tsx b/x-pack/plugins/discover_log_explorer/public/components/dataset_selector/utils.tsx index b135c0c22202b..f174e045ed549 100644 --- a/x-pack/plugins/discover_log_explorer/public/components/dataset_selector/utils.tsx +++ b/x-pack/plugins/discover_log_explorer/public/components/dataset_selector/utils.tsx @@ -55,19 +55,27 @@ export const buildIntegrationsTree = ({ }: IntegrationsTreeParams) => { return integrations.reduce( (integrationsTree: IntegrationsTree, integration, pos) => { - const { name, version, datasets } = integration; + const { name, title, version, datasets, icons } = integration; const isLastIntegration = pos === integrations.length - 1; integrationsTree.items.push({ - name, - icon: , + name: title ?? name, + icon: ( + + ), panel: integration.id, ...(isLastIntegration && { buttonRef: spyRef }), }); integrationsTree.panels.push({ id: integration.id, - title: name, + title: title ?? name, width: DATA_VIEW_POPOVER_CONTENT_WIDTH, items: datasets.map((dataset) => ({ name: dataset.title, diff --git a/x-pack/plugins/fleet/common/types/rest_spec/epm.ts b/x-pack/plugins/fleet/common/types/rest_spec/epm.ts index 5658dea03c826..fcec6fc0f8fa5 100644 --- a/x-pack/plugins/fleet/common/types/rest_spec/epm.ts +++ b/x-pack/plugins/fleet/common/types/rest_spec/epm.ts @@ -51,7 +51,7 @@ export interface GetPackagesResponse { response?: PackageList; } -interface InstalledPackage { +export interface InstalledPackage { name: string; version: string; status: EpmPackageInstallStatus; diff --git a/x-pack/plugins/fleet/server/services/epm/packages/get.test.ts b/x-pack/plugins/fleet/server/services/epm/packages/get.test.ts index 836be792db068..445bc6fca6719 100644 --- a/x-pack/plugins/fleet/server/services/epm/packages/get.test.ts +++ b/x-pack/plugins/fleet/server/services/epm/packages/get.test.ts @@ -10,7 +10,11 @@ import type { SavedObjectsClientContract, SavedObjectsFindResult } from '@kbn/co import { SavedObjectsErrorHelpers } from '@kbn/core/server'; import { savedObjectsClientMock } from '@kbn/core/server/mocks'; -import { PACKAGES_SAVED_OBJECT_TYPE, PACKAGE_POLICY_SAVED_OBJECT_TYPE } from '../../../../common'; +import { + ASSETS_SAVED_OBJECT_TYPE, + PACKAGES_SAVED_OBJECT_TYPE, + PACKAGE_POLICY_SAVED_OBJECT_TYPE, +} from '../../../../common'; import type { RegistryPackage } from '../../../../common/types'; import type { PackagePolicySOAttributes } from '../../../types'; @@ -435,24 +439,51 @@ owner: elastic`, it('Passes the correct parameters to the SavedObjects client', async () => { const soClient = savedObjectsClientMock.create(); - soClient.find.mockResolvedValue({ - saved_objects: [ - { - type: 'epm-packages', - id: 'elastic_agent', - attributes: { - es_index_patterns: { - apm_server_logs: 'logs-elastic_agent.apm_server-*', - apm_server_metrics: 'metrics-elastic_agent.apm_server-*', + soClient.find.mockImplementation(async (options) => { + if (options.type === PACKAGES_SAVED_OBJECT_TYPE) { + return { + total: 5, + saved_objects: [ + { + type: 'epm-packages', + id: 'elastic_agent', + attributes: { + es_index_patterns: { + apm_server_logs: 'logs-elastic_agent.apm_server-*', + apm_server_metrics: 'metrics-elastic_agent.apm_server-*', + }, + name: 'elastic_agent', + version: '1.7.0', + install_status: 'installed', + }, + references: [], + sort: ['elastic_agent'], }, - name: 'elastic_agent', - version: '1.7.0', - install_status: 'installed', - }, - references: [], - }, - ], - } as any); + ], + } as any; + } else if (options.type === ASSETS_SAVED_OBJECT_TYPE) { + return { + total: 5, + saved_objects: [ + { + type: 'epm-packages-assets', + id: '338b6f9e-e126-5f1e-abb9-afe017d4788b', + attributes: { + package_name: 'elastic_agent', + package_version: '1.8.0', + install_source: 'upload', + asset_path: 'elastic_agent-1.8.0/manifest.yml', + media_type: 'text/yaml; charset=utf-8', + data_utf8: + 'name: elastic_agent\ntitle: Elastic Agent\nversion: 1.8.0\ndescription: Collect logs and metrics from Elastic Agents.\ntype: integration\nformat_version: 1.0.0\nlicense: basic\ncategories: ["elastic_stack"]\nconditions:\n kibana.version: "^8.7.1"\nowner:\n github: elastic/elastic-agent\nicons:\n - src: /img/logo_elastic_agent.svg\n title: logo Elastic Agent\n size: 64x64\n type: image/svg+xml\nscreenshots:\n - src: /img/elastic_agent_overview.png\n title: Elastic Agent Overview\n size: 2560×1234\n type: image/png\n - src: /img/elastic_agent_metrics.png\n title: Elastic Agent Metrics\n size: 2560×1234\n type: image/png\n - src: /img/elastic_agent_info.png\n title: Elastic Agent Information\n size: 2560×1234\n type: image/png\n - src: /img/elastic_agent_integrations.png\n title: Elastic Agent Integrations\n size: 2560×1234\n type: image/png\n', + data_base64: '', + }, + references: [], + }, + ], + } as any; + } + }); await getInstalledPackages({ savedObjectsClient: soClient, @@ -550,26 +581,51 @@ owner: elastic`, it('Formats items correctly', async () => { const soClient = savedObjectsClientMock.create(); - soClient.find.mockResolvedValue({ - total: 5, - saved_objects: [ - { - type: 'epm-packages', - id: 'elastic_agent', - attributes: { - es_index_patterns: { - apm_server_logs: 'logs-elastic_agent.apm_server-*', - apm_server_metrics: 'metrics-elastic_agent.apm_server-*', + soClient.find.mockImplementation(async (options) => { + if (options.type === PACKAGES_SAVED_OBJECT_TYPE) { + return { + total: 5, + saved_objects: [ + { + type: 'epm-packages', + id: 'elastic_agent', + attributes: { + es_index_patterns: { + apm_server_logs: 'logs-elastic_agent.apm_server-*', + apm_server_metrics: 'metrics-elastic_agent.apm_server-*', + }, + name: 'elastic_agent', + version: '1.8.0', + install_status: 'installed', + }, + references: [], + sort: ['elastic_agent'], }, - name: 'elastic_agent', - version: '1.7.0', - install_status: 'installed', - }, - references: [], - sort: ['elastic_agent'], - }, - ], - } as any); + ], + } as any; + } else if (options.type === ASSETS_SAVED_OBJECT_TYPE) { + return { + total: 5, + saved_objects: [ + { + type: 'epm-packages-assets', + id: '338b6f9e-e126-5f1e-abb9-afe017d4788b', + attributes: { + package_name: 'elastic_agent', + package_version: '1.8.0', + install_source: 'upload', + asset_path: 'elastic_agent-1.8.0/manifest.yml', + media_type: 'text/yaml; charset=utf-8', + data_utf8: + 'name: elastic_agent\ntitle: Elastic Agent\nversion: 1.8.0\ndescription: Collect logs and metrics from Elastic Agents.\ntype: integration\nformat_version: 1.0.0\nlicense: basic\ncategories: ["elastic_stack"]\nconditions:\n kibana.version: "^8.7.1"\nowner:\n github: elastic/elastic-agent\nicons:\n - src: /img/logo_elastic_agent.svg\n title: logo Elastic Agent\n size: 64x64\n type: image/svg+xml\nscreenshots:\n - src: /img/elastic_agent_overview.png\n title: Elastic Agent Overview\n size: 2560×1234\n type: image/png\n - src: /img/elastic_agent_metrics.png\n title: Elastic Agent Metrics\n size: 2560×1234\n type: image/png\n - src: /img/elastic_agent_info.png\n title: Elastic Agent Information\n size: 2560×1234\n type: image/png\n - src: /img/elastic_agent_integrations.png\n title: Elastic Agent Integrations\n size: 2560×1234\n type: image/png\n', + data_base64: '', + }, + references: [], + }, + ], + } as any; + } + }); const results = await getInstalledPackages({ savedObjectsClient: soClient, @@ -586,7 +642,17 @@ owner: elastic`, dataStreams: [{ name: 'logs-elastic_agent.apm_server-*', title: 'apm_server_logs' }], name: 'elastic_agent', status: 'installed', - version: '1.7.0', + version: '1.8.0', + title: 'Elastic Agent', + description: 'Collect logs and metrics from Elastic Agents.', + icons: [ + { + size: '64x64', + src: '/img/logo_elastic_agent.svg', + title: 'logo Elastic Agent', + type: 'image/svg+xml', + }, + ], }, ], searchAfter: ['elastic_agent'], diff --git a/x-pack/plugins/fleet/server/services/epm/packages/get.ts b/x-pack/plugins/fleet/server/services/epm/packages/get.ts index 19bedd8ff842e..ea7539c33568d 100644 --- a/x-pack/plugins/fleet/server/services/epm/packages/get.ts +++ b/x-pack/plugins/fleet/server/services/epm/packages/get.ts @@ -4,6 +4,8 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ + +import * as yaml from 'js-yaml'; import pMap from 'p-map'; import type { SavedObjectsClientContract, SavedObjectsFindOptions } from '@kbn/core/server'; import semverGte from 'semver/functions/gte'; @@ -18,6 +20,7 @@ import { buildNode as buildFunctionNode } from '@kbn/es-query/src/kuery/node_typ import { buildNode as buildWildcardNode } from '@kbn/es-query/src/kuery/node_types/wildcard'; import { + ASSETS_SAVED_OBJECT_TYPE, installationStatuses, PACKAGE_POLICY_SAVED_OBJECT_TYPE, SO_SEARCH_LIMIT, @@ -28,6 +31,7 @@ import type { Installable, PackageDataStreamTypes, PackageList, + InstalledPackage, } from '../../../../common/types'; import { PACKAGES_SAVED_OBJECT_TYPE } from '../../../constants'; import type { @@ -45,6 +49,7 @@ import { } from '../../../errors'; import { appContextService } from '../..'; import * as Registry from '../registry'; +import type { PackageAsset } from '../archive/storage'; import { getEsPackage } from '../archive/storage'; import { getArchivePackage } from '../archive'; import { normalizeKuery } from '../../saved_object'; @@ -189,8 +194,27 @@ export async function getInstalledPackages(options: GetInstalledPackagesOptions) }; }); + const integrationManifests = + integrations.length > 0 + ? await getInstalledPackageManifests(savedObjectsClient, integrations) + : []; + + const integrationsWithManifestContent = integrations.map((integration) => { + const { name, version } = integration; + const integrationAsset = integrationManifests.find((asset) => { + return asset.assetPath.startsWith(`${name}-${version}`); + }); + + return { + ...integration, + title: integrationAsset?.manifest.title ?? undefined, + description: integrationAsset?.manifest.description ?? undefined, + icons: integrationAsset?.manifest.icons ?? undefined, + }; + }); + return { - items: integrations, + items: integrationsWithManifestContent, total: packageSavedObjects.total, searchAfter: packageSavedObjects.saved_objects.at(-1)?.sort, // Enable ability to use searchAfter in subsequent queries }; @@ -306,6 +330,41 @@ export async function getInstalledPackageSavedObjects( return result; } +export async function getInstalledPackageManifests( + savedObjectsClient: SavedObjectsClientContract, + packages: InstalledPackage[] +) { + const pathFilters = packages.map((_package) => { + const { name, version } = _package; + return nodeBuilder.is( + `${ASSETS_SAVED_OBJECT_TYPE}.attributes.asset_path`, + `${name}-${version}/manifest.yml` + ); + }); + + const result = await savedObjectsClient.find({ + type: ASSETS_SAVED_OBJECT_TYPE, + filter: nodeBuilder.or(pathFilters), + }); + + const parsedManifests = result.saved_objects.map((asset) => { + return { + assetPath: asset.attributes.asset_path, + manifest: yaml.load(asset.attributes.data_utf8), + }; + }); + + for (const savedObject of result.saved_objects) { + auditLoggingService.writeCustomSoAuditLog({ + action: 'find', + id: savedObject.id, + savedObjectType: ASSETS_SAVED_OBJECT_TYPE, + }); + } + + return parsedManifests; +} + function getInstalledPackageSavedObjectDataStreams( indexPatterns: Record, dataStreamType?: string From 8a3e4314a8750c225acbaa7f9431b0c525bc4f3c Mon Sep 17 00:00:00 2001 From: Kerry Gallagher Date: Wed, 12 Jul 2023 16:59:14 +0100 Subject: [PATCH 2/4] Switch to Map Slightly more elegant for access. Shouldn't be less performant vs multiple finds. --- .../fleet/server/services/epm/packages/get.ts | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/x-pack/plugins/fleet/server/services/epm/packages/get.ts b/x-pack/plugins/fleet/server/services/epm/packages/get.ts index ea7539c33568d..96113407222f6 100644 --- a/x-pack/plugins/fleet/server/services/epm/packages/get.ts +++ b/x-pack/plugins/fleet/server/services/epm/packages/get.ts @@ -32,6 +32,7 @@ import type { PackageDataStreamTypes, PackageList, InstalledPackage, + PackageSpecManifest, } from '../../../../common/types'; import { PACKAGES_SAVED_OBJECT_TYPE } from '../../../constants'; import type { @@ -197,19 +198,17 @@ export async function getInstalledPackages(options: GetInstalledPackagesOptions) const integrationManifests = integrations.length > 0 ? await getInstalledPackageManifests(savedObjectsClient, integrations) - : []; + : new Map(); const integrationsWithManifestContent = integrations.map((integration) => { const { name, version } = integration; - const integrationAsset = integrationManifests.find((asset) => { - return asset.assetPath.startsWith(`${name}-${version}`); - }); + const integrationAsset = integrationManifests.get(`${name}-${version}/manifest.yml`); return { ...integration, - title: integrationAsset?.manifest.title ?? undefined, - description: integrationAsset?.manifest.description ?? undefined, - icons: integrationAsset?.manifest.icons ?? undefined, + title: integrationAsset?.title ?? undefined, + description: integrationAsset?.description ?? undefined, + icons: integrationAsset?.icons ?? undefined, }; }); @@ -332,10 +331,10 @@ export async function getInstalledPackageSavedObjects( export async function getInstalledPackageManifests( savedObjectsClient: SavedObjectsClientContract, - packages: InstalledPackage[] + installedPackages: InstalledPackage[] ) { - const pathFilters = packages.map((_package) => { - const { name, version } = _package; + const pathFilters = installedPackages.map((installedPackage) => { + const { name, version } = installedPackage; return nodeBuilder.is( `${ASSETS_SAVED_OBJECT_TYPE}.attributes.asset_path`, `${name}-${version}/manifest.yml` @@ -347,12 +346,13 @@ export async function getInstalledPackageManifests( filter: nodeBuilder.or(pathFilters), }); - const parsedManifests = result.saved_objects.map((asset) => { - return { - assetPath: asset.attributes.asset_path, - manifest: yaml.load(asset.attributes.data_utf8), - }; - }); + const parsedManifests = result.saved_objects.reduce>( + (acc, asset) => { + acc.set(asset.attributes.asset_path, yaml.load(asset.attributes.data_utf8)); + return acc; + }, + new Map() + ); for (const savedObject of result.saved_objects) { auditLoggingService.writeCustomSoAuditLog({ From 9ddca179c63e63fbd6919d64111f58a185ed6e61 Mon Sep 17 00:00:00 2001 From: Kerry Gallagher Date: Wed, 12 Jul 2023 17:15:17 +0100 Subject: [PATCH 3/4] Update Discover references --- .../common/datasets/models/dataset.ts | 8 +++++--- .../common/datasets/models/integration.ts | 2 +- .../sub_components/datasets_popover.tsx | 1 + .../public/components/dataset_selector/utils.tsx | 14 +++----------- 4 files changed, 10 insertions(+), 15 deletions(-) diff --git a/x-pack/plugins/discover_log_explorer/common/datasets/models/dataset.ts b/x-pack/plugins/discover_log_explorer/common/datasets/models/dataset.ts index 38a6024de277c..9db74da31543f 100644 --- a/x-pack/plugins/discover_log_explorer/common/datasets/models/dataset.ts +++ b/x-pack/plugins/discover_log_explorer/common/datasets/models/dataset.ts @@ -10,7 +10,7 @@ import { DataViewSpec } from '@kbn/data-views-plugin/common'; import { IndexPattern } from '@kbn/io-ts-utils'; import { DatasetId, DatasetType, IntegrationType } from '../types'; -type IntegrationBase = Pick; +type IntegrationBase = Pick; interface DatasetDeps extends DatasetType { iconType?: IconType; } @@ -29,13 +29,15 @@ export class Dataset { this.title = dataset.title ?? dataset.name; this.parentIntegration = parentIntegration && { name: parentIntegration.name, + title: parentIntegration.title, + icons: parentIntegration.icons, version: parentIntegration.version, }; } getFullTitle(): string { - return this.parentIntegration?.name - ? `[${this.parentIntegration.name}] ${this.title}` + return this.parentIntegration?.title + ? `[${this.parentIntegration.title}] ${this.title}` : this.title; } diff --git a/x-pack/plugins/discover_log_explorer/common/datasets/models/integration.ts b/x-pack/plugins/discover_log_explorer/common/datasets/models/integration.ts index 2c5e6f077c918..00db505c8a55b 100644 --- a/x-pack/plugins/discover_log_explorer/common/datasets/models/integration.ts +++ b/x-pack/plugins/discover_log_explorer/common/datasets/models/integration.ts @@ -21,7 +21,7 @@ export class Integration { private constructor(integration: Integration) { this.id = integration.id; this.name = integration.name; - this.title = integration.title; + this.title = integration.title ?? integration.name; this.description = integration.description; this.icons = integration.icons; this.status = integration.status; diff --git a/x-pack/plugins/discover_log_explorer/public/components/dataset_selector/sub_components/datasets_popover.tsx b/x-pack/plugins/discover_log_explorer/public/components/dataset_selector/sub_components/datasets_popover.tsx index 0ed65b4e50d8b..77e142d81b999 100644 --- a/x-pack/plugins/discover_log_explorer/public/components/dataset_selector/sub_components/datasets_popover.tsx +++ b/x-pack/plugins/discover_log_explorer/public/components/dataset_selector/sub_components/datasets_popover.tsx @@ -60,6 +60,7 @@ export const DatasetsPopover = ({ diff --git a/x-pack/plugins/discover_log_explorer/public/components/dataset_selector/utils.tsx b/x-pack/plugins/discover_log_explorer/public/components/dataset_selector/utils.tsx index f174e045ed549..b3e5822dfbbbd 100644 --- a/x-pack/plugins/discover_log_explorer/public/components/dataset_selector/utils.tsx +++ b/x-pack/plugins/discover_log_explorer/public/components/dataset_selector/utils.tsx @@ -59,23 +59,15 @@ export const buildIntegrationsTree = ({ const isLastIntegration = pos === integrations.length - 1; integrationsTree.items.push({ - name: title ?? name, - icon: ( - - ), + name: title, + icon: , panel: integration.id, ...(isLastIntegration && { buttonRef: spyRef }), }); integrationsTree.panels.push({ id: integration.id, - title: title ?? name, + title, width: DATA_VIEW_POPOVER_CONTENT_WIDTH, items: datasets.map((dataset) => ({ name: dataset.title, From 37d5ae07eebc0e2e8dd728b3ad52b90f0c8c3bd2 Mon Sep 17 00:00:00 2001 From: Kerry Gallagher Date: Thu, 13 Jul 2023 01:05:43 +0100 Subject: [PATCH 4/4] Slightly alter types Slightly changes the types so the properties are optional vs just defining undefined in the union. This is mostly to appease fromSelection() as it tries to reconstruct from a partial integration payload. Once the hydration function is used (it's only call location) we should probably expand the payload for the integration properties. Also appeases storybook. # Please enter the commit message for your changes. Lines starting --- .../common/datasets/models/integration.ts | 6 +-- .../common/datasets/types.ts | 42 ++++++++++--------- 2 files changed, 26 insertions(+), 22 deletions(-) diff --git a/x-pack/plugins/discover_log_explorer/common/datasets/models/integration.ts b/x-pack/plugins/discover_log_explorer/common/datasets/models/integration.ts index 00db505c8a55b..95411309d2cd2 100644 --- a/x-pack/plugins/discover_log_explorer/common/datasets/models/integration.ts +++ b/x-pack/plugins/discover_log_explorer/common/datasets/models/integration.ts @@ -11,9 +11,9 @@ import { IntegrationId, IntegrationType } from '../types'; export class Integration { id: IntegrationId; name: IntegrationType['name']; - title: IntegrationType['title']; - description: IntegrationType['description']; - icons: IntegrationType['icons']; + title?: IntegrationType['title']; + description?: IntegrationType['description']; + icons?: IntegrationType['icons']; status: IntegrationType['status']; version: IntegrationType['version']; datasets: Dataset[]; diff --git a/x-pack/plugins/discover_log_explorer/common/datasets/types.ts b/x-pack/plugins/discover_log_explorer/common/datasets/types.ts index 22881522928f5..b15916a1162b1 100644 --- a/x-pack/plugins/discover_log_explorer/common/datasets/types.ts +++ b/x-pack/plugins/discover_log_explorer/common/datasets/types.ts @@ -25,25 +25,29 @@ const integrationStatusRT = rt.keyof({ install_failed: null, }); -export const integrationRT = rt.type({ - name: rt.string, - title: rt.union([rt.string, rt.undefined]), - description: rt.union([rt.string, rt.undefined]), - icons: rt.union([ - rt.array( - rt.type({ - src: rt.string, - title: rt.string, - size: rt.string, - type: rt.string, - }) - ), - rt.undefined, - ]), - status: integrationStatusRT, - version: rt.string, - dataStreams: rt.array(datasetRT), -}); +export const integrationRT = rt.intersection([ + rt.type({ + name: rt.string, + status: integrationStatusRT, + version: rt.string, + dataStreams: rt.array(datasetRT), + }), + rt.partial({ + title: rt.union([rt.string, rt.undefined]), + icons: rt.union([ + rt.array( + rt.type({ + src: rt.string, + title: rt.string, + size: rt.string, + type: rt.string, + }) + ), + rt.undefined, + ]), + description: rt.union([rt.string, rt.undefined]), + }), +]); export type DatasetId = `dataset-${string}`; export type IntegrationId = `integration-${string}-${string}`;