Feat: fe: logs pipelines severity parsing processor (#4149)

This commit is contained in:
Raj Kamal Singh 2023-12-05 18:30:46 +05:30 committed by GitHub
parent 4644b1c200
commit 112783d618
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 200 additions and 27 deletions

View File

@ -0,0 +1,25 @@
import { Input, InputProps } from 'antd';
import { ChangeEventHandler, useState } from 'react';
function CSVInput({ value, onChange, ...otherProps }: InputProps): JSX.Element {
const [inputValue, setInputValue] = useState(
((value as string[]) || []).join(', '),
);
const onChangeHandler = (onChange as unknown) as (v: string[]) => void;
const onInputChange: ChangeEventHandler<HTMLInputElement> = (e) => {
const newValue = e.target.value;
setInputValue(newValue);
if (onChangeHandler) {
const splitValues = newValue.split(',').map((v) => v.trim());
onChangeHandler(splitValues);
}
};
// eslint-disable-next-line react/jsx-props-no-spreading
return <Input value={inputValue} onChange={onInputChange} {...otherProps} />;
}
export default CSVInput;

View File

@ -1,15 +1,13 @@
import './styles.scss';
import { Form, Input, Select } from 'antd'; import { Form, Input, Select } from 'antd';
import { ModalFooterTitle } from 'container/PipelinePage/styles'; import { ModalFooterTitle } from 'container/PipelinePage/styles';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { formValidationRules } from '../config'; import { formValidationRules } from '../config';
import { processorFields, ProcessorFormField } from './config'; import { processorFields, ProcessorFormField } from './config';
import { import CSVInput from './FormFields/CSVInput';
Container, import { FormWrapper, PipelineIndexIcon, StyledSelect } from './styles';
FormWrapper,
PipelineIndexIcon,
StyledSelect,
} from './styles';
function ProcessorFieldInput({ function ProcessorFieldInput({
fieldData, fieldData,
@ -25,35 +23,63 @@ function ProcessorFieldInput({
return null; return null;
} }
// Do not render display elements for hidden inputs.
if (fieldData?.hidden) {
return (
<Form.Item
name={fieldData.name}
initialValue={fieldData.initialValue}
dependencies={fieldData.dependencies || []}
style={{ display: 'none' }}
>
<Input type="hidden" />
</Form.Item>
);
}
let inputField;
if (fieldData?.options) {
inputField = (
<StyledSelect>
{fieldData.options.map(({ value, label }) => (
<Select.Option key={value + label} value={value}>
{label}
</Select.Option>
))}
</StyledSelect>
);
} else if (Array.isArray(fieldData?.initialValue)) {
inputField = <CSVInput placeholder={t(fieldData.placeholder)} />;
} else {
inputField = <Input placeholder={t(fieldData.placeholder)} />;
}
return ( return (
<Container> <div
<PipelineIndexIcon size="small"> className={
{Number(fieldData.id) + 1} fieldData?.compact
</PipelineIndexIcon> ? 'compact-processor-field-container'
: 'processor-field-container'
}
>
{!fieldData?.compact && (
<PipelineIndexIcon size="small">
{Number(fieldData.id) + 1}
</PipelineIndexIcon>
)}
<FormWrapper> <FormWrapper>
<Form.Item <Form.Item
required={false} required={false}
label={<ModalFooterTitle>{fieldData.fieldName}</ModalFooterTitle>} label={<ModalFooterTitle>{fieldData.fieldName}</ModalFooterTitle>}
key={fieldData.id}
name={fieldData.name} name={fieldData.name}
initialValue={fieldData.initialValue} initialValue={fieldData.initialValue}
rules={fieldData.rules ? fieldData.rules : formValidationRules} rules={fieldData.rules ? fieldData.rules : formValidationRules}
dependencies={fieldData.dependencies || []} dependencies={fieldData.dependencies || []}
> >
{fieldData?.options ? ( {inputField}
<StyledSelect>
{fieldData.options.map(({ value, label }) => (
<Select.Option key={value + label} value={value}>
{label}
</Select.Option>
))}
</StyledSelect>
) : (
<Input placeholder={t(fieldData.placeholder)} />
)}
</Form.Item> </Form.Item>
</FormWrapper> </FormWrapper>
</Container> </div>
); );
} }
@ -63,9 +89,12 @@ interface ProcessorFieldInputProps {
function ProcessorForm({ processorType }: ProcessorFormProps): JSX.Element { function ProcessorForm({ processorType }: ProcessorFormProps): JSX.Element {
return ( return (
<div> <div className="processor-form-container">
{processorFields[processorType]?.map((fieldData: ProcessorFormField) => ( {processorFields[processorType]?.map((fieldData: ProcessorFormField) => (
<ProcessorFieldInput key={fieldData.id} fieldData={fieldData} /> <ProcessorFieldInput
key={fieldData.name + String(fieldData.initialValue)}
fieldData={fieldData}
/>
))} ))}
</div> </div>
); );

View File

@ -17,6 +17,7 @@ export const processorTypes: Array<ProcessorType> = [
{ key: 'json_parser', value: 'json_parser', label: 'Json Parser' }, { key: 'json_parser', value: 'json_parser', label: 'Json Parser' },
{ key: 'trace_parser', value: 'trace_parser', label: 'Trace Parser' }, { key: 'trace_parser', value: 'trace_parser', label: 'Trace Parser' },
{ key: 'time_parser', value: 'time_parser', label: 'Timestamp Parser' }, { key: 'time_parser', value: 'time_parser', label: 'Timestamp Parser' },
{ key: 'severity_parser', value: 'severity_parser', label: 'Severity Parser' },
{ key: 'add', value: 'add', label: 'Add' }, { key: 'add', value: 'add', label: 'Add' },
{ key: 'remove', value: 'remove', label: 'Remove' }, { key: 'remove', value: 'remove', label: 'Remove' },
// { key: 'retain', value: 'retain', label: 'Retain' }, @Chintan - Commented as per Nitya's suggestion // { key: 'retain', value: 'retain', label: 'Retain' }, @Chintan - Commented as per Nitya's suggestion
@ -31,13 +32,15 @@ export type ProcessorFieldOption = {
value: string; value: string;
}; };
// TODO(Raj): Refactor Processor Form code after putting e2e UI tests in place.
export type ProcessorFormField = { export type ProcessorFormField = {
id: number; id: number;
fieldName: string; fieldName: string;
placeholder: string; placeholder: string;
name: string | NamePath; name: string | NamePath;
rules?: Array<Rule>; rules?: Array<Rule>;
initialValue?: string; hidden?: boolean;
initialValue?: boolean | string | Array<string>;
dependencies?: Array<string | NamePath>; dependencies?: Array<string | NamePath>;
options?: Array<ProcessorFieldOption>; options?: Array<ProcessorFieldOption>;
shouldRender?: (form: FormInstance) => boolean; shouldRender?: (form: FormInstance) => boolean;
@ -45,6 +48,10 @@ export type ProcessorFormField = {
changedValues: ProcessorData, changedValues: ProcessorData,
form: FormInstance, form: FormInstance,
) => void; ) => void;
// Should this field have its own row or should it
// be packed with other compact fields.
compact?: boolean;
}; };
const traceParserFieldValidator: RuleRender = (form) => ({ const traceParserFieldValidator: RuleRender = (form) => ({
@ -317,6 +324,85 @@ export const processorFields: { [key: string]: Array<ProcessorFormField> } = {
initialValue: '%Y-%m-%dT%H:%M:%S.%f%z', initialValue: '%Y-%m-%dT%H:%M:%S.%f%z',
}, },
], ],
severity_parser: [
{
id: 1,
fieldName: 'Name of Severity Parsing Processor',
placeholder: 'processor_name_placeholder',
name: 'name',
},
{
id: 2,
fieldName: 'Parse Severity Value From',
placeholder: 'processor_parsefrom_placeholder',
name: 'parse_from',
initialValue: 'attributes.logLevel',
},
{
id: 3,
fieldName: 'Values for level TRACE',
placeholder: 'Specify comma separated values. Eg: trace, 0',
name: ['mapping', 'trace'],
rules: [],
initialValue: ['trace'],
compact: true,
},
{
id: 4,
fieldName: 'Values for level DEBUG',
placeholder: 'Specify comma separated values. Eg: debug, 2xx',
name: ['mapping', 'debug'],
rules: [],
initialValue: ['debug'],
compact: true,
},
{
id: 5,
fieldName: 'Values for level INFO',
placeholder: 'Specify comma separated values. Eg: info, 3xx',
name: ['mapping', 'info'],
rules: [],
initialValue: ['info'],
compact: true,
},
{
id: 6,
fieldName: 'Values for level WARN',
placeholder: 'Specify comma separated values. Eg: warning, 4xx',
name: ['mapping', 'warn'],
rules: [],
initialValue: ['warn'],
compact: true,
},
{
id: 7,
fieldName: 'Values for level ERROR',
placeholder: 'Specify comma separated values. Eg: error, 5xx',
name: ['mapping', 'error'],
rules: [],
initialValue: ['error'],
compact: true,
},
{
id: 8,
fieldName: 'Values for level FATAL',
placeholder: 'Specify comma separated values. Eg: fatal, panic',
name: ['mapping', 'fatal'],
rules: [],
initialValue: ['fatal'],
compact: true,
},
{
id: 9,
fieldName: 'Override Severity Text',
placeholder:
'Should the parsed severity set both severity and severityText?',
name: ['overwrite_text'],
rules: [],
initialValue: true,
hidden: true,
},
],
retain: [ retain: [
{ {
id: 1, id: 1,

View File

@ -0,0 +1,27 @@
.processor-form-container {
position: relative;
width: 100%;
display: flex;
flex-wrap: wrap
}
.processor-field-container {
display: flex;
flex-direction: row;
align-items: flex-start;
padding: 0rem;
gap: 1rem;
width: 100%;
}
.compact-processor-field-container {
display: flex;
flex-direction: row;
align-items: flex-start;
padding: 0rem;
min-width: 40%;
flex-grow: 1;
margin-left: 2.5rem;
}

View File

@ -5,7 +5,9 @@ import Spinner from 'components/Spinner';
import ChangeHistory from 'container/PipelinePage/Layouts/ChangeHistory'; import ChangeHistory from 'container/PipelinePage/Layouts/ChangeHistory';
import PipelinePage from 'container/PipelinePage/Layouts/Pipeline'; import PipelinePage from 'container/PipelinePage/Layouts/Pipeline';
import { useNotifications } from 'hooks/useNotifications'; import { useNotifications } from 'hooks/useNotifications';
import ErrorBoundaryFallback from 'pages/ErrorBoundaryFallback/ErrorBoundaryFallback';
import { useEffect, useMemo } from 'react'; import { useEffect, useMemo } from 'react';
import { ErrorBoundary } from 'react-error-boundary';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useQuery } from 'react-query'; import { useQuery } from 'react-query';
import { SuccessResponse } from 'types/api'; import { SuccessResponse } from 'types/api';
@ -77,7 +79,11 @@ function Pipelines(): JSX.Element {
return <Spinner height="75vh" tip="Loading Pipelines..." />; return <Spinner height="75vh" tip="Loading Pipelines..." />;
} }
return <Tabs defaultActiveKey="pipelines" items={tabItems} />; return (
<ErrorBoundary FallbackComponent={ErrorBoundaryFallback}>
<Tabs defaultActiveKey="pipelines" items={tabItems} />;
</ErrorBoundary>
);
} }
export default Pipelines; export default Pipelines;