chore: add logs pipelines nav and update logs pipelines title and routes (#3858)

* chore: update logs pipelines title and routes

* chore: add nav for logs pipelines

* feat: debounced pipelines sarch on change, navigation text changes

* fix: get lint passing

* fix: update snapshots for tests

---------

Co-authored-by: Yunus A M <myounis.ar@live.com>
This commit is contained in:
Raj Kamal Singh 2023-11-01 20:37:27 +05:30 committed by GitHub
parent ec3eba612c
commit ed3017d247
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 574 additions and 630 deletions

View File

@ -32,6 +32,7 @@
"LOGS": "SigNoz | Logs",
"LOGS_EXPLORER": "SigNoz | Logs Explorer",
"LIVE_LOGS": "SigNoz | Live Logs",
"LOGS_PIPELINES": "SigNoz | Logs Pipelines",
"HOME_PAGE": "Open source Observability Platform | SigNoz",
"PASSWORD_RESET": "SigNoz | Password Reset",
"LIST_LICENSES": "SigNoz | List of Licenses",

View File

@ -282,10 +282,10 @@ const routes: AppRoutes[] = [
isPrivate: false,
},
{
path: ROUTES.PIPELINES,
path: ROUTES.LOGS_PIPELINES,
exact: true,
component: PipelinePage,
key: 'PIPELINES',
key: 'LOGS_PIPELINES',
isPrivate: true,
},
{

View File

@ -31,13 +31,12 @@ const ROUTES = {
LOGS: '/logs',
LOGS_EXPLORER: '/logs-explorer',
LIVE_LOGS: '/logs-explorer/live',
LOGS_PIPELINES: '/pipelines',
HOME_PAGE: '/',
PASSWORD_RESET: '/password-reset',
LIST_LICENSES: '/licenses',
LOGS_INDEX_FIELDS: '/logs-explorer/index-fields',
LOGS_PIPELINE: '/logs-explorer/pipeline',
TRACE_EXPLORER: '/trace-explorer',
PIPELINES: '/pipelines',
BILLING: '/billing',
SUPPORT: '/support',
WORKSPACE_LOCKED: '/workspace-locked',

View File

@ -1,5 +1,6 @@
import { Input } from 'antd';
import React, { Dispatch, SetStateAction, useCallback } from 'react';
import { debounce } from 'lodash-es';
import { BaseSyntheticEvent, Dispatch, SetStateAction } from 'react';
import { useTranslation } from 'react-i18next';
function PipelinesSearchSection({
@ -7,18 +8,18 @@ function PipelinesSearchSection({
}: PipelinesSearchSectionProps): JSX.Element {
const { t } = useTranslation(['pipeline']);
const onSeachHandler = useCallback(
(event: React.SetStateAction<string>) => {
setPipelineSearchValue(event);
},
[setPipelineSearchValue],
);
const handleSearch = (searchEv: BaseSyntheticEvent): void => {
setPipelineSearchValue(searchEv?.target?.value || '');
};
const debouncedHandleSearch = debounce(handleSearch, 300);
return (
<Input.Search
<Input
type="text"
allowClear
placeholder={t('search_pipeline_placeholder')}
onSearch={onSeachHandler}
onChange={debouncedHandleSearch}
/>
);
}

View File

@ -14,8 +14,8 @@ import {
import { tableComponents } from '../config';
import { ModalFooterTitle } from '../styles';
import { AlertMessage } from '.';
import { processorColumns } from './config';
import { AlertMessage } from './PipelineListsView';
import { FooterButton, StyledTable } from './styles';
import DragAction from './TableComponents/DragAction';
import ProcessorActions from './TableComponents/ProcessorActions';

View File

@ -0,0 +1,489 @@
import { ExclamationCircleOutlined, PlusOutlined } from '@ant-design/icons';
import { Modal, Table } from 'antd';
import { ExpandableConfig } from 'antd/es/table/interface';
import savePipeline from 'api/pipeline/post';
import { useNotifications } from 'hooks/useNotifications';
import cloneDeep from 'lodash-es/cloneDeep';
import React, { useCallback, useMemo, useState } from 'react';
import { DndProvider } from 'react-dnd';
import { HTML5Backend } from 'react-dnd-html5-backend';
import { useTranslation } from 'react-i18next';
import {
ActionMode,
ActionType,
Pipeline,
PipelineData,
ProcessorData,
} from 'types/api/pipeline/def';
import { v4 } from 'uuid';
import { tableComponents } from '../config';
import AddNewPipeline from './AddNewPipeline';
import AddNewProcessor from './AddNewProcessor';
import { pipelineColumns } from './config';
import ModeAndConfiguration from './ModeAndConfiguration';
import PipelineExpanView from './PipelineExpandView';
import SaveConfigButton from './SaveConfigButton';
import {
AlertContentWrapper,
AlertModalTitle,
Container,
FooterButton,
} from './styles';
import DragAction from './TableComponents/DragAction';
import PipelineActions from './TableComponents/PipelineActions';
import PreviewAction from './TableComponents/PipelineActions/components/PreviewAction';
import TableExpandIcon from './TableComponents/TableExpandIcon';
import {
getDataOnSearch,
getEditedDataSource,
getElementFromArray,
getRecordIndex,
getTableColumn,
getUpdatedRow,
} from './utils';
function PipelineListsView({
isActionType,
setActionType,
isActionMode,
setActionMode,
pipelineData,
refetchPipelineLists,
pipelineSearchValue,
}: PipelineListsViewProps): JSX.Element {
const { t } = useTranslation(['pipeline', 'common']);
const [modal, contextHolder] = Modal.useModal();
const { notifications } = useNotifications();
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],
);
const setExpandedPipelineData = useCallback(
(newData: PipelineData): void => {
if (expandedPipelineId) {
const pipelineIdx = currPipelineData?.findIndex(
(p) => p.id === expandedPipelineId,
);
if (pipelineIdx >= 0) {
const newPipelineData = [...currPipelineData];
newPipelineData[pipelineIdx] = newData;
setCurrPipelineData(newPipelineData);
}
}
},
[expandedPipelineId, currPipelineData],
);
const [
selectedProcessorData,
setSelectedProcessorData,
] = useState<ProcessorData>();
const [
selectedPipelineData,
setSelectedPipelineData,
] = 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 currPipelineData.filter((data) =>
getDataOnSearch(data as never, pipelineSearchValue),
);
}, [currPipelineData, pipelineSearchValue]);
const handleAlert = useCallback(
({ title, descrition, buttontext, onCancel, onOk }: AlertMessage) => {
modal.confirm({
title: <AlertModalTitle>{title}</AlertModalTitle>,
icon: <ExclamationCircleOutlined />,
content: <AlertContentWrapper>{descrition}</AlertContentWrapper>,
okText: <span>{buttontext}</span>,
cancelText: <span>{t('cancel')}</span>,
onOk,
onCancel,
});
},
[modal, t],
);
const pipelineEditAction = useCallback(
(record: PipelineData) => (): void => {
setActionType(ActionType.EditPipeline);
setSelectedPipelineData(record);
},
[setActionType],
);
const pipelineDeleteHandler = useCallback(
(record: PipelineData) => (): void => {
setShowSaveButton(ActionMode.Editing);
const filteredData = getElementFromArray(currPipelineData, record, 'id');
filteredData.forEach((item, index) => {
const obj = item;
obj.orderId = index + 1;
});
setCurrPipelineData(filteredData);
},
[currPipelineData],
);
const pipelineDeleteAction = useCallback(
(record: PipelineData) => (): void => {
handleAlert({
title: `${t('delete_pipeline')} : ${record.name}?`,
descrition: t('delete_pipeline_description'),
buttontext: t('delete'),
onOk: pipelineDeleteHandler(record),
});
},
[handleAlert, pipelineDeleteHandler, t],
);
const processorEditAction = useCallback(
(record: ProcessorData) => (): void => {
setActionType(ActionType.EditProcessor);
setSelectedProcessorData(record);
},
[setActionType],
);
const onSwitchPipelineChange = useCallback(
(checked: boolean, record: PipelineData): void => {
setShowSaveButton(ActionMode.Editing);
const findRecordIndex = getRecordIndex(currPipelineData, record, 'id');
const updateSwitch = {
...currPipelineData[findRecordIndex],
enabled: checked,
};
const editedPipelineData = getEditedDataSource(
currPipelineData,
record,
'id',
updateSwitch,
);
setCurrPipelineData(editedPipelineData);
},
[currPipelineData],
);
const columns = useMemo(() => {
const fieldColumns = getTableColumn(pipelineColumns);
if (isEditingActionMode) {
fieldColumns.push(
{
title: 'Actions',
dataIndex: 'smartAction',
key: 'smartAction',
align: 'center',
render: (_value, record): JSX.Element => (
<PipelineActions
pipeline={record}
editAction={pipelineEditAction(record)}
deleteAction={pipelineDeleteAction(record)}
/>
),
},
{
title: '',
dataIndex: 'enabled',
key: 'enabled',
render: (value, record) => (
<DragAction
isEnabled={value}
onChange={(checked: boolean): void =>
onSwitchPipelineChange(checked, record)
}
/>
),
},
);
} else {
fieldColumns.push({
title: 'Actions',
dataIndex: 'smartAction',
key: 'smartAction',
align: 'center',
render: (_value, record): JSX.Element => (
<PreviewAction pipeline={record} />
),
});
}
return fieldColumns;
}, [
isEditingActionMode,
pipelineEditAction,
pipelineDeleteAction,
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;
const updatedRows = getUpdatedRow(
currPipelineData,
visibleCurrPipelines[dragIndex].orderId - 1,
visibleCurrPipelines[hoverIndex].orderId - 1,
);
updatedRows.forEach((item, index) => {
const obj = item;
obj.orderId = index + 1;
});
handleAlert({
title: t('reorder_pipeline'),
descrition: t('reorder_pipeline_description'),
buttontext: t('reorder'),
onOk: updatePipelineSequence(updatedRows),
onCancel: onCancelPipelineSequence(rawData),
});
}
},
[
currPipelineData,
isEditingActionMode,
visibleCurrPipelines,
handleAlert,
t,
updatePipelineSequence,
onCancelPipelineSequence,
],
);
const expandedRowView = useCallback(
(): JSX.Element => (
<PipelineExpanView
handleAlert={handleAlert}
isActionMode={isActionMode}
setActionType={setActionType}
processorEditAction={processorEditAction}
setShowSaveButton={setShowSaveButton}
expandedPipelineData={expandedPipelineData()}
setExpandedPipelineData={setExpandedPipelineData}
prevPipelineData={prevPipelineData}
/>
),
[
handleAlert,
processorEditAction,
isActionMode,
expandedPipelineData,
setActionType,
prevPipelineData,
setExpandedPipelineData,
],
);
const onExpand = useCallback(
(expanded: boolean, record: PipelineData): void => {
const keys = [];
if (expanded && record.id) {
keys.push(record?.id);
}
setExpandedRowKeys(keys);
setExpandedPipelineId(record.id);
},
[],
);
const getExpandIcon = (
expanded: boolean,
onExpand: (record: PipelineData, e: React.MouseEvent<HTMLElement>) => void,
record: PipelineData,
): JSX.Element => (
<TableExpandIcon expanded={expanded} onExpand={onExpand} record={record} />
);
const addNewPipelineHandler = useCallback((): void => {
setActionType(ActionType.AddPipeline);
}, [setActionType]);
const footer = useCallback((): JSX.Element | undefined => {
if (isEditingActionMode) {
return (
<FooterButton
type="link"
onClick={addNewPipelineHandler}
icon={<PlusOutlined />}
>
{t('add_new_pipeline')}
</FooterButton>
);
}
return undefined;
}, [isEditingActionMode, addNewPipelineHandler, t]);
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);
setShowSaveButton(undefined);
setCurrPipelineData(response.payload?.pipelines || []);
setPrevPipelineData(response.payload?.pipelines || []);
} else {
modifiedPipelineData.forEach((item: PipelineData) => {
const pipelineData = item;
pipelineData.id = v4();
return pipelineData;
});
setActionMode(ActionMode.Editing);
setShowSaveButton(ActionMode.Editing);
notifications.error({
message: 'Error',
description: response.error || t('something_went_wrong'),
});
setCurrPipelineData(modifiedPipelineData);
setPrevPipelineData(modifiedPipelineData);
}
}, [currPipelineData, notifications, refetchPipelineLists, setActionMode, t]);
const onCancelConfigurationHandler = useCallback((): void => {
setActionMode(ActionMode.Viewing);
setShowSaveButton(undefined);
prevPipelineData.forEach((item, index) => {
const obj = item;
obj.orderId = index + 1;
if (obj.config) {
obj.config?.forEach((configItem, index) => {
const config = configItem;
config.orderId = index + 1;
});
for (let i = 0; i < obj.config.length - 1; i += 1) {
obj.config[i].output = obj.config[i + 1].id;
}
}
});
setCurrPipelineData(prevPipelineData);
setExpandedRowKeys([]);
}, [prevPipelineData, setActionMode]);
const onRowHandler = (
_data: PipelineData,
index?: number,
): React.HTMLAttributes<unknown> =>
({
index,
moveRow: movePipelineRow,
} as React.HTMLAttributes<unknown>);
const expandableConfig: ExpandableConfig<PipelineData> = {
expandedRowKeys,
onExpand,
expandIcon: ({ expanded, onExpand, record }: ExpandRowConfig) =>
getExpandIcon(expanded, onExpand, record),
};
return (
<>
{contextHolder}
<AddNewPipeline
isActionType={isActionType}
setActionType={setActionType}
selectedPipelineData={selectedPipelineData}
setShowSaveButton={setShowSaveButton}
setCurrPipelineData={setCurrPipelineData}
currPipelineData={currPipelineData}
/>
<AddNewProcessor
isActionType={isActionType}
setActionType={setActionType}
selectedProcessorData={selectedProcessorData}
setShowSaveButton={setShowSaveButton}
expandedPipelineData={expandedPipelineData()}
setExpandedPipelineData={setExpandedPipelineData}
/>
<Container>
<ModeAndConfiguration
isActionMode={isActionMode}
version={pipelineData?.version}
/>
<DndProvider backend={HTML5Backend}>
<Table
rowKey="id"
columns={columns}
expandedRowRender={expandedRowView}
expandable={expandableConfig}
components={tableComponents}
dataSource={visibleCurrPipelines}
onRow={onRowHandler}
footer={footer}
pagination={false}
/>
</DndProvider>
{showSaveButton && (
<SaveConfigButton
onSaveConfigurationHandler={onSaveConfigurationHandler}
onCancelConfigurationHandler={onCancelConfigurationHandler}
/>
)}
</Container>
</>
);
}
interface PipelineListsViewProps {
isActionType: string;
setActionType: (actionType?: ActionType) => void;
isActionMode: string;
setActionMode: (actionMode: ActionMode) => void;
pipelineData: Pipeline;
refetchPipelineLists: VoidFunction;
pipelineSearchValue: string;
}
interface ExpandRowConfig {
expanded: boolean;
onExpand: (record: PipelineData, e: React.MouseEvent<HTMLElement>) => void;
record: PipelineData;
}
export interface AlertMessage {
title: string;
descrition: string;
buttontext: string;
onOk: VoidFunction;
onCancel?: VoidFunction;
}
export default PipelineListsView;

View File

@ -1,489 +1,3 @@
import { ExclamationCircleOutlined, PlusOutlined } from '@ant-design/icons';
import { Modal, Table } from 'antd';
import { ExpandableConfig } from 'antd/es/table/interface';
import savePipeline from 'api/pipeline/post';
import { useNotifications } from 'hooks/useNotifications';
import cloneDeep from 'lodash-es/cloneDeep';
import React, { useCallback, useMemo, useState } from 'react';
import { DndProvider } from 'react-dnd';
import { HTML5Backend } from 'react-dnd-html5-backend';
import { useTranslation } from 'react-i18next';
import {
ActionMode,
ActionType,
Pipeline,
PipelineData,
ProcessorData,
} from 'types/api/pipeline/def';
import { v4 } from 'uuid';
import { tableComponents } from '../config';
import AddNewPipeline from './AddNewPipeline';
import AddNewProcessor from './AddNewProcessor';
import { pipelineColumns } from './config';
import ModeAndConfiguration from './ModeAndConfiguration';
import PipelineExpanView from './PipelineExpandView';
import SaveConfigButton from './SaveConfigButton';
import {
AlertContentWrapper,
AlertModalTitle,
Container,
FooterButton,
} from './styles';
import DragAction from './TableComponents/DragAction';
import PipelineActions from './TableComponents/PipelineActions';
import PreviewAction from './TableComponents/PipelineActions/components/PreviewAction';
import TableExpandIcon from './TableComponents/TableExpandIcon';
import {
getDataOnSearch,
getEditedDataSource,
getElementFromArray,
getRecordIndex,
getTableColumn,
getUpdatedRow,
} from './utils';
function PipelineListsView({
isActionType,
setActionType,
isActionMode,
setActionMode,
pipelineData,
refetchPipelineLists,
pipelineSearchValue,
}: PipelineListsViewProps): JSX.Element {
const { t } = useTranslation(['pipeline', 'common']);
const [modal, contextHolder] = Modal.useModal();
const { notifications } = useNotifications();
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],
);
const setExpandedPipelineData = useCallback(
(newData: PipelineData): void => {
if (expandedPipelineId) {
const pipelineIdx = currPipelineData?.findIndex(
(p) => p.id === expandedPipelineId,
);
if (pipelineIdx >= 0) {
const newPipelineData = [...currPipelineData];
newPipelineData[pipelineIdx] = newData;
setCurrPipelineData(newPipelineData);
}
}
},
[expandedPipelineId, currPipelineData],
);
const [
selectedProcessorData,
setSelectedProcessorData,
] = useState<ProcessorData>();
const [
selectedPipelineData,
setSelectedPipelineData,
] = 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 currPipelineData.filter((data) =>
getDataOnSearch(data as never, pipelineSearchValue),
);
}, [currPipelineData, pipelineSearchValue]);
const handleAlert = useCallback(
({ title, descrition, buttontext, onCancel, onOk }: AlertMessage) => {
modal.confirm({
title: <AlertModalTitle>{title}</AlertModalTitle>,
icon: <ExclamationCircleOutlined />,
content: <AlertContentWrapper>{descrition}</AlertContentWrapper>,
okText: <span>{buttontext}</span>,
cancelText: <span>{t('cancel')}</span>,
onOk,
onCancel,
});
},
[modal, t],
);
const pipelineEditAction = useCallback(
(record: PipelineData) => (): void => {
setActionType(ActionType.EditPipeline);
setSelectedPipelineData(record);
},
[setActionType],
);
const pipelineDeleteHandler = useCallback(
(record: PipelineData) => (): void => {
setShowSaveButton(ActionMode.Editing);
const filteredData = getElementFromArray(currPipelineData, record, 'id');
filteredData.forEach((item, index) => {
const obj = item;
obj.orderId = index + 1;
});
setCurrPipelineData(filteredData);
},
[currPipelineData],
);
const pipelineDeleteAction = useCallback(
(record: PipelineData) => (): void => {
handleAlert({
title: `${t('delete_pipeline')} : ${record.name}?`,
descrition: t('delete_pipeline_description'),
buttontext: t('delete'),
onOk: pipelineDeleteHandler(record),
});
},
[handleAlert, pipelineDeleteHandler, t],
);
const processorEditAction = useCallback(
(record: ProcessorData) => (): void => {
setActionType(ActionType.EditProcessor);
setSelectedProcessorData(record);
},
[setActionType],
);
const onSwitchPipelineChange = useCallback(
(checked: boolean, record: PipelineData): void => {
setShowSaveButton(ActionMode.Editing);
const findRecordIndex = getRecordIndex(currPipelineData, record, 'id');
const updateSwitch = {
...currPipelineData[findRecordIndex],
enabled: checked,
};
const editedPipelineData = getEditedDataSource(
currPipelineData,
record,
'id',
updateSwitch,
);
setCurrPipelineData(editedPipelineData);
},
[currPipelineData],
);
const columns = useMemo(() => {
const fieldColumns = getTableColumn(pipelineColumns);
if (isEditingActionMode) {
fieldColumns.push(
{
title: 'Actions',
dataIndex: 'smartAction',
key: 'smartAction',
align: 'center',
render: (_value, record): JSX.Element => (
<PipelineActions
pipeline={record}
editAction={pipelineEditAction(record)}
deleteAction={pipelineDeleteAction(record)}
/>
),
},
{
title: '',
dataIndex: 'enabled',
key: 'enabled',
render: (value, record) => (
<DragAction
isEnabled={value}
onChange={(checked: boolean): void =>
onSwitchPipelineChange(checked, record)
}
/>
),
},
);
} else {
fieldColumns.push({
title: 'Actions',
dataIndex: 'smartAction',
key: 'smartAction',
align: 'center',
render: (_value, record): JSX.Element => (
<PreviewAction pipeline={record} />
),
});
}
return fieldColumns;
}, [
isEditingActionMode,
pipelineEditAction,
pipelineDeleteAction,
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;
const updatedRows = getUpdatedRow(
currPipelineData,
visibleCurrPipelines[dragIndex].orderId - 1,
visibleCurrPipelines[hoverIndex].orderId - 1,
);
updatedRows.forEach((item, index) => {
const obj = item;
obj.orderId = index + 1;
});
handleAlert({
title: t('reorder_pipeline'),
descrition: t('reorder_pipeline_description'),
buttontext: t('reorder'),
onOk: updatePipelineSequence(updatedRows),
onCancel: onCancelPipelineSequence(rawData),
});
}
},
[
currPipelineData,
isEditingActionMode,
visibleCurrPipelines,
handleAlert,
t,
updatePipelineSequence,
onCancelPipelineSequence,
],
);
const expandedRowView = useCallback(
(): JSX.Element => (
<PipelineExpanView
handleAlert={handleAlert}
isActionMode={isActionMode}
setActionType={setActionType}
processorEditAction={processorEditAction}
setShowSaveButton={setShowSaveButton}
expandedPipelineData={expandedPipelineData()}
setExpandedPipelineData={setExpandedPipelineData}
prevPipelineData={prevPipelineData}
/>
),
[
handleAlert,
processorEditAction,
isActionMode,
expandedPipelineData,
setActionType,
prevPipelineData,
setExpandedPipelineData,
],
);
const onExpand = useCallback(
(expanded: boolean, record: PipelineData): void => {
const keys = [];
if (expanded && record.id) {
keys.push(record?.id);
}
setExpandedRowKeys(keys);
setExpandedPipelineId(record.id);
},
[],
);
const getExpandIcon = (
expanded: boolean,
onExpand: (record: PipelineData, e: React.MouseEvent<HTMLElement>) => void,
record: PipelineData,
): JSX.Element => (
<TableExpandIcon expanded={expanded} onExpand={onExpand} record={record} />
);
const addNewPipelineHandler = useCallback((): void => {
setActionType(ActionType.AddPipeline);
}, [setActionType]);
const footer = useCallback((): JSX.Element | undefined => {
if (isEditingActionMode) {
return (
<FooterButton
type="link"
onClick={addNewPipelineHandler}
icon={<PlusOutlined />}
>
{t('add_new_pipeline')}
</FooterButton>
);
}
return undefined;
}, [isEditingActionMode, addNewPipelineHandler, t]);
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);
setShowSaveButton(undefined);
setCurrPipelineData(response.payload?.pipelines || []);
setPrevPipelineData(response.payload?.pipelines || []);
} else {
modifiedPipelineData.forEach((item: PipelineData) => {
const pipelineData = item;
pipelineData.id = v4();
return pipelineData;
});
setActionMode(ActionMode.Editing);
setShowSaveButton(ActionMode.Editing);
notifications.error({
message: 'Error',
description: response.error || t('something_went_wrong'),
});
setCurrPipelineData(modifiedPipelineData);
setPrevPipelineData(modifiedPipelineData);
}
}, [currPipelineData, notifications, refetchPipelineLists, setActionMode, t]);
const onCancelConfigurationHandler = useCallback((): void => {
setActionMode(ActionMode.Viewing);
setShowSaveButton(undefined);
prevPipelineData.forEach((item, index) => {
const obj = item;
obj.orderId = index + 1;
if (obj.config) {
obj.config?.forEach((configItem, index) => {
const config = configItem;
config.orderId = index + 1;
});
for (let i = 0; i < obj.config.length - 1; i += 1) {
obj.config[i].output = obj.config[i + 1].id;
}
}
});
setCurrPipelineData(prevPipelineData);
setExpandedRowKeys([]);
}, [prevPipelineData, setActionMode]);
const onRowHandler = (
_data: PipelineData,
index?: number,
): React.HTMLAttributes<unknown> =>
({
index,
moveRow: movePipelineRow,
} as React.HTMLAttributes<unknown>);
const expandableConfig: ExpandableConfig<PipelineData> = {
expandedRowKeys,
onExpand,
expandIcon: ({ expanded, onExpand, record }: ExpandRowConfig) =>
getExpandIcon(expanded, onExpand, record),
};
return (
<>
{contextHolder}
<AddNewPipeline
isActionType={isActionType}
setActionType={setActionType}
selectedPipelineData={selectedPipelineData}
setShowSaveButton={setShowSaveButton}
setCurrPipelineData={setCurrPipelineData}
currPipelineData={currPipelineData}
/>
<AddNewProcessor
isActionType={isActionType}
setActionType={setActionType}
selectedProcessorData={selectedProcessorData}
setShowSaveButton={setShowSaveButton}
expandedPipelineData={expandedPipelineData()}
setExpandedPipelineData={setExpandedPipelineData}
/>
<Container>
<ModeAndConfiguration
isActionMode={isActionMode}
version={pipelineData?.version}
/>
<DndProvider backend={HTML5Backend}>
<Table
rowKey="id"
columns={columns}
expandedRowRender={expandedRowView}
expandable={expandableConfig}
components={tableComponents}
dataSource={visibleCurrPipelines}
onRow={onRowHandler}
footer={footer}
pagination={false}
/>
</DndProvider>
{showSaveButton && (
<SaveConfigButton
onSaveConfigurationHandler={onSaveConfigurationHandler}
onCancelConfigurationHandler={onCancelConfigurationHandler}
/>
)}
</Container>
</>
);
}
interface PipelineListsViewProps {
isActionType: string;
setActionType: (actionType?: ActionType) => void;
isActionMode: string;
setActionMode: (actionMode: ActionMode) => void;
pipelineData: Pipeline;
refetchPipelineLists: VoidFunction;
pipelineSearchValue: string;
}
interface ExpandRowConfig {
expanded: boolean;
onExpand: (record: PipelineData, e: React.MouseEvent<HTMLElement>) => void;
record: PipelineData;
}
export interface AlertMessage {
title: string;
descrition: string;
buttontext: string;
onOk: VoidFunction;
onCancel?: VoidFunction;
}
import PipelineListsView from './PipelineListsView';
export default PipelineListsView;

View File

@ -82,78 +82,42 @@ exports[`PipelinePage container test should render PipelinePageLayout section 1`
</button>
</div>
<span
class="ant-input-group-wrapper ant-input-search css-dev-only-do-not-override-1i536d8"
class="ant-input-affix-wrapper css-dev-only-do-not-override-1i536d8"
>
<input
class="ant-input css-dev-only-do-not-override-1i536d8"
placeholder="search_pipeline_placeholder"
type="text"
value=""
/>
<span
class="ant-input-wrapper ant-input-group css-dev-only-do-not-override-1i536d8"
class="ant-input-suffix"
>
<span
class="ant-input-affix-wrapper css-dev-only-do-not-override-1i536d8"
class="ant-input-clear-icon ant-input-clear-icon-hidden"
role="button"
tabindex="-1"
>
<input
class="ant-input css-dev-only-do-not-override-1i536d8"
placeholder="search_pipeline_placeholder"
type="text"
value=""
/>
<span
class="ant-input-suffix"
aria-label="close-circle"
class="anticon anticon-close-circle"
role="img"
>
<span
class="ant-input-clear-icon ant-input-clear-icon-hidden"
role="button"
tabindex="-1"
<svg
aria-hidden="true"
data-icon="close-circle"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<span
aria-label="close-circle"
class="anticon anticon-close-circle"
role="img"
>
<svg
aria-hidden="true"
data-icon="close-circle"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm165.4 618.2l-66-.3L512 563.4l-99.3 118.4-66.1.3c-4.4 0-8-3.5-8-8 0-1.9.7-3.7 1.9-5.2l130.1-155L340.5 359a8.32 8.32 0 01-1.9-5.2c0-4.4 3.6-8 8-8l66.1.3L512 464.6l99.3-118.4 66-.3c4.4 0 8 3.5 8 8 0 1.9-.7 3.7-1.9 5.2L553.5 514l130 155c1.2 1.5 1.9 3.3 1.9 5.2 0 4.4-3.6 8-8 8z"
/>
</svg>
</span>
</span>
<path
d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm165.4 618.2l-66-.3L512 563.4l-99.3 118.4-66.1.3c-4.4 0-8-3.5-8-8 0-1.9.7-3.7 1.9-5.2l130.1-155L340.5 359a8.32 8.32 0 01-1.9-5.2c0-4.4 3.6-8 8-8l66.1.3L512 464.6l99.3-118.4 66-.3c4.4 0 8 3.5 8 8 0 1.9-.7 3.7-1.9 5.2L553.5 514l130 155c1.2 1.5 1.9 3.3 1.9 5.2 0 4.4-3.6 8-8 8z"
/>
</svg>
</span>
</span>
<span
class="ant-input-group-addon"
>
<button
class="ant-btn css-dev-only-do-not-override-1i536d8 ant-btn-default ant-btn-icon-only ant-input-search-button"
type="button"
>
<span
aria-label="search"
class="anticon anticon-search"
role="img"
>
<svg
aria-hidden="true"
data-icon="search"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M909.6 854.5L649.9 594.8C690.2 542.7 712 479 712 412c0-80.2-31.3-155.4-87.9-212.1-56.6-56.7-132-87.9-212.1-87.9s-155.5 31.3-212.1 87.9C143.2 256.5 112 331.8 112 412c0 80.1 31.3 155.5 87.9 212.1C256.5 680.8 331.8 712 412 712c67 0 130.6-21.8 182.7-62l259.7 259.6a8.2 8.2 0 0011.6 0l43.6-43.5a8.2 8.2 0 000-11.6zM570.4 570.4C528 612.7 471.8 636 412 636s-116-23.3-158.4-65.6C211.3 528 188 471.8 188 412s23.3-116.1 65.6-158.4C296 211.3 352.2 188 412 188s116.1 23.2 158.4 65.6S636 352.2 636 412s-23.3 116.1-65.6 158.4z"
/>
</svg>
</span>
</button>
</span>
</span>
</span>
.c0 {

View File

@ -3,78 +3,42 @@
exports[`PipelinePage container test should render PipelinesSearchSection section 1`] = `
<DocumentFragment>
<span
class="ant-input-group-wrapper ant-input-search css-dev-only-do-not-override-1i536d8"
class="ant-input-affix-wrapper css-dev-only-do-not-override-1i536d8"
>
<input
class="ant-input css-dev-only-do-not-override-1i536d8"
placeholder="search_pipeline_placeholder"
type="text"
value=""
/>
<span
class="ant-input-wrapper ant-input-group css-dev-only-do-not-override-1i536d8"
class="ant-input-suffix"
>
<span
class="ant-input-affix-wrapper css-dev-only-do-not-override-1i536d8"
class="ant-input-clear-icon ant-input-clear-icon-hidden"
role="button"
tabindex="-1"
>
<input
class="ant-input css-dev-only-do-not-override-1i536d8"
placeholder="search_pipeline_placeholder"
type="text"
value=""
/>
<span
class="ant-input-suffix"
aria-label="close-circle"
class="anticon anticon-close-circle"
role="img"
>
<span
class="ant-input-clear-icon ant-input-clear-icon-hidden"
role="button"
tabindex="-1"
<svg
aria-hidden="true"
data-icon="close-circle"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<span
aria-label="close-circle"
class="anticon anticon-close-circle"
role="img"
>
<svg
aria-hidden="true"
data-icon="close-circle"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm165.4 618.2l-66-.3L512 563.4l-99.3 118.4-66.1.3c-4.4 0-8-3.5-8-8 0-1.9.7-3.7 1.9-5.2l130.1-155L340.5 359a8.32 8.32 0 01-1.9-5.2c0-4.4 3.6-8 8-8l66.1.3L512 464.6l99.3-118.4 66-.3c4.4 0 8 3.5 8 8 0 1.9-.7 3.7-1.9 5.2L553.5 514l130 155c1.2 1.5 1.9 3.3 1.9 5.2 0 4.4-3.6 8-8 8z"
/>
</svg>
</span>
</span>
<path
d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm165.4 618.2l-66-.3L512 563.4l-99.3 118.4-66.1.3c-4.4 0-8-3.5-8-8 0-1.9.7-3.7 1.9-5.2l130.1-155L340.5 359a8.32 8.32 0 01-1.9-5.2c0-4.4 3.6-8 8-8l66.1.3L512 464.6l99.3-118.4 66-.3c4.4 0 8 3.5 8 8 0 1.9-.7 3.7-1.9 5.2L553.5 514l130 155c1.2 1.5 1.9 3.3 1.9 5.2 0 4.4-3.6 8-8 8z"
/>
</svg>
</span>
</span>
<span
class="ant-input-group-addon"
>
<button
class="ant-btn css-dev-only-do-not-override-1i536d8 ant-btn-default ant-btn-icon-only ant-input-search-button"
type="button"
>
<span
aria-label="search"
class="anticon anticon-search"
role="img"
>
<svg
aria-hidden="true"
data-icon="search"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M909.6 854.5L649.9 594.8C690.2 542.7 712 479 712 412c0-80.2-31.3-155.4-87.9-212.1-56.6-56.7-132-87.9-212.1-87.9s-155.5 31.3-212.1 87.9C143.2 256.5 112 331.8 112 412c0 80.1 31.3 155.5 87.9 212.1C256.5 680.8 331.8 712 412 712c67 0 130.6-21.8 182.7-62l259.7 259.6a8.2 8.2 0 0011.6 0l43.6-43.5a8.2 8.2 0 000-11.6zM570.4 570.4C528 612.7 471.8 636 412 636s-116-23.3-158.4-65.6C211.3 528 188 471.8 188 412s23.3-116.1 65.6-158.4C296 211.3 352.2 188 412 188s116.1 23.2 158.4 65.6S636 352.2 636 412s-23.3 116.1-65.6 158.4z"
/>
</svg>
</span>
</button>
</span>
</span>
</span>
</DocumentFragment>

View File

@ -45,6 +45,6 @@ export const routeConfig: Record<string, QueryParams[]> = {
[ROUTES.USAGE_EXPLORER]: [QueryParams.resourceAttributes],
[ROUTES.VERSION]: [QueryParams.resourceAttributes],
[ROUTES.TRACE_EXPLORER]: [QueryParams.resourceAttributes],
[ROUTES.PIPELINES]: [QueryParams.resourceAttributes],
[ROUTES.LOGS_PIPELINES]: [QueryParams.resourceAttributes],
[ROUTES.WORKSPACE_LOCKED]: [QueryParams.resourceAttributes],
};

View File

@ -9,6 +9,7 @@ import {
LineChartOutlined,
MenuOutlined,
RocketOutlined,
SearchOutlined,
SettingOutlined,
} from '@ant-design/icons';
import ROUTES from 'constants/routes';
@ -35,6 +36,18 @@ const menuItems: SidebarMenu[] = [
key: ROUTES.LOGS_EXPLORER,
label: 'Logs',
icon: <AlignLeftOutlined />,
children: [
{
key: ROUTES.LOGS_EXPLORER,
icon: <SearchOutlined />,
label: 'Logs Explorer',
},
{
key: ROUTES.LOGS_PIPELINES,
icon: <DeploymentUnitOutlined />,
label: 'Logs Pipelines',
},
],
},
{
key: ROUTES.ALL_DASHBOARD,

View File

@ -23,7 +23,7 @@ const breadcrumbNameMap = {
[ROUTES.LOGS]: 'Logs',
[ROUTES.LOGS_EXPLORER]: 'Logs Explorer',
[ROUTES.LIVE_LOGS]: 'Live View',
[ROUTES.PIPELINES]: 'Pipelines',
[ROUTES.LOGS_PIPELINES]: 'Logs Pipelines',
[ROUTES.BILLING]: 'Billing',
[ROUTES.SUPPORT]: 'Support',
[ROUTES.WORKSPACE_LOCKED]: 'Workspace Locked',

View File

@ -83,7 +83,7 @@ export const routesToSkip = [
ROUTES.ALERTS_NEW,
ROUTES.EDIT_ALERTS,
ROUTES.LIST_ALL_ALERT,
ROUTES.PIPELINES,
ROUTES.LOGS_PIPELINES,
ROUTES.BILLING,
ROUTES.SUPPORT,
ROUTES.WORKSPACE_LOCKED,

View File

@ -75,9 +75,8 @@ export const routePermission: Record<keyof typeof ROUTES, ROLES[]> = {
LIVE_LOGS: ['ADMIN', 'EDITOR', 'VIEWER'],
LIST_LICENSES: ['ADMIN'],
LOGS_INDEX_FIELDS: ['ADMIN', 'EDITOR', 'VIEWER'],
LOGS_PIPELINE: ['ADMIN', 'EDITOR', 'VIEWER'],
LOGS_PIPELINES: ['ADMIN', 'EDITOR', 'VIEWER'],
TRACE_EXPLORER: ['ADMIN', 'EDITOR', 'VIEWER'],
PIPELINES: ['ADMIN', 'EDITOR', 'VIEWER'],
GET_STARTED: ['ADMIN', 'EDITOR', 'VIEWER'],
WORKSPACE_LOCKED: ['ADMIN', 'EDITOR', 'VIEWER'],
BILLING: ['ADMIN', 'EDITOR', 'VIEWER'],