diff --git a/x-pack/platform/plugins/shared/fleet/common/services/package_to_package_policy.ts b/x-pack/platform/plugins/shared/fleet/common/services/package_to_package_policy.ts index 12921d059ce2a..9083cdecf3d3b 100644 --- a/x-pack/platform/plugins/shared/fleet/common/services/package_to_package_policy.ts +++ b/x-pack/platform/plugins/shared/fleet/common/services/package_to_package_policy.ts @@ -112,10 +112,6 @@ export const varsReducer = ( configObject: PackagePolicyConfigRecord, registryVar: RegistryVarsEntry ): PackagePolicyConfigRecord => { - // section_header vars are decorative only and hold no value - if (registryVar.type === 'section_header') { - return configObject; - } const configEntry: PackagePolicyConfigRecordEntry = { value: !registryVar.default && registryVar.multi ? [] : registryVar.default, }; diff --git a/x-pack/platform/plugins/shared/fleet/common/types/models/epm.ts b/x-pack/platform/plugins/shared/fleet/common/types/models/epm.ts index 5e442b415601e..4ebd19e09c82b 100644 --- a/x-pack/platform/plugins/shared/fleet/common/types/models/epm.ts +++ b/x-pack/platform/plugins/shared/fleet/common/types/models/epm.ts @@ -272,6 +272,7 @@ export enum RegistryPolicyTemplateKeys { dynamic_signal_types = 'dynamic_signal_types', var_groups = 'var_groups', deprecated = 'deprecated', + sections = 'sections', } interface BaseTemplate { [RegistryPolicyTemplateKeys.name]: string; @@ -300,6 +301,7 @@ export interface RegistryPolicyInputOnlyTemplate extends BaseTemplate { [RegistryPolicyTemplateKeys.required_vars]?: RegistryRequiredVars; [RegistryPolicyTemplateKeys.vars]?: RegistryVarsEntry[]; [RegistryPolicyTemplateKeys.var_groups]?: RegistryVarGroup[]; + [RegistryPolicyTemplateKeys.sections]?: RegistrySection[]; [RegistryPolicyTemplateKeys.dynamic_signal_types]?: boolean; } @@ -325,6 +327,7 @@ export enum RegistryInputKeys { migrate_from = 'migrate_from', dynamic_signal_types = 'dynamic_signal_types', show_divider = 'show_divider', + sections = 'sections', } export type RegistryInputGroup = 'logs' | 'metrics'; @@ -350,6 +353,7 @@ export interface RegistryInput { [RegistryInputKeys.dynamic_signal_types]?: boolean; /** When false, suppresses the automatic horizontal divider rendered after the input-level config section. Defaults to true. */ [RegistryInputKeys.show_divider]?: boolean; + [RegistryInputKeys.sections]?: RegistrySection[]; } export enum RegistryStreamKeys { @@ -365,6 +369,7 @@ export enum RegistryStreamKeys { var_groups = 'var_groups', deprecated = 'deprecated', migrate_from = 'migrate_from', + sections = 'sections', } export interface RegistryStream { @@ -380,6 +385,7 @@ export interface RegistryStream { [RegistryStreamKeys.var_groups]?: RegistryVarGroup[]; [RegistryStreamKeys.deprecated]?: DeprecationInfo; [RegistryStreamKeys.migrate_from]?: string; + [RegistryStreamKeys.sections]?: RegistrySection[]; } export type RegistryStreamWithDataStream = RegistryStream & { data_stream: RegistryDataStream }; @@ -554,8 +560,13 @@ export type RegistryVarType = | 'string' | 'textarea' | 'duration' - | 'url' - | 'section_header'; + | 'url'; + +export interface RegistrySection { + name: string; + title: string; + description?: string; +} export enum RegistryVarsEntryKeys { name = 'name', title = 'title', @@ -574,6 +585,7 @@ export enum RegistryVarsEntryKeys { max_duration = 'max_duration', url_allowed_schemes = 'url_allowed_schemes', deprecated = 'deprecated', + section = 'section', } // EPR types this as `[]map[string]interface{}` @@ -601,6 +613,7 @@ export interface RegistryVarsEntry { [RegistryVarsEntryKeys.max_duration]?: string; [RegistryVarsEntryKeys.url_allowed_schemes]?: string[]; [RegistryVarsEntryKeys.deprecated]?: DeprecationInfo; + [RegistryVarsEntryKeys.section]?: string; } // Deprecated as part of the removing public references to saved object schemas diff --git a/x-pack/platform/plugins/shared/fleet/common/types/models/package_spec.ts b/x-pack/platform/plugins/shared/fleet/common/types/models/package_spec.ts index 39c34505d5466..894555058da85 100644 --- a/x-pack/platform/plugins/shared/fleet/common/types/models/package_spec.ts +++ b/x-pack/platform/plugins/shared/fleet/common/types/models/package_spec.ts @@ -9,6 +9,7 @@ import type { DeprecationInfo, RegistryElasticsearch, RegistryPolicyTemplate, + RegistrySection, RegistryVarsEntry, } from './epm'; @@ -63,6 +64,7 @@ export interface PackageSpecManifest { policy_templates?: RegistryPolicyTemplate[]; vars?: RegistryVarsEntry[]; var_groups?: RegistryVarGroup[]; + sections?: RegistrySection[]; owner: { github?: string; type?: 'elastic' | 'partner' | 'community' }; elasticsearch?: Pick< RegistryElasticsearch, diff --git a/x-pack/platform/plugins/shared/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/components/package_policy_input_config.test.tsx b/x-pack/platform/plugins/shared/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/components/package_policy_input_config.test.tsx index 4bc70f1cdd50a..6c7dc68982b0b 100644 --- a/x-pack/platform/plugins/shared/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/components/package_policy_input_config.test.tsx +++ b/x-pack/platform/plugins/shared/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/components/package_policy_input_config.test.tsx @@ -86,6 +86,106 @@ describe('PackagePolicyInputConfig', () => { expect(utils.queryByText('Deprecated Var')).not.toBeInTheDocument(); }); + it('should render section titles for vars with a matching section attribute', () => { + const renderer = createFleetTestRendererMock(); + const mockOnChange = jest.fn(); + + const utils = renderer.render( + + ); + + expect(utils.queryByText('Ungrouped Var')).toBeInTheDocument(); + expect(utils.queryByText('Authentication')).toBeInTheDocument(); + expect(utils.queryByText('Auth settings')).toBeInTheDocument(); + expect(utils.queryByText('Auth Var')).toBeInTheDocument(); + }); + + it('should render vars without a section attribute as ungrouped even when sections are defined', () => { + const renderer = createFleetTestRendererMock(); + const mockOnChange = jest.fn(); + + const utils = renderer.render( + + ); + + expect(utils.queryByText('Ungrouped Var')).toBeInTheDocument(); + // Section header should not render if no vars reference it + expect(utils.queryByText('Authentication')).not.toBeInTheDocument(); + }); + + it('should not render section titles when no sections prop is provided', () => { + const renderer = createFleetTestRendererMock(); + const mockOnChange = jest.fn(); + + const utils = renderer.render( + + ); + + expect(utils.queryByText('Auth Var')).toBeInTheDocument(); + expect(utils.queryByText('Authentication')).not.toBeInTheDocument(); + }); + it('should show deprecated vars on edit page (isEditPage=true)', () => { const renderer = createFleetTestRendererMock(); const mockOnChange = jest.fn(); diff --git a/x-pack/platform/plugins/shared/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/components/package_policy_input_config.tsx b/x-pack/platform/plugins/shared/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/components/package_policy_input_config.tsx index 4363ec92d88da..716d605f87991 100644 --- a/x-pack/platform/plugins/shared/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/components/package_policy_input_config.tsx +++ b/x-pack/platform/plugins/shared/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/components/package_policy_input_config.tsx @@ -12,6 +12,7 @@ import { EuiFlexGroup, EuiFlexItem, EuiText, + EuiTitle, EuiSpacer, EuiButtonEmpty, useIsWithinMinBreakpoint, @@ -21,6 +22,7 @@ import { import type { NewPackagePolicyInput, NewPackagePolicyInputStream, + RegistrySection, RegistryVarGroup, RegistryVarsEntry, } from '../../../../../../types'; @@ -33,6 +35,59 @@ import { useAgentless } from '../../../single_page_layout/hooks/setup_technology import { PackagePolicyInputVarField } from './package_policy_input_var_field'; import { VarGroupSelector } from './var_group_selector'; +const renderVarsWithSections = ( + vars: RegistryVarsEntry[], + renderVarItem: (varDef: RegistryVarsEntry) => React.ReactNode, + sectionDefs?: RegistrySection[] +): React.ReactNode => { + if (!sectionDefs?.length) { + return <>{vars.map((varDef) => renderVarItem(varDef))}; + } + + const sectionSet = new Set(sectionDefs.map((s) => s.name)); + const varsBySectionName = new Map(); + const varsWithoutSection: RegistryVarsEntry[] = []; + + for (const varDef of vars) { + const sectionName = varDef.section; + if (sectionName && sectionSet.has(sectionName)) { + if (!varsBySectionName.has(sectionName)) { + varsBySectionName.set(sectionName, []); + } + varsBySectionName.get(sectionName)!.push(varDef); + } else { + varsWithoutSection.push(varDef); + } + } + + return ( + <> + {varsWithoutSection.map((varDef) => renderVarItem(varDef))} + {sectionDefs.map((section) => { + const sectionVars = varsBySectionName.get(section.name); + if (!sectionVars?.length) return null; + return ( + + + + +

{section.title}

+
+ {section.description && ( + +

{section.description}

+
+ )} +
+ {sectionVars.map((varDef) => renderVarItem(varDef))} +
+
+ ); + })} + + ); +}; + export interface StreamAdvancedVarsConfig { vars: RegistryVarsEntry[]; packagePolicyInputStream: NewPackagePolicyInputStream; @@ -53,6 +108,7 @@ export const PackagePolicyInputConfig: React.FunctionComponent<{ onVarGroupSelectionChange?: (groupName: string, optionName: string) => void; showDescriptionColumn?: boolean; streamAdvancedVars?: StreamAdvancedVarsConfig; + sections?: RegistrySection[]; }> = memo( ({ hasInputStreams, @@ -67,6 +123,7 @@ export const PackagePolicyInputConfig: React.FunctionComponent<{ onVarGroupSelectionChange, showDescriptionColumn = true, streamAdvancedVars, + sections, }) => { // Showing advanced options toggle state const [isShowingAdvanced, setIsShowingAdvanced] = useState(false); @@ -198,36 +255,38 @@ export const PackagePolicyInputConfig: React.FunctionComponent<{ ) : null} - {preGroupRequiredVars.map((varDef) => { - const { name: varName, type: varType } = varDef; - - const value = packagePolicyInput.vars?.[varName]?.value; - const frozen = packagePolicyInput.vars?.[varName]?.frozen; - - return ( - - { - updatePackagePolicyInput({ - vars: { - ...packagePolicyInput.vars, - [varName]: { - type: varType, - value: newValue, + {renderVarsWithSections( + preGroupRequiredVars, + (varDef) => { + const { name: varName, type: varType } = varDef; + const value = packagePolicyInput.vars?.[varName]?.value; + const frozen = packagePolicyInput.vars?.[varName]?.frozen; + return ( + + { + updatePackagePolicyInput({ + vars: { + ...packagePolicyInput.vars, + [varName]: { + type: varType, + value: newValue, + }, }, - }, - }); - }} - errors={inputValidationResults.vars?.[varName]} - forceShowErrors={forceShowErrors} - isEditPage={isEditPage} - /> - - ); - })} + }); + }} + errors={inputValidationResults.vars?.[varName]} + forceShowErrors={forceShowErrors} + isEditPage={isEditPage} + /> + + ); + }, + sections + )} {varGroups?.map((varGroup) => ( ))} - {postGroupRequiredVars.map((varDef) => { - const { name: varName, type: varType } = varDef; - - const value = packagePolicyInput.vars?.[varName]?.value; - const frozen = packagePolicyInput.vars?.[varName]?.frozen; - - return ( - - { - updatePackagePolicyInput({ - vars: { - ...packagePolicyInput.vars, - [varName]: { - type: varType, - value: newValue, + {renderVarsWithSections( + postGroupRequiredVars, + (varDef) => { + const { name: varName, type: varType } = varDef; + const value = packagePolicyInput.vars?.[varName]?.value; + const frozen = packagePolicyInput.vars?.[varName]?.frozen; + return ( + + { + updatePackagePolicyInput({ + vars: { + ...packagePolicyInput.vars, + [varName]: { + type: varType, + value: newValue, + }, }, - }, - }); - }} - errors={inputValidationResults.vars?.[varName]} - forceShowErrors={forceShowErrors} - isEditPage={isEditPage} - /> - - ); - })} + }); + }} + errors={inputValidationResults.vars?.[varName]} + forceShowErrors={forceShowErrors} + isEditPage={isEditPage} + /> + + ); + }, + sections + )} {allAdvancedVars.length ? ( diff --git a/x-pack/platform/plugins/shared/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/components/package_policy_input_panel.tsx b/x-pack/platform/plugins/shared/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/components/package_policy_input_panel.tsx index 72092f3f1122c..59c2ec345cd4f 100644 --- a/x-pack/platform/plugins/shared/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/components/package_policy_input_panel.tsx +++ b/x-pack/platform/plugins/shared/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/components/package_policy_input_panel.tsx @@ -524,6 +524,7 @@ export const PackagePolicyInputPanel: React.FunctionComponent<{ onVarGroupSelectionChange={handleInputVarGroupSelectionChange} showDescriptionColumn={!isSingleInputAndStreams} streamAdvancedVars={consolidatedStreamAdvancedVars} + sections={packageInput.sections} /> {hasInputStreams && !shouldConsolidateAdvancedSections && diff --git a/x-pack/platform/plugins/shared/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/components/package_policy_input_var_field.tsx b/x-pack/platform/plugins/shared/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/components/package_policy_input_var_field.tsx index 5f6aa973b4bab..811d67b32515a 100644 --- a/x-pack/platform/plugins/shared/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/components/package_policy_input_var_field.tsx +++ b/x-pack/platform/plugins/shared/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/components/package_policy_input_var_field.tsx @@ -13,7 +13,6 @@ import { EuiSwitch, EuiFieldText, EuiText, - EuiTitle, EuiFieldPassword, EuiCodeBlock, EuiTextArea, @@ -113,21 +112,6 @@ export const PackagePolicyInputVarField: React.FunctionComponent - -

{title || name}

-
- {description && ( - - - - )} - - ); - } - if (name === DATASET_VAR_NAME && packageType === 'input') { return ( ([]); const toggleIsManaged = (newIsManaged: boolean) => { @@ -85,6 +92,17 @@ export const CreatePackagePolicyMultiPage: CreatePackagePolicyParams = ({ const packageInfo = useMemo(() => packageInfoData?.item, [packageInfoData]); + // Skip the splash for agentless-only or agentless-default integrations — the + // "Install Elastic Agent" first step is not applicable to those flows. + const isAgentlessByDefault = useMemo( + () => + isAgentlessEnabled && + !!packageInfo && + (isOnlyAgentlessIntegration(packageInfo, integration) || + isAgentlessSetupDefault(isAgentlessDefault, packageInfo, integration)), + [isAgentlessEnabled, isAgentlessDefault, packageInfo, integration] + ); + const integrationInfo = useMemo(() => { if (!integration) return; return packageInfo?.policy_templates?.find( @@ -107,7 +125,7 @@ export const CreatePackagePolicyMultiPage: CreatePackagePolicyParams = ({ ...(prerelease ? { prerelease: 'true' } : {}), }); - if (onSplash || !packageInfo) { + if ((onSplash && !isAgentlessByDefault) || !packageInfo) { return ( { - // section_header vars are decorative and always render with required vars, never under "Advanced options" - if (varDef.type === 'section_header') { - return false; - } - // If var is in a selected var_group option, treat as non-advanced (override show_user: false) if ( varGroups && diff --git a/x-pack/platform/plugins/shared/fleet/public/types/index.ts b/x-pack/platform/plugins/shared/fleet/public/types/index.ts index a3b11d049dd9e..59585834b47c2 100644 --- a/x-pack/platform/plugins/shared/fleet/public/types/index.ts +++ b/x-pack/platform/plugins/shared/fleet/public/types/index.ts @@ -102,6 +102,7 @@ export type { CategorySummaryList, PackageInfo, PackageMetadata, + RegistrySection, RegistryVarsEntry, RegistryInput, RegistryStream,