mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-08-08 16:49:01 +08:00
Feat: fe: logs pipelines severity parsing processor (#4149)
This commit is contained in:
parent
4644b1c200
commit
112783d618
@ -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;
|
@ -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>
|
||||||
);
|
);
|
||||||
|
@ -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,
|
||||||
|
@ -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;
|
||||||
|
}
|
@ -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;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user