mirror of
https://github.com/SigNoz/signoz.git
synced 2025-12-18 16:07:10 +00:00
feat: add support for JSON flattening in pipeline processor create and edit (#8331)
* feat: add support for JSON flattening in pipeline processor create and edit * chore: add fallback value for mapping state * feat: improve the UI of json flattening form and show path_prefix if enable_path is true * fix: improve the state management * chore: get enablePaths state using useWatch * chore: json flattening tests * chore: improve the test descriptions * fix: update snapshot and adjust the existing failing test * chore: overall improvements * fix: update the JSON flattening keys map * fix: destroy the new processor modal on closing to fix the unintended persistent mapping state * chore: log event on saving pipeline if json_parser processors get modified * chore: fix the alignment of json flattening switch * chore: overall improvement * refactor: improve the mapping comparison by using lodash's isEqual * chore: update the snapshot * refactor: improve the pipeline json_parser processor filtering logic --------- Co-authored-by: Aditya Singh <adityasinghssj1@gmail.com> Co-authored-by: Nityananda Gohain <nityanandagohain@gmail.com>
This commit is contained in:
parent
893b11c4a0
commit
ddb08b3883
@ -0,0 +1,6 @@
|
||||
.json-flattening-form {
|
||||
margin-top: 16px;
|
||||
&__item {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,110 @@
|
||||
import './JsonFlattening.styles.scss';
|
||||
|
||||
import { InfoCircleOutlined } from '@ant-design/icons';
|
||||
import { Form, Input, Space, Switch, Tooltip } from 'antd';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { ProcessorData } from 'types/api/pipeline/def';
|
||||
|
||||
import { PREDEFINED_MAPPING } from '../config';
|
||||
import KeyValueList from './KeyValueList';
|
||||
|
||||
interface JsonFlatteningProps {
|
||||
selectedProcessorData?: ProcessorData;
|
||||
isAdd: boolean;
|
||||
}
|
||||
|
||||
function JsonFlattening({
|
||||
selectedProcessorData,
|
||||
isAdd,
|
||||
}: JsonFlatteningProps): JSX.Element | null {
|
||||
const form = Form.useFormInstance();
|
||||
const mappingValue = selectedProcessorData?.mapping || {};
|
||||
const enableFlattening = Form.useWatch('enable_flattening', form);
|
||||
const enablePaths = Form.useWatch('enable_paths', form);
|
||||
|
||||
const [enableMapping, setEnableMapping] = useState(
|
||||
!!mappingValue && Object.keys(mappingValue).length > 0,
|
||||
);
|
||||
|
||||
const selectedMapping = selectedProcessorData?.mapping;
|
||||
useEffect(() => {
|
||||
if (!enableMapping) {
|
||||
form.setFieldsValue({ mapping: undefined });
|
||||
} else if (form.getFieldValue('mapping') === undefined) {
|
||||
form.setFieldsValue({
|
||||
mapping: selectedMapping || PREDEFINED_MAPPING,
|
||||
});
|
||||
}
|
||||
}, [enableMapping, form, selectedMapping]);
|
||||
|
||||
const handleEnableMappingChange = (checked: boolean): void => {
|
||||
setEnableMapping(checked);
|
||||
};
|
||||
|
||||
const handleEnablePathsChange = (checked: boolean): void => {
|
||||
form.setFieldValue('enable_paths', checked);
|
||||
};
|
||||
|
||||
if (!enableFlattening) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="json-flattening-form">
|
||||
<Form.Item
|
||||
className="json-flattening-form__item"
|
||||
name="enable_paths"
|
||||
valuePropName="checked"
|
||||
initialValue={isAdd ? true : selectedProcessorData?.enable_paths}
|
||||
>
|
||||
<Space>
|
||||
<Switch
|
||||
size="small"
|
||||
checked={enablePaths}
|
||||
onChange={handleEnablePathsChange}
|
||||
/>
|
||||
Enable Paths
|
||||
</Space>
|
||||
</Form.Item>
|
||||
|
||||
{enablePaths && (
|
||||
<Form.Item
|
||||
name="path_prefix"
|
||||
label="Path Prefix"
|
||||
initialValue={selectedProcessorData?.path_prefix}
|
||||
>
|
||||
<Input placeholder="Path Prefix" />
|
||||
</Form.Item>
|
||||
)}
|
||||
|
||||
<Form.Item className="json-flattening-form__item">
|
||||
<Space>
|
||||
<Switch
|
||||
size="small"
|
||||
checked={enableMapping}
|
||||
onChange={handleEnableMappingChange}
|
||||
/>
|
||||
Enable Mapping
|
||||
<Tooltip title="The order of filled keys will determine the priority of keys i.e. earlier keys have higher precedence">
|
||||
<InfoCircleOutlined />
|
||||
</Tooltip>
|
||||
</Space>
|
||||
</Form.Item>
|
||||
|
||||
{enableMapping && (
|
||||
<Form.Item
|
||||
name="mapping"
|
||||
initialValue={selectedProcessorData?.mapping || PREDEFINED_MAPPING}
|
||||
>
|
||||
<KeyValueList />
|
||||
</Form.Item>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
JsonFlattening.defaultProps = {
|
||||
selectedProcessorData: undefined,
|
||||
};
|
||||
|
||||
export default JsonFlattening;
|
||||
@ -0,0 +1,47 @@
|
||||
import { Form, Select } from 'antd';
|
||||
|
||||
import { PREDEFINED_MAPPING } from '../config';
|
||||
|
||||
interface KeyValueListProps {
|
||||
value?: Record<string, string[]>;
|
||||
onChange?: (value: Record<string, string[]>) => void;
|
||||
}
|
||||
|
||||
function KeyValueList({
|
||||
value = PREDEFINED_MAPPING,
|
||||
onChange,
|
||||
}: KeyValueListProps): JSX.Element {
|
||||
const handleValueChange = (key: string, newValue: string[]): void => {
|
||||
const newMapping = {
|
||||
...value,
|
||||
[key]: newValue,
|
||||
};
|
||||
if (onChange) {
|
||||
onChange(newMapping);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
{Object.keys(value).map((key) => (
|
||||
<Form.Item key={key} label={key}>
|
||||
<Select
|
||||
mode="tags"
|
||||
style={{ width: '100%' }}
|
||||
placeholder="Values"
|
||||
onChange={(newValue: string[]): void => handleValueChange(key, newValue)}
|
||||
value={value[key]}
|
||||
tokenSeparators={[',']}
|
||||
/>
|
||||
</Form.Item>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
KeyValueList.defaultProps = {
|
||||
value: PREDEFINED_MAPPING,
|
||||
onChange: (): void => {},
|
||||
};
|
||||
|
||||
export default KeyValueList;
|
||||
@ -1,16 +1,21 @@
|
||||
import './styles.scss';
|
||||
|
||||
import { Form, Input, Select } from 'antd';
|
||||
import { Form, Input, Select, Space, Switch } from 'antd';
|
||||
import { ModalFooterTitle } from 'container/PipelinePage/styles';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { ProcessorData } from 'types/api/pipeline/def';
|
||||
|
||||
import { formValidationRules } from '../config';
|
||||
import { processorFields, ProcessorFormField } from './config';
|
||||
import CSVInput from './FormFields/CSVInput';
|
||||
import JsonFlattening from './FormFields/JsonFlattening';
|
||||
import { FormWrapper, PipelineIndexIcon, StyledSelect } from './styles';
|
||||
|
||||
// eslint-disable-next-line sonarjs/cognitive-complexity
|
||||
function ProcessorFieldInput({
|
||||
fieldData,
|
||||
selectedProcessorData,
|
||||
isAdd,
|
||||
}: ProcessorFieldInputProps): JSX.Element | null {
|
||||
const { t } = useTranslation('pipeline');
|
||||
|
||||
@ -50,6 +55,13 @@ function ProcessorFieldInput({
|
||||
);
|
||||
} else if (Array.isArray(fieldData?.initialValue)) {
|
||||
inputField = <CSVInput placeholder={t(fieldData.placeholder)} />;
|
||||
} else if (fieldData?.name === 'enable_flattening') {
|
||||
inputField = (
|
||||
<JsonFlattening
|
||||
selectedProcessorData={selectedProcessorData}
|
||||
isAdd={isAdd}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
inputField = <Input placeholder={t(fieldData.placeholder)} />;
|
||||
}
|
||||
@ -68,40 +80,82 @@ function ProcessorFieldInput({
|
||||
</PipelineIndexIcon>
|
||||
)}
|
||||
<FormWrapper>
|
||||
<Form.Item
|
||||
required={false}
|
||||
label={<ModalFooterTitle>{fieldData.fieldName}</ModalFooterTitle>}
|
||||
name={fieldData.name}
|
||||
initialValue={fieldData.initialValue}
|
||||
rules={fieldData.rules ? fieldData.rules : formValidationRules}
|
||||
dependencies={fieldData.dependencies || []}
|
||||
>
|
||||
{inputField}
|
||||
</Form.Item>
|
||||
{fieldData.name === 'enable_flattening' ? (
|
||||
<Form.Item
|
||||
required={false}
|
||||
name="enable_flattening"
|
||||
initialValue={
|
||||
selectedProcessorData?.enable_flattening ?? fieldData.initialValue
|
||||
}
|
||||
valuePropName="checked"
|
||||
className="enable-flattening-switch"
|
||||
>
|
||||
<Space>
|
||||
<Switch
|
||||
size="small"
|
||||
checked={form.getFieldValue('enable_flattening')}
|
||||
onChange={(checked: boolean): void => {
|
||||
form.setFieldValue('enable_flattening', checked);
|
||||
}}
|
||||
/>
|
||||
<ModalFooterTitle>{fieldData.fieldName}</ModalFooterTitle>
|
||||
</Space>
|
||||
</Form.Item>
|
||||
) : (
|
||||
<Form.Item
|
||||
required={false}
|
||||
label={<ModalFooterTitle>{fieldData.fieldName}</ModalFooterTitle>}
|
||||
name={fieldData.name}
|
||||
initialValue={fieldData.initialValue}
|
||||
rules={fieldData.rules ? fieldData.rules : formValidationRules}
|
||||
dependencies={fieldData.dependencies || []}
|
||||
>
|
||||
{inputField}
|
||||
</Form.Item>
|
||||
)}
|
||||
{fieldData.name === 'enable_flattening' && inputField}
|
||||
</FormWrapper>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
ProcessorFieldInput.defaultProps = {
|
||||
selectedProcessorData: undefined,
|
||||
};
|
||||
|
||||
interface ProcessorFieldInputProps {
|
||||
fieldData: ProcessorFormField;
|
||||
selectedProcessorData?: ProcessorData;
|
||||
isAdd: boolean;
|
||||
}
|
||||
|
||||
function ProcessorForm({ processorType }: ProcessorFormProps): JSX.Element {
|
||||
function ProcessorForm({
|
||||
processorType,
|
||||
selectedProcessorData,
|
||||
isAdd,
|
||||
}: ProcessorFormProps): JSX.Element {
|
||||
return (
|
||||
<div className="processor-form-container">
|
||||
{processorFields[processorType]?.map((fieldData: ProcessorFormField) => (
|
||||
<ProcessorFieldInput
|
||||
key={fieldData.name + String(fieldData.initialValue)}
|
||||
fieldData={fieldData}
|
||||
selectedProcessorData={selectedProcessorData}
|
||||
isAdd={isAdd}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
ProcessorForm.defaultProps = {
|
||||
selectedProcessorData: undefined,
|
||||
};
|
||||
|
||||
interface ProcessorFormProps {
|
||||
processorType: string;
|
||||
selectedProcessorData?: ProcessorData;
|
||||
isAdd: boolean;
|
||||
}
|
||||
|
||||
export default ProcessorForm;
|
||||
|
||||
@ -136,6 +136,13 @@ export const processorFields: { [key: string]: Array<ProcessorFormField> } = {
|
||||
name: 'parse_to',
|
||||
initialValue: 'attributes',
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
fieldName: 'Enable Flattening',
|
||||
placeholder: '',
|
||||
name: 'enable_flattening',
|
||||
initialValue: false,
|
||||
},
|
||||
],
|
||||
regex_parser: [
|
||||
{
|
||||
@ -458,3 +465,14 @@ export const processorFields: { [key: string]: Array<ProcessorFormField> } = {
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const PREDEFINED_MAPPING = {
|
||||
environment: ['service.env', 'environment', 'env'],
|
||||
host: ['host', 'hostname', 'host.name'],
|
||||
message: ['message', 'msg', 'log'],
|
||||
service: ['service', 'appname'],
|
||||
severity: ['status', 'severity', 'level'],
|
||||
span_id: ['span_id', 'span.id'],
|
||||
trace_flags: ['flags'],
|
||||
trace_id: ['trace_id', 'trace.id'],
|
||||
};
|
||||
|
||||
@ -160,6 +160,7 @@ function AddNewProcessor({
|
||||
width={800}
|
||||
footer={null}
|
||||
onCancel={onCancelModal}
|
||||
destroyOnClose
|
||||
>
|
||||
<Divider plain />
|
||||
<Form
|
||||
@ -171,7 +172,11 @@ function AddNewProcessor({
|
||||
onValuesChange={onFormValuesChanged}
|
||||
>
|
||||
<TypeSelect value={processorType} onChange={handleProcessorType} />
|
||||
<ProcessorForm processorType={processorType} />
|
||||
<ProcessorForm
|
||||
processorType={processorType}
|
||||
selectedProcessorData={selectedProcessorData}
|
||||
isAdd={isAdd}
|
||||
/>
|
||||
<Divider plain />
|
||||
<Form.Item>
|
||||
<ModalButtonWrapper>
|
||||
|
||||
@ -24,3 +24,7 @@
|
||||
flex-grow: 1;
|
||||
margin-left: 2.5rem;
|
||||
}
|
||||
|
||||
.enable-flattening-switch .ant-form-item-control-input {
|
||||
min-height: unset !important;
|
||||
}
|
||||
|
||||
@ -219,21 +219,6 @@ function PipelineExpandView({
|
||||
moveRow: moveProcessorRow,
|
||||
} as React.HTMLAttributes<unknown>);
|
||||
|
||||
const processorData = useMemo(
|
||||
() =>
|
||||
expandedPipelineData?.config &&
|
||||
expandedPipelineData?.config.map(
|
||||
(item: ProcessorData): ProcessorData => ({
|
||||
id: item.id,
|
||||
orderId: item.orderId,
|
||||
type: item.type,
|
||||
name: item.name,
|
||||
enabled: item.enabled,
|
||||
}),
|
||||
),
|
||||
[expandedPipelineData],
|
||||
);
|
||||
|
||||
const getLocales = (): TableLocale => ({
|
||||
emptyText: <span />,
|
||||
});
|
||||
@ -248,7 +233,7 @@ function PipelineExpandView({
|
||||
rowKey="id"
|
||||
size="small"
|
||||
components={tableComponents}
|
||||
dataSource={processorData}
|
||||
dataSource={expandedPipelineData?.config}
|
||||
pagination={false}
|
||||
onRow={onRowHandler}
|
||||
footer={footer}
|
||||
|
||||
@ -6,7 +6,7 @@ import { ExpandableConfig } from 'antd/es/table/interface';
|
||||
import logEvent from 'api/common/logEvent';
|
||||
import savePipeline from 'api/pipeline/post';
|
||||
import { useNotifications } from 'hooks/useNotifications';
|
||||
import { isUndefined } from 'lodash-es';
|
||||
import { isEqual, isUndefined } from 'lodash-es';
|
||||
import cloneDeep from 'lodash-es/cloneDeep';
|
||||
import React, {
|
||||
useCallback,
|
||||
@ -75,7 +75,7 @@ function PipelinesListEmptyState(): JSX.Element {
|
||||
<a
|
||||
href="https://signoz.io/docs/logs-pipelines/introduction/?utm_source=product&utm_medium=pipelines-tab"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
here
|
||||
</a>
|
||||
@ -407,15 +407,46 @@ function PipelineListsView({
|
||||
return undefined;
|
||||
}, [isEditingActionMode, addNewPipelineHandler, t]);
|
||||
|
||||
const getModifiedJsonFlatteningConfigs = useCallback(
|
||||
() =>
|
||||
currPipelineData.flatMap((pipeline) => {
|
||||
const prevPipeline = prevPipelineData.find((p) => p.name === pipeline.name);
|
||||
|
||||
return (pipeline.config || [])
|
||||
.filter((processor) => {
|
||||
const prevProcessor = prevPipeline?.config?.find(
|
||||
(p) => p.name === processor.name,
|
||||
);
|
||||
return (
|
||||
processor.type === 'json_parser' &&
|
||||
(!prevProcessor ||
|
||||
prevProcessor.enable_flattening !== processor.enable_flattening ||
|
||||
prevProcessor.enable_paths !== processor.enable_paths ||
|
||||
prevProcessor.path_prefix !== processor.path_prefix ||
|
||||
!isEqual(prevProcessor.mapping, processor.mapping))
|
||||
);
|
||||
})
|
||||
.map((processor) => ({
|
||||
enableFlattening: !!processor.enable_flattening,
|
||||
enablePaths: !!processor.enable_paths,
|
||||
pathPrefix: processor.path_prefix || '',
|
||||
mapping: processor.mapping || {},
|
||||
}));
|
||||
}),
|
||||
[currPipelineData, prevPipelineData],
|
||||
);
|
||||
|
||||
const onSaveConfigurationHandler = useCallback(async () => {
|
||||
const modifiedPipelineData = currPipelineData.map((item: PipelineData) => {
|
||||
const pipelineData = { ...item };
|
||||
delete pipelineData?.id;
|
||||
return pipelineData;
|
||||
});
|
||||
|
||||
const response = await savePipeline({
|
||||
data: { pipelines: modifiedPipelineData },
|
||||
});
|
||||
|
||||
if (response.statusCode === 200) {
|
||||
refetchPipelineLists();
|
||||
setActionMode(ActionMode.Viewing);
|
||||
@ -425,6 +456,15 @@ function PipelineListsView({
|
||||
setCurrPipelineData(pipelinesInDB);
|
||||
setPrevPipelineData(pipelinesInDB);
|
||||
|
||||
// Log modified JSON flattening configurations
|
||||
const modifiedConfigs = getModifiedJsonFlatteningConfigs();
|
||||
if (modifiedConfigs.length > 0) {
|
||||
logEvent('Logs pipeline: Saved JSON Flattening Configuration', {
|
||||
count: modifiedConfigs.length,
|
||||
configurations: modifiedConfigs,
|
||||
});
|
||||
}
|
||||
|
||||
logEvent('Logs: Pipelines: Saved Pipelines', {
|
||||
count: pipelinesInDB.length,
|
||||
enabled: pipelinesInDB.filter((p) => p.enabled).length,
|
||||
@ -446,7 +486,14 @@ function PipelineListsView({
|
||||
setPrevPipelineData(modifiedPipelineData);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [currPipelineData, notifications, refetchPipelineLists, setActionMode, t]);
|
||||
}, [
|
||||
currPipelineData,
|
||||
notifications,
|
||||
refetchPipelineLists,
|
||||
setActionMode,
|
||||
t,
|
||||
getModifiedJsonFlatteningConfigs,
|
||||
]);
|
||||
|
||||
const onCancelConfigurationHandler = useCallback((): void => {
|
||||
setActionMode(ActionMode.Viewing);
|
||||
|
||||
@ -58,6 +58,15 @@ export const pipelineMockData: Array<PipelineData> = [
|
||||
from: 'attributes.auth',
|
||||
to: 'attributes.username',
|
||||
},
|
||||
{
|
||||
orderId: 3,
|
||||
enabled: true,
|
||||
type: 'json_parser',
|
||||
id: 'jsonparser',
|
||||
name: 'json parser',
|
||||
from: 'attributes.auth',
|
||||
to: 'attributes.username',
|
||||
},
|
||||
],
|
||||
createdBy: 'nityananda@signoz.io',
|
||||
createdAt: '2023-03-07T16:56:53.36071141Z',
|
||||
|
||||
@ -1,8 +1,16 @@
|
||||
import { render } from 'tests/test-utils';
|
||||
import { fireEvent, screen, waitFor } from '@testing-library/react';
|
||||
import { render as customRender } from 'tests/test-utils';
|
||||
import { ProcessorData } from 'types/api/pipeline/def';
|
||||
|
||||
import { pipelineMockData } from '../mocks/pipeline';
|
||||
import AddNewProcessor from '../PipelineListsView/AddNewProcessor';
|
||||
|
||||
// Mock the config module to set JSON parser as default
|
||||
jest.mock('../PipelineListsView/AddNewProcessor/config', () => ({
|
||||
...jest.requireActual('../PipelineListsView/AddNewProcessor/config'),
|
||||
DEFAULT_PROCESSOR_TYPE: 'json_parser',
|
||||
}));
|
||||
|
||||
jest.mock('uplot', () => {
|
||||
const paths = {
|
||||
spline: jest.fn(),
|
||||
@ -17,44 +25,233 @@ jest.mock('uplot', () => {
|
||||
};
|
||||
});
|
||||
|
||||
beforeAll(() => {
|
||||
Object.defineProperty(window, 'matchMedia', {
|
||||
writable: true,
|
||||
value: jest.fn().mockImplementation((query) => ({
|
||||
matches: false,
|
||||
media: query,
|
||||
onchange: null,
|
||||
addListener: jest.fn(),
|
||||
removeListener: jest.fn(),
|
||||
addEventListener: jest.fn(),
|
||||
removeEventListener: jest.fn(),
|
||||
dispatchEvent: jest.fn(),
|
||||
})),
|
||||
});
|
||||
});
|
||||
|
||||
const selectedProcessorData = {
|
||||
id: '1',
|
||||
orderId: 1,
|
||||
type: 'grok_parser',
|
||||
name: 'grok use common',
|
||||
output: 'grokusecommon',
|
||||
type: 'json_parser',
|
||||
name: 'json parser',
|
||||
output: 'jsonparser',
|
||||
};
|
||||
describe('PipelinePage container test', () => {
|
||||
it('should render AddNewProcessor section', () => {
|
||||
const setActionType = jest.fn();
|
||||
const isActionType = 'add-processor';
|
||||
|
||||
const { asFragment } = render(
|
||||
<AddNewProcessor
|
||||
isActionType={isActionType}
|
||||
setActionType={setActionType}
|
||||
selectedProcessorData={selectedProcessorData}
|
||||
setShowSaveButton={jest.fn()}
|
||||
expandedPipelineData={pipelineMockData[0]}
|
||||
setExpandedPipelineData={jest.fn()}
|
||||
/>,
|
||||
);
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
// Constants for repeated text
|
||||
const ENABLE_PATHS_TEXT = 'Enable Paths';
|
||||
const ENABLE_MAPPING_TEXT = 'Enable Mapping';
|
||||
const PATH_PREFIX_LABEL = 'Path Prefix';
|
||||
|
||||
// Helper function to render AddNewProcessor with JSON parser type
|
||||
const renderJsonProcessor = ({
|
||||
selectedProcessorData: processorData = selectedProcessorData,
|
||||
isActionType = 'add-processor',
|
||||
}: {
|
||||
selectedProcessorData?: ProcessorData;
|
||||
isActionType?: 'add-processor' | 'edit-processor';
|
||||
}): ReturnType<typeof customRender> => {
|
||||
const defaultProps = {
|
||||
isActionType,
|
||||
setActionType: jest.fn(),
|
||||
selectedProcessorData: processorData,
|
||||
setShowSaveButton: jest.fn(),
|
||||
expandedPipelineData: pipelineMockData[2],
|
||||
setExpandedPipelineData: jest.fn(),
|
||||
};
|
||||
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
return customRender(<AddNewProcessor {...defaultProps} />);
|
||||
};
|
||||
|
||||
describe('JSON Flattening Processor Tests', () => {
|
||||
describe('Enable/Disable Flattening', () => {
|
||||
it('should display the form when enable flattening is turned on', async () => {
|
||||
renderJsonProcessor({
|
||||
selectedProcessorData: {
|
||||
...selectedProcessorData,
|
||||
enable_flattening: true,
|
||||
},
|
||||
});
|
||||
|
||||
// Verify the JSON flattening form is displayed
|
||||
expect(screen.queryByText(ENABLE_PATHS_TEXT)).toBeInTheDocument();
|
||||
expect(screen.queryByText(ENABLE_MAPPING_TEXT)).toBeInTheDocument();
|
||||
});
|
||||
it('should not display the form when enable flattening is turned off', async () => {
|
||||
renderJsonProcessor({
|
||||
selectedProcessorData: {
|
||||
...selectedProcessorData,
|
||||
enable_flattening: false,
|
||||
},
|
||||
});
|
||||
|
||||
// Verify the JSON flattening form is not displayed
|
||||
expect(screen.queryByText(ENABLE_PATHS_TEXT)).not.toBeInTheDocument();
|
||||
expect(screen.queryByText(ENABLE_MAPPING_TEXT)).not.toBeInTheDocument();
|
||||
});
|
||||
it('should display the form when enable flattening switch is toggled on', async () => {
|
||||
renderJsonProcessor({});
|
||||
|
||||
// Wait for the component to render and find the enable flattening switch
|
||||
await waitFor(() => {
|
||||
expect(screen.getByRole('switch')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
// Find the enable flattening switch
|
||||
const enableFlatteningSwitch = screen.getByRole('switch');
|
||||
// Turn on the switch
|
||||
fireEvent.click(enableFlatteningSwitch);
|
||||
|
||||
// Verify the JSON flattening form is displayed
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText(ENABLE_PATHS_TEXT)).toBeInTheDocument();
|
||||
expect(screen.getByText(ENABLE_MAPPING_TEXT)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
it('should hide the form when enable flattening switch is toggled off', async () => {
|
||||
renderJsonProcessor({
|
||||
selectedProcessorData: {
|
||||
...selectedProcessorData,
|
||||
enable_flattening: true,
|
||||
},
|
||||
});
|
||||
|
||||
// Wait for the component to render and find the switches
|
||||
await waitFor(() => {
|
||||
expect(screen.getAllByRole('switch')[0]).toBeInTheDocument();
|
||||
});
|
||||
|
||||
// Find the enable flattening switch
|
||||
const enableFlatteningSwitch = screen.getAllByRole('switch')[0];
|
||||
// Turn off the switch
|
||||
fireEvent.click(enableFlatteningSwitch);
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByText(ENABLE_PATHS_TEXT)).not.toBeInTheDocument();
|
||||
expect(screen.queryByText(ENABLE_MAPPING_TEXT)).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Enable/Disable Paths', () => {
|
||||
it('should toggle path prefix visibility when enable paths switch is toggled', async () => {
|
||||
renderJsonProcessor({
|
||||
selectedProcessorData: {
|
||||
...selectedProcessorData,
|
||||
enable_flattening: true,
|
||||
enable_paths: false,
|
||||
},
|
||||
});
|
||||
|
||||
// Wait for the component to render and find the switches
|
||||
await waitFor(() => {
|
||||
expect(screen.getAllByRole('switch')[1]).toBeInTheDocument();
|
||||
});
|
||||
|
||||
// In add mode, enable_paths is always true initially, so the path prefix should be visible
|
||||
await waitFor(() => {
|
||||
expect(screen.getByLabelText(PATH_PREFIX_LABEL)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
// Find the enable paths switch (second switch in the form) and turn it off
|
||||
const enablePathsSwitch = screen.getAllByRole('switch')[1];
|
||||
fireEvent.click(enablePathsSwitch);
|
||||
|
||||
// Verify the path prefix field is now hidden
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByLabelText(PATH_PREFIX_LABEL)).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
// Turn the paths switch back on
|
||||
fireEvent.click(enablePathsSwitch);
|
||||
|
||||
// Verify the path prefix field is displayed again
|
||||
await waitFor(() => {
|
||||
expect(screen.getByLabelText(PATH_PREFIX_LABEL)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
it('should hide path prefix when enable paths switch is turned off', async () => {
|
||||
renderJsonProcessor({
|
||||
selectedProcessorData: {
|
||||
...selectedProcessorData,
|
||||
enable_flattening: true,
|
||||
enable_paths: true,
|
||||
},
|
||||
});
|
||||
|
||||
// Wait for the component to render and find the switches
|
||||
await waitFor(() => {
|
||||
expect(screen.getAllByRole('switch')[1]).toBeInTheDocument();
|
||||
});
|
||||
|
||||
// Verify the path prefix is initially visible
|
||||
await waitFor(() => {
|
||||
expect(screen.getByLabelText(PATH_PREFIX_LABEL)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
// Find the enable paths switch and turn it off
|
||||
const enablePathsSwitch = screen.getAllByRole('switch')[1];
|
||||
fireEvent.click(enablePathsSwitch);
|
||||
|
||||
// Verify the path prefix field is now hidden
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByLabelText(PATH_PREFIX_LABEL)).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Enable/Disable Mapping', () => {
|
||||
it('should display the mapping fields when enable mapping is turned on', async () => {
|
||||
renderJsonProcessor({
|
||||
selectedProcessorData: {
|
||||
...selectedProcessorData,
|
||||
enable_flattening: true,
|
||||
enable_paths: true,
|
||||
mapping: {
|
||||
environment: ['existing.env'],
|
||||
host: ['existing.host'],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// Verify the mapping fields are displayed
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('environment')).toBeInTheDocument();
|
||||
expect(screen.getByText('host')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Edit Processor Flow', () => {
|
||||
it('should load existing processor data correctly when editing', async () => {
|
||||
const existingProcessorData = {
|
||||
id: '1',
|
||||
orderId: 1,
|
||||
type: 'json_parser',
|
||||
name: 'test json parser',
|
||||
output: 'testoutput',
|
||||
enable_flattening: true,
|
||||
enable_paths: true,
|
||||
path_prefix: 'existing.prefix',
|
||||
enable_mapping: true,
|
||||
mapping: {
|
||||
environment: ['existing.env'],
|
||||
host: ['existing.host'],
|
||||
},
|
||||
};
|
||||
|
||||
renderJsonProcessor({
|
||||
selectedProcessorData: existingProcessorData,
|
||||
isActionType: 'edit-processor',
|
||||
});
|
||||
|
||||
// Verify the form is displayed with existing data
|
||||
await waitFor(() => {
|
||||
expect(screen.getByDisplayValue('existing.prefix')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
// Verify flattening is enabled
|
||||
const enableFlatteningSwitch = screen.getAllByRole('switch')[0];
|
||||
expect(enableFlatteningSwitch).toBeChecked();
|
||||
|
||||
// Verify paths is enabled
|
||||
const enablePathsSwitch = screen.getAllByRole('switch')[1];
|
||||
expect(enablePathsSwitch).toBeChecked();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -192,7 +192,7 @@ describe('PipelinePage container test', () => {
|
||||
'.ant-table-expanded-row [data-icon="delete"]',
|
||||
);
|
||||
|
||||
expect(deleteBtns.length).toBe(2);
|
||||
expect(deleteBtns.length).toBe(3);
|
||||
|
||||
// delete pipeline
|
||||
await fireEvent.click(deleteBtns[0] as HTMLElement);
|
||||
@ -213,7 +213,7 @@ describe('PipelinePage container test', () => {
|
||||
expect(
|
||||
document.querySelectorAll('.ant-table-expanded-row [data-icon="delete"]')
|
||||
.length,
|
||||
).toBe(1);
|
||||
).toBe(2);
|
||||
});
|
||||
|
||||
it('should be able to toggle and delete pipeline', async () => {
|
||||
|
||||
@ -1,3 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`PipelinePage container test should render AddNewProcessor section 1`] = `<DocumentFragment />`;
|
||||
@ -124,6 +124,37 @@ exports[`PipelinePage should render PipelineExpandView section 1`] = `
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr
|
||||
class="ant-table-row ant-table-row-level-0"
|
||||
data-row-key="jsonparser"
|
||||
draggable="true"
|
||||
>
|
||||
<td
|
||||
class="ant-table-cell"
|
||||
style="text-align: right;"
|
||||
>
|
||||
<span
|
||||
class="ant-avatar ant-avatar-circle c1 css-dev-only-do-not-override-2i2tap"
|
||||
>
|
||||
<span
|
||||
class="ant-avatar-string"
|
||||
style="transform: scale(1) translateX(-50%);"
|
||||
>
|
||||
3
|
||||
</span>
|
||||
</span>
|
||||
</td>
|
||||
<td
|
||||
class="ant-table-cell"
|
||||
style="text-align: left;"
|
||||
>
|
||||
<div
|
||||
class="c2"
|
||||
>
|
||||
json parser
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
@ -118,7 +118,7 @@ exports[`PipelinePage container test should render PipelinePageLayout section 1`
|
||||
learn_more
|
||||
<a
|
||||
href="https://signoz.io/docs/logs-pipelines/introduction/?utm_source=product&utm_medium=pipelines-tab"
|
||||
rel="noreferrer"
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
here
|
||||
|
||||
@ -31,6 +31,13 @@ export interface ProcessorData {
|
||||
// time parser fields
|
||||
layout_type?: string;
|
||||
layout?: string;
|
||||
|
||||
// json flattening fields
|
||||
enable_flattening?: boolean;
|
||||
enable_paths?: boolean;
|
||||
path_prefix?: string;
|
||||
enable_mapping?: boolean;
|
||||
mapping?: Record<string, string[]>;
|
||||
}
|
||||
|
||||
export interface PipelineData {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user