From fd6dc18b3ab9ccf951be15bfd412d023e4e9946b Mon Sep 17 00:00:00 2001 From: Jen Huang Date: Wed, 20 Nov 2024 14:08:07 -0800 Subject: [PATCH] [8.x] [UII] Expose advanced file logging config in UI (#200274) (#201021) # Backport This will backport the following commits from `main` to `8.x`: - [[UII] Expose advanced file logging config in UI (#200274)](https://github.com/elastic/kibana/pull/200274) ### Questions ? Please refer to the [Backport tool documentation](https://github.com/sqren/backport) --- .../common/settings/agent_policy_settings.tsx | 78 ++++++++++++++++++- .../fleet/common/types/models/agent_policy.ts | 12 +++ .../components/form_settings/index.test.tsx | 29 +++++++ .../fleet/components/form_settings/index.tsx | 32 +++++++- .../form_settings/settings_field_wrapper.tsx | 22 ++++-- .../agent_policies/full_agent_policy.test.ts | 10 ++- .../form_settings/form_settings.test.ts | 4 +- .../services/form_settings/form_settings.ts | 48 ++++-------- 8 files changed, 185 insertions(+), 50 deletions(-) diff --git a/x-pack/plugins/fleet/common/settings/agent_policy_settings.tsx b/x-pack/plugins/fleet/common/settings/agent_policy_settings.tsx index a4b41979840b2..55a5885fa4e6b 100644 --- a/x-pack/plugins/fleet/common/settings/agent_policy_settings.tsx +++ b/x-pack/plugins/fleet/common/settings/agent_policy_settings.tsx @@ -40,7 +40,7 @@ export const AGENT_POLICY_ADVANCED_SETTINGS: SettingsConfig[] = [ api_field: { name: 'agent_limits_go_max_procs', }, - schema: z.number().int().min(0).default(0), + schema: z.number().int().min(0), }, { name: 'agent.download.timeout', @@ -59,7 +59,7 @@ export const AGENT_POLICY_ADVANCED_SETTINGS: SettingsConfig[] = [ api_field: { name: 'agent_download_timeout', }, - schema: zodStringWithDurationValidation.default('2h'), + schema: zodStringWithDurationValidation, }, { name: 'agent.download.target_directory', @@ -103,7 +103,7 @@ export const AGENT_POLICY_ADVANCED_SETTINGS: SettingsConfig[] = [ ), learnMoreLink: 'https://www.elastic.co/guide/en/fleet/current/elastic-agent-standalone-logging-config.html#elastic-agent-standalone-logging-settings', - schema: zodStringWithDurationValidation.default('30s'), + schema: zodStringWithDurationValidation, }, { name: 'agent.logging.level', @@ -124,4 +124,76 @@ export const AGENT_POLICY_ADVANCED_SETTINGS: SettingsConfig[] = [ 'https://www.elastic.co/guide/en/fleet/current/agent-policy.html#agent-policy-log-level', schema: z.enum(AGENT_LOG_LEVELS).default(DEFAULT_LOG_LEVEL), }, + { + name: 'agent.logging.to_files', + title: i18n.translate('xpack.fleet.settings.agentPolicyAdvanced.agentLoggingToFilesTitle', { + defaultMessage: 'Agent logging to files', + }), + description: ( + + ), + api_field: { + name: 'agent_logging_to_files', + }, + learnMoreLink: + 'https://www.elastic.co/guide/en/fleet/current/elastic-agent-standalone-logging-config.html#elastic-agent-standalone-logging-settings', + schema: z.boolean().default(true), + }, + { + name: 'agent.logging.files.rotateeverybytes', + title: i18n.translate('xpack.fleet.settings.agentPolicyAdvanced.agentLoggingFileSizeTitle', { + defaultMessage: 'Agent logging file size limit', + }), + description: ( + + ), + api_field: { + name: 'agent_logging_files_rotateeverybytes', + }, + learnMoreLink: + 'https://www.elastic.co/guide/en/fleet/current/elastic-agent-standalone-logging-config.html#elastic-agent-standalone-logging-settings', + schema: z.number().int().min(0), + }, + { + name: 'agent.logging.files.keepfiles', + title: i18n.translate('xpack.fleet.settings.agentPolicyAdvanced.agentLoggingFileLimitTitle', { + defaultMessage: 'Agent logging number of files', + }), + description: ( + + ), + api_field: { + name: 'agent_logging_files_keepfiles', + }, + learnMoreLink: + 'https://www.elastic.co/guide/en/fleet/current/elastic-agent-standalone-logging-config.html#elastic-agent-standalone-logging-settings', + schema: z.number().int().min(0), + }, + { + name: 'agent.logging.files.interval', + title: i18n.translate('xpack.fleet.settings.agentPolicyAdvanced.agentLoggingFileIntervalitle', { + defaultMessage: 'Agent logging number of files', + }), + description: ( + + ), + api_field: { + name: 'agent_logging_files_interval', + }, + learnMoreLink: + 'https://www.elastic.co/guide/en/fleet/current/elastic-agent-standalone-logging-config.html#elastic-agent-standalone-logging-settings', + schema: zodStringWithDurationValidation, + }, ]; diff --git a/x-pack/plugins/fleet/common/types/models/agent_policy.ts b/x-pack/plugins/fleet/common/types/models/agent_policy.ts index 72ba130e77379..b33b5b71b8c76 100644 --- a/x-pack/plugins/fleet/common/types/models/agent_policy.ts +++ b/x-pack/plugins/fleet/common/types/models/agent_policy.ts @@ -182,6 +182,18 @@ export interface FullAgentPolicy { uninstall_token_hash: string; signing_key: string; }; + logging?: { + level?: string; + to_files?: boolean; + files?: { + rotateeverybytes?: number; + keepfiles?: number; + interval?: string; + }; + }; + limits?: { + go_max_procs?: number; + }; }; secret_references?: PolicySecretReference[]; signed?: { diff --git a/x-pack/plugins/fleet/public/applications/fleet/components/form_settings/index.test.tsx b/x-pack/plugins/fleet/public/applications/fleet/components/form_settings/index.test.tsx index 8bafc124ec36b..768f9f913607d 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/components/form_settings/index.test.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/components/form_settings/index.test.tsx @@ -102,6 +102,35 @@ describe('ConfiguredSettings', () => { expect(mockUpdateAdvancedSettingsHasErrors).toHaveBeenCalledWith(true); }); + it('should render boolean field using checkbox', () => { + const result = render([ + { + name: 'agent.logging.to_files', + title: 'Agent logging to files', + description: 'Description', + learnMoreLink: '', + api_field: { + name: 'agent_logging_to_files', + }, + schema: z.boolean().default(false), + }, + ]); + + expect(result.getByText('Agent logging to files')).not.toBeNull(); + const input = result.getByTestId('configuredSetting-agent.logging.to_files'); + expect(input).not.toBeChecked(); + + act(() => { + fireEvent.click(input); + }); + + expect(mockUpdateAgentPolicy).toHaveBeenCalledWith( + expect.objectContaining({ + advanced_settings: expect.objectContaining({ agent_logging_to_files: true }), + }) + ); + }); + it('should not render field if hidden', () => { const result = render([ { diff --git a/x-pack/plugins/fleet/public/applications/fleet/components/form_settings/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/components/form_settings/index.tsx index 7ebf80141c554..e93dd0b6cdd82 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/components/form_settings/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/components/form_settings/index.tsx @@ -7,7 +7,9 @@ import { ZodFirstPartyTypeKind } from '@kbn/zod'; import React from 'react'; -import { EuiFieldNumber, EuiFieldText, EuiSelect } from '@elastic/eui'; +import { EuiCheckbox, EuiFieldNumber, EuiFieldText, EuiSelect } from '@elastic/eui'; + +import { i18n } from '@kbn/i18n'; import type { SettingsConfig } from '../../../../../common/settings/types'; @@ -68,7 +70,7 @@ settingComponentRegistry.set(ZodFirstPartyTypeKind.ZodEnum, ({ disabled, ...sett ( { + return ( + ( + + )} + /> + ); + } +); + export function ConfiguredSettings({ configuredSettings, disabled, @@ -101,7 +127,7 @@ export function ConfiguredSettings({ const Component = settingComponentRegistry.get(getInnerType(configuredSetting.schema)); if (!Component) { - throw new Error(`Unknown setting type: ${configuredSetting.schema._type}}`); + throw new Error(`Unknown setting type: ${configuredSetting.schema._type}`); } return ( diff --git a/x-pack/plugins/fleet/public/applications/fleet/components/form_settings/settings_field_wrapper.tsx b/x-pack/plugins/fleet/public/applications/fleet/components/form_settings/settings_field_wrapper.tsx index 61adef4729a27..1885d466711fb 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/components/form_settings/settings_field_wrapper.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/components/form_settings/settings_field_wrapper.tsx @@ -13,12 +13,15 @@ import { EuiDescribedFormGroup, EuiFormRow, EuiLink } from '@elastic/eui'; import type { SettingsConfig } from '../../../../../common/settings/types'; import { useAgentPolicyFormContext } from '../../sections/agent_policy/components/agent_policy_form'; -export const convertValue = (value: string, type: keyof typeof ZodFirstPartyTypeKind): any => { +export const convertValue = ( + value: string | boolean, + type: keyof typeof ZodFirstPartyTypeKind +): any => { if (type === ZodFirstPartyTypeKind.ZodNumber) { if (value === '') { return 0; } - return parseInt(value, 10); + return parseInt(value as string, 10); } return value; }; @@ -48,7 +51,8 @@ export const SettingsFieldWrapper: React.FC<{ const coercedSchema = settingsConfig.schema as z.ZodString; const handleChange = (e: React.ChangeEvent) => { - const newValue = convertValue(e.target.value, typeName); + const value = typeName === ZodFirstPartyTypeKind.ZodBoolean ? e.target.checked : e.target.value; + const newValue = convertValue(value, typeName); const validationError = validateSchema(coercedSchema, newValue); if (validationError) { @@ -97,9 +101,13 @@ export const SettingsFieldWrapper: React.FC<{ }; export const getInnerType = (schema: z.ZodType) => { - return schema instanceof z.ZodDefault - ? schema._def.innerType._def.typeName === 'ZodEffects' + if (schema._def.innerType) { + return schema._def.innerType._def.typeName === 'ZodEffects' ? schema._def.innerType._def.schema._def.typeName - : schema._def.innerType._def.typeName - : schema._def.typeName; + : schema._def.innerType._def.typeName; + } + if (schema._def.typeName === 'ZodEffects') { + return schema._def.schema._def.typeName; + } + return schema._def.typeName; }; diff --git a/x-pack/plugins/fleet/server/services/agent_policies/full_agent_policy.test.ts b/x-pack/plugins/fleet/server/services/agent_policies/full_agent_policy.test.ts index fa5522d50802b..b62724b0cea50 100644 --- a/x-pack/plugins/fleet/server/services/agent_policies/full_agent_policy.test.ts +++ b/x-pack/plugins/fleet/server/services/agent_policies/full_agent_policy.test.ts @@ -888,6 +888,10 @@ describe('getFullAgentPolicy', () => { advanced_settings: { agent_limits_go_max_procs: 2, agent_logging_level: 'debug', + agent_logging_to_files: true, + agent_logging_files_rotateeverybytes: 10000, + agent_logging_files_keepfiles: 10, + agent_logging_files_interval: '7h', }, }); const agentPolicy = await getFullAgentPolicy(savedObjectsClientMock.create(), 'agent-policy'); @@ -896,7 +900,11 @@ describe('getFullAgentPolicy', () => { id: 'agent-policy', agent: { limits: { go_max_procs: 2 }, - logging: { level: 'debug' }, + logging: { + level: 'debug', + to_files: true, + files: { rotateeverybytes: 10000, keepfiles: 10, interval: '7h' }, + }, }, }); }); diff --git a/x-pack/plugins/fleet/server/services/form_settings/form_settings.test.ts b/x-pack/plugins/fleet/server/services/form_settings/form_settings.test.ts index afcba824f1a33..710d862bf3ad1 100644 --- a/x-pack/plugins/fleet/server/services/form_settings/form_settings.test.ts +++ b/x-pack/plugins/fleet/server/services/form_settings/form_settings.test.ts @@ -55,10 +55,10 @@ describe('form_settings', () => { ).not.toThrow(); }); - it('generate a valid API schema for api_field with default value', () => { + it('generate a valid API schema for api_field with default value but not add the value', () => { const apiSchema = schema.object(_getSettingsAPISchema(TEST_SETTINGS)); const res = apiSchema.validate({ advanced_settings: {} }); - expect(res).toEqual({ advanced_settings: { test_foo_default_value: 'test' } }); + expect(res).toEqual({ advanced_settings: {} }); }); }); diff --git a/x-pack/plugins/fleet/server/services/form_settings/form_settings.ts b/x-pack/plugins/fleet/server/services/form_settings/form_settings.ts index a381fcd55b4ba..2c625f6bd0574 100644 --- a/x-pack/plugins/fleet/server/services/form_settings/form_settings.ts +++ b/x-pack/plugins/fleet/server/services/form_settings/form_settings.ts @@ -24,39 +24,19 @@ export function _getSettingsAPISchema(settings: SettingsConfig[]): Props { if (!setting.api_field) { return; } - const defaultValueRes = setting.schema.safeParse(undefined); - const defaultValue = defaultValueRes.success ? defaultValueRes.data : undefined; - if (defaultValue) { - validations[setting.api_field.name] = schema.oneOf( - [ - schema.any({ - validate: (val: any) => { - const res = setting.schema.safeParse(val); - if (!res.success) { - return stringifyZodError(res.error); - } - }, - }), - schema.literal(null), - ], - { - defaultValue, - } - ); - } else { - validations[setting.api_field.name] = schema.maybe( - schema.nullable( - schema.any({ - validate: (val: any) => { - const res = setting.schema.safeParse(val); - if (!res.success) { - return stringifyZodError(res.error); - } - }, - }) - ) - ); - } + validations[setting.api_field.name] = schema.maybe( + schema.oneOf([ + schema.literal(null), + schema.any({ + validate: (val: any) => { + const res = setting.schema.safeParse(val); + if (!res.success) { + return stringifyZodError(res.error); + } + }, + }), + ]) + ); }); const advancedSettingsValidations: Props = { @@ -89,7 +69,7 @@ export function _getSettingsValuesForAgentPolicy( } const val = agentPolicy.advanced_settings?.[setting.api_field.name]; - if (val) { + if (val !== undefined) { settingsValues[setting.name] = val; } });