Compare commits

...

14 Commits

16 changed files with 674 additions and 187 deletions

View File

@@ -42,5 +42,7 @@
"processor_span_id_placeholder": "Parse Span ID from",
"processor_trace_flags_placeholder": "Parse Trace flags from",
"processor_from_placeholder": "From",
"processor_to_placeholder": "To"
"processor_to_placeholder": "To",
"share_pipelines": "Share Pipelines",
"import_pipelines": "Import Pipelines"
}

View File

@@ -9,6 +9,8 @@ function Editor({
readOnly,
height,
options,
beforeMount,
onValidate,
}: MEditorProps): JSX.Element {
const isDarkMode = useIsDarkMode();
@@ -31,6 +33,8 @@ function Editor({
options={editorOptions}
height={height}
onChange={onChangeHandler}
beforeMount={beforeMount}
onValidate={onValidate}
data-testid="monaco-editor"
/>
);
@@ -43,6 +47,8 @@ interface MEditorProps {
readOnly?: boolean;
height?: string;
options?: EditorProps['options'];
beforeMount?: EditorProps['beforeMount'];
onValidate?: EditorProps['onValidate'];
}
Editor.defaultProps = {
@@ -51,6 +57,8 @@ Editor.defaultProps = {
height: '40vh',
options: {},
onChange: (): void => {},
beforeMount: (): void => {},
onValidate: (): void => {},
};
export default Editor;

View File

@@ -1,77 +0,0 @@
import { EditFilled, PlusOutlined } from '@ant-design/icons';
import TextToolTip from 'components/TextToolTip';
import useAnalytics from 'hooks/analytics/useAnalytics';
import { useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { ActionMode, ActionType, Pipeline } from 'types/api/pipeline/def';
import { ButtonContainer, CustomButton } from '../../styles';
import { checkDataLength } from '../utils';
function CreatePipelineButton({
setActionType,
isActionMode,
setActionMode,
pipelineData,
}: CreatePipelineButtonProps): JSX.Element {
const { t } = useTranslation(['pipeline']);
const { trackEvent } = useAnalytics();
const isAddNewPipelineVisible = useMemo(
() => checkDataLength(pipelineData?.pipelines),
[pipelineData?.pipelines],
);
const isDisabled = isActionMode === ActionMode.Editing;
const onEnterEditMode = (): void => {
setActionMode(ActionMode.Editing);
trackEvent('Logs: Pipelines: Entered Edit Mode', {
source: 'signoz-ui',
});
};
const onAddNewPipeline = (): void => {
setActionMode(ActionMode.Editing);
setActionType(ActionType.AddPipeline);
trackEvent('Logs: Pipelines: Clicked Add New Pipeline', {
source: 'signoz-ui',
});
};
return (
<ButtonContainer>
<TextToolTip
text={t('learn_more')}
url="https://signoz.io/docs/logs-pipelines/introduction/"
/>
{isAddNewPipelineVisible && (
<CustomButton
icon={<EditFilled />}
onClick={onEnterEditMode}
disabled={isDisabled}
>
{t('enter_edit_mode')}
</CustomButton>
)}
{!isAddNewPipelineVisible && (
<CustomButton
icon={<PlusOutlined />}
onClick={onAddNewPipeline}
type="primary"
>
{t('new_pipeline')}
</CustomButton>
)}
</ButtonContainer>
);
}
interface CreatePipelineButtonProps {
setActionType: (actionType: string) => void;
isActionMode: string;
setActionMode: (actionMode: string) => void;
pipelineData: Pipeline;
}
export default CreatePipelineButton;

View File

@@ -0,0 +1,125 @@
import {
EditFilled,
ImportOutlined,
PlusOutlined,
ShareAltOutlined,
} from '@ant-design/icons';
import TextToolTip from 'components/TextToolTip';
import useAnalytics from 'hooks/analytics/useAnalytics';
import { useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import {
ActionMode,
ActionType,
Pipeline,
PipelineData,
} from 'types/api/pipeline/def';
import { ButtonContainer, CustomButton } from '../../styles';
import PipelinesExportModal from './PipelinesExportModal';
import PipelinesImportModal from './PipelinesImportModal/PipelinesImportModal';
function PipelinesActions({
setActionType,
isActionMode,
setActionMode,
pipelineData,
setCurrentPipelines,
}: PipelinesActionsProps): JSX.Element {
const { t } = useTranslation(['pipeline']);
const { trackEvent } = useAnalytics();
const [isExportModalVisible, setIsExportModalVisible] = useState(false);
const [isImportModalVisible, setIsImportModalVisible] = useState(false);
const pipelinesExist = useMemo(() => pipelineData?.pipelines?.length > 0, [
pipelineData?.pipelines,
]);
const inEditMode = isActionMode === ActionMode.Editing;
const onEnterEditMode = (): void => {
setActionMode(ActionMode.Editing);
trackEvent('Logs: Pipelines: Entered Edit Mode', {
source: 'signoz-ui',
});
};
const onAddNewPipeline = (): void => {
setActionMode(ActionMode.Editing);
setActionType(ActionType.AddPipeline);
trackEvent('Logs: Pipelines: Clicked Add New Pipeline', {
source: 'signoz-ui',
});
};
return (
<>
<ButtonContainer>
<TextToolTip
text={t('learn_more')}
url="https://signoz.io/docs/logs-pipelines/introduction/"
/>
{pipelinesExist && !inEditMode && (
<CustomButton
onClick={(): void => setIsExportModalVisible(true)}
icon={<ShareAltOutlined />}
>
{t('share_pipelines')}
</CustomButton>
)}
{(inEditMode || !pipelinesExist) && (
<CustomButton
onClick={(): void => {
onEnterEditMode();
setIsImportModalVisible(true);
}}
icon={<ImportOutlined />}
>
{t('import_pipelines')}
</CustomButton>
)}
{pipelinesExist && (
<CustomButton
icon={<EditFilled />}
onClick={onEnterEditMode}
disabled={inEditMode}
>
{t('enter_edit_mode')}
</CustomButton>
)}
{!pipelinesExist && (
<CustomButton
icon={<PlusOutlined />}
onClick={onAddNewPipeline}
type="primary"
>
{t('new_pipeline')}
</CustomButton>
)}
</ButtonContainer>
<PipelinesExportModal
open={isExportModalVisible}
onClose={(): void => setIsExportModalVisible(false)}
pipelines={pipelineData.pipelines}
/>
<PipelinesImportModal
open={isImportModalVisible}
onClose={(): void => setIsImportModalVisible(false)}
setCurrentPipelines={setCurrentPipelines}
/>
</>
);
}
interface PipelinesActionsProps {
setActionType: (actionType: string) => void;
isActionMode: string;
setActionMode: (actionMode: string) => void;
pipelineData: Pipeline;
setCurrentPipelines: (
value: React.SetStateAction<Array<PipelineData>>,
) => void;
}
export default PipelinesActions;

View File

@@ -0,0 +1,99 @@
import { CopyFilled, DownloadOutlined } from '@ant-design/icons';
import { Button, Modal } from 'antd';
import Editor from 'components/Editor';
import { downloadObjectAsJson } from 'container/NewDashboard/DashboardDescription/utils';
import { useNotifications } from 'hooks/useNotifications';
import { useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useCopyToClipboard } from 'react-use';
import { PipelineData } from 'types/api/pipeline/def';
export default function PipelinesExportModal({
open,
onClose,
pipelines,
}: PipelinesExportModalProps): JSX.Element {
const { t } = useTranslation(['pipeline']);
const postablePipelines = pipelines.map((p) =>
Object.fromEntries(
Object.entries(p).filter((e) => !['createdBy', 'createdAt'].includes(e[0])),
),
);
const pipelinesPropJson = JSON.stringify(postablePipelines, null, 2);
const [pipelinesJson, setPipelinesJson] = useState(pipelinesPropJson);
useEffect(() => {
setPipelinesJson(pipelinesPropJson);
}, [open, pipelinesPropJson]);
const { notifications } = useNotifications();
const [clipboardContent, setClipboardContent] = useCopyToClipboard();
useEffect(() => {
if (clipboardContent.error) {
notifications.error({
message: t('something_went_wrong', {
ns: 'common',
}),
});
}
if (clipboardContent.value) {
notifications.success({
message: t('success', {
ns: 'common',
}),
});
}
}, [clipboardContent.error, clipboardContent.value, t, notifications]);
const footer = useMemo(
() => (
<>
<Button
style={{
marginTop: '16px',
}}
onClick={(): void => setClipboardContent(pipelinesJson)}
type="primary"
size="small"
>
<CopyFilled /> {t('copy_to_clipboard')}
</Button>
<Button
type="primary"
size="small"
onClick={(): void => {
downloadObjectAsJson(JSON.parse(pipelinesJson), 'pipelines');
}}
>
<DownloadOutlined /> {t('download_json')}
</Button>
</>
),
[pipelinesJson, t, setClipboardContent],
);
return (
<Modal
open={open}
onCancel={onClose}
width="80vw"
centered
title={t('share')}
destroyOnClose
footer={footer}
>
<Editor
height="70vh"
onChange={(value): void => setPipelinesJson(value)}
value={pipelinesJson}
/>
</Modal>
);
}
interface PipelinesExportModalProps {
open: boolean;
onClose: VoidFunction;
pipelines: Array<PipelineData>;
}

View File

@@ -0,0 +1,95 @@
import './styles.scss';
import { ImportOutlined } from '@ant-design/icons';
import { Monaco } from '@monaco-editor/react';
import { Button, Modal } from 'antd';
import Editor from 'components/Editor';
import { useCallback, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { PipelineData } from 'types/api/pipeline/def';
import { PipelinesJSONSchema } from '../schema';
export default function PipelinesImportModal({
open,
onClose,
setCurrentPipelines,
}: PipelinesImportModalProps): JSX.Element {
const { t } = useTranslation(['pipeline']);
const [pipelinesJson, setPipelinesJson] = useState('');
const [editorErrors, setEditorErrors] = useState<string[]>([]);
const isEmpty = pipelinesJson.trim().length < 1;
const isInvalid = (editorErrors || []).length > 0;
const firstError = editorErrors?.[0];
const onImport = useCallback((): void => {
try {
const pipelines = JSON.parse(pipelinesJson);
setCurrentPipelines(pipelines);
onClose();
} catch (error) {
setEditorErrors([String(error)]);
}
}, [pipelinesJson, setCurrentPipelines, onClose]);
const footer = useMemo(
() => (
<div className="pipelines-import-modal-footer">
<div className="pipelines-import-modal-error">{firstError || ''}</div>
<Button
disabled={isEmpty || isInvalid}
type="primary"
size="small"
onClick={onImport}
>
<ImportOutlined /> {t('import')}
</Button>
</div>
),
[t, isEmpty, isInvalid, firstError, onImport],
);
return (
<Modal
open={open}
onCancel={onClose}
width="80vw"
centered
title={t('import')}
destroyOnClose
footer={footer}
>
<Editor
height="70vh"
onChange={(value): void => setPipelinesJson(value)}
value={pipelinesJson}
language="json"
beforeMount={(monaco: Monaco): void => {
monaco.languages.json.jsonDefaults.setDiagnosticsOptions({
validate: true,
schemas: [
{
fileMatch: ['*'],
schema: PipelinesJSONSchema,
},
],
});
}}
onValidate={(markers): void =>
setEditorErrors(
markers.map(
(m) => `Ln ${m.startLineNumber}, Col ${m.startColumn}: ${m.message}`,
),
)
}
/>
</Modal>
);
}
interface PipelinesImportModalProps {
open: boolean;
onClose: VoidFunction;
setCurrentPipelines: (
value: React.SetStateAction<Array<PipelineData>>,
) => void;
}

View File

@@ -0,0 +1,3 @@
import PipelinesImportModal from './PipelinesImportModal';
export default PipelinesImportModal;

View File

@@ -0,0 +1,10 @@
.pipelines-import-modal-footer {
display: flex;
justify-content: space-between;
align-items: center;
}
.pipelines-import-modal-error {
flex-grow: 1;
text-align: left;
}

View File

@@ -1,8 +1,9 @@
import cloneDeep from 'lodash-es/cloneDeep';
import { useState } from 'react';
import { Pipeline } from 'types/api/pipeline/def';
import { Pipeline, PipelineData } from 'types/api/pipeline/def';
import PipelineListsView from '../../PipelineListsView';
import CreatePipelineButton from './CreatePipelineButton';
import PipelinesActions from './PipelinesActions';
function PipelinePageLayout({
refetchPipelineLists,
@@ -10,21 +11,32 @@ function PipelinePageLayout({
}: PipelinePageLayoutProps): JSX.Element {
const [isActionType, setActionType] = useState<string>();
const [isActionMode, setActionMode] = useState<string>('viewing-mode');
const [savedPipelines, setSavedPipelines] = useState<Array<PipelineData>>(
cloneDeep(pipelineData?.pipelines || []),
);
const [currentPipelines, setCurrentPipelines] = useState<Array<PipelineData>>(
cloneDeep(pipelineData?.pipelines || []),
);
return (
<>
<CreatePipelineButton
<PipelinesActions
setActionType={setActionType}
setActionMode={setActionMode}
isActionMode={isActionMode}
pipelineData={pipelineData}
setCurrentPipelines={setCurrentPipelines}
/>
<PipelineListsView
isActionType={String(isActionType)}
setActionType={setActionType}
setActionMode={setActionMode}
isActionMode={isActionMode}
pipelineData={pipelineData}
savedPipelinesVersion={pipelineData?.version}
savedPipelines={savedPipelines}
setSavedPipelines={setSavedPipelines}
currentPipelines={currentPipelines}
setCurrentPipelines={setCurrentPipelines}
refetchPipelineLists={refetchPipelineLists}
/>
</>

View File

@@ -0,0 +1,247 @@
// JSON schema for pipelines payload.
export const PipelinesJSONSchema = JSON.parse(`
{
"items": {
"properties": {
"id": {
"type": "string"
},
"orderId": {
"type": "integer"
},
"name": {
"type": "string"
},
"alias": {
"type": "string"
},
"description": {
"type": "string"
},
"enabled": {
"type": "boolean"
},
"filter": {
"properties": {
"op": {
"type": "string"
},
"items": {
"items": {
"properties": {
"key": {
"properties": {
"key": {
"type": "string"
},
"dataType": {
"type": "string"
},
"type": {
"type": "string"
},
"isColumn": {
"type": "boolean"
},
"isJSON": {
"type": "boolean"
}
},
"additionalProperties": false,
"type": "object",
"required": [
"key",
"dataType",
"type",
"isColumn",
"isJSON"
]
},
"value": true,
"op": {
"type": "string"
}
},
"additionalProperties": false,
"type": "object",
"required": [
"key",
"value",
"op"
]
},
"type": "array"
}
},
"additionalProperties": false,
"type": "object",
"required": [
"op",
"items"
]
},
"config": {
"items": {
"properties": {
"type": {
"type": "string"
},
"id": {
"type": "string"
},
"output": {
"type": "string"
},
"on_error": {
"type": "string"
},
"if": {
"type": "string"
},
"orderId": {
"type": "integer"
},
"enabled": {
"type": "boolean"
},
"name": {
"type": "string"
},
"parse_to": {
"type": "string"
},
"pattern": {
"type": "string"
},
"regex": {
"type": "string"
},
"parse_from": {
"type": "string"
},
"trace_id": {
"properties": {
"parse_from": {
"type": "string"
}
},
"additionalProperties": false,
"type": "object",
"required": [
"parse_from"
]
},
"span_id": {
"properties": {
"parse_from": {
"type": "string"
}
},
"additionalProperties": false,
"type": "object",
"required": [
"parse_from"
]
},
"trace_flags": {
"properties": {
"parse_from": {
"type": "string"
}
},
"additionalProperties": false,
"type": "object",
"required": [
"parse_from"
]
},
"field": {
"type": "string"
},
"value": {
"type": "string"
},
"from": {
"type": "string"
},
"to": {
"type": "string"
},
"expr": {
"type": "string"
},
"routes": {
"items": {
"properties": {
"output": {
"type": "string"
},
"expr": {
"type": "string"
}
},
"additionalProperties": false,
"type": "object",
"required": [
"output",
"expr"
]
},
"type": "array"
},
"fields": {
"items": {
"type": "string"
},
"type": "array"
},
"default": {
"type": "string"
},
"layout": {
"type": "string"
},
"layout_type": {
"type": "string"
},
"mapping": {
"additionalProperties": {
"items": {
"type": "string"
},
"type": "array"
},
"type": "object"
},
"overwrite_text": {
"type": "boolean"
}
},
"additionalProperties": false,
"type": "object",
"required": [
"type",
"orderId",
"enabled"
]
},
"type": "array"
}
},
"additionalProperties": false,
"type": "object",
"required": [
"id",
"orderId",
"name",
"alias",
"description",
"enabled",
"filter",
"config"
]
},
"type": "array"
}
`);

View File

@@ -1,9 +1,9 @@
import { Button, Divider, Form, Modal } from 'antd';
import React, { useCallback, useEffect, useMemo } from 'react';
import React, { useEffect, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { useSelector } from 'react-redux';
import { AppState } from 'store/reducers';
import { ActionMode, ActionType, PipelineData } from 'types/api/pipeline/def';
import { ActionType, PipelineData } from 'types/api/pipeline/def';
import AppReducer from 'types/reducer/app';
import { v4 } from 'uuid';
@@ -15,7 +15,6 @@ function AddNewPipeline({
isActionType,
setActionType,
selectedPipelineData,
setShowSaveButton,
setCurrPipelineData,
currPipelineData,
}: AddNewPipelineProps): JSX.Element {
@@ -90,11 +89,6 @@ function AddNewPipeline({
[isEdit, selectedPipelineData?.name, t],
);
const onOkModalHandler = useCallback(
() => setShowSaveButton(ActionMode.Editing),
[setShowSaveButton],
);
const isOpen = useMemo(() => isEdit || isAdd, [isAdd, isEdit]);
return (
@@ -122,7 +116,7 @@ function AddNewPipeline({
key="submit"
type="primary"
htmlType="submit"
onClick={onOkModalHandler}
onClick={(): void => {}}
>
{isEdit ? t('update') : t('create')}
</Button>
@@ -140,7 +134,6 @@ interface AddNewPipelineProps {
isActionType: string;
setActionType: (actionType?: ActionType) => void;
selectedPipelineData: PipelineData | undefined;
setShowSaveButton: (actionMode: ActionMode) => void;
setCurrPipelineData: (
value: React.SetStateAction<Array<PipelineData>>,
) => void;

View File

@@ -2,7 +2,6 @@ import { Button, Divider, Form, Modal } from 'antd';
import { useCallback, useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import {
ActionMode,
ActionType,
PipelineData,
ProcessorData,
@@ -19,7 +18,6 @@ function AddNewProcessor({
isActionType,
setActionType,
selectedProcessorData,
setShowSaveButton,
expandedPipelineData,
setExpandedPipelineData,
}: AddNewProcessorProps): JSX.Element {
@@ -134,11 +132,6 @@ function AddNewProcessor({
[isEdit, selectedProcessorData?.name, t],
);
const onOkModalHandler = useCallback(
() => setShowSaveButton(ActionMode.Editing),
[setShowSaveButton],
);
const isOpen = useMemo(() => isEdit || isAdd, [isAdd, isEdit]);
const onFormValuesChanged = useCallback(
@@ -179,7 +172,7 @@ function AddNewProcessor({
key="submit"
type="primary"
htmlType="submit"
onClick={onOkModalHandler}
onClick={(): void => {}}
>
{isEdit ? t('update') : t('create')}
</Button>
@@ -202,7 +195,6 @@ interface AddNewProcessorProps {
isActionType: string;
setActionType: (actionType?: ActionType) => void;
selectedProcessorData?: ProcessorData;
setShowSaveButton: (actionMode: ActionMode) => void;
expandedPipelineData?: PipelineData;
setExpandedPipelineData: (data: PipelineData) => void;
}

View File

@@ -32,7 +32,6 @@ function PipelineExpandView({
setActionType,
processorEditAction,
isActionMode,
setShowSaveButton,
expandedPipelineData,
setExpandedPipelineData,
prevPipelineData,
@@ -44,7 +43,6 @@ function PipelineExpandView({
const deleteProcessorHandler = useCallback(
(record: ProcessorData) => (): void => {
setShowSaveButton(ActionMode.Editing);
if (expandedPipelineData && expandedPipelineData?.config) {
const filteredData = expandedPipelineData?.config.filter(
(item: ProcessorData) => item.id !== record.id,
@@ -62,7 +60,7 @@ function PipelineExpandView({
setExpandedPipelineData(pipelineData);
}
},
[expandedPipelineData, setShowSaveButton, setExpandedPipelineData],
[expandedPipelineData, setExpandedPipelineData],
);
const processorDeleteAction = useCallback(
@@ -80,7 +78,6 @@ function PipelineExpandView({
const onSwitchProcessorChange = useCallback(
(checked: boolean, record: ProcessorData): void => {
if (expandedPipelineData && expandedPipelineData?.config) {
setShowSaveButton(ActionMode.Editing);
const findRecordIndex = getRecordIndex(
expandedPipelineData?.config,
record,
@@ -102,7 +99,7 @@ function PipelineExpandView({
setExpandedPipelineData(modifiedProcessorData);
}
},
[expandedPipelineData, setExpandedPipelineData, setShowSaveButton],
[expandedPipelineData, setExpandedPipelineData],
);
const columns = useMemo(() => {
@@ -145,14 +142,13 @@ function PipelineExpandView({
const reorderProcessorRow = useCallback(
(updatedRow: ProcessorData[]) => (): void => {
setShowSaveButton(ActionMode.Editing);
if (expandedPipelineData) {
const modifiedProcessorData = { ...expandedPipelineData };
modifiedProcessorData.config = updatedRow;
setExpandedPipelineData(modifiedProcessorData);
}
},
[expandedPipelineData, setShowSaveButton, setExpandedPipelineData],
[expandedPipelineData, setExpandedPipelineData],
);
const onCancelReorderProcessorRow = useCallback(
@@ -267,7 +263,6 @@ interface PipelineExpandViewProps {
setActionType: (actionType?: ActionType) => void;
processorEditAction: (record: ProcessorData) => () => void;
isActionMode: string;
setShowSaveButton: (actionMode: ActionMode) => void;
expandedPipelineData?: PipelineData;
setExpandedPipelineData: (data: PipelineData) => void;
prevPipelineData: Array<PipelineData>;

View File

@@ -7,6 +7,7 @@ import savePipeline from 'api/pipeline/post';
import useAnalytics from 'hooks/analytics/useAnalytics';
import { useNotifications } from 'hooks/useNotifications';
import cloneDeep from 'lodash-es/cloneDeep';
import isEqual from 'lodash-es/isEqual';
import React, { useCallback, useMemo, useState } from 'react';
import { DndProvider } from 'react-dnd';
import { HTML5Backend } from 'react-dnd-html5-backend';
@@ -14,7 +15,6 @@ import { useTranslation } from 'react-i18next';
import {
ActionMode,
ActionType,
Pipeline,
PipelineData,
ProcessorData,
} from 'types/api/pipeline/def';
@@ -85,7 +85,11 @@ function PipelineListsView({
setActionType,
isActionMode,
setActionMode,
pipelineData,
savedPipelinesVersion,
savedPipelines,
setSavedPipelines,
currentPipelines,
setCurrentPipelines,
refetchPipelineLists,
}: PipelineListsViewProps): JSX.Element {
const { t } = useTranslation(['pipeline', 'common']);
@@ -93,34 +97,28 @@ function PipelineListsView({
const { notifications } = useNotifications();
const [pipelineSearchValue, setPipelineSearchValue] = useState<string>('');
const { trackEvent } = useAnalytics();
const [prevPipelineData, setPrevPipelineData] = useState<Array<PipelineData>>(
cloneDeep(pipelineData?.pipelines || []),
);
const [currPipelineData, setCurrPipelineData] = useState<Array<PipelineData>>(
cloneDeep(pipelineData?.pipelines || []),
);
const [expandedPipelineId, setExpandedPipelineId] = useState<
string | undefined
>(undefined);
const expandedPipelineData = useCallback(
() => currPipelineData?.find((p) => p.id === expandedPipelineId),
[currPipelineData, expandedPipelineId],
() => currentPipelines?.find((p) => p.id === expandedPipelineId),
[currentPipelines, expandedPipelineId],
);
const setExpandedPipelineData = useCallback(
(newData: PipelineData): void => {
if (expandedPipelineId) {
const pipelineIdx = currPipelineData?.findIndex(
const pipelineIdx = currentPipelines?.findIndex(
(p) => p.id === expandedPipelineId,
);
if (pipelineIdx >= 0) {
const newPipelineData = [...currPipelineData];
const newPipelineData = cloneDeep(currentPipelines);
newPipelineData[pipelineIdx] = newData;
setCurrPipelineData(newPipelineData);
setCurrentPipelines(newPipelineData);
}
}
},
[expandedPipelineId, currPipelineData],
[expandedPipelineId, currentPipelines, setCurrentPipelines],
);
const [
@@ -134,17 +132,16 @@ function PipelineListsView({
] = useState<PipelineData>();
const [expandedRowKeys, setExpandedRowKeys] = useState<Array<string>>();
const [showSaveButton, setShowSaveButton] = useState<string>();
const isEditingActionMode = isActionMode === ActionMode.Editing;
const visibleCurrPipelines = useMemo((): Array<PipelineData> => {
if (pipelineSearchValue === '') {
return currPipelineData;
return currentPipelines;
}
return currPipelineData.filter((data) =>
return currentPipelines.filter((data) =>
getDataOnSearch(data as never, pipelineSearchValue),
);
}, [currPipelineData, pipelineSearchValue]);
}, [currentPipelines, pipelineSearchValue]);
const handleAlert = useCallback(
({ title, descrition, buttontext, onCancel, onOk }: AlertMessage) => {
@@ -171,15 +168,18 @@ function PipelineListsView({
const pipelineDeleteHandler = useCallback(
(record: PipelineData) => (): void => {
setShowSaveButton(ActionMode.Editing);
const filteredData = getElementFromArray(currPipelineData, record, 'id');
const filteredData = getElementFromArray(
cloneDeep(currentPipelines),
record,
'id',
);
filteredData.forEach((item, index) => {
const obj = item;
obj.orderId = index + 1;
});
setCurrPipelineData(filteredData);
setCurrentPipelines(filteredData);
},
[currPipelineData],
[currentPipelines, setCurrentPipelines],
);
const pipelineDeleteAction = useCallback(
@@ -204,21 +204,20 @@ function PipelineListsView({
const onSwitchPipelineChange = useCallback(
(checked: boolean, record: PipelineData): void => {
setShowSaveButton(ActionMode.Editing);
const findRecordIndex = getRecordIndex(currPipelineData, record, 'id');
const findRecordIndex = getRecordIndex(currentPipelines, record, 'id');
const updateSwitch = {
...currPipelineData[findRecordIndex],
...currentPipelines[findRecordIndex],
enabled: checked,
};
const editedPipelineData = getEditedDataSource(
currPipelineData,
cloneDeep(currentPipelines),
record,
'id',
updateSwitch,
);
setCurrPipelineData(editedPipelineData);
setCurrentPipelines(editedPipelineData);
},
[currPipelineData],
[currentPipelines, setCurrentPipelines],
);
const columns = useMemo(() => {
@@ -271,28 +270,13 @@ function PipelineListsView({
onSwitchPipelineChange,
]);
const updatePipelineSequence = useCallback(
(updatedRow: PipelineData[]) => (): void => {
setShowSaveButton(ActionMode.Editing);
setCurrPipelineData(updatedRow);
},
[],
);
const onCancelPipelineSequence = useCallback(
(rawData: PipelineData[]) => (): void => {
setCurrPipelineData(rawData);
},
[],
);
const movePipelineRow = useCallback(
(dragIndex: number, hoverIndex: number) => {
if (currPipelineData && isEditingActionMode) {
const rawData = currPipelineData;
if (currentPipelines && isEditingActionMode) {
const rawData = currentPipelines;
const updatedRows = getUpdatedRow(
currPipelineData,
cloneDeep(currentPipelines),
visibleCurrPipelines[dragIndex].orderId - 1,
visibleCurrPipelines[hoverIndex].orderId - 1,
);
@@ -305,19 +289,18 @@ function PipelineListsView({
title: t('reorder_pipeline'),
descrition: t('reorder_pipeline_description'),
buttontext: t('reorder'),
onOk: updatePipelineSequence(updatedRows),
onCancel: onCancelPipelineSequence(rawData),
onOk: (): void => setCurrentPipelines(updatedRows),
onCancel: (): void => setCurrentPipelines(rawData),
});
}
},
[
currPipelineData,
currentPipelines,
isEditingActionMode,
visibleCurrPipelines,
handleAlert,
t,
updatePipelineSequence,
onCancelPipelineSequence,
setCurrentPipelines,
],
);
@@ -328,10 +311,9 @@ function PipelineListsView({
isActionMode={isActionMode}
setActionType={setActionType}
processorEditAction={processorEditAction}
setShowSaveButton={setShowSaveButton}
expandedPipelineData={expandedPipelineData()}
setExpandedPipelineData={setExpandedPipelineData}
prevPipelineData={prevPipelineData}
prevPipelineData={savedPipelines}
/>
),
[
@@ -340,7 +322,7 @@ function PipelineListsView({
isActionMode,
expandedPipelineData,
setActionType,
prevPipelineData,
savedPipelines,
setExpandedPipelineData,
],
);
@@ -390,7 +372,7 @@ function PipelineListsView({
}, [isEditingActionMode, addNewPipelineHandler, t]);
const onSaveConfigurationHandler = useCallback(async () => {
const modifiedPipelineData = currPipelineData.map((item: PipelineData) => {
const modifiedPipelineData = currentPipelines.map((item: PipelineData) => {
const pipelineData = { ...item };
delete pipelineData?.id;
return pipelineData;
@@ -401,11 +383,10 @@ function PipelineListsView({
if (response.statusCode === 200) {
refetchPipelineLists();
setActionMode(ActionMode.Viewing);
setShowSaveButton(undefined);
const pipelinesInDB = response.payload?.pipelines || [];
setCurrPipelineData(pipelinesInDB);
setPrevPipelineData(pipelinesInDB);
setCurrentPipelines(cloneDeep(pipelinesInDB));
setSavedPipelines(cloneDeep(pipelinesInDB));
trackEvent('Logs: Pipelines: Saved Pipelines', {
count: pipelinesInDB.length,
@@ -419,21 +400,19 @@ function PipelineListsView({
return pipelineData;
});
setActionMode(ActionMode.Editing);
setShowSaveButton(ActionMode.Editing);
notifications.error({
message: 'Error',
description: response.error || t('something_went_wrong'),
});
setCurrPipelineData(modifiedPipelineData);
setPrevPipelineData(modifiedPipelineData);
setCurrentPipelines(cloneDeep(modifiedPipelineData));
setSavedPipelines(cloneDeep(modifiedPipelineData));
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [currPipelineData, notifications, refetchPipelineLists, setActionMode, t]);
}, [currentPipelines, notifications, refetchPipelineLists, setActionMode, t]);
const onCancelConfigurationHandler = useCallback((): void => {
setActionMode(ActionMode.Viewing);
setShowSaveButton(undefined);
prevPipelineData.forEach((item, index) => {
savedPipelines.forEach((item, index) => {
const obj = item;
obj.orderId = index + 1;
if (obj.config) {
@@ -446,9 +425,9 @@ function PipelineListsView({
}
}
});
setCurrPipelineData(prevPipelineData);
setCurrentPipelines(cloneDeep(savedPipelines));
setExpandedRowKeys([]);
}, [prevPipelineData, setActionMode]);
}, [savedPipelines, setCurrentPipelines, setActionMode]);
const onRowHandler = (
_data: PipelineData,
@@ -473,25 +452,23 @@ function PipelineListsView({
isActionType={isActionType}
setActionType={setActionType}
selectedPipelineData={selectedPipelineData}
setShowSaveButton={setShowSaveButton}
setCurrPipelineData={setCurrPipelineData}
currPipelineData={currPipelineData}
setCurrPipelineData={setCurrentPipelines}
currPipelineData={currentPipelines}
/>
<AddNewProcessor
isActionType={isActionType}
setActionType={setActionType}
selectedProcessorData={selectedProcessorData}
setShowSaveButton={setShowSaveButton}
expandedPipelineData={expandedPipelineData()}
setExpandedPipelineData={setExpandedPipelineData}
/>
{prevPipelineData?.length > 0 || currPipelineData?.length > 0 ? (
{savedPipelines?.length > 0 || currentPipelines?.length > 0 ? (
<>
<PipelinesSearchSection setPipelineSearchValue={setPipelineSearchValue} />
<Container>
<ModeAndConfiguration
isActionMode={isActionMode}
version={pipelineData?.version}
version={savedPipelinesVersion}
/>
<DndProvider backend={HTML5Backend}>
<Table
@@ -508,7 +485,7 @@ function PipelineListsView({
</DndProvider>
{isEditingActionMode && (
<SaveConfigButton
showSaveButton={Boolean(showSaveButton)}
showSaveButton={!isEqual(currentPipelines, savedPipelines)}
onSaveConfigurationHandler={onSaveConfigurationHandler}
onCancelConfigurationHandler={onCancelConfigurationHandler}
/>
@@ -529,7 +506,13 @@ interface PipelineListsViewProps {
setActionType: (actionType?: ActionType) => void;
isActionMode: string;
setActionMode: (actionMode: ActionMode) => void;
pipelineData: Pipeline;
savedPipelinesVersion: number | string;
savedPipelines: Array<PipelineData>;
setSavedPipelines: (value: React.SetStateAction<Array<PipelineData>>) => void;
currentPipelines: Array<PipelineData>;
setCurrentPipelines: (
value: React.SetStateAction<Array<PipelineData>>,
) => void;
refetchPipelineLists: VoidFunction;
}

View File

@@ -12,7 +12,7 @@ export const ButtonContainer = styled.div`
export const CustomButton = styled(Button)`
&&& {
margin-left: 1rem;
margin-left: 0.5rem;
}
`;

View File

@@ -5,7 +5,7 @@ import { MemoryRouter } from 'react-router-dom';
import i18n from 'ReactI18';
import store from 'store';
import CreatePipelineButton from '../Layouts/Pipeline/CreatePipelineButton';
import PipelinesActions from '../Layouts/Pipeline/PipelinesActions';
import { pipelineApiResponseMockData } from '../mocks/pipeline';
describe('PipelinePage container test', () => {
@@ -14,7 +14,7 @@ describe('PipelinePage container test', () => {
<MemoryRouter>
<Provider store={store}>
<I18nextProvider i18n={i18n}>
<CreatePipelineButton
<PipelinesActions
setActionType={jest.fn()}
isActionMode="viewing-mode"
setActionMode={jest.fn()}