mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
* Manual cherry pick of work to support integration tiles and package-level vars * Fix types * Remove registry input group typings * Show integration-specific readme, title, and icon in package details page * Revert unnecessary changes * Add package-level `vars` field to package policy SO mappings * Fix types * Fix test Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Jen Huang <its.jenetic@gmail.com>
This commit is contained in:
parent
a89dd49316
commit
112e174c31
34 changed files with 684 additions and 355 deletions
|
@ -16,3 +16,4 @@ export { isValidNamespace } from './is_valid_namespace';
|
|||
export { isDiffPathProtocol } from './is_diff_path_protocol';
|
||||
export { LicenseService } from './license';
|
||||
export { isAgentUpgradeable } from './is_agent_upgradeable';
|
||||
export { doesPackageHaveIntegrations } from './packages_with_integrations';
|
||||
|
|
|
@ -40,6 +40,21 @@ const getStreamsForInputType = (
|
|||
return streams;
|
||||
};
|
||||
|
||||
// Reduces registry var def into config object entry
|
||||
const varsReducer = (
|
||||
configObject: PackagePolicyConfigRecord,
|
||||
registryVar: RegistryVarsEntry
|
||||
): PackagePolicyConfigRecord => {
|
||||
const configEntry: PackagePolicyConfigRecordEntry = {
|
||||
value: !registryVar.default && registryVar.multi ? [] : registryVar.default,
|
||||
};
|
||||
if (registryVar.type) {
|
||||
configEntry.type = registryVar.type;
|
||||
}
|
||||
configObject![registryVar.name] = configEntry;
|
||||
return configObject;
|
||||
};
|
||||
|
||||
/*
|
||||
* This service creates a package policy inputs definition from defaults provided in package info
|
||||
*/
|
||||
|
@ -58,21 +73,6 @@ export const packageToPackagePolicyInputs = (
|
|||
if (packagePolicyTemplate?.inputs?.length) {
|
||||
// Map each package package policy input to agent policy package policy input
|
||||
packagePolicyTemplate.inputs.forEach((packageInput) => {
|
||||
// Reduces registry var def into config object entry
|
||||
const varsReducer = (
|
||||
configObject: PackagePolicyConfigRecord,
|
||||
registryVar: RegistryVarsEntry
|
||||
): PackagePolicyConfigRecord => {
|
||||
const configEntry: PackagePolicyConfigRecordEntry = {
|
||||
value: !registryVar.default && registryVar.multi ? [] : registryVar.default,
|
||||
};
|
||||
if (registryVar.type) {
|
||||
configEntry.type = registryVar.type;
|
||||
}
|
||||
configObject![registryVar.name] = configEntry;
|
||||
return configObject;
|
||||
};
|
||||
|
||||
// Map each package input stream into package policy input stream
|
||||
const streams: NewPackagePolicyInputStream[] = getStreamsForInputType(
|
||||
packageInput.type,
|
||||
|
@ -121,7 +121,7 @@ export const packageToPackagePolicy = (
|
|||
packagePolicyName?: string,
|
||||
description?: string
|
||||
): NewPackagePolicy => {
|
||||
return {
|
||||
const packagePolicy: NewPackagePolicy = {
|
||||
name: packagePolicyName || `${packageInfo.name}-1`,
|
||||
namespace,
|
||||
description,
|
||||
|
@ -135,4 +135,10 @@ export const packageToPackagePolicy = (
|
|||
output_id: outputId,
|
||||
inputs: packageToPackagePolicyInputs(packageInfo),
|
||||
};
|
||||
|
||||
if (packageInfo.vars?.length) {
|
||||
packagePolicy.vars = packageInfo.vars.reduce(varsReducer, {});
|
||||
}
|
||||
|
||||
return packagePolicy;
|
||||
};
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
/*
|
||||
* 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 { PackageInfo, PackageListItem } from '../types';
|
||||
|
||||
export const doesPackageHaveIntegrations = (pkgInfo: PackageInfo | PackageListItem) => {
|
||||
return (pkgInfo.policy_templates || []).length > 1;
|
||||
};
|
|
@ -19,7 +19,12 @@ import type {
|
|||
} from '../../constants';
|
||||
import type { ValueOf } from '../../types';
|
||||
|
||||
import type { PackageSpecManifest, PackageSpecScreenshot } from './package_spec';
|
||||
import type {
|
||||
PackageSpecManifest,
|
||||
PackageSpecIcon,
|
||||
PackageSpecScreenshot,
|
||||
PackageSpecCategory,
|
||||
} from './package_spec';
|
||||
|
||||
export type InstallationStatus = typeof installationStatuses;
|
||||
|
||||
|
@ -118,19 +123,20 @@ interface RegistryOverridePropertyValue {
|
|||
}
|
||||
|
||||
export type RegistryRelease = PackageSpecManifest['release'];
|
||||
export interface RegistryImage {
|
||||
src: string;
|
||||
export interface RegistryImage extends PackageSpecIcon {
|
||||
path: string;
|
||||
title?: string;
|
||||
size?: string;
|
||||
type?: string;
|
||||
}
|
||||
|
||||
export enum RegistryPolicyTemplateKeys {
|
||||
name = 'name',
|
||||
title = 'title',
|
||||
description = 'description',
|
||||
icons = 'icons',
|
||||
screenshots = 'screenshots',
|
||||
categories = 'categories',
|
||||
data_streams = 'data_streams',
|
||||
inputs = 'inputs',
|
||||
readme = 'readme',
|
||||
multiple = 'multiple',
|
||||
}
|
||||
|
||||
|
@ -138,7 +144,12 @@ export interface RegistryPolicyTemplate {
|
|||
[RegistryPolicyTemplateKeys.name]: string;
|
||||
[RegistryPolicyTemplateKeys.title]: string;
|
||||
[RegistryPolicyTemplateKeys.description]: string;
|
||||
[RegistryPolicyTemplateKeys.icons]?: RegistryImage[];
|
||||
[RegistryPolicyTemplateKeys.screenshots]?: RegistryImage[];
|
||||
[RegistryPolicyTemplateKeys.categories]?: Array<PackageSpecCategory | undefined>;
|
||||
[RegistryPolicyTemplateKeys.data_streams]?: string[];
|
||||
[RegistryPolicyTemplateKeys.inputs]?: RegistryInput[];
|
||||
[RegistryPolicyTemplateKeys.readme]?: string;
|
||||
[RegistryPolicyTemplateKeys.multiple]?: boolean;
|
||||
}
|
||||
|
||||
|
@ -148,15 +159,19 @@ export enum RegistryInputKeys {
|
|||
description = 'description',
|
||||
template_path = 'template_path',
|
||||
condition = 'condition',
|
||||
input_group = 'input_group',
|
||||
vars = 'vars',
|
||||
}
|
||||
|
||||
export type RegistryInputGroup = 'logs' | 'metrics';
|
||||
|
||||
export interface RegistryInput {
|
||||
[RegistryInputKeys.type]: string;
|
||||
[RegistryInputKeys.title]: string;
|
||||
[RegistryInputKeys.description]: string;
|
||||
[RegistryInputKeys.template_path]?: string;
|
||||
[RegistryInputKeys.condition]?: string;
|
||||
[RegistryInputKeys.input_group]?: RegistryInputGroup;
|
||||
[RegistryInputKeys.vars]?: RegistryVarsEntry[];
|
||||
}
|
||||
|
||||
|
@ -273,7 +288,7 @@ export interface RegistryDataStream {
|
|||
[RegistryDataStreamKeys.streams]?: RegistryStream[];
|
||||
[RegistryDataStreamKeys.package]: string;
|
||||
[RegistryDataStreamKeys.path]: string;
|
||||
[RegistryDataStreamKeys.ingest_pipeline]: string;
|
||||
[RegistryDataStreamKeys.ingest_pipeline]?: string;
|
||||
[RegistryDataStreamKeys.elasticsearch]?: RegistryElasticsearch;
|
||||
[RegistryDataStreamKeys.dataset_is_prefix]?: boolean;
|
||||
}
|
||||
|
@ -307,7 +322,7 @@ export interface RegistryVarsEntry {
|
|||
[RegistryVarsEntryKeys.required]?: boolean;
|
||||
[RegistryVarsEntryKeys.show_user]?: boolean;
|
||||
[RegistryVarsEntryKeys.multi]?: boolean;
|
||||
[RegistryVarsEntryKeys.default]?: string | string[];
|
||||
[RegistryVarsEntryKeys.default]?: string | string[] | boolean;
|
||||
[RegistryVarsEntryKeys.os]?: {
|
||||
[key: string]: {
|
||||
default: string | string[];
|
||||
|
@ -329,8 +344,11 @@ type Merge<FirstType, SecondType> = Omit<FirstType, Extract<keyof FirstType, key
|
|||
|
||||
// Managers public HTTP response types
|
||||
export type PackageList = PackageListItem[];
|
||||
export type PackageListItem = Installable<RegistrySearchResult> & {
|
||||
integration?: string;
|
||||
id: string;
|
||||
};
|
||||
|
||||
export type PackageListItem = Installable<RegistrySearchResult>;
|
||||
export type PackagesGroupedByStatus = Record<ValueOf<InstallationStatus>, PackageList>;
|
||||
export type PackageInfo =
|
||||
| Installable<Merge<RegistryPackage, EpmPackageAdditions>>
|
||||
|
|
|
@ -58,6 +58,7 @@ export interface NewPackagePolicy {
|
|||
output_id: string;
|
||||
package?: PackagePolicyPackage;
|
||||
inputs: NewPackagePolicyInput[];
|
||||
vars?: PackagePolicyConfigRecord;
|
||||
}
|
||||
|
||||
export interface UpdatePackagePolicy extends NewPackagePolicy {
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { RegistryPolicyTemplate } from './epm';
|
||||
import type { RegistryPolicyTemplate, RegistryVarsEntry } from './epm';
|
||||
|
||||
// Based on https://github.com/elastic/package-spec/blob/master/versions/1/manifest.spec.yml#L8
|
||||
export interface PackageSpecManifest {
|
||||
|
@ -22,6 +22,7 @@ export interface PackageSpecManifest {
|
|||
icons?: PackageSpecIcon[];
|
||||
screenshots?: PackageSpecScreenshot[];
|
||||
policy_templates?: RegistryPolicyTemplate[];
|
||||
vars?: RegistryVarsEntry[];
|
||||
owner: { github: string };
|
||||
}
|
||||
|
||||
|
|
|
@ -8,8 +8,7 @@
|
|||
import type {
|
||||
AssetReference,
|
||||
CategorySummaryList,
|
||||
Installable,
|
||||
RegistrySearchResult,
|
||||
PackageList,
|
||||
PackageInfo,
|
||||
PackageUsageStats,
|
||||
InstallType,
|
||||
|
@ -18,6 +17,7 @@ import type {
|
|||
export interface GetCategoriesRequest {
|
||||
query: {
|
||||
experimental?: boolean;
|
||||
include_policy_templates?: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -33,7 +33,7 @@ export interface GetPackagesRequest {
|
|||
}
|
||||
|
||||
export interface GetPackagesResponse {
|
||||
response: Array<Installable<RegistrySearchResult>>;
|
||||
response: PackageList;
|
||||
}
|
||||
|
||||
export interface GetLimitedPackagesResponse {
|
||||
|
|
|
@ -14,7 +14,7 @@ import { usePackageIconType } from '../hooks';
|
|||
|
||||
export const PackageIcon: React.FunctionComponent<
|
||||
UsePackageIconType & Omit<EuiIconProps, 'type'>
|
||||
> = ({ packageName, version, icons, tryApi, ...euiIconProps }) => {
|
||||
const iconType = usePackageIconType({ packageName, version, icons, tryApi });
|
||||
> = ({ packageName, integrationName, version, icons, tryApi, ...euiIconProps }) => {
|
||||
const iconType = usePackageIconType({ packageName, integrationName, version, icons, tryApi });
|
||||
return <EuiIcon size="s" type={iconType} {...euiIconProps} />;
|
||||
};
|
||||
|
|
|
@ -56,7 +56,7 @@ export const PAGE_ROUTING_PATHS = {
|
|||
policy_details: '/policies/:policyId/:tabId?',
|
||||
policy_details_settings: '/policies/:policyId/settings',
|
||||
add_integration_from_policy: '/policies/:policyId/add-integration',
|
||||
add_integration_to_policy: '/integrations/:pkgkey/add-integration',
|
||||
add_integration_to_policy: '/integrations/:pkgkey/add-integration/:integration?',
|
||||
edit_integration: '/policies/:policyId/edit-integration/:packagePolicyId',
|
||||
fleet: '/fleet',
|
||||
fleet_agent_list: '/fleet/agents',
|
||||
|
@ -77,17 +77,22 @@ export const pagePathGetters: {
|
|||
integrations: () => '/integrations',
|
||||
integrations_all: () => '/integrations',
|
||||
integrations_installed: () => '/integrations/installed',
|
||||
integration_details_overview: ({ pkgkey }) => `/integrations/detail/${pkgkey}/overview`,
|
||||
integration_details_policies: ({ pkgkey }) => `/integrations/detail/${pkgkey}/policies`,
|
||||
integration_details_settings: ({ pkgkey }) => `/integrations/detail/${pkgkey}/settings`,
|
||||
integration_details_custom: ({ pkgkey }) => `/integrations/detail/${pkgkey}/custom`,
|
||||
integration_details_overview: ({ pkgkey, integration }) =>
|
||||
`/integrations/detail/${pkgkey}/overview${integration ? `?integration=${integration}` : ''}`,
|
||||
integration_details_policies: ({ pkgkey, integration }) =>
|
||||
`/integrations/detail/${pkgkey}/policies${integration ? `?integration=${integration}` : ''}`,
|
||||
integration_details_settings: ({ pkgkey, integration }) =>
|
||||
`/integrations/detail/${pkgkey}/settings${integration ? `?integration=${integration}` : ''}`,
|
||||
integration_details_custom: ({ pkgkey, integration }) =>
|
||||
`/integrations/detail/${pkgkey}/custom${integration ? `?integration=${integration}` : ''}`,
|
||||
integration_policy_edit: ({ packagePolicyId }) =>
|
||||
`/integrations/edit-integration/${packagePolicyId}`,
|
||||
policies: () => '/policies',
|
||||
policies_list: () => '/policies',
|
||||
policy_details: ({ policyId, tabId }) => `/policies/${policyId}${tabId ? `/${tabId}` : ''}`,
|
||||
add_integration_from_policy: ({ policyId }) => `/policies/${policyId}/add-integration`,
|
||||
add_integration_to_policy: ({ pkgkey }) => `/integrations/${pkgkey}/add-integration`,
|
||||
add_integration_to_policy: ({ pkgkey, integration }) =>
|
||||
`/integrations/${pkgkey}/add-integration${integration ? `/${integration}` : ''}`,
|
||||
edit_integration: ({ policyId, packagePolicyId }) =>
|
||||
`/policies/${policyId}/edit-integration/${packagePolicyId}`,
|
||||
fleet: () => '/fleet',
|
||||
|
|
|
@ -16,7 +16,8 @@ import { sendGetPackageInfoByKey } from './index';
|
|||
type Package = PackageInfo | PackageListItem;
|
||||
|
||||
export interface UsePackageIconType {
|
||||
packageName: Package['name'];
|
||||
packageName: string;
|
||||
integrationName?: string;
|
||||
version: Package['version'];
|
||||
icons?: Package['icons'];
|
||||
tryApi?: boolean; // should it call API to try to find missing icons?
|
||||
|
@ -26,6 +27,7 @@ const CACHED_ICONS = new Map<string, string>();
|
|||
|
||||
export const usePackageIconType = ({
|
||||
packageName,
|
||||
integrationName,
|
||||
version,
|
||||
icons: paramIcons,
|
||||
tryApi = false,
|
||||
|
@ -33,13 +35,13 @@ export const usePackageIconType = ({
|
|||
const { toPackageImage } = useLinks();
|
||||
const [iconList, setIconList] = useState<UsePackageIconType['icons']>();
|
||||
const [iconType, setIconType] = useState<string>(''); // FIXME: use `empty` icon during initialization - see: https://github.com/elastic/kibana/issues/60622
|
||||
const pkgKey = `${packageName}-${version}`;
|
||||
const cacheKey = `${packageName}-${version}${integrationName ? `-${integrationName}` : ''}`;
|
||||
|
||||
// Generates an icon path or Eui Icon name based on an icon list from the package
|
||||
// or by using the package name against logo icons from Eui
|
||||
useEffect(() => {
|
||||
if (CACHED_ICONS.has(pkgKey)) {
|
||||
setIconType(CACHED_ICONS.get(pkgKey) || '');
|
||||
if (CACHED_ICONS.has(cacheKey)) {
|
||||
setIconType(CACHED_ICONS.get(cacheKey) || '');
|
||||
return;
|
||||
}
|
||||
const svgIcons = (paramIcons || iconList)?.filter(
|
||||
|
@ -48,29 +50,29 @@ export const usePackageIconType = ({
|
|||
const localIconSrc =
|
||||
Array.isArray(svgIcons) && toPackageImage(svgIcons[0], packageName, version);
|
||||
if (localIconSrc) {
|
||||
CACHED_ICONS.set(pkgKey, localIconSrc);
|
||||
setIconType(CACHED_ICONS.get(pkgKey) || '');
|
||||
CACHED_ICONS.set(cacheKey, localIconSrc);
|
||||
setIconType(CACHED_ICONS.get(cacheKey) || '');
|
||||
return;
|
||||
}
|
||||
|
||||
const euiLogoIcon = ICON_TYPES.find((key) => key.toLowerCase() === `logo${packageName}`);
|
||||
if (euiLogoIcon) {
|
||||
CACHED_ICONS.set(pkgKey, euiLogoIcon);
|
||||
CACHED_ICONS.set(cacheKey, euiLogoIcon);
|
||||
setIconType(euiLogoIcon);
|
||||
return;
|
||||
}
|
||||
|
||||
if (tryApi && !paramIcons && !iconList) {
|
||||
sendGetPackageInfoByKey(pkgKey)
|
||||
sendGetPackageInfoByKey(cacheKey)
|
||||
.catch((error) => undefined) // Ignore API errors
|
||||
.then((res) => {
|
||||
CACHED_ICONS.delete(pkgKey);
|
||||
CACHED_ICONS.delete(cacheKey);
|
||||
setIconList(res?.data?.response?.icons);
|
||||
});
|
||||
}
|
||||
|
||||
CACHED_ICONS.set(pkgKey, 'package');
|
||||
CACHED_ICONS.set(cacheKey, 'package');
|
||||
setIconType('package');
|
||||
}, [paramIcons, pkgKey, toPackageImage, iconList, packageName, iconType, tryApi, version]);
|
||||
}, [paramIcons, cacheKey, toPackageImage, iconList, packageName, iconType, tryApi, version]);
|
||||
return iconType;
|
||||
};
|
||||
|
|
|
@ -333,6 +333,7 @@ export const CreatePackagePolicyPage: React.FunctionComponent = () => {
|
|||
packagePolicy={packagePolicy}
|
||||
updatePackagePolicy={updatePackagePolicy}
|
||||
validationResults={validationResults!}
|
||||
submitAttempted={formState === 'INVALID'}
|
||||
/>
|
||||
|
||||
{/* Only show the out-of-box configuration step if a UI extension is NOT registered */}
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { safeLoad } from 'js-yaml';
|
||||
import { keyBy } from 'lodash';
|
||||
|
||||
import { getFlattenedObject, isValidNamespace } from '../../../../services';
|
||||
import type {
|
||||
|
@ -32,12 +33,12 @@ export type PackagePolicyInputValidationResults = PackagePolicyConfigValidationR
|
|||
streams?: Record<PackagePolicyInputStream['id'], PackagePolicyConfigValidationResults>;
|
||||
};
|
||||
|
||||
export interface PackagePolicyValidationResults {
|
||||
export type PackagePolicyValidationResults = {
|
||||
name: Errors;
|
||||
description: Errors;
|
||||
namespace: Errors;
|
||||
inputs: Record<PackagePolicyInput['type'], PackagePolicyInputValidationResults> | null;
|
||||
}
|
||||
} & PackagePolicyConfigValidationResults;
|
||||
|
||||
/*
|
||||
* Returns validation information for a given package policy and package info
|
||||
|
@ -67,6 +68,16 @@ export const validatePackagePolicy = (
|
|||
validationResults.namespace = [namespaceValidation.error];
|
||||
}
|
||||
|
||||
// Validate package-level vars
|
||||
const packageVarsByName = keyBy(packageInfo.vars || [], 'name');
|
||||
const packageVars = Object.entries(packagePolicy.vars || {});
|
||||
if (packageVars.length) {
|
||||
validationResults.vars = packageVars.reduce((results, [name, varEntry]) => {
|
||||
results[name] = validatePackagePolicyConfig(varEntry, packageVarsByName[name]);
|
||||
return results;
|
||||
}, {} as ValidationEntry);
|
||||
}
|
||||
|
||||
if (
|
||||
!packageInfo.policy_templates ||
|
||||
packageInfo.policy_templates.length === 0 ||
|
||||
|
|
|
@ -25,7 +25,6 @@ import { Loading } from '../../../components';
|
|||
|
||||
import type { PackagePolicyValidationResults } from './services';
|
||||
import { PackagePolicyInputPanel } from './components';
|
||||
import type { CreatePackagePolicyFrom } from './types';
|
||||
|
||||
const findStreamsForInputType = (
|
||||
inputType: string,
|
||||
|
@ -50,22 +49,12 @@ const findStreamsForInputType = (
|
|||
};
|
||||
|
||||
export const StepConfigurePackagePolicy: React.FunctionComponent<{
|
||||
from?: CreatePackagePolicyFrom;
|
||||
packageInfo: PackageInfo;
|
||||
packagePolicy: NewPackagePolicy;
|
||||
packagePolicyId?: string;
|
||||
updatePackagePolicy: (fields: Partial<NewPackagePolicy>) => void;
|
||||
validationResults: PackagePolicyValidationResults;
|
||||
submitAttempted: boolean;
|
||||
}> = ({
|
||||
from = 'policy',
|
||||
packageInfo,
|
||||
packagePolicy,
|
||||
packagePolicyId,
|
||||
updatePackagePolicy,
|
||||
validationResults,
|
||||
submitAttempted,
|
||||
}) => {
|
||||
}> = ({ packageInfo, packagePolicy, updatePackagePolicy, validationResults, submitAttempted }) => {
|
||||
// Configure inputs (and their streams)
|
||||
// Assume packages only export one config template for now
|
||||
const renderConfigureInputs = () =>
|
||||
|
|
|
@ -5,14 +5,13 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import React, { memo, useEffect, useState } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import {
|
||||
EuiFormRow,
|
||||
EuiFieldText,
|
||||
EuiButtonEmpty,
|
||||
EuiSpacer,
|
||||
EuiText,
|
||||
EuiComboBox,
|
||||
EuiDescribedFormGroup,
|
||||
|
@ -21,180 +20,145 @@ import {
|
|||
EuiLink,
|
||||
} from '@elastic/eui';
|
||||
|
||||
import type { AgentPolicy, PackageInfo, PackagePolicy, NewPackagePolicy } from '../../../types';
|
||||
import { packageToPackagePolicyInputs } from '../../../services';
|
||||
import type {
|
||||
AgentPolicy,
|
||||
PackageInfo,
|
||||
PackagePolicy,
|
||||
NewPackagePolicy,
|
||||
RegistryVarsEntry,
|
||||
} from '../../../types';
|
||||
import { packageToPackagePolicy } from '../../../services';
|
||||
import { Loading } from '../../../components';
|
||||
import { pkgKeyFromPackageInfo } from '../../../services/pkg_key_from_package_info';
|
||||
|
||||
import { isAdvancedVar } from './services';
|
||||
import type { PackagePolicyValidationResults } from './services';
|
||||
import { PackagePolicyInputVarField } from './components';
|
||||
|
||||
export const StepDefinePackagePolicy: React.FunctionComponent<{
|
||||
agentPolicy: AgentPolicy;
|
||||
packageInfo: PackageInfo;
|
||||
packagePolicy: NewPackagePolicy;
|
||||
integration?: string;
|
||||
updatePackagePolicy: (fields: Partial<NewPackagePolicy>) => void;
|
||||
validationResults: PackagePolicyValidationResults;
|
||||
}> = ({ agentPolicy, packageInfo, packagePolicy, updatePackagePolicy, validationResults }) => {
|
||||
// Form show/hide states
|
||||
const [isShowingAdvanced, setIsShowingAdvanced] = useState<boolean>(false);
|
||||
|
||||
// Update package policy's package and agent policy info
|
||||
useEffect(() => {
|
||||
const pkg = packagePolicy.package;
|
||||
const currentPkgKey = pkg ? pkgKeyFromPackageInfo(pkg) : '';
|
||||
const pkgKey = pkgKeyFromPackageInfo(packageInfo);
|
||||
|
||||
// If package has changed, create shell package policy with input&stream values based on package info
|
||||
if (currentPkgKey !== pkgKey) {
|
||||
// Existing package policies on the agent policy using the package name, retrieve highest number appended to package policy name
|
||||
const pkgPoliciesNamePattern = new RegExp(`${packageInfo.name}-(\\d+)`);
|
||||
const pkgPoliciesWithMatchingNames = (agentPolicy.package_policies as PackagePolicy[])
|
||||
.filter((ds) => Boolean(ds.name.match(pkgPoliciesNamePattern)))
|
||||
.map((ds) => parseInt(ds.name.match(pkgPoliciesNamePattern)![1], 10))
|
||||
.sort((a, b) => a - b);
|
||||
|
||||
updatePackagePolicy({
|
||||
// FIXME: Improve package policies name uniqueness - https://github.com/elastic/kibana/issues/72948
|
||||
name: `${packageInfo.name}-${
|
||||
pkgPoliciesWithMatchingNames.length
|
||||
? pkgPoliciesWithMatchingNames[pkgPoliciesWithMatchingNames.length - 1] + 1
|
||||
: 1
|
||||
}`,
|
||||
package: {
|
||||
name: packageInfo.name,
|
||||
title: packageInfo.title,
|
||||
version: packageInfo.version,
|
||||
},
|
||||
inputs: packageToPackagePolicyInputs(packageInfo),
|
||||
});
|
||||
}
|
||||
|
||||
// If agent policy has changed, update package policy's agent policy ID and namespace
|
||||
if (packagePolicy.policy_id !== agentPolicy.id) {
|
||||
updatePackagePolicy({
|
||||
policy_id: agentPolicy.id,
|
||||
namespace: agentPolicy.namespace,
|
||||
});
|
||||
}
|
||||
}, [
|
||||
packagePolicy.package,
|
||||
packagePolicy.policy_id,
|
||||
submitAttempted: boolean;
|
||||
}> = memo(
|
||||
({
|
||||
agentPolicy,
|
||||
packageInfo,
|
||||
packagePolicy,
|
||||
integration,
|
||||
updatePackagePolicy,
|
||||
]);
|
||||
validationResults,
|
||||
submitAttempted,
|
||||
}) => {
|
||||
// Form show/hide states
|
||||
const [isShowingAdvanced, setIsShowingAdvanced] = useState<boolean>(false);
|
||||
|
||||
return validationResults ? (
|
||||
<EuiDescribedFormGroup
|
||||
title={
|
||||
<h4>
|
||||
// Package-level vars
|
||||
const requiredVars: RegistryVarsEntry[] = [];
|
||||
const advancedVars: RegistryVarsEntry[] = [];
|
||||
|
||||
if (packageInfo.vars) {
|
||||
packageInfo.vars.forEach((varDef) => {
|
||||
if (isAdvancedVar(varDef)) {
|
||||
advancedVars.push(varDef);
|
||||
} else {
|
||||
requiredVars.push(varDef);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Update package policy's package and agent policy info
|
||||
useEffect(() => {
|
||||
const pkg = packagePolicy.package;
|
||||
const currentPkgKey = pkg ? pkgKeyFromPackageInfo(pkg) : '';
|
||||
const pkgKey = pkgKeyFromPackageInfo(packageInfo);
|
||||
|
||||
// If package has changed, create shell package policy with input&stream values based on package info
|
||||
if (currentPkgKey !== pkgKey) {
|
||||
// Existing package policies on the agent policy using the package name, retrieve highest number appended to package policy name
|
||||
const pkgPoliciesNamePattern = new RegExp(`${packageInfo.name}-(\\d+)`);
|
||||
const pkgPoliciesWithMatchingNames = (agentPolicy.package_policies as PackagePolicy[])
|
||||
.filter((ds) => Boolean(ds.name.match(pkgPoliciesNamePattern)))
|
||||
.map((ds) => parseInt(ds.name.match(pkgPoliciesNamePattern)![1], 10))
|
||||
.sort((a, b) => a - b);
|
||||
|
||||
updatePackagePolicy(
|
||||
packageToPackagePolicy(
|
||||
packageInfo,
|
||||
agentPolicy.id,
|
||||
packagePolicy.output_id,
|
||||
packagePolicy.namespace,
|
||||
`${packageInfo.name}-${
|
||||
pkgPoliciesWithMatchingNames.length
|
||||
? pkgPoliciesWithMatchingNames[pkgPoliciesWithMatchingNames.length - 1] + 1
|
||||
: 1
|
||||
}`,
|
||||
packagePolicy.description
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// If agent policy has changed, update package policy's agent policy ID and namespace
|
||||
if (packagePolicy.policy_id !== agentPolicy.id) {
|
||||
updatePackagePolicy({
|
||||
policy_id: agentPolicy.id,
|
||||
namespace: agentPolicy.namespace,
|
||||
});
|
||||
}
|
||||
}, [packagePolicy, agentPolicy, packageInfo, updatePackagePolicy, integration]);
|
||||
|
||||
return validationResults ? (
|
||||
<EuiDescribedFormGroup
|
||||
title={
|
||||
<h4>
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.createPackagePolicy.stepConfigure.integrationSettingsSectionTitle"
|
||||
defaultMessage="Integration settings"
|
||||
/>
|
||||
</h4>
|
||||
}
|
||||
description={
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.createPackagePolicy.stepConfigure.integrationSettingsSectionTitle"
|
||||
defaultMessage="Integration settings"
|
||||
id="xpack.fleet.createPackagePolicy.stepConfigure.integrationSettingsSectionDescription"
|
||||
defaultMessage="Choose a name and description to help identify how this integration will be used."
|
||||
/>
|
||||
</h4>
|
||||
}
|
||||
description={
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.createPackagePolicy.stepConfigure.integrationSettingsSectionDescription"
|
||||
defaultMessage="Choose a name and description to help identify how this integration will be used."
|
||||
/>
|
||||
}
|
||||
>
|
||||
<>
|
||||
{/* Name */}
|
||||
<EuiFormRow
|
||||
isInvalid={!!validationResults.name}
|
||||
error={validationResults.name}
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.createPackagePolicy.stepConfigure.packagePolicyNameInputLabel"
|
||||
defaultMessage="Integration name"
|
||||
/>
|
||||
}
|
||||
>
|
||||
<EuiFieldText
|
||||
value={packagePolicy.name}
|
||||
onChange={(e) =>
|
||||
updatePackagePolicy({
|
||||
name: e.target.value,
|
||||
})
|
||||
}
|
||||
data-test-subj="packagePolicyNameInput"
|
||||
/>
|
||||
</EuiFormRow>
|
||||
|
||||
{/* Description */}
|
||||
<EuiFormRow
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.createPackagePolicy.stepConfigure.packagePolicyDescriptionInputLabel"
|
||||
defaultMessage="Description"
|
||||
/>
|
||||
}
|
||||
labelAppend={
|
||||
<EuiText size="xs" color="subdued">
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.createPackagePolicy.stepConfigure.inputVarFieldOptionalLabel"
|
||||
defaultMessage="Optional"
|
||||
/>
|
||||
</EuiText>
|
||||
}
|
||||
isInvalid={!!validationResults.description}
|
||||
error={validationResults.description}
|
||||
>
|
||||
<EuiFieldText
|
||||
value={packagePolicy.description}
|
||||
onChange={(e) =>
|
||||
updatePackagePolicy({
|
||||
description: e.target.value,
|
||||
})
|
||||
}
|
||||
data-test-subj="packagePolicyDescriptionInput"
|
||||
/>
|
||||
</EuiFormRow>
|
||||
<EuiSpacer size="m" />
|
||||
|
||||
{/* Advanced options toggle */}
|
||||
<EuiFlexGroup justifyContent="spaceBetween" alignItems="center">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonEmpty
|
||||
size="xs"
|
||||
iconType={isShowingAdvanced ? 'arrowDown' : 'arrowRight'}
|
||||
onClick={() => setIsShowingAdvanced(!isShowingAdvanced)}
|
||||
flush="left"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.createPackagePolicy.stepConfigure.advancedOptionsToggleLinkText"
|
||||
defaultMessage="Advanced options"
|
||||
/>
|
||||
</EuiButtonEmpty>
|
||||
</EuiFlexItem>
|
||||
{!isShowingAdvanced && !!validationResults.namespace ? (
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiText color="danger" size="s">
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.createPackagePolicy.stepConfigure.errorCountText"
|
||||
defaultMessage="{count, plural, one {# error} other {# errors}}"
|
||||
values={{ count: 1 }}
|
||||
/>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
) : null}
|
||||
</EuiFlexGroup>
|
||||
|
||||
{/* Advanced options content */}
|
||||
{/* Todo: Populate list of existing namespaces */}
|
||||
{isShowingAdvanced ? (
|
||||
<>
|
||||
<EuiSpacer size="m" />
|
||||
}
|
||||
>
|
||||
<EuiFlexGroup direction="column" gutterSize="m">
|
||||
{/* Name */}
|
||||
<EuiFlexItem>
|
||||
<EuiFormRow
|
||||
isInvalid={!!validationResults.namespace}
|
||||
error={validationResults.namespace}
|
||||
isInvalid={!!validationResults.name}
|
||||
error={validationResults.name}
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.createPackagePolicy.stepConfigure.packagePolicyNamespaceInputLabel"
|
||||
defaultMessage="Namespace"
|
||||
id="xpack.fleet.createPackagePolicy.stepConfigure.packagePolicyNameInputLabel"
|
||||
defaultMessage="Integration name"
|
||||
/>
|
||||
}
|
||||
>
|
||||
<EuiFieldText
|
||||
value={packagePolicy.name}
|
||||
onChange={(e) =>
|
||||
updatePackagePolicy({
|
||||
name: e.target.value,
|
||||
})
|
||||
}
|
||||
data-test-subj="packagePolicyNameInput"
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</EuiFlexItem>
|
||||
|
||||
{/* Description */}
|
||||
<EuiFlexItem>
|
||||
<EuiFormRow
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.createPackagePolicy.stepConfigure.packagePolicyDescriptionInputLabel"
|
||||
defaultMessage="Description"
|
||||
/>
|
||||
}
|
||||
helpText={
|
||||
|
@ -216,30 +180,156 @@ export const StepDefinePackagePolicy: React.FunctionComponent<{
|
|||
}}
|
||||
/>
|
||||
}
|
||||
labelAppend={
|
||||
<EuiText size="xs" color="subdued">
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.createPackagePolicy.stepConfigure.inputVarFieldOptionalLabel"
|
||||
defaultMessage="Optional"
|
||||
/>
|
||||
</EuiText>
|
||||
}
|
||||
isInvalid={!!validationResults.description}
|
||||
error={validationResults.description}
|
||||
>
|
||||
<EuiComboBox
|
||||
noSuggestions
|
||||
singleSelection={true}
|
||||
selectedOptions={
|
||||
packagePolicy.namespace ? [{ label: packagePolicy.namespace }] : []
|
||||
<EuiFieldText
|
||||
value={packagePolicy.description}
|
||||
onChange={(e) =>
|
||||
updatePackagePolicy({
|
||||
description: e.target.value,
|
||||
})
|
||||
}
|
||||
onCreateOption={(newNamespace: string) => {
|
||||
updatePackagePolicy({
|
||||
namespace: newNamespace,
|
||||
});
|
||||
}}
|
||||
onChange={(newNamespaces: Array<{ label: string }>) => {
|
||||
updatePackagePolicy({
|
||||
namespace: newNamespaces.length ? newNamespaces[0].label : '',
|
||||
});
|
||||
}}
|
||||
data-test-subj="packagePolicyDescriptionInput"
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</>
|
||||
) : null}
|
||||
</>
|
||||
</EuiDescribedFormGroup>
|
||||
) : (
|
||||
<Loading />
|
||||
);
|
||||
};
|
||||
</EuiFlexItem>
|
||||
|
||||
{/* Required vars */}
|
||||
{requiredVars.map((varDef) => {
|
||||
const { name: varName, type: varType } = varDef;
|
||||
if (!packagePolicy.vars || !packagePolicy.vars[varName]) return null;
|
||||
const value = packagePolicy.vars[varName].value;
|
||||
return (
|
||||
<EuiFlexItem key={varName}>
|
||||
<PackagePolicyInputVarField
|
||||
varDef={varDef}
|
||||
value={value}
|
||||
onChange={(newValue: any) => {
|
||||
updatePackagePolicy({
|
||||
vars: {
|
||||
...packagePolicy.vars,
|
||||
[varName]: {
|
||||
type: varType,
|
||||
value: newValue,
|
||||
},
|
||||
},
|
||||
});
|
||||
}}
|
||||
errors={validationResults.vars![varName]}
|
||||
forceShowErrors={submitAttempted}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
);
|
||||
})}
|
||||
|
||||
{/* Advanced options toggle */}
|
||||
<EuiFlexItem>
|
||||
<EuiFlexGroup justifyContent="spaceBetween" alignItems="center">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonEmpty
|
||||
size="xs"
|
||||
iconType={isShowingAdvanced ? 'arrowDown' : 'arrowRight'}
|
||||
onClick={() => setIsShowingAdvanced(!isShowingAdvanced)}
|
||||
flush="left"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.createPackagePolicy.stepConfigure.advancedOptionsToggleLinkText"
|
||||
defaultMessage="Advanced options"
|
||||
/>
|
||||
</EuiButtonEmpty>
|
||||
</EuiFlexItem>
|
||||
{!isShowingAdvanced && !!validationResults.namespace ? (
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiText color="danger" size="s">
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.createPackagePolicy.stepConfigure.errorCountText"
|
||||
defaultMessage="{count, plural, one {# error} other {# errors}}"
|
||||
values={{ count: 1 }}
|
||||
/>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
) : null}
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
|
||||
{/* Advanced options content */}
|
||||
{/* Todo: Populate list of existing namespaces */}
|
||||
{isShowingAdvanced ? (
|
||||
<EuiFlexItem>
|
||||
<EuiFlexGroup direction="column" gutterSize="m">
|
||||
<EuiFlexItem>
|
||||
<EuiFormRow
|
||||
isInvalid={!!validationResults.namespace}
|
||||
error={validationResults.namespace}
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.createPackagePolicy.stepConfigure.packagePolicyNamespaceInputLabel"
|
||||
defaultMessage="Namespace"
|
||||
/>
|
||||
}
|
||||
>
|
||||
<EuiComboBox
|
||||
noSuggestions
|
||||
singleSelection={true}
|
||||
selectedOptions={
|
||||
packagePolicy.namespace ? [{ label: packagePolicy.namespace }] : []
|
||||
}
|
||||
onCreateOption={(newNamespace: string) => {
|
||||
updatePackagePolicy({
|
||||
namespace: newNamespace,
|
||||
});
|
||||
}}
|
||||
onChange={(newNamespaces: Array<{ label: string }>) => {
|
||||
updatePackagePolicy({
|
||||
namespace: newNamespaces.length ? newNamespaces[0].label : '',
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</EuiFlexItem>
|
||||
{/* Advanced vars */}
|
||||
{advancedVars.map((varDef) => {
|
||||
const { name: varName, type: varType } = varDef;
|
||||
if (!packagePolicy.vars || !packagePolicy.vars[varName]) return null;
|
||||
const value = packagePolicy.vars![varName].value;
|
||||
return (
|
||||
<EuiFlexItem key={varName}>
|
||||
<PackagePolicyInputVarField
|
||||
varDef={varDef}
|
||||
value={value}
|
||||
onChange={(newValue: any) => {
|
||||
updatePackagePolicy({
|
||||
vars: {
|
||||
...packagePolicy.vars,
|
||||
[varName]: {
|
||||
type: varType,
|
||||
value: newValue,
|
||||
},
|
||||
},
|
||||
});
|
||||
}}
|
||||
errors={validationResults.vars![varName]}
|
||||
forceShowErrors={submitAttempted}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
);
|
||||
})}
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
) : null}
|
||||
</EuiFlexGroup>
|
||||
</EuiDescribedFormGroup>
|
||||
) : (
|
||||
<Loading />
|
||||
);
|
||||
}
|
||||
);
|
||||
|
|
|
@ -351,15 +351,14 @@ export const EditPackagePolicyForm = memo<{
|
|||
packagePolicy={packagePolicy}
|
||||
updatePackagePolicy={updatePackagePolicy}
|
||||
validationResults={validationResults!}
|
||||
submitAttempted={formState === 'INVALID'}
|
||||
/>
|
||||
|
||||
{/* Only show the out-of-box configuration step if a UI extension is NOT registered */}
|
||||
{!ExtensionView && (
|
||||
<StepConfigurePackagePolicy
|
||||
from={'edit'}
|
||||
packageInfo={packageInfo}
|
||||
packagePolicy={packagePolicy}
|
||||
packagePolicyId={packagePolicyId}
|
||||
updatePackagePolicy={updatePackagePolicy}
|
||||
validationResults={validationResults!}
|
||||
submitAttempted={formState === 'INVALID'}
|
||||
|
@ -386,7 +385,6 @@ export const EditPackagePolicyForm = memo<{
|
|||
packagePolicy,
|
||||
updatePackagePolicy,
|
||||
validationResults,
|
||||
packagePolicyId,
|
||||
formState,
|
||||
originalPackagePolicy,
|
||||
ExtensionView,
|
||||
|
|
|
@ -9,13 +9,13 @@ import React from 'react';
|
|||
import styled from 'styled-components';
|
||||
import { EuiCard } from '@elastic/eui';
|
||||
|
||||
import type { PackageInfo, PackageListItem } from '../../../types';
|
||||
import type { PackageListItem } from '../../../types';
|
||||
import { useLink } from '../../../hooks';
|
||||
import { PackageIcon } from '../../../components/package_icon';
|
||||
|
||||
import { RELEASE_BADGE_LABEL, RELEASE_BADGE_DESCRIPTION } from './release_badge';
|
||||
|
||||
type PackageCardProps = PackageListItem | PackageInfo;
|
||||
type PackageCardProps = PackageListItem;
|
||||
|
||||
// adding the `href` causes EuiCard to use a `a` instead of a `button`
|
||||
// `a` tags use `euiLinkColor` which results in blueish Badge text
|
||||
|
@ -31,6 +31,7 @@ export function PackageCard({
|
|||
release,
|
||||
status,
|
||||
icons,
|
||||
integration,
|
||||
...restProps
|
||||
}: PackageCardProps) {
|
||||
const { getHref } = useLink();
|
||||
|
@ -44,8 +45,19 @@ export function PackageCard({
|
|||
<Card
|
||||
title={title || ''}
|
||||
description={description}
|
||||
icon={<PackageIcon icons={icons} packageName={name} version={version} size="xl" />}
|
||||
href={getHref('integration_details_overview', { pkgkey: `${name}-${urlVersion}` })}
|
||||
icon={
|
||||
<PackageIcon
|
||||
icons={icons}
|
||||
packageName={name}
|
||||
integrationName={integration}
|
||||
version={version}
|
||||
size="xl"
|
||||
/>
|
||||
}
|
||||
href={getHref('integration_details_overview', {
|
||||
pkgkey: `${name}-${urlVersion}`,
|
||||
...(integration ? { integration } : {}),
|
||||
})}
|
||||
betaBadgeLabel={release && release !== 'ga' ? RELEASE_BADGE_LABEL[release] : undefined}
|
||||
betaBadgeTooltipContent={
|
||||
release && release !== 'ga' ? RELEASE_BADGE_DESCRIPTION[release] : undefined
|
||||
|
|
|
@ -25,7 +25,6 @@ import { FormattedMessage } from '@kbn/i18n/react';
|
|||
import { Loading } from '../../../components';
|
||||
import type { PackageList } from '../../../types';
|
||||
import { useLocalSearch, searchIdField } from '../hooks';
|
||||
import { pkgKeyFromPackageInfo } from '../../../services/pkg_key_from_package_info';
|
||||
|
||||
import { PackageCard } from './package_card';
|
||||
|
||||
|
@ -153,11 +152,13 @@ function GridColumn({ list, showMissingIntegrationMessage = false }: GridColumnP
|
|||
return (
|
||||
<EuiFlexGrid gutterSize="l" columns={3}>
|
||||
{list.length ? (
|
||||
list.map((item) => (
|
||||
<EuiFlexItem key={pkgKeyFromPackageInfo(item)}>
|
||||
<PackageCard {...item} />
|
||||
</EuiFlexItem>
|
||||
))
|
||||
list.map((item) => {
|
||||
return (
|
||||
<EuiFlexItem key={item.id}>
|
||||
<PackageCard {...item} />
|
||||
</EuiFlexItem>
|
||||
);
|
||||
})
|
||||
) : (
|
||||
<EuiFlexItem grow={3}>
|
||||
<EuiText>
|
||||
|
|
|
@ -8,11 +8,10 @@
|
|||
import { Search as LocalSearch } from 'js-search';
|
||||
import { useEffect, useRef } from 'react';
|
||||
|
||||
import type { PackageList, PackageListItem } from '../../../types';
|
||||
import type { PackageList } from '../../../types';
|
||||
|
||||
export type SearchField = keyof PackageListItem;
|
||||
export const searchIdField: SearchField = 'name';
|
||||
export const fieldsToSearch: SearchField[] = ['description', 'name', 'title'];
|
||||
export const searchIdField = 'id';
|
||||
export const fieldsToSearch = ['description', 'name', 'title'];
|
||||
|
||||
export function useLocalSearch(packageList: PackageList) {
|
||||
const localSearchRef = useRef<LocalSearch | null>(null);
|
||||
|
|
|
@ -37,10 +37,11 @@ const Panel = styled(EuiPanel)`
|
|||
|
||||
export function IconPanel({
|
||||
packageName,
|
||||
integrationName,
|
||||
version,
|
||||
icons,
|
||||
}: Pick<UsePackageIconType, 'packageName' | 'version' | 'icons'>) {
|
||||
const iconType = usePackageIconType({ packageName, version, icons });
|
||||
}: Pick<UsePackageIconType, 'packageName' | 'integrationName' | 'version' | 'icons'>) {
|
||||
const iconType = usePackageIconType({ packageName, integrationName, version, icons });
|
||||
|
||||
return (
|
||||
<PanelWrapper>
|
||||
|
|
|
@ -74,7 +74,9 @@ export function Detail() {
|
|||
const { getHref, getPath } = useLink();
|
||||
const hasWriteCapabilites = useCapabilities().write;
|
||||
const history = useHistory();
|
||||
const location = useLocation();
|
||||
const { pathname, search, hash } = useLocation();
|
||||
const queryParams = useMemo(() => new URLSearchParams(search), [search]);
|
||||
const integration = useMemo(() => queryParams.get('integration'), [queryParams]);
|
||||
|
||||
// Package info state
|
||||
const [packageInfo, setPackageInfo] = useState<PackageInfo | null>(null);
|
||||
|
@ -120,6 +122,16 @@ export function Detail() {
|
|||
}
|
||||
}, [packageInfoData, setPackageInstallStatus, setPackageInfo]);
|
||||
|
||||
const integrationInfo = useMemo(
|
||||
() =>
|
||||
integration
|
||||
? packageInfo?.policy_templates?.find(
|
||||
(policyTemplate) => policyTemplate.name === integration
|
||||
)
|
||||
: undefined,
|
||||
[integration, packageInfo]
|
||||
);
|
||||
|
||||
const headerLeftContent = useMemo(
|
||||
() => (
|
||||
<EuiFlexGroup direction="column" gutterSize="m">
|
||||
|
@ -147,8 +159,9 @@ export function Detail() {
|
|||
) : (
|
||||
<IconPanel
|
||||
packageName={packageInfo.name}
|
||||
integrationName={integrationInfo?.name}
|
||||
version={packageInfo.version}
|
||||
icons={packageInfo.icons}
|
||||
icons={integrationInfo?.icons || packageInfo.icons}
|
||||
/>
|
||||
)}
|
||||
</EuiFlexItem>
|
||||
|
@ -157,7 +170,7 @@ export function Detail() {
|
|||
<FlexItemWithMinWidth grow={false}>
|
||||
<EuiText>
|
||||
{/* Render space in place of package name while package info loads to prevent layout from jumping around */}
|
||||
<h1>{packageInfo?.title || '\u00A0'}</h1>
|
||||
<h1>{integrationInfo?.title || packageInfo?.title || '\u00A0'}</h1>
|
||||
</EuiText>
|
||||
</FlexItemWithMinWidth>
|
||||
{packageInfo?.release && packageInfo.release !== 'ga' ? (
|
||||
|
@ -174,7 +187,7 @@ export function Detail() {
|
|||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
),
|
||||
[getHref, isLoading, packageInfo]
|
||||
[getHref, integrationInfo, isLoading, packageInfo]
|
||||
);
|
||||
|
||||
const handleAddIntegrationPolicyClick = useCallback<ReactEventHandler>(
|
||||
|
@ -184,9 +197,9 @@ export function Detail() {
|
|||
// The object below, given to `createHref` is explicitly accessing keys of `location` in order
|
||||
// to ensure that dependencies to this `useCallback` is set correctly (because `location` is mutable)
|
||||
const currentPath = history.createHref({
|
||||
pathname: location.pathname,
|
||||
search: location.search,
|
||||
hash: location.hash,
|
||||
pathname,
|
||||
search,
|
||||
hash,
|
||||
});
|
||||
const redirectToPath: CreatePackagePolicyRouteState['onSaveNavigateTo'] &
|
||||
CreatePackagePolicyRouteState['onCancelNavigateTo'] = [
|
||||
|
@ -204,11 +217,12 @@ export function Detail() {
|
|||
history.push({
|
||||
pathname: getPath('add_integration_to_policy', {
|
||||
pkgkey,
|
||||
...(integration ? { integration } : {}),
|
||||
}),
|
||||
state: redirectBackRouteState,
|
||||
});
|
||||
},
|
||||
[getPath, history, location.hash, location.pathname, location.search, pkgkey]
|
||||
[getPath, history, hash, pathname, search, pkgkey, integration]
|
||||
);
|
||||
|
||||
const headerRightContent = useMemo(
|
||||
|
@ -255,6 +269,7 @@ export function Detail() {
|
|||
iconType="plusInCircle"
|
||||
href={getHref('add_integration_to_policy', {
|
||||
pkgkey,
|
||||
...(integration ? { integration } : {}),
|
||||
})}
|
||||
onClick={handleAddIntegrationPolicyClick}
|
||||
data-test-subj="addIntegrationPolicyButton"
|
||||
|
@ -263,7 +278,7 @@ export function Detail() {
|
|||
id="xpack.fleet.epm.addPackagePolicyButtonText"
|
||||
defaultMessage="Add {packageName}"
|
||||
values={{
|
||||
packageName: packageInfo.title,
|
||||
packageName: integrationInfo?.title || packageInfo.title,
|
||||
}}
|
||||
/>
|
||||
</EuiButton>
|
||||
|
@ -290,6 +305,8 @@ export function Detail() {
|
|||
getHref,
|
||||
handleAddIntegrationPolicyClick,
|
||||
hasWriteCapabilites,
|
||||
integration,
|
||||
integrationInfo,
|
||||
packageInfo,
|
||||
packageInstallStatus,
|
||||
pkgkey,
|
||||
|
@ -316,6 +333,7 @@ export function Detail() {
|
|||
'data-test-subj': `tab-overview`,
|
||||
href: getHref('integration_details_overview', {
|
||||
pkgkey: packageInfoKey,
|
||||
...(integration ? { integration } : {}),
|
||||
}),
|
||||
},
|
||||
];
|
||||
|
@ -333,6 +351,7 @@ export function Detail() {
|
|||
'data-test-subj': `tab-policies`,
|
||||
href: getHref('integration_details_policies', {
|
||||
pkgkey: packageInfoKey,
|
||||
...(integration ? { integration } : {}),
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
@ -349,6 +368,7 @@ export function Detail() {
|
|||
'data-test-subj': `tab-settings`,
|
||||
href: getHref('integration_details_settings', {
|
||||
pkgkey: packageInfoKey,
|
||||
...(integration ? { integration } : {}),
|
||||
}),
|
||||
});
|
||||
|
||||
|
@ -365,12 +385,13 @@ export function Detail() {
|
|||
'data-test-subj': `tab-custom`,
|
||||
href: getHref('integration_details_custom', {
|
||||
pkgkey: packageInfoKey,
|
||||
...(integration ? { integration } : {}),
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
return tabs;
|
||||
}, [getHref, packageInfo, panel, showCustomTab, packageInstallStatus]);
|
||||
}, [packageInfo, panel, getHref, integration, packageInstallStatus, showCustomTab]);
|
||||
|
||||
return (
|
||||
<WithHeaderLayout
|
||||
|
@ -380,7 +401,7 @@ export function Detail() {
|
|||
tabs={headerTabs}
|
||||
tabsClassName="fleet__epm__shiftNavTabs"
|
||||
>
|
||||
{packageInfo ? <Breadcrumbs packageTitle={packageInfo.title} /> : null}
|
||||
{integrationInfo ? <Breadcrumbs packageTitle={integrationInfo.title} /> : null}
|
||||
{packageInfoError ? (
|
||||
<Error
|
||||
title={
|
||||
|
@ -396,7 +417,7 @@ export function Detail() {
|
|||
) : (
|
||||
<Switch>
|
||||
<Route path={PAGE_ROUTING_PATHS.integration_details_overview}>
|
||||
<OverviewPage packageInfo={packageInfo} />
|
||||
<OverviewPage packageInfo={packageInfo} integrationInfo={integrationInfo} />
|
||||
</Route>
|
||||
<Route path={PAGE_ROUTING_PATHS.integration_details_settings}>
|
||||
<SettingsPage packageInfo={packageInfo} />
|
||||
|
|
|
@ -4,11 +4,11 @@
|
|||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import React, { memo } from 'react';
|
||||
import React, { memo, useMemo } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
||||
|
||||
import type { PackageInfo } from '../../../../../types';
|
||||
import type { PackageInfo, RegistryPolicyTemplate } from '../../../../../types';
|
||||
|
||||
import { Screenshots } from './screenshots';
|
||||
import { Readme } from './readme';
|
||||
|
@ -16,6 +16,7 @@ import { Details } from './details';
|
|||
|
||||
interface Props {
|
||||
packageInfo: PackageInfo;
|
||||
integrationInfo?: RegistryPolicyTemplate;
|
||||
}
|
||||
|
||||
const LeftColumn = styled(EuiFlexItem)`
|
||||
|
@ -25,14 +26,19 @@ const LeftColumn = styled(EuiFlexItem)`
|
|||
}
|
||||
`;
|
||||
|
||||
export const OverviewPage: React.FC<Props> = memo(({ packageInfo }: Props) => {
|
||||
export const OverviewPage: React.FC<Props> = memo(({ packageInfo, integrationInfo }) => {
|
||||
const screenshots = useMemo(() => integrationInfo?.screenshots || packageInfo.screenshots || [], [
|
||||
integrationInfo,
|
||||
packageInfo.screenshots,
|
||||
]);
|
||||
|
||||
return (
|
||||
<EuiFlexGroup alignItems="flexStart">
|
||||
<LeftColumn grow={2} />
|
||||
<EuiFlexItem grow={9} className="eui-textBreakWord">
|
||||
{packageInfo.readme ? (
|
||||
<Readme
|
||||
readmePath={packageInfo.readme}
|
||||
readmePath={integrationInfo?.readme || packageInfo.readme}
|
||||
packageName={packageInfo.name}
|
||||
version={packageInfo.version}
|
||||
/>
|
||||
|
@ -40,10 +46,10 @@ export const OverviewPage: React.FC<Props> = memo(({ packageInfo }: Props) => {
|
|||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={3}>
|
||||
<EuiFlexGroup direction="column" gutterSize="l" alignItems="flexStart">
|
||||
{packageInfo.screenshots && packageInfo.screenshots.length ? (
|
||||
{screenshots.length ? (
|
||||
<EuiFlexItem>
|
||||
<Screenshots
|
||||
images={packageInfo.screenshots}
|
||||
images={screenshots}
|
||||
packageName={packageInfo.name}
|
||||
version={packageInfo.version}
|
||||
/>
|
||||
|
|
|
@ -5,22 +5,24 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { useState } from 'react';
|
||||
import React, { memo, useState, useMemo } from 'react';
|
||||
import { useRouteMatch, Switch, Route, useLocation, useHistory } from 'react-router-dom';
|
||||
import semverLt from 'semver/functions/lt';
|
||||
import type { Props as EuiTabProps } from '@elastic/eui/src/components/tabs/tab';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { installationStatuses } from '../../../../../../../common/constants';
|
||||
import { PAGE_ROUTING_PATHS } from '../../../../constants';
|
||||
import { useLink, useGetCategories, useGetPackages, useBreadcrumbs } from '../../../../hooks';
|
||||
import { doesPackageHaveIntegrations } from '../../../../services';
|
||||
import { WithHeaderLayout } from '../../../../layouts';
|
||||
import type { CategorySummaryItem } from '../../../../types';
|
||||
import type { CategorySummaryItem, PackageList } from '../../../../types';
|
||||
import { PackageListGrid } from '../../components/package_list_grid';
|
||||
|
||||
import { CategoryFacets } from './category_facets';
|
||||
import { HeroCopy, HeroImage } from './header';
|
||||
|
||||
export function EPMHomePage() {
|
||||
export const EPMHomePage: React.FC = memo(() => {
|
||||
const {
|
||||
params: { tabId },
|
||||
} = useRouteMatch<{ tabId?: string }>();
|
||||
|
@ -61,51 +63,94 @@ export function EPMHomePage() {
|
|||
</Switch>
|
||||
</WithHeaderLayout>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
function InstalledPackages() {
|
||||
// Packages can export multiple integrations, aka `policy_templates`
|
||||
// In the case where packages ship >1 `policy_templates`, we flatten out the
|
||||
// list of packages by bringing all integrations to top-level so that
|
||||
// each integration is displayed as its own tile
|
||||
const packageListToIntegrationsList = (packages: PackageList): PackageList => {
|
||||
return packages.reduce((acc: PackageList, pkg) => {
|
||||
const { policy_templates: policyTemplates = [], ...restOfPackage } = pkg;
|
||||
return [
|
||||
...acc,
|
||||
restOfPackage,
|
||||
...(doesPackageHaveIntegrations(pkg)
|
||||
? policyTemplates.map((integration) => {
|
||||
const { name, title, description, icons } = integration;
|
||||
return {
|
||||
...restOfPackage,
|
||||
id: `${restOfPackage}-${name}`,
|
||||
integration: name,
|
||||
title,
|
||||
description,
|
||||
icons: icons || restOfPackage.icons,
|
||||
};
|
||||
})
|
||||
: []),
|
||||
];
|
||||
}, []);
|
||||
};
|
||||
|
||||
const InstalledPackages: React.FC = memo(() => {
|
||||
useBreadcrumbs('integrations_installed');
|
||||
const { data: allPackages, isLoading: isLoadingPackages } = useGetPackages({
|
||||
experimental: true,
|
||||
});
|
||||
const [selectedCategory, setSelectedCategory] = useState('');
|
||||
|
||||
const title = i18n.translate('xpack.fleet.epmList.installedTitle', {
|
||||
defaultMessage: 'Installed integrations',
|
||||
});
|
||||
|
||||
const allInstalledPackages =
|
||||
allPackages && allPackages.response
|
||||
? allPackages.response.filter((pkg) => pkg.status === installationStatuses.Installed)
|
||||
: [];
|
||||
|
||||
const updatablePackages = allInstalledPackages.filter(
|
||||
(item) => 'savedObject' in item && item.version > item.savedObject.attributes.version
|
||||
const allInstalledPackages = useMemo(
|
||||
() =>
|
||||
(allPackages?.response || []).filter((pkg) => pkg.status === installationStatuses.Installed),
|
||||
[allPackages?.response]
|
||||
);
|
||||
|
||||
const categories = [
|
||||
{
|
||||
id: '',
|
||||
title: i18n.translate('xpack.fleet.epmList.allFilterLinkText', {
|
||||
defaultMessage: 'All',
|
||||
}),
|
||||
count: allInstalledPackages.length,
|
||||
},
|
||||
{
|
||||
id: 'updates_available',
|
||||
title: i18n.translate('xpack.fleet.epmList.updatesAvailableFilterLinkText', {
|
||||
defaultMessage: 'Updates available',
|
||||
}),
|
||||
count: updatablePackages.length,
|
||||
},
|
||||
];
|
||||
const updatablePackages = useMemo(
|
||||
() =>
|
||||
allInstalledPackages.filter(
|
||||
(item) =>
|
||||
'savedObject' in item && semverLt(item.savedObject.attributes.version, item.version)
|
||||
),
|
||||
[allInstalledPackages]
|
||||
);
|
||||
|
||||
const controls = (
|
||||
<CategoryFacets
|
||||
categories={categories}
|
||||
selectedCategory={selectedCategory}
|
||||
onCategoryChange={({ id }: CategorySummaryItem) => setSelectedCategory(id)}
|
||||
/>
|
||||
const title = useMemo(
|
||||
() =>
|
||||
i18n.translate('xpack.fleet.epmList.installedTitle', {
|
||||
defaultMessage: 'Installed integrations',
|
||||
}),
|
||||
[]
|
||||
);
|
||||
|
||||
const categories = useMemo(
|
||||
() => [
|
||||
{
|
||||
id: '',
|
||||
title: i18n.translate('xpack.fleet.epmList.allFilterLinkText', {
|
||||
defaultMessage: 'All',
|
||||
}),
|
||||
count: allInstalledPackages.length,
|
||||
},
|
||||
{
|
||||
id: 'updates_available',
|
||||
title: i18n.translate('xpack.fleet.epmList.updatesAvailableFilterLinkText', {
|
||||
defaultMessage: 'Updates available',
|
||||
}),
|
||||
count: updatablePackages.length,
|
||||
},
|
||||
],
|
||||
[allInstalledPackages.length, updatablePackages.length]
|
||||
);
|
||||
|
||||
const controls = useMemo(
|
||||
() => (
|
||||
<CategoryFacets
|
||||
categories={categories}
|
||||
selectedCategory={selectedCategory}
|
||||
onCategoryChange={({ id }: CategorySummaryItem) => setSelectedCategory(id)}
|
||||
/>
|
||||
),
|
||||
[categories, selectedCategory]
|
||||
);
|
||||
|
||||
return (
|
||||
|
@ -116,9 +161,9 @@ function InstalledPackages() {
|
|||
list={selectedCategory === 'updates_available' ? updatablePackages : allInstalledPackages}
|
||||
/>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
function AvailablePackages() {
|
||||
const AvailablePackages: React.FC = memo(() => {
|
||||
useBreadcrumbs('integrations_all');
|
||||
const history = useHistory();
|
||||
const queryParams = new URLSearchParams(useLocation().search);
|
||||
|
@ -128,24 +173,36 @@ function AvailablePackages() {
|
|||
const { data: categoryPackagesRes, isLoading: isLoadingCategoryPackages } = useGetPackages({
|
||||
category: selectedCategory,
|
||||
});
|
||||
const { data: categoriesRes, isLoading: isLoadingCategories } = useGetCategories();
|
||||
const packages =
|
||||
categoryPackagesRes && categoryPackagesRes.response ? categoryPackagesRes.response : [];
|
||||
|
||||
const title = i18n.translate('xpack.fleet.epmList.allTitle', {
|
||||
defaultMessage: 'Browse by category',
|
||||
const { data: categoriesRes, isLoading: isLoadingCategories } = useGetCategories({
|
||||
include_policy_templates: true,
|
||||
});
|
||||
const packages = useMemo(
|
||||
() => packageListToIntegrationsList(categoryPackagesRes?.response || []),
|
||||
[categoryPackagesRes]
|
||||
);
|
||||
|
||||
const categories = [
|
||||
{
|
||||
id: '',
|
||||
title: i18n.translate('xpack.fleet.epmList.allPackagesFilterLinkText', {
|
||||
defaultMessage: 'All',
|
||||
const title = useMemo(
|
||||
() =>
|
||||
i18n.translate('xpack.fleet.epmList.allTitle', {
|
||||
defaultMessage: 'Browse by category',
|
||||
}),
|
||||
count: allPackagesRes?.response?.length || 0,
|
||||
},
|
||||
...(categoriesRes ? categoriesRes.response : []),
|
||||
];
|
||||
[]
|
||||
);
|
||||
|
||||
const categories = useMemo(
|
||||
() => [
|
||||
{
|
||||
id: '',
|
||||
title: i18n.translate('xpack.fleet.epmList.allPackagesFilterLinkText', {
|
||||
defaultMessage: 'All',
|
||||
}),
|
||||
count: allPackagesRes?.response?.length || 0,
|
||||
},
|
||||
...(categoriesRes ? categoriesRes.response : []),
|
||||
],
|
||||
[allPackagesRes?.response?.length, categoriesRes]
|
||||
);
|
||||
|
||||
const controls = categories ? (
|
||||
<CategoryFacets
|
||||
isLoading={isLoadingCategories || isLoadingAllPackages}
|
||||
|
@ -171,4 +228,4 @@ function AvailablePackages() {
|
|||
showMissingIntegrationMessage
|
||||
/>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
|
|
@ -20,6 +20,7 @@ export {
|
|||
outputRoutesService,
|
||||
settingsRoutesService,
|
||||
appRoutesService,
|
||||
packageToPackagePolicy,
|
||||
packageToPackagePolicyInputs,
|
||||
storedPackagePoliciesToAgentInputs,
|
||||
fullAgentPolicyToYaml,
|
||||
|
@ -28,4 +29,5 @@ export {
|
|||
isValidNamespace,
|
||||
LicenseService,
|
||||
isAgentUpgradeable,
|
||||
doesPackageHaveIntegrations,
|
||||
} from '../../../../common';
|
||||
|
|
|
@ -24,7 +24,9 @@ jest.mock('../../services/package_policy', (): {
|
|||
} => {
|
||||
return {
|
||||
packagePolicyService: {
|
||||
compilePackagePolicyInputs: jest.fn((packageInfo, dataInputs) => Promise.resolve(dataInputs)),
|
||||
compilePackagePolicyInputs: jest.fn((packageInfo, vars, dataInputs) =>
|
||||
Promise.resolve(dataInputs)
|
||||
),
|
||||
buildPackagePolicyFromPackage: jest.fn(),
|
||||
bulkCreate: jest.fn(),
|
||||
create: jest.fn((soClient, esClient, newData) =>
|
||||
|
|
|
@ -229,6 +229,7 @@ const getSavedObjectTypes = (
|
|||
version: { type: 'keyword' },
|
||||
},
|
||||
},
|
||||
vars: { type: 'flattened' },
|
||||
inputs: {
|
||||
type: 'nested',
|
||||
enabled: false,
|
||||
|
|
|
@ -121,7 +121,7 @@ test('getPipelineNameForInstallation gets correct name', () => {
|
|||
const packageVersion = '1.0.1';
|
||||
const pipelineRefName = 'pipeline-json';
|
||||
const pipelineEntryNameForInstallation = getPipelineNameForInstallation({
|
||||
pipelineName: dataStream.ingest_pipeline,
|
||||
pipelineName: dataStream.ingest_pipeline!,
|
||||
dataStream,
|
||||
packageVersion,
|
||||
});
|
||||
|
|
|
@ -18,6 +18,7 @@ import type {
|
|||
ArchivePackage,
|
||||
RegistryPackage,
|
||||
EpmPackageAdditions,
|
||||
GetCategoriesRequest,
|
||||
} from '../../../../common/types';
|
||||
import type { Installation, PackageInfo } from '../../../types';
|
||||
import { IngestManagerError } from '../../../errors';
|
||||
|
@ -35,7 +36,7 @@ function nameAsTitle(name: string) {
|
|||
return name.charAt(0).toUpperCase() + name.substr(1).toLowerCase();
|
||||
}
|
||||
|
||||
export async function getCategories(options: Registry.CategoriesParams) {
|
||||
export async function getCategories(options: GetCategoriesRequest['query']) {
|
||||
return Registry.fetchCategories(options);
|
||||
}
|
||||
|
||||
|
@ -47,7 +48,7 @@ export async function getPackages(
|
|||
const { savedObjectsClient, experimental, category } = options;
|
||||
const registryItems = await Registry.fetchList({ category, experimental }).then((items) => {
|
||||
return items.map((item) =>
|
||||
Object.assign({}, item, { title: item.title || nameAsTitle(item.name) })
|
||||
Object.assign({}, item, { title: item.title || nameAsTitle(item.name) }, { id: item.name })
|
||||
);
|
||||
});
|
||||
// get the installed packages
|
||||
|
|
|
@ -20,6 +20,7 @@ import type {
|
|||
RegistryPackage,
|
||||
RegistrySearchResults,
|
||||
RegistrySearchResult,
|
||||
GetCategoriesRequest,
|
||||
} from '../../../types';
|
||||
import {
|
||||
getArchiveFilelist,
|
||||
|
@ -45,10 +46,6 @@ export interface SearchParams {
|
|||
experimental?: boolean;
|
||||
}
|
||||
|
||||
export interface CategoriesParams {
|
||||
experimental?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the package name and package version from a string.
|
||||
*
|
||||
|
@ -150,13 +147,18 @@ function setKibanaVersion(url: URL) {
|
|||
}
|
||||
}
|
||||
|
||||
export async function fetchCategories(params?: CategoriesParams): Promise<CategorySummaryList> {
|
||||
export async function fetchCategories(
|
||||
params?: GetCategoriesRequest['query']
|
||||
): Promise<CategorySummaryList> {
|
||||
const registryUrl = getRegistryUrl();
|
||||
const url = new URL(`${registryUrl}/categories`);
|
||||
if (params) {
|
||||
if (params.experimental) {
|
||||
url.searchParams.set('experimental', params.experimental.toString());
|
||||
}
|
||||
if (params.include_policy_templates) {
|
||||
url.searchParams.set('include_policy_templates', params.include_policy_templates.toString());
|
||||
}
|
||||
}
|
||||
|
||||
setKibanaVersion(url);
|
||||
|
|
|
@ -34,6 +34,12 @@ paths:
|
|||
{{#each paths}}
|
||||
- {{this}}
|
||||
{{/each}}
|
||||
{{#if hosts}}
|
||||
hosts:
|
||||
{{#each hosts}}
|
||||
- {{this}}
|
||||
{{/each}}
|
||||
{{/if}}
|
||||
`),
|
||||
},
|
||||
];
|
||||
|
@ -118,6 +124,7 @@ describe('Package policy service', () => {
|
|||
},
|
||||
],
|
||||
} as unknown) as PackageInfo,
|
||||
{},
|
||||
[
|
||||
{
|
||||
type: 'log',
|
||||
|
@ -180,6 +187,7 @@ describe('Package policy service', () => {
|
|||
},
|
||||
],
|
||||
} as unknown) as PackageInfo,
|
||||
{},
|
||||
[
|
||||
{
|
||||
type: 'log',
|
||||
|
@ -231,6 +239,7 @@ describe('Package policy service', () => {
|
|||
},
|
||||
],
|
||||
} as unknown) as PackageInfo,
|
||||
{},
|
||||
[
|
||||
{
|
||||
type: 'log',
|
||||
|
@ -276,6 +285,74 @@ describe('Package policy service', () => {
|
|||
]);
|
||||
});
|
||||
|
||||
it('should work with config variables at the package level', async () => {
|
||||
const inputs = await packagePolicyService.compilePackagePolicyInputs(
|
||||
({
|
||||
data_streams: [
|
||||
{
|
||||
dataset: 'package.dataset1',
|
||||
type: 'logs',
|
||||
streams: [{ input: 'log', template_path: 'some_template_path.yml' }],
|
||||
path: 'dataset1',
|
||||
},
|
||||
],
|
||||
policy_templates: [
|
||||
{
|
||||
inputs: [{ type: 'log' }],
|
||||
},
|
||||
],
|
||||
} as unknown) as PackageInfo,
|
||||
{
|
||||
hosts: {
|
||||
value: ['localhost'],
|
||||
},
|
||||
},
|
||||
[
|
||||
{
|
||||
type: 'log',
|
||||
enabled: true,
|
||||
vars: {
|
||||
paths: {
|
||||
value: ['/var/log/set.log'],
|
||||
},
|
||||
},
|
||||
streams: [
|
||||
{
|
||||
id: 'datastream01',
|
||||
data_stream: { dataset: 'package.dataset1', type: 'logs' },
|
||||
enabled: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
);
|
||||
|
||||
expect(inputs).toEqual([
|
||||
{
|
||||
type: 'log',
|
||||
enabled: true,
|
||||
vars: {
|
||||
paths: {
|
||||
value: ['/var/log/set.log'],
|
||||
},
|
||||
},
|
||||
streams: [
|
||||
{
|
||||
id: 'datastream01',
|
||||
data_stream: { dataset: 'package.dataset1', type: 'logs' },
|
||||
enabled: true,
|
||||
compiled_stream: {
|
||||
metricset: ['dataset1'],
|
||||
paths: ['/var/log/set.log'],
|
||||
type: 'log',
|
||||
hosts: ['localhost'],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('should work with an input with a template and no streams', async () => {
|
||||
const inputs = await packagePolicyService.compilePackagePolicyInputs(
|
||||
({
|
||||
|
@ -286,6 +363,7 @@ describe('Package policy service', () => {
|
|||
},
|
||||
],
|
||||
} as unknown) as PackageInfo,
|
||||
{},
|
||||
[
|
||||
{
|
||||
type: 'log',
|
||||
|
@ -334,6 +412,7 @@ describe('Package policy service', () => {
|
|||
},
|
||||
],
|
||||
} as unknown) as PackageInfo,
|
||||
{},
|
||||
[
|
||||
{
|
||||
type: 'log',
|
||||
|
@ -380,6 +459,7 @@ describe('Package policy service', () => {
|
|||
compiled_stream: {
|
||||
metricset: ['dataset1'],
|
||||
paths: ['/var/log/set.log'],
|
||||
hosts: ['localhost'],
|
||||
type: 'log',
|
||||
},
|
||||
},
|
||||
|
@ -397,6 +477,7 @@ describe('Package policy service', () => {
|
|||
},
|
||||
],
|
||||
} as unknown) as PackageInfo,
|
||||
{},
|
||||
[]
|
||||
);
|
||||
|
||||
|
@ -412,6 +493,7 @@ describe('Package policy service', () => {
|
|||
},
|
||||
],
|
||||
} as unknown) as PackageInfo,
|
||||
{},
|
||||
[]
|
||||
);
|
||||
|
||||
|
|
|
@ -128,7 +128,7 @@ class PackagePolicyService {
|
|||
}
|
||||
}
|
||||
|
||||
inputs = await this.compilePackagePolicyInputs(pkgInfo, inputs);
|
||||
inputs = await this.compilePackagePolicyInputs(pkgInfo, packagePolicy.vars || {}, inputs);
|
||||
}
|
||||
|
||||
const isoDate = new Date().toISOString();
|
||||
|
@ -356,7 +356,7 @@ class PackagePolicyService {
|
|||
pkgVersion: packagePolicy.package.version,
|
||||
});
|
||||
|
||||
inputs = await this.compilePackagePolicyInputs(pkgInfo, inputs);
|
||||
inputs = await this.compilePackagePolicyInputs(pkgInfo, packagePolicy.vars || {}, inputs);
|
||||
}
|
||||
|
||||
await soClient.update<PackagePolicySOAttributes>(
|
||||
|
@ -432,7 +432,7 @@ class PackagePolicyService {
|
|||
): Promise<NewPackagePolicy | undefined> {
|
||||
const pkgInstall = await getInstallation({ savedObjectsClient: soClient, pkgName });
|
||||
if (pkgInstall) {
|
||||
const [pkgInfo, defaultOutputId] = await Promise.all([
|
||||
const [packageInfo, defaultOutputId] = await Promise.all([
|
||||
getPackageInfo({
|
||||
savedObjectsClient: soClient,
|
||||
pkgName: pkgInstall.name,
|
||||
|
@ -440,23 +440,24 @@ class PackagePolicyService {
|
|||
}),
|
||||
outputService.getDefaultOutputId(soClient),
|
||||
]);
|
||||
if (pkgInfo) {
|
||||
if (packageInfo) {
|
||||
if (!defaultOutputId) {
|
||||
throw new Error('Default output is not set');
|
||||
}
|
||||
return packageToPackagePolicy(pkgInfo, '', defaultOutputId);
|
||||
return packageToPackagePolicy(packageInfo, '', defaultOutputId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async compilePackagePolicyInputs(
|
||||
pkgInfo: PackageInfo,
|
||||
vars: PackagePolicy['vars'],
|
||||
inputs: PackagePolicyInput[]
|
||||
): Promise<PackagePolicyInput[]> {
|
||||
const registryPkgInfo = await Registry.fetchInfo(pkgInfo.name, pkgInfo.version);
|
||||
const inputsPromises = inputs.map(async (input) => {
|
||||
const compiledInput = await _compilePackagePolicyInput(registryPkgInfo, pkgInfo, input);
|
||||
const compiledStreams = await _compilePackageStreams(registryPkgInfo, pkgInfo, input);
|
||||
const compiledInput = await _compilePackagePolicyInput(registryPkgInfo, pkgInfo, vars, input);
|
||||
const compiledStreams = await _compilePackageStreams(registryPkgInfo, pkgInfo, vars, input);
|
||||
return {
|
||||
...input,
|
||||
compiled_input: compiledInput,
|
||||
|
@ -506,6 +507,7 @@ function assignStreamIdToInput(packagePolicyId: string, input: NewPackagePolicyI
|
|||
async function _compilePackagePolicyInput(
|
||||
registryPkgInfo: RegistryPackage,
|
||||
pkgInfo: PackageInfo,
|
||||
vars: PackagePolicy['vars'],
|
||||
input: PackagePolicyInput
|
||||
) {
|
||||
if ((!input.enabled || !pkgInfo.policy_templates?.[0]?.inputs?.length) ?? 0 > 0) {
|
||||
|
@ -531,8 +533,8 @@ async function _compilePackagePolicyInput(
|
|||
}
|
||||
|
||||
return compileTemplate(
|
||||
// Populate template variables from input vars
|
||||
Object.assign({}, input.vars),
|
||||
// Populate template variables from package- and input-level vars
|
||||
Object.assign({}, vars, input.vars),
|
||||
pkgInputTemplate.buffer.toString()
|
||||
);
|
||||
}
|
||||
|
@ -540,10 +542,11 @@ async function _compilePackagePolicyInput(
|
|||
async function _compilePackageStreams(
|
||||
registryPkgInfo: RegistryPackage,
|
||||
pkgInfo: PackageInfo,
|
||||
vars: PackagePolicy['vars'],
|
||||
input: PackagePolicyInput
|
||||
) {
|
||||
const streamsPromises = input.streams.map((stream) =>
|
||||
_compilePackageStream(registryPkgInfo, pkgInfo, input, stream)
|
||||
_compilePackageStream(registryPkgInfo, pkgInfo, vars, input, stream)
|
||||
);
|
||||
|
||||
return await Promise.all(streamsPromises);
|
||||
|
@ -552,6 +555,7 @@ async function _compilePackageStreams(
|
|||
async function _compilePackageStream(
|
||||
registryPkgInfo: RegistryPackage,
|
||||
pkgInfo: PackageInfo,
|
||||
vars: PackagePolicy['vars'],
|
||||
input: PackagePolicyInput,
|
||||
stream: PackagePolicyInputStream
|
||||
) {
|
||||
|
@ -600,8 +604,8 @@ async function _compilePackageStream(
|
|||
}
|
||||
|
||||
const yaml = compileTemplate(
|
||||
// Populate template variables from input vars and stream vars
|
||||
Object.assign({}, input.vars, stream.vars),
|
||||
// Populate template variables from package-, input-, and stream-level vars
|
||||
Object.assign({}, vars, input.vars, stream.vars),
|
||||
pkgStreamTemplate.buffer.toString()
|
||||
);
|
||||
|
||||
|
|
|
@ -71,6 +71,7 @@ export {
|
|||
InstallType,
|
||||
InstallSource,
|
||||
InstallResult,
|
||||
GetCategoriesRequest,
|
||||
DataType,
|
||||
dataTypes,
|
||||
// Fleet Server types
|
||||
|
|
|
@ -77,6 +77,7 @@ const PackagePolicyBaseSchema = {
|
|||
),
|
||||
})
|
||||
),
|
||||
vars: schema.maybe(ConfigRecordSchema),
|
||||
};
|
||||
|
||||
export const NewPackagePolicySchema = schema.object({
|
||||
|
|
|
@ -10,6 +10,7 @@ import { schema } from '@kbn/config-schema';
|
|||
export const GetCategoriesRequestSchema = {
|
||||
query: schema.object({
|
||||
experimental: schema.maybe(schema.boolean()),
|
||||
include_policy_templates: schema.maybe(schema.boolean()),
|
||||
}),
|
||||
};
|
||||
|
||||
|
|
|
@ -1302,6 +1302,7 @@ export class EndpointDocGenerator extends BaseDataGenerator {
|
|||
*/
|
||||
public generateEpmPackage(): GetPackagesResponse['response'][0] {
|
||||
return {
|
||||
id: this.seededUUIDv4(),
|
||||
name: 'endpoint',
|
||||
title: 'Elastic Endpoint',
|
||||
version: '0.5.0',
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue