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] Improve validation for dynamic Kafka topics #212422

Merged
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ export const getDocLinks = ({ kibanaBranch, buildFlavor }: GetDocLinkOptions): D
configuration: `${ELASTIC_WEBSITE_URL}guide/en/beats/filebeat/${DOC_LINK_VERSION}/configuring-howto-filebeat.html`,
elasticsearchModule: `${ELASTIC_WEBSITE_URL}guide/en/beats/filebeat/${DOC_LINK_VERSION}/filebeat-module-elasticsearch.html`,
elasticsearchOutput: `${ELASTIC_WEBSITE_URL}guide/en/beats/filebeat/${DOC_LINK_VERSION}/elasticsearch-output.html`,
kafkaOutput: `${ELASTIC_WEBSITE_URL}guide/en/beats/filebeat/${DOC_LINK_VERSION}/kafka-output.html`,
startup: `${ELASTIC_WEBSITE_URL}guide/en/beats/filebeat/${DOC_LINK_VERSION}/filebeat-starting.html`,
exportedFields: `${ELASTIC_WEBSITE_URL}guide/en/beats/filebeat/${DOC_LINK_VERSION}/exported-fields.html`,
suricataModule: `${ELASTIC_WEBSITE_URL}guide/en/beats/filebeat/${DOC_LINK_VERSION}/filebeat-module-suricata.html`,
Expand Down
1 change: 1 addition & 0 deletions src/platform/packages/shared/kbn-doc-links/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ export interface DocLinks {
readonly configuration: string;
readonly elasticsearchOutput: string;
readonly elasticsearchModule: string;
readonly kafkaOutput: string;
readonly startup: string;
readonly exportedFields: string;
readonly suricataModule: string;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19025,7 +19025,6 @@
"xpack.fleet.settings.editOutputFlyout.kafkaCompressionTitle": "Compression",
"xpack.fleet.settings.editOutputFlyout.kafkaConnectionTypeLabel": "Connexion",
"xpack.fleet.settings.editOutputFlyout.kafkaDynamicTopicHelptext": "Sélectionnez un sujet dans la liste. Si un sujet n'est pas disponible, créez un sujet personnalisé.",
"xpack.fleet.settings.editOutputFlyout.kafkaDynamicTopicLabel": "Sujet du champ",
"xpack.fleet.settings.editOutputFlyout.kafkaHeaderKeyInputLabel": "Clé",
"xpack.fleet.settings.editOutputFlyout.kafkaHeadersTitle": "En-têtes",
"xpack.fleet.settings.editOutputFlyout.kafkaHeaderValueInputLabel": "Valeur",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18886,7 +18886,6 @@
"xpack.fleet.settings.editOutputFlyout.kafkaCompressionTitle": "圧縮",
"xpack.fleet.settings.editOutputFlyout.kafkaConnectionTypeLabel": "接続",
"xpack.fleet.settings.editOutputFlyout.kafkaDynamicTopicHelptext": "リストからトピックを選択してください。トピックがない場合は、カスタムトピックを作成してください。",
"xpack.fleet.settings.editOutputFlyout.kafkaDynamicTopicLabel": "フィールドからのトピック",
"xpack.fleet.settings.editOutputFlyout.kafkaHeaderKeyInputLabel": "キー",
"xpack.fleet.settings.editOutputFlyout.kafkaHeadersTitle": "ヘッダー",
"xpack.fleet.settings.editOutputFlyout.kafkaHeaderValueInputLabel": "値",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18585,7 +18585,6 @@
"xpack.fleet.settings.editOutputFlyout.kafkaCompressionTitle": "压缩",
"xpack.fleet.settings.editOutputFlyout.kafkaConnectionTypeLabel": "连接",
"xpack.fleet.settings.editOutputFlyout.kafkaDynamicTopicHelptext": "从列表中选择主题。如果主题不可用,请创建定制主题。",
"xpack.fleet.settings.editOutputFlyout.kafkaDynamicTopicLabel": "来自字段的主题",
"xpack.fleet.settings.editOutputFlyout.kafkaHeaderKeyInputLabel": "钥匙",
"xpack.fleet.settings.editOutputFlyout.kafkaHeadersTitle": "标题",
"xpack.fleet.settings.editOutputFlyout.kafkaHeaderValueInputLabel": "值",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,13 @@ import {
EuiTitle,
EuiRadioGroup,
EuiComboBox,
EuiLink,
} from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n-react';
import { i18n } from '@kbn/i18n';

import { useStartServices } from '../../../../hooks';

import {
kafkaTopicsType,
KAFKA_DYNAMIC_FIELDS,
Expand All @@ -30,10 +33,12 @@ import type { OutputFormInputsType } from './use_output_form';
export const OutputFormKafkaTopics: React.FunctionComponent<{ inputs: OutputFormInputsType }> = ({
inputs,
}) => {
const { docLinks } = useStartServices();

const dynamicOptions: Array<EuiComboBoxOptionOption<string>> = useMemo(() => {
const options = KAFKA_DYNAMIC_FIELDS.map((option) => ({
label: option,
value: option,
value: `%{[${option}]}`,
}));
return options;
}, []);
Expand Down Expand Up @@ -73,7 +78,17 @@ export const OutputFormKafkaTopics: React.FunctionComponent<{ inputs: OutputForm
label={
<FormattedMessage
id="xpack.fleet.settings.editOutputFlyout.kafkaDynamicTopicLabel"
defaultMessage="Topic from field"
defaultMessage="Topic from field(s). For more info, see our {guideLink}"
values={{
guideLink: (
<EuiLink href={docLinks.links.filebeat.kafkaOutput} target="_blank" external>
<FormattedMessage
id="xpack.fleet.settings.kafkaGuideLink"
defaultMessage="docs."
/>
</EuiLink>
),
}}
/>
}
{...inputs.kafkaDynamicTopicInput.formRowProps}
Expand All @@ -83,7 +98,7 @@ export const OutputFormKafkaTopics: React.FunctionComponent<{ inputs: OutputForm
fullWidth
isClearable={true}
options={dynamicOptions}
customOptionText="Use custom field (not recommended)"
customOptionText="Use custom field"
singleSelection={{ asPlainText: true }}
{...inputs.kafkaDynamicTopicInput.props}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
validateKafkaHosts,
validateKibanaURL,
validateKibanaAPIKey,
validateDynamicKafkaTopics,
} from './output_form_validators';

describe('Output form validation', () => {
Expand Down Expand Up @@ -336,4 +337,26 @@ describe('Output form validation', () => {
]);
});
});

describe('validateDynamicKafkaTopics', () => {
const validTopics = [
{ label: 'field1', value: '%{[field]}' },
{ label: 'field2', value: 'field2' },
{ label: 'field3', value: '%{[field2]}-%{[field3]}' },
];
const invalidBracketTopic = [{ label: '%{[field}', value: '%{[field}' }];
const invalidPercentTopic = [{ label: '{[field]}', value: '{[field]}' }];
it('should work with valid topics', () => {
const res = validateDynamicKafkaTopics(validTopics);
expect(res).toBeUndefined();
});
it("should return error with missing brackets in topic's name", () => {
const res = validateDynamicKafkaTopics(invalidBracketTopic);
expect(res).toEqual(['Topic should have matching amounts of opening and closing brackets']);
});
it("should return error with missing percent sign before opening brackets in topic's name", () => {
const res = validateDynamicKafkaTopics(invalidPercentTopic);
expect(res).toEqual(['Opening brackets should be preceded by a percent sign']);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,15 @@ const toSecretValidator =
return validator(value ?? '');
};

const getAllIndices = (str: string, substring: string): number[] => {
const indices = [];
let index = str.indexOf(substring);
while (index !== -1) {
indices.push(index);
index = str.indexOf(substring, index + 1);
}
return indices;
};
export function validateKafkaHosts(value: string[]) {
const res: Array<{ message: string; index?: number }> = [];
const urlIndexes: { [key: string]: number[] } = {};
Expand Down Expand Up @@ -362,12 +371,30 @@ export function validateKafkaStaticTopic(value: string) {
export function validateDynamicKafkaTopics(value: Array<EuiComboBoxOptionOption<string>>) {
const res = [];
value.forEach((val, idx) => {
if (!val) {
if (!val || !val.value) {
res.push(
i18n.translate('xpack.fleet.settings.outputForm.kafkaTopicFieldRequiredMessage', {
defaultMessage: 'Topic is required',
})
);
} else {
const openingBrackets = getAllIndices(val.value, '{[');
const closingBrackets = getAllIndices(val.value, ']}');
if (openingBrackets.length !== closingBrackets.length) {
res.push(
i18n.translate('xpack.fleet.settings.outputForm.kafkaTopicBracketsError', {
defaultMessage: 'Topic should have matching amounts of opening and closing brackets',
})
);
}
// check for preceding percent sign
if (!openingBrackets.every((item) => val?.value![item - 1] === '%')) {
res.push(
i18n.translate('xpack.fleet.settings.outputForm.kafkaTopicPercentError', {
defaultMessage: 'Opening brackets should be preceded by a percent sign',
})
);
}
}
});

Expand All @@ -378,6 +405,7 @@ export function validateDynamicKafkaTopics(value: Array<EuiComboBoxOptionOption<
})
);
}

if (res.length) {
return res;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -923,7 +923,7 @@ export function useOutputForm(onSucess: () => void, output?: Output, defaultOupu
}
: kafkaTopicsInput.value === kafkaTopicsType.Dynamic && kafkaDynamicTopicInput.value
? {
topic: `%{[${kafkaDynamicTopicInput.value}]}`,
topic: kafkaDynamicTopicInput.value,
}
: {}),
headers: kafkaHeadersInput.value,
Expand Down