Skip to content

Commit 5903c7a

Browse files
[Fleet] Improve validation for dynamic Kafka topics (#212422)
Closes #206194 ## Summary - Removed hardcoded wrapping of user-entered topics with `%{[]}` to fix issues arising from the user pre-wrapping, and also allow greater flexibility in naming - Added validation rules to check for unclosed brackets & brackets with missing `%` preceding - Added the auto-wrapping to the `value` field of items chosen from the dropdown to ensure they were always wrapped as intended ### Checklist Check the PR satisfies following conditions. Reviewers should verify this PR satisfies this list as well. - [ ] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/src/platform/packages/shared/kbn-i18n/README.md) - [ ] [Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html) was added for features that require explanation or tutorials - [ ] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios - [ ] If a plugin configuration key changed, check if it needs to be allowlisted in the cloud and added to the [docker list](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker) - [ ] This was checked for breaking HTTP API changes, and any breaking changes have been approved by the breaking-change committee. The `release_note:breaking` label should be applied in these situations. - [ ] [Flaky Test Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was used on any tests changed - [ ] The PR description includes the appropriate Release Notes section, and the correct `release_note:*` label is applied per the [guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process) ### Identify risks n/a --------- Co-authored-by: Elastic Machine <[email protected]>
1 parent 02b9f8f commit 5903c7a

File tree

9 files changed

+76
-8
lines changed

9 files changed

+76
-8
lines changed

src/platform/packages/shared/kbn-doc-links/src/get_doc_links.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ export const getDocLinks = ({ kibanaBranch, buildFlavor }: GetDocLinkOptions): D
108108
configuration: `${ELASTIC_WEBSITE_URL}guide/en/beats/filebeat/${DOC_LINK_VERSION}/configuring-howto-filebeat.html`,
109109
elasticsearchModule: `${ELASTIC_WEBSITE_URL}guide/en/beats/filebeat/${DOC_LINK_VERSION}/filebeat-module-elasticsearch.html`,
110110
elasticsearchOutput: `${ELASTIC_WEBSITE_URL}guide/en/beats/filebeat/${DOC_LINK_VERSION}/elasticsearch-output.html`,
111+
kafkaOutput: `${ELASTIC_WEBSITE_URL}guide/en/beats/filebeat/${DOC_LINK_VERSION}/kafka-output.html`,
111112
startup: `${ELASTIC_WEBSITE_URL}guide/en/beats/filebeat/${DOC_LINK_VERSION}/filebeat-starting.html`,
112113
exportedFields: `${ELASTIC_WEBSITE_URL}guide/en/beats/filebeat/${DOC_LINK_VERSION}/exported-fields.html`,
113114
suricataModule: `${ELASTIC_WEBSITE_URL}guide/en/beats/filebeat/${DOC_LINK_VERSION}/filebeat-module-suricata.html`,

src/platform/packages/shared/kbn-doc-links/src/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ export interface DocLinks {
7070
readonly configuration: string;
7171
readonly elasticsearchOutput: string;
7272
readonly elasticsearchModule: string;
73+
readonly kafkaOutput: string;
7374
readonly startup: string;
7475
readonly exportedFields: string;
7576
readonly suricataModule: string;

x-pack/platform/plugins/private/translations/translations/fr-FR.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19015,7 +19015,6 @@
1901519015
"xpack.fleet.settings.editOutputFlyout.kafkaCompressionTitle": "Compression",
1901619016
"xpack.fleet.settings.editOutputFlyout.kafkaConnectionTypeLabel": "Connexion",
1901719017
"xpack.fleet.settings.editOutputFlyout.kafkaDynamicTopicHelptext": "Sélectionnez un sujet dans la liste. Si un sujet n'est pas disponible, créez un sujet personnalisé.",
19018-
"xpack.fleet.settings.editOutputFlyout.kafkaDynamicTopicLabel": "Sujet du champ",
1901919018
"xpack.fleet.settings.editOutputFlyout.kafkaHeaderKeyInputLabel": "Clé",
1902019019
"xpack.fleet.settings.editOutputFlyout.kafkaHeadersTitle": "En-têtes",
1902119020
"xpack.fleet.settings.editOutputFlyout.kafkaHeaderValueInputLabel": "Valeur",

x-pack/platform/plugins/private/translations/translations/ja-JP.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18876,7 +18876,6 @@
1887618876
"xpack.fleet.settings.editOutputFlyout.kafkaCompressionTitle": "圧縮",
1887718877
"xpack.fleet.settings.editOutputFlyout.kafkaConnectionTypeLabel": "接続",
1887818878
"xpack.fleet.settings.editOutputFlyout.kafkaDynamicTopicHelptext": "リストからトピックを選択してください。トピックがない場合は、カスタムトピックを作成してください。",
18879-
"xpack.fleet.settings.editOutputFlyout.kafkaDynamicTopicLabel": "フィールドからのトピック",
1888018879
"xpack.fleet.settings.editOutputFlyout.kafkaHeaderKeyInputLabel": "キー",
1888118880
"xpack.fleet.settings.editOutputFlyout.kafkaHeadersTitle": "ヘッダー",
1888218881
"xpack.fleet.settings.editOutputFlyout.kafkaHeaderValueInputLabel": "値",

x-pack/platform/plugins/private/translations/translations/zh-CN.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18575,7 +18575,6 @@
1857518575
"xpack.fleet.settings.editOutputFlyout.kafkaCompressionTitle": "压缩",
1857618576
"xpack.fleet.settings.editOutputFlyout.kafkaConnectionTypeLabel": "连接",
1857718577
"xpack.fleet.settings.editOutputFlyout.kafkaDynamicTopicHelptext": "从列表中选择主题。如果主题不可用,请创建定制主题。",
18578-
"xpack.fleet.settings.editOutputFlyout.kafkaDynamicTopicLabel": "来自字段的主题",
1857918578
"xpack.fleet.settings.editOutputFlyout.kafkaHeaderKeyInputLabel": "钥匙",
1858018579
"xpack.fleet.settings.editOutputFlyout.kafkaHeadersTitle": "标题",
1858118580
"xpack.fleet.settings.editOutputFlyout.kafkaHeaderValueInputLabel": "值",

x-pack/platform/plugins/shared/fleet/public/applications/fleet/sections/settings/components/edit_output_flyout/output_form_kafka_topics.tsx

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,13 @@ import {
1515
EuiTitle,
1616
EuiRadioGroup,
1717
EuiComboBox,
18+
EuiLink,
1819
} from '@elastic/eui';
1920
import { FormattedMessage } from '@kbn/i18n-react';
2021
import { i18n } from '@kbn/i18n';
2122

23+
import { useStartServices } from '../../../../hooks';
24+
2225
import {
2326
kafkaTopicsType,
2427
KAFKA_DYNAMIC_FIELDS,
@@ -30,10 +33,12 @@ import type { OutputFormInputsType } from './use_output_form';
3033
export const OutputFormKafkaTopics: React.FunctionComponent<{ inputs: OutputFormInputsType }> = ({
3134
inputs,
3235
}) => {
36+
const { docLinks } = useStartServices();
37+
3338
const dynamicOptions: Array<EuiComboBoxOptionOption<string>> = useMemo(() => {
3439
const options = KAFKA_DYNAMIC_FIELDS.map((option) => ({
3540
label: option,
36-
value: option,
41+
value: `%{[${option}]}`,
3742
}));
3843
return options;
3944
}, []);
@@ -73,7 +78,17 @@ export const OutputFormKafkaTopics: React.FunctionComponent<{ inputs: OutputForm
7378
label={
7479
<FormattedMessage
7580
id="xpack.fleet.settings.editOutputFlyout.kafkaDynamicTopicLabel"
76-
defaultMessage="Topic from field"
81+
defaultMessage="Topic from field(s). For more info, see our {guideLink}"
82+
values={{
83+
guideLink: (
84+
<EuiLink href={docLinks.links.filebeat.kafkaOutput} target="_blank" external>
85+
<FormattedMessage
86+
id="xpack.fleet.settings.kafkaGuideLink"
87+
defaultMessage="docs."
88+
/>
89+
</EuiLink>
90+
),
91+
}}
7792
/>
7893
}
7994
{...inputs.kafkaDynamicTopicInput.formRowProps}
@@ -83,7 +98,7 @@ export const OutputFormKafkaTopics: React.FunctionComponent<{ inputs: OutputForm
8398
fullWidth
8499
isClearable={true}
85100
options={dynamicOptions}
86-
customOptionText="Use custom field (not recommended)"
101+
customOptionText="Use custom field"
87102
singleSelection={{ asPlainText: true }}
88103
{...inputs.kafkaDynamicTopicInput.props}
89104
/>

x-pack/platform/plugins/shared/fleet/public/applications/fleet/sections/settings/components/edit_output_flyout/output_form_validators.test.tsx

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
validateKafkaHosts,
1515
validateKibanaURL,
1616
validateKibanaAPIKey,
17+
validateDynamicKafkaTopics,
1718
} from './output_form_validators';
1819

1920
describe('Output form validation', () => {
@@ -336,4 +337,28 @@ describe('Output form validation', () => {
336337
]);
337338
});
338339
});
340+
341+
describe('validateDynamicKafkaTopics', () => {
342+
const validTopics = [
343+
{ label: 'field1', value: '%{[field]}' },
344+
{ label: 'field2', value: 'field2' },
345+
{ label: 'field3', value: '%{[field2]}-%{[field3]}' },
346+
];
347+
const invalidBracketTopic = [{ label: '%{[field}', value: '%{[field}' }];
348+
const invalidPercentTopic = [{ label: '{[field]}', value: '{[field]}' }];
349+
it('should work with valid topics', () => {
350+
const res = validateDynamicKafkaTopics(validTopics);
351+
expect(res).toBeUndefined();
352+
});
353+
it("should return error with missing brackets in topic's name", () => {
354+
const res = validateDynamicKafkaTopics(invalidBracketTopic);
355+
expect(res).toEqual([
356+
'The topic should have a matching number of opening and closing brackets',
357+
]);
358+
});
359+
it("should return error with missing percent sign before opening brackets in topic's name", () => {
360+
const res = validateDynamicKafkaTopics(invalidPercentTopic);
361+
expect(res).toEqual(['Opening brackets should be preceded by a percent sign']);
362+
});
363+
});
339364
});

x-pack/platform/plugins/shared/fleet/public/applications/fleet/sections/settings/components/edit_output_flyout/output_form_validators.tsx

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,15 @@ const toSecretValidator =
1919
return validator(value ?? '');
2020
};
2121

22+
const getAllIndices = (str: string, substring: string): number[] => {
23+
const indices = [];
24+
let index = str.indexOf(substring);
25+
while (index !== -1) {
26+
indices.push(index);
27+
index = str.indexOf(substring, index + 1);
28+
}
29+
return indices;
30+
};
2231
export function validateKafkaHosts(value: string[]) {
2332
const res: Array<{ message: string; index?: number }> = [];
2433
const urlIndexes: { [key: string]: number[] } = {};
@@ -362,12 +371,31 @@ export function validateKafkaStaticTopic(value: string) {
362371
export function validateDynamicKafkaTopics(value: Array<EuiComboBoxOptionOption<string>>) {
363372
const res = [];
364373
value.forEach((val, idx) => {
365-
if (!val) {
374+
if (!val || !val.value) {
366375
res.push(
367376
i18n.translate('xpack.fleet.settings.outputForm.kafkaTopicFieldRequiredMessage', {
368377
defaultMessage: 'Topic is required',
369378
})
370379
);
380+
} else {
381+
const openingBrackets = getAllIndices(val.value, '{[');
382+
const closingBrackets = getAllIndices(val.value, ']}');
383+
if (openingBrackets.length !== closingBrackets.length) {
384+
res.push(
385+
i18n.translate('xpack.fleet.settings.outputForm.kafkaTopicBracketsError', {
386+
defaultMessage:
387+
'The topic should have a matching number of opening and closing brackets',
388+
})
389+
);
390+
}
391+
// check for preceding percent sign
392+
if (!openingBrackets.every((item) => val?.value![item - 1] === '%')) {
393+
res.push(
394+
i18n.translate('xpack.fleet.settings.outputForm.kafkaTopicPercentError', {
395+
defaultMessage: 'Opening brackets should be preceded by a percent sign',
396+
})
397+
);
398+
}
371399
}
372400
});
373401

@@ -378,6 +406,7 @@ export function validateDynamicKafkaTopics(value: Array<EuiComboBoxOptionOption<
378406
})
379407
);
380408
}
409+
381410
if (res.length) {
382411
return res;
383412
}

x-pack/platform/plugins/shared/fleet/public/applications/fleet/sections/settings/components/edit_output_flyout/use_output_form.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -923,7 +923,7 @@ export function useOutputForm(onSucess: () => void, output?: Output, defaultOupu
923923
}
924924
: kafkaTopicsInput.value === kafkaTopicsType.Dynamic && kafkaDynamicTopicInput.value
925925
? {
926-
topic: `%{[${kafkaDynamicTopicInput.value}]}`,
926+
topic: kafkaDynamicTopicInput.value,
927927
}
928928
: {}),
929929
headers: kafkaHeadersInput.value,

0 commit comments

Comments
 (0)