mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
# 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) <!--- Backport version: 8.9.8 --> ### Questions ? Please refer to the [Backport tool documentation](https://github.com/sqren/backport) <!--BACKPORT [{"author":{"name":"Jen Huang","email":"its.jenetic@gmail.com"},"sourceCommit":{"committedDate":"2024-11-20T17:41:50Z","message":"[UII] Expose advanced file logging config in UI (#200274)\n\n## Summary\r\n\r\nResolves [#192237](https://github.com/elastic/kibana/issues/192237).\r\nThis PR exposes the following Elastic Agent file logging configuration\r\noptions in the agent policy advanced settings UI:\r\n\r\n```\r\nagent.logging.to_files\r\nagent.logging.files.rotateeverybytes \r\nagent.logging.files.keepfiles\r\nagent.logging.files.interval\r\n```\r\n\r\n<img width=\"1237\" alt=\"image\"\r\nsrc=\"https://github.com/user-attachments/assets/8de9023c-29a0-4ecf-803a-d8c0c4b87616\">\r\n\r\nThis PR also does some clean up on the default values for all these\r\nconfigured advanced settings so that when user has not touched them, the\r\ndefault values do not get written into the agent policy saved object.\r\n[More info\r\nhere](https://github.com/elastic/kibana/pull/200274#discussion_r1849142612).\r\n\r\nIt also fixes adds missing response schemas for the advanced settings.\r\n\r\n### Checklist\r\n\r\nCheck the PR satisfies following conditions. \r\n\r\nReviewers should verify this PR satisfies this list as well.\r\n\r\n- [x] Any text added follows [EUI's writing\r\nguidelines](https://elastic.github.io/eui/#/guidelines/writing), uses\r\nsentence case text and includes [i18n\r\nsupport](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md)\r\n- [x] [Unit or functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere updated or added to match the most common scenarios\r\n\r\n---------\r\n\r\nCo-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>","sha":"0aa63a7eccea009174db782c57577226ea252bff","branchLabelMapping":{"^v9.0.0$":"main","^v8.17.0$":"8.x","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["Team:Fleet","v9.0.0","release_note:feature","backport:prev-minor"],"number":200274,"url":"https://github.com/elastic/kibana/pull/200274","mergeCommit":{"message":"[UII] Expose advanced file logging config in UI (#200274)\n\n## Summary\r\n\r\nResolves [#192237](https://github.com/elastic/kibana/issues/192237).\r\nThis PR exposes the following Elastic Agent file logging configuration\r\noptions in the agent policy advanced settings UI:\r\n\r\n```\r\nagent.logging.to_files\r\nagent.logging.files.rotateeverybytes \r\nagent.logging.files.keepfiles\r\nagent.logging.files.interval\r\n```\r\n\r\n<img width=\"1237\" alt=\"image\"\r\nsrc=\"https://github.com/user-attachments/assets/8de9023c-29a0-4ecf-803a-d8c0c4b87616\">\r\n\r\nThis PR also does some clean up on the default values for all these\r\nconfigured advanced settings so that when user has not touched them, the\r\ndefault values do not get written into the agent policy saved object.\r\n[More info\r\nhere](https://github.com/elastic/kibana/pull/200274#discussion_r1849142612).\r\n\r\nIt also fixes adds missing response schemas for the advanced settings.\r\n\r\n### Checklist\r\n\r\nCheck the PR satisfies following conditions. \r\n\r\nReviewers should verify this PR satisfies this list as well.\r\n\r\n- [x] Any text added follows [EUI's writing\r\nguidelines](https://elastic.github.io/eui/#/guidelines/writing), uses\r\nsentence case text and includes [i18n\r\nsupport](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md)\r\n- [x] [Unit or functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere updated or added to match the most common scenarios\r\n\r\n---------\r\n\r\nCo-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>","sha":"0aa63a7eccea009174db782c57577226ea252bff"}},"sourceBranch":"main","suggestedTargetBranches":[],"targetPullRequestStates":[{"branch":"main","label":"v9.0.0","labelRegex":"^v9.0.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/200274","number":200274,"mergeCommit":{"message":"[UII] Expose advanced file logging config in UI (#200274)\n\n## Summary\r\n\r\nResolves [#192237](https://github.com/elastic/kibana/issues/192237).\r\nThis PR exposes the following Elastic Agent file logging configuration\r\noptions in the agent policy advanced settings UI:\r\n\r\n```\r\nagent.logging.to_files\r\nagent.logging.files.rotateeverybytes \r\nagent.logging.files.keepfiles\r\nagent.logging.files.interval\r\n```\r\n\r\n<img width=\"1237\" alt=\"image\"\r\nsrc=\"https://github.com/user-attachments/assets/8de9023c-29a0-4ecf-803a-d8c0c4b87616\">\r\n\r\nThis PR also does some clean up on the default values for all these\r\nconfigured advanced settings so that when user has not touched them, the\r\ndefault values do not get written into the agent policy saved object.\r\n[More info\r\nhere](https://github.com/elastic/kibana/pull/200274#discussion_r1849142612).\r\n\r\nIt also fixes adds missing response schemas for the advanced settings.\r\n\r\n### Checklist\r\n\r\nCheck the PR satisfies following conditions. \r\n\r\nReviewers should verify this PR satisfies this list as well.\r\n\r\n- [x] Any text added follows [EUI's writing\r\nguidelines](https://elastic.github.io/eui/#/guidelines/writing), uses\r\nsentence case text and includes [i18n\r\nsupport](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md)\r\n- [x] [Unit or functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere updated or added to match the most common scenarios\r\n\r\n---------\r\n\r\nCo-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>","sha":"0aa63a7eccea009174db782c57577226ea252bff"}}]}] BACKPORT-->
This commit is contained in:
parent
058ea381ff
commit
fd6dc18b3a
8 changed files with 185 additions and 50 deletions
|
@ -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: (
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.settings.agentPolicyAdvanced.agentLoggingToFilesDescription"
|
||||
defaultMessage="Enables logging to rotating files."
|
||||
/>
|
||||
),
|
||||
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: (
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.settings.agentPolicyAdvanced.agentLoggingFileSizeDescription"
|
||||
defaultMessage="Configure log file size limit in bytes. If limit is reached, log file will be automatically rotated."
|
||||
/>
|
||||
),
|
||||
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: (
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.settings.agentPolicyAdvanced.agentLoggingFileLimitDescription"
|
||||
defaultMessage="Number of rotated log files to keep. Oldest files will be deleted first."
|
||||
/>
|
||||
),
|
||||
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: (
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.settings.agentPolicyAdvanced.agentLoggingFileIntervalescription"
|
||||
defaultMessage="Enable log file rotation on time intervals in addition to size-based rotation, i.e. 24h, 7d."
|
||||
/>
|
||||
),
|
||||
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,
|
||||
},
|
||||
];
|
||||
|
|
|
@ -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?: {
|
||||
|
|
|
@ -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([
|
||||
{
|
||||
|
|
|
@ -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
|
|||
<SettingsFieldWrapper
|
||||
disabled={disabled}
|
||||
settingsConfig={settingsConfig}
|
||||
typeName={ZodFirstPartyTypeKind.ZodString}
|
||||
typeName={ZodFirstPartyTypeKind.ZodEnum}
|
||||
renderItem={({ fieldKey, fieldValue, handleChange }: any) => (
|
||||
<EuiSelect
|
||||
data-test-subj={fieldKey}
|
||||
|
@ -86,6 +88,30 @@ settingComponentRegistry.set(ZodFirstPartyTypeKind.ZodEnum, ({ disabled, ...sett
|
|||
);
|
||||
});
|
||||
|
||||
settingComponentRegistry.set(
|
||||
ZodFirstPartyTypeKind.ZodBoolean,
|
||||
({ disabled, ...settingsConfig }) => {
|
||||
return (
|
||||
<SettingsFieldWrapper
|
||||
disabled={disabled}
|
||||
settingsConfig={settingsConfig}
|
||||
typeName={ZodFirstPartyTypeKind.ZodBoolean}
|
||||
renderItem={({ fieldKey, fieldValue, handleChange }: any) => (
|
||||
<EuiCheckbox
|
||||
data-test-subj={fieldKey}
|
||||
id={fieldKey}
|
||||
label={i18n.translate('xpack.fleet.configuredSettings.genericCheckboxLabel', {
|
||||
defaultMessage: 'Enable',
|
||||
})}
|
||||
checked={fieldValue}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
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 (
|
||||
|
|
|
@ -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<HTMLInputElement>) => {
|
||||
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<any, any>) => {
|
||||
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;
|
||||
};
|
||||
|
|
|
@ -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' },
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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: {} });
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
});
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue