Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Fleet] Settings Framework API and UI #179795

Merged
merged 32 commits into from
Apr 9, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
459ed22
[Fleet] Settings Framework API and SO
nchaulet Apr 2, 2024
b9b1d89
Add unit tests
nchaulet Apr 3, 2024
e9da8bf
[CI] Auto-commit changed files from 'node scripts/lint_ts_projects --…
kibanamachine Apr 3, 2024
137b40e
Merge branch 'main' into feature-settings-configurable
juliaElastic Apr 3, 2024
ba36f2a
fix lint
juliaElastic Apr 4, 2024
c319d0c
storing dynamic settings under advanced_settings in SO
juliaElastic Apr 4, 2024
cc9e820
remove saved_object_field
juliaElastic Apr 4, 2024
c87e0d6
[CI] Auto-commit changed files from 'node scripts/check_mappings_upda…
kibanamachine Apr 4, 2024
4e639a2
remove unused export
juliaElastic Apr 4, 2024
07130c4
[CI] Auto-commit changed files from 'node scripts/jest_integration -u…
kibanamachine Apr 4, 2024
6761aa1
added openapi spec for advanced_settings
juliaElastic Apr 4, 2024
5223904
move i18n to form settings component
juliaElastic Apr 4, 2024
85df867
use i18n.translate
juliaElastic Apr 4, 2024
f6f4b56
moved defaultValue to value
juliaElastic Apr 5, 2024
01c9d53
moved back i18n.translate to config
juliaElastic Apr 5, 2024
28741c7
fix tests
juliaElastic Apr 5, 2024
a52d6bb
Merge branch 'main' into feature-settings-configurable
juliaElastic Apr 5, 2024
878bf20
extracted common code in form_settings to a wrapper component
juliaElastic Apr 5, 2024
dab2cf2
use zod enum values
juliaElastic Apr 5, 2024
ba7a56f
renamed path to target_directory, added logging metrics period
juliaElastic Apr 5, 2024
f10222f
Merge branch 'main' into feature-settings-configurable
juliaElastic Apr 8, 2024
8f10f68
added feature flag
juliaElastic Apr 8, 2024
24f7fab
added doc link
juliaElastic Apr 8, 2024
bc62731
updated doc link, change download timeout to string
juliaElastic Apr 8, 2024
b3a83a7
Merge branch 'main' into feature-settings-configurable
juliaElastic Apr 8, 2024
5020c56
added regex validation on duration fields
juliaElastic Apr 8, 2024
c47b966
changed regex to allow empty value
juliaElastic Apr 8, 2024
6879094
add unit test on form_settings
juliaElastic Apr 8, 2024
7a78b57
move updateAdvancedSettingsHasErrors to AgentPolicyFormContext
juliaElastic Apr 8, 2024
779acf5
add more unit test
juliaElastic Apr 8, 2024
ab47145
Merge branch 'main' into feature-settings-configurable
juliaElastic Apr 9, 2024
ddb0925
add unit test on full_agent_policy
juliaElastic Apr 9, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 69 additions & 0 deletions x-pack/plugins/fleet/common/settings/agent_policy_settings.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/*
* 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 { z } from 'zod';
import { i18n } from '@kbn/i18n';

import type { SettingsConfig } from './types';

export const AGENT_POLICY_ADVANCED_SETTINGS: SettingsConfig[] = [
{
name: 'agent.limits.go_max_procs',
saved_object_field: {
name: 'agent_limits_go_max_procs',
mapping: {
type: 'integer',
index: false,
},
},
title: i18n.translate('xpack.fleet.settings.agentPolicyAdvance.goMaxProcsDescription', {
juliaElastic marked this conversation as resolved.
Show resolved Hide resolved
defaultMessage: 'GO_MAX_PROCS',
}),
description: i18n.translate('xpack.fleet.settings.agentPolicyAdvance.goMaxProcsDescription', {
defaultMessage: 'Limits the maximum number of CPUs that can be executing simultaneously',
}),
learnMoreLink: 'https://docs.elastic.co/...',
api_field: {
name: 'agent_limits_go_max_procs',
},
schema: z.number().int().min(0).default(0),
},
{
name: 'agent.download.timeout',
title: i18n.translate('xpack.fleet.settings.agentPolicyAdvance.downloadTimeoutTitle', {
defaultMessage: 'Agent binary download timeout',
}),
description: i18n.translate(
'xpack.fleet.settings.agentPolicyAdvance.downloadTimeoutDescription',
{
defaultMessage: 'Timeout in seconds for downloading the agent binary',
}
),
learnMoreLink: 'https://docs.elastic.co/...',
saved_object_field: {
name: 'agent_download_timeout',
mapping: {
type: 'integer',
index: false,
},
},
api_field: {
name: 'agent_download_timeout',
},
schema: z.number().int().min(0).default(0),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should the download timeout be a string instead? it seems to accept values like 120s

https://github.com/elastic/elastic-agent/blob/3dab14d123e2e116f61805ef4b67efc47283b03f/elastic-agent.reference.yml#L95

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes I think this is a Go duration string so it should be validated as a string here

},
{
name: 'agent.download.path',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should be download.target_directory according to the issue

agent.download.target_directory field - Link

api_field: {
name: 'agent_download_path',
},
title: 'Agent binary download path',
description: 'The disk path to which the agent binary will be downloaded the agent binary',
learnMoreLink: 'https://docs.elastic.co/...',
schema: z.string(),
},
];
8 changes: 8 additions & 0 deletions x-pack/plugins/fleet/common/settings/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/*
* 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.
*/

export { AGENT_POLICY_ADVANCED_SETTINGS } from './agent_policy_settings';
24 changes: 24 additions & 0 deletions x-pack/plugins/fleet/common/settings/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
* 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 { SavedObjectsFieldMapping } from '@kbn/core-saved-objects-server';
import type { z } from 'zod';

export interface SettingsConfig {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added the saved_object_field and api_field definition here

name: string;
title: string;
description: string;
learnMoreLink?: string;
schema: z.ZodTypeAny;
saved_object_field?: {
name: string;
mapping: SavedObjectsFieldMapping;
};
api_field: {
Copy link
Member Author

@nchaulet nchaulet Apr 3, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

used an object here so it easily extendable in the future,

name: string;
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
/*
* 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 { z } from 'zod';
import React, { useState } from 'react';
import {
EuiDescribedFormGroup,
EuiFieldNumber,
EuiFieldText,
EuiFormRow,
EuiLink,
} from '@elastic/eui';

import type { SettingsConfig } from '../../../../../common/settings/types';
import { useAgentPolicyFormContext } from '../../sections/agent_policy/components/agent_policy_form';

export const settingComponentRegistry = new Map<
string,
(settingsconfig: SettingsConfig) => React.ReactElement
>();

settingComponentRegistry.set(z.number()._def.typeName, (settingsConfig) => {
const [error, setError] = useState('');

const agentPolicyFormContext = useAgentPolicyFormContext();
const fieldKey = `configuredSetting-${settingsConfig.name}`;

const defaultValue: number =
settingsConfig.schema instanceof z.ZodDefault
? settingsConfig.schema._def.defaultValue()
: undefined;
const coercedSchema = settingsConfig.schema as z.ZodNumber;

const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const validationResults = coercedSchema.safeParse(Number(e.target.value));

if (!validationResults.success) {
setError(validationResults.error.issues[0].message);
return;
}

agentPolicyFormContext?.updateAgentPolicy({ [settingsConfig.api_field.name]: e.target.value });
setError('');
};

return (
<EuiDescribedFormGroup
fullWidth
title={<h4>{settingsConfig.title}</h4>}
description={
<>
{settingsConfig.description}.{' '}
<EuiLink href={settingsConfig.learnMoreLink} external>
Learn more.
</EuiLink>
</>
}
>
<EuiFormRow fullWidth key={fieldKey} error={error} isInvalid={!!error}>
<EuiFieldNumber
fullWidth
data-test-subj={fieldKey}
value={agentPolicyFormContext?.agentPolicy[settingsConfig.api_field.name]}
min={coercedSchema.minValue ?? undefined}
max={coercedSchema.maxValue ?? undefined}
defaultValue={defaultValue}
onChange={handleChange}
isInvalid={!!error}
/>
</EuiFormRow>
</EuiDescribedFormGroup>
);
});

settingComponentRegistry.set(z.string()._def.typeName, (settingsConfig) => {
const [error, setError] = useState('');
const agentPolicyFormContext = useAgentPolicyFormContext();

const fieldKey = `configuredSetting-${settingsConfig.name}`;
const defaultValue: number =
settingsConfig.schema instanceof z.ZodDefault
? settingsConfig.schema._def.defaultValue()
: undefined;
const coercedSchema = settingsConfig.schema as z.ZodString;

const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const validationResults = coercedSchema.safeParse(e.target.value);

if (!validationResults.success) {
setError(validationResults.error.issues[0].message);
return;
}

agentPolicyFormContext?.updateAgentPolicy({ [settingsConfig.api_field.name]: e.target.value });
setError('');
};

return (
<EuiDescribedFormGroup
fullWidth
title={<h4>{settingsConfig.title}</h4>}
description={
<>
{settingsConfig.description}.{' '}
<EuiLink href={settingsConfig.learnMoreLink} external>
Learn more.
</EuiLink>
</>
}
>
<EuiFormRow fullWidth key={fieldKey} error={error} isInvalid={!!error}>
<EuiFieldText
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there is a lot of duplication between number and text type, I wonder if there could be a common component as a wrapper

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

refactored this component to use a common SettingsFieldWrapper

fullWidth
data-test-subj={fieldKey}
value={agentPolicyFormContext?.agentPolicy[settingsConfig.api_field.name]}
defaultValue={defaultValue}
onChange={handleChange}
isInvalid={!!error}
/>
</EuiFormRow>
</EuiDescribedFormGroup>
);
});

export function ConfiguredSettings({
configuredSettings,
}: {
configuredSettings: SettingsConfig[];
}) {
return (
<>
{configuredSettings.map((configuredSetting) => {
const Component = settingComponentRegistry.get(
configuredSetting.schema instanceof z.ZodDefault
? configuredSetting.schema._def.innerType._def.typeName
: configuredSetting.schema._def.typeName
);

if (!Component) {
throw new Error(`Unknown setting type: ${configuredSetting.schema._type}}`);
}

return <Component key={configuredSetting.name} {...configuredSetting} />;
})}
</>
);
}
Loading