diff --git a/frontend/src/container/PipelinePage/PipelineListsView/AddNewProcessor/ProcessorForm.tsx b/frontend/src/container/PipelinePage/PipelineListsView/AddNewProcessor/ProcessorForm.tsx new file mode 100644 index 0000000000..373f2063ac --- /dev/null +++ b/frontend/src/container/PipelinePage/PipelineListsView/AddNewProcessor/ProcessorForm.tsx @@ -0,0 +1,78 @@ +import { Form, Input, Select } from 'antd'; +import { ModalFooterTitle } from 'container/PipelinePage/styles'; +import { useTranslation } from 'react-i18next'; + +import { formValidationRules } from '../config'; +import { processorFields, ProcessorFormField } from './config'; +import { + Container, + FormWrapper, + PipelineIndexIcon, + StyledSelect, +} from './styles'; + +function ProcessorFieldInput({ + fieldData, +}: ProcessorFieldInputProps): JSX.Element | null { + const { t } = useTranslation('pipeline'); + + // Watch form values so we can evaluate shouldRender on + // conditional fields when form values are updated. + const form = Form.useFormInstance(); + Form.useWatch(fieldData?.dependencies || [], form); + + if (fieldData.shouldRender && !fieldData.shouldRender(form)) { + return null; + } + + return ( + + + {Number(fieldData.id) + 1} + + + {fieldData.fieldName}} + key={fieldData.id} + name={fieldData.name} + initialValue={fieldData.initialValue} + rules={fieldData.rules ? fieldData.rules : formValidationRules} + dependencies={fieldData.dependencies || []} + > + {fieldData?.options ? ( + + {fieldData.options.map(({ value, label }) => ( + + {label} + + ))} + + ) : ( + + )} + + + + ); +} + +interface ProcessorFieldInputProps { + fieldData: ProcessorFormField; +} + +function ProcessorForm({ processorType }: ProcessorFormProps): JSX.Element { + return ( +
+ {processorFields[processorType]?.map((fieldData: ProcessorFormField) => ( + + ))} +
+ ); +} + +interface ProcessorFormProps { + processorType: string; +} + +export default ProcessorForm; diff --git a/frontend/src/container/PipelinePage/PipelineListsView/AddNewProcessor/config.ts b/frontend/src/container/PipelinePage/PipelineListsView/AddNewProcessor/config.ts index d6337e2b5c..9dc4f1d1f8 100644 --- a/frontend/src/container/PipelinePage/PipelineListsView/AddNewProcessor/config.ts +++ b/frontend/src/container/PipelinePage/PipelineListsView/AddNewProcessor/config.ts @@ -1,5 +1,7 @@ +import { FormInstance } from 'antd'; import { Rule, RuleRender } from 'antd/es/form'; import { NamePath } from 'antd/es/form/interface'; +import { ProcessorData } from 'types/api/pipeline/def'; type ProcessorType = { key: string; @@ -14,6 +16,7 @@ export const processorTypes: Array = [ { key: 'regex_parser', value: 'regex_parser', label: 'Regex' }, { key: 'json_parser', value: 'json_parser', label: 'Json Parser' }, { key: 'trace_parser', value: 'trace_parser', label: 'Trace Parser' }, + { key: 'time_parser', value: 'time_parser', label: 'Timestamp Parser' }, { key: 'add', value: 'add', label: 'Add' }, { key: 'remove', value: 'remove', label: 'Remove' }, // { key: 'retain', value: 'retain', label: 'Retain' }, @Chintan - Commented as per Nitya's suggestion @@ -23,6 +26,11 @@ export const processorTypes: Array = [ export const DEFAULT_PROCESSOR_TYPE = processorTypes[0].value; +export type ProcessorFieldOption = { + label: string; + value: string; +}; + export type ProcessorFormField = { id: number; fieldName: string; @@ -31,6 +39,12 @@ export type ProcessorFormField = { rules?: Array; initialValue?: string; dependencies?: Array; + options?: Array; + shouldRender?: (form: FormInstance) => boolean; + onFormValuesChanged?: ( + changedValues: ProcessorData, + form: FormInstance, + ) => void; }; const traceParserFieldValidator: RuleRender = (form) => ({ @@ -206,6 +220,103 @@ export const processorFields: { [key: string]: Array } = { ], }, ], + time_parser: [ + { + id: 1, + fieldName: 'Name of Timestamp Parsing Processor', + placeholder: 'processor_name_placeholder', + name: 'name', + }, + { + id: 2, + fieldName: 'Parse Timestamp Value From', + placeholder: 'processor_parsefrom_placeholder', + name: 'parse_from', + initialValue: 'attributes.timestamp', + }, + { + id: 3, + fieldName: 'Timestamp Format Type', + placeholder: '', + name: 'layout_type', + initialValue: 'strptime', + options: [ + { + label: 'Unix Epoch', + value: 'epoch', + }, + { + label: 'strptime Format', + value: 'strptime', + }, + ], + onFormValuesChanged: ( + changedValues: ProcessorData, + form: FormInstance, + ): void => { + if (changedValues?.layout_type) { + const newLayoutValue = + changedValues.layout_type === 'strptime' ? '%Y-%m-%dT%H:%M:%S.%f%z' : 's'; + + form.setFieldValue('layout', newLayoutValue); + } + }, + }, + { + id: 4, + fieldName: 'Epoch Format', + placeholder: '', + name: 'layout', + dependencies: ['layout_type'], + shouldRender: (form: FormInstance): boolean => { + const layoutType = form.getFieldValue('layout_type'); + return layoutType === 'epoch'; + }, + initialValue: 's', + options: [ + { + label: 'seconds', + value: 's', + }, + { + label: 'milliseconds', + value: 'ms', + }, + { + label: 'microseconds', + value: 'us', + }, + { + label: 'nanoseconds', + value: 'ns', + }, + { + label: 'seconds.milliseconds (eg: 1136214245.123)', + value: 's.ms', + }, + { + label: 'seconds.microseconds (eg: 1136214245.123456)', + value: 's.us', + }, + { + label: 'seconds.nanoseconds (eg: 1136214245.123456789)', + value: 's.ns', + }, + ], + }, + { + id: 4, + fieldName: 'Timestamp Format', + placeholder: 'strptime directives based format. Eg: %Y-%m-%dT%H:%M:%S.%f%z', + name: 'layout', + dependencies: ['layout_type'], + shouldRender: (form: FormInstance): boolean => { + const layoutType = form.getFieldValue('layout_type'); + return layoutType === 'strptime'; + }, + initialValue: '%Y-%m-%dT%H:%M:%S.%f%z', + }, + ], retain: [ { id: 1, diff --git a/frontend/src/container/PipelinePage/PipelineListsView/AddNewProcessor/index.tsx b/frontend/src/container/PipelinePage/PipelineListsView/AddNewProcessor/index.tsx index 8281655952..661fc4043a 100644 --- a/frontend/src/container/PipelinePage/PipelineListsView/AddNewProcessor/index.tsx +++ b/frontend/src/container/PipelinePage/PipelineListsView/AddNewProcessor/index.tsx @@ -11,9 +11,9 @@ import { v4 } from 'uuid'; import { ModalButtonWrapper, ModalTitle } from '../styles'; import { getEditedDataSource, getRecordIndex } from '../utils'; -import { DEFAULT_PROCESSOR_TYPE } from './config'; +import { DEFAULT_PROCESSOR_TYPE, processorFields } from './config'; import TypeSelect from './FormFields/TypeSelect'; -import { renderProcessorForm } from './utils'; +import ProcessorForm from './ProcessorForm'; function AddNewProcessor({ isActionType, @@ -141,6 +141,17 @@ function AddNewProcessor({ const isOpen = useMemo(() => isEdit || isAdd, [isAdd, isEdit]); + const onFormValuesChanged = useCallback( + (changedValues: ProcessorData): void => { + processorFields[processorType].forEach((field) => { + if (field.onFormValuesChanged) { + field.onFormValuesChanged(changedValues, form); + } + }); + }, + [form, processorType], + ); + return ( {modalTitle}} @@ -157,9 +168,10 @@ function AddNewProcessor({ onFinish={onFinish} autoComplete="off" form={form} + onValuesChange={onFormValuesChanged} > - {renderProcessorForm(processorType)} + diff --git a/frontend/src/container/PipelinePage/PipelineListsView/AddNewProcessor/utils.tsx b/frontend/src/container/PipelinePage/PipelineListsView/AddNewProcessor/utils.tsx deleted file mode 100644 index 1534b704ea..0000000000 --- a/frontend/src/container/PipelinePage/PipelineListsView/AddNewProcessor/utils.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import { processorFields, ProcessorFormField } from './config'; -import NameInput from './FormFields/NameInput'; - -export const renderProcessorForm = ( - processorType: string, -): Array => - processorFields[processorType]?.map((fieldData: ProcessorFormField) => ( - - )); diff --git a/frontend/src/container/PipelinePage/PipelineListsView/Preview/hooks/usePipelinePreview.ts b/frontend/src/container/PipelinePage/PipelineListsView/Preview/hooks/usePipelinePreview.ts index ad875cdcd3..7e739fdf52 100644 --- a/frontend/src/container/PipelinePage/PipelineListsView/Preview/hooks/usePipelinePreview.ts +++ b/frontend/src/container/PipelinePage/PipelineListsView/Preview/hooks/usePipelinePreview.ts @@ -27,7 +27,8 @@ const usePipelinePreview = ({ // ILog allows both number and string while the API needs a number const simulationInput = inputLogs.map((l) => ({ ...l, - timestamp: new Date(l.timestamp).getTime(), + // log timestamps in query service API are unix nanos + timestamp: new Date(l.timestamp).getTime() * 10 ** 6, })); const response = useQuery({ @@ -42,9 +43,15 @@ const usePipelinePreview = ({ const { isFetching, isError, data, error } = response; + const outputLogs = (data?.logs || []).map((l: ILog) => ({ + ...l, + // log timestamps in query service API are unix nanos + timestamp: (l.timestamp as number) / 10 ** 6, + })); + return { isLoading: isFetching, - outputLogs: data?.logs || [], + outputLogs, isError, errorMsg: error?.response?.data?.error || '', }; diff --git a/frontend/src/types/api/pipeline/def.ts b/frontend/src/types/api/pipeline/def.ts index 5462dfe611..3aef70f1fd 100644 --- a/frontend/src/types/api/pipeline/def.ts +++ b/frontend/src/types/api/pipeline/def.ts @@ -27,6 +27,10 @@ export interface ProcessorData { trace_flags?: { parse_from: string; }; + + // time parser fields + layout_type?: string; + layout?: string; } export interface PipelineData {